mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6269 from ethereum/develop
Merge develop into release for 0.5.6
This commit is contained in:
commit
b259423eb8
@ -10,7 +10,7 @@ include(EthPolicy)
|
||||
eth_policy()
|
||||
|
||||
# project name and version should be set after cmake_policy CMP0048
|
||||
set(PROJECT_VERSION "0.5.5")
|
||||
set(PROJECT_VERSION "0.5.6")
|
||||
project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX)
|
||||
|
||||
option(LLL "Build LLL" OFF)
|
||||
|
35
Changelog.md
35
Changelog.md
@ -1,3 +1,35 @@
|
||||
### 0.5.6 (2019-03-13)
|
||||
|
||||
Important Bugfixes:
|
||||
* Yul Optimizer: Fix visitation order bug for the structural simplifier.
|
||||
* Optimizer: Fix overflow in optimization rule that simplifies double shift by constant.
|
||||
|
||||
Language Features:
|
||||
* Allow calldata arrays with dynamically encoded base types with ABIEncoderV2.
|
||||
* Allow dynamically encoded calldata structs with ABIEncoderV2.
|
||||
|
||||
|
||||
Compiler Features:
|
||||
* Optimizer: Add rules for ``lt``-comparisons with constants.
|
||||
* Peephole Optimizer: Remove double ``iszero`` before ``jumpi``.
|
||||
* SMTChecker: Support enums without typecast.
|
||||
* SMTChecker: Support one-dimensional arrays.
|
||||
* Type Checker: Provide better error messages for some literal conversions.
|
||||
* Yul Optimizer: Add rule to remove empty default switch cases.
|
||||
* Yul Optimizer: Add rule to remove empty cases if no default exists.
|
||||
* Yul Optimizer: Add rule to replace a switch with no cases with ``pop(expression)``.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
* JSON ABI: Json description of library ABIs no longer contains functions with internal types like storage structs.
|
||||
* SMTChecker: Fix internal compiler error when contract contains too large rational number.
|
||||
* Type system: Detect if a contract's base uses types that require the experimental abi encoder while the contract still uses the old encoder.
|
||||
|
||||
|
||||
Build System:
|
||||
* Soltest: Add support for arrays in function signatures.
|
||||
* Soltest: Add support for struct arrays in function signatures.
|
||||
|
||||
### 0.5.5 (2019-03-05)
|
||||
|
||||
Language Features:
|
||||
@ -7,10 +39,12 @@ Language Features:
|
||||
|
||||
Compiler Features:
|
||||
* Support ``petersburg`` as ``evmVersion`` and set as default.
|
||||
* Commandline Interface: Option to activate the experimental yul optimizer using ``-optimize-yul``.
|
||||
* Inline Assembly: Consider ``extcodehash`` as part of Constantinople.
|
||||
* Inline Assembly: Instructions unavailable to the currently configured EVM are errors now.
|
||||
* SMTChecker: Do not report underflow/overflow if they always revert. This removes false positives when using ``SafeMath``.
|
||||
* Standard JSON Interface: Allow retrieving metadata without triggering bytecode generation.
|
||||
* Standard JSON Interface: Provide fine-grained control over the optimizer via the settings.
|
||||
* Static Analyzer: Warn about expressions with custom types when they have no effect.
|
||||
* Optimizer: Add new rules with constants including ``LT``, ``GT``, ``AND`` and ``BYTE``.
|
||||
* Optimizer: Add rule for shifts with constants for Constantinople.
|
||||
@ -36,6 +70,7 @@ Bugfixes:
|
||||
|
||||
Build System:
|
||||
* Soltest: Add support for left-aligned, padded hex literals.
|
||||
* Soltest: Add support for left-aligned, unpadded hex string literals.
|
||||
* Soltest: Add support for right-aligned, padded boolean literals.
|
||||
|
||||
### 0.5.4 (2019-02-12)
|
||||
|
@ -38,14 +38,15 @@
|
||||
- [ ] Update the version and the hash (``sha256sum solidity_x.x.x.tar.gz``) in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb
|
||||
|
||||
### Documentation
|
||||
- [ ] Update the default version on readthedocs.
|
||||
- [ ] Build the new version on https://readthedocs.org/projects/solidity/ (select `latest` on the bottom of the page and click `BUILD`)
|
||||
- [ ] In the admin panel, select `Versions` in the menu and set the default version to the released one.
|
||||
|
||||
### Release solc-js
|
||||
- [ ] Increment the version number, create a pull request for that, merge it after tests succeeded.
|
||||
- [ ] Run ``npm publish`` in the updated ``solc-js`` repository.
|
||||
- [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry.
|
||||
- [ ] Merge ``release`` back into ``develop``.
|
||||
|
||||
### Post-release
|
||||
- [ ] Create a commit to increase the version number on ``develop`` in ``CMakeLists.txt`` and add a new skeleton changelog entry.
|
||||
- [ ] Merge ``release`` back into ``develop``.
|
||||
- [ ] Announce on Twitter and Reddit.
|
||||
- [ ] Lean back, wait for bug reports and repeat from step 1 :)
|
||||
|
@ -308,7 +308,7 @@ This will no longer compile with Solidity v0.5.0. However, you can define a comp
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
interface OldContract {
|
||||
function someOldFunction(uint8 a) external;
|
||||
function anotherOldFunction() external returns (bool);
|
||||
@ -325,7 +325,7 @@ Given the interface defined above, you can now easily use the already deployed p
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
interface OldContract {
|
||||
function someOldFunction(uint8 a) external;
|
||||
@ -345,7 +345,7 @@ commandline compiler for linking):
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
library OldLibrary {
|
||||
function someFunction(uint8 a) public returns(bool);
|
||||
@ -430,7 +430,7 @@ New version:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract OtherContract {
|
||||
uint x;
|
||||
|
@ -212,7 +212,7 @@ Given the contract:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract Foo {
|
||||
function bar(bytes3[2] memory) public pure {}
|
||||
@ -483,7 +483,7 @@ For example,
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract Test {
|
||||
constructor() public { b = hex"12345678901234567890123456789012"; }
|
||||
@ -530,7 +530,7 @@ As an example, the code
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.19 <0.6.0;
|
||||
pragma solidity >=0.4.19 <0.7.0;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
contract Test {
|
||||
|
@ -76,7 +76,7 @@ idea is that assembly libraries will be used to enhance the Solidity language.
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
library GetCode {
|
||||
function at(address _addr) public view returns (bytes memory o_code) {
|
||||
@ -101,7 +101,7 @@ efficient code, for example:
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
library VectorSum {
|
||||
// This function is less efficient because the optimizer currently fails to
|
||||
@ -394,7 +394,7 @@ Local Solidity variables are available for assignments, for example:
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity >=0.4.11 <0.6.0;
|
||||
pragma solidity >=0.4.11 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint b;
|
||||
@ -433,7 +433,7 @@ be just ``0``, but it can also be a complex functional-style expression.
|
||||
|
||||
.. code::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f(uint x) public view returns (uint b) {
|
||||
@ -690,7 +690,7 @@ Example:
|
||||
We will follow an example compilation from Solidity to assembly.
|
||||
We consider the runtime bytecode of the following Solidity program::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f(uint x) public pure returns (uint y) {
|
||||
|
@ -1,4 +1,16 @@
|
||||
[
|
||||
{
|
||||
"name": "DoubleShiftSizeOverflow",
|
||||
"summary": "Double bitwise shifts by large constants whose sum overflows 256 bits can result in unexpected values.",
|
||||
"description": "Nested logical shift operations whose total shift size is 2**256 or more are incorrectly optimized. This only applies to shifts by numbers of bits that are compile-time constant expressions.",
|
||||
"introduced": "0.5.5",
|
||||
"fixed": "0.5.6",
|
||||
"severity": "low",
|
||||
"conditions": {
|
||||
"optimizer": true,
|
||||
"evmVersion": ">=constantinople"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ExpExponentCleanup",
|
||||
"summary": "Using the ** operator with an exponent of type shorter than 256 bits can result in unexpected values.",
|
||||
|
@ -52,9 +52,15 @@ severity
|
||||
discoverability in contract tests, likelihood of occurrence and
|
||||
potential damage by exploits.
|
||||
conditions
|
||||
Conditions that have to be met to trigger the bug. Currently, this
|
||||
is an object that can contain a boolean value ``optimizer``, which
|
||||
Conditions that have to be met to trigger the bug. The following
|
||||
keys can be used:
|
||||
``optimizer``, Boolean value which
|
||||
means that the optimizer has to be switched on to enable the bug.
|
||||
``evmVersion``, a string that indicates which EVM version compiler
|
||||
settings trigger the bug. The string can contain comparison
|
||||
operators. For example, ``">=constantinople"`` means that the bug
|
||||
is present when the EVM version is set to ``constantinople`` or
|
||||
later.
|
||||
If no conditions are given, assume that the bug is present.
|
||||
check
|
||||
This field contains different checks that report whether the smart contract
|
||||
|
@ -630,7 +630,13 @@
|
||||
"released": "2019-02-12"
|
||||
},
|
||||
"0.5.5": {
|
||||
"bugs": [],
|
||||
"bugs": [
|
||||
"DoubleShiftSizeOverflow"
|
||||
],
|
||||
"released": "2019-03-05"
|
||||
},
|
||||
"0.5.6": {
|
||||
"bugs": [],
|
||||
"released": "2019-03-13"
|
||||
}
|
||||
}
|
@ -28,7 +28,7 @@ become the new richest.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract WithdrawalContract {
|
||||
address public richest;
|
||||
@ -65,7 +65,7 @@ This is as opposed to the more intuitive sending pattern:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract SendContract {
|
||||
address payable public richest;
|
||||
@ -130,7 +130,7 @@ restrictions highly readable.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract AccessRestriction {
|
||||
// These will be assigned at the construction
|
||||
@ -282,7 +282,7 @@ function finishes.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract StateMachine {
|
||||
enum Stages {
|
||||
|
@ -8,7 +8,7 @@ Abstract Contracts
|
||||
|
||||
Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``)::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Feline {
|
||||
function utterance() public returns (bytes32);
|
||||
@ -16,7 +16,7 @@ Contracts are marked as abstract when at least one of their functions lacks an i
|
||||
|
||||
Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Feline {
|
||||
function utterance() public returns (bytes32);
|
||||
|
@ -26,7 +26,7 @@ value types and strings.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint constant x = 32**22 + 8;
|
||||
|
@ -33,7 +33,7 @@ This means that cyclic creation dependencies are impossible.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract OwnedToken {
|
||||
// `TokenCreator` is a contract type that is defined below.
|
||||
|
@ -63,7 +63,7 @@ not possible to filter for specific anonymous events by name.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.21 <0.6.0;
|
||||
pragma solidity >=0.4.21 <0.7.0;
|
||||
|
||||
contract ClientReceipt {
|
||||
event Deposit(
|
||||
@ -136,7 +136,7 @@ as topics. The event call above can be performed in the same way as
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.10 <0.6.0;
|
||||
pragma solidity >=0.4.10 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f() public payable {
|
||||
|
@ -12,7 +12,7 @@ inheritable properties of contracts and may be overridden by derived contracts.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
|
@ -23,7 +23,7 @@ unused parameters can be omitted.
|
||||
For example, if you want your contract to accept one kind of external call
|
||||
with two integers, you would use something like::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract Simple {
|
||||
uint sum;
|
||||
@ -55,7 +55,7 @@ Function return variables are declared with the same syntax after the
|
||||
For example, suppose you want to return two results: the sum and the product of
|
||||
two integers passed as function parameters, then you use something like::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract Simple {
|
||||
function arithmetic(uint _a, uint _b)
|
||||
@ -78,7 +78,7 @@ or you can provide return values
|
||||
(either a single or :ref:`multiple ones<multi-return>`) directly with the ``return``
|
||||
statement::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract Simple {
|
||||
function arithmetic(uint _a, uint _b)
|
||||
@ -140,7 +140,7 @@ The following statements are considered modifying the state:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f(uint a, uint b) public view returns (uint) {
|
||||
@ -185,7 +185,7 @@ In addition to the list of state modifying statements explained above, the follo
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f(uint a, uint b) public pure returns (uint) {
|
||||
@ -279,7 +279,7 @@ Like any function, the fallback function can execute complex operations as long
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract Test {
|
||||
// This function is called for all messages sent to
|
||||
@ -330,7 +330,7 @@ The following example shows overloading of the function
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract A {
|
||||
function f(uint _in) public pure returns (uint out) {
|
||||
@ -348,7 +348,7 @@ externally visible functions differ by their Solidity types but not by their ext
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
// This will not compile
|
||||
contract A {
|
||||
@ -381,7 +381,7 @@ candidate, resolution fails.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract A {
|
||||
function f(uint8 _in) public pure returns (uint8 out) {
|
||||
|
@ -23,7 +23,7 @@ Details are given in the following example.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
@ -95,7 +95,7 @@ Note that above, we call ``mortal.kill()`` to "forward" the
|
||||
destruction request. The way this is done is problematic, as
|
||||
seen in the following example::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
@ -124,7 +124,7 @@ derived override, but this function will bypass
|
||||
``Base1.kill``, basically because it does not even know about
|
||||
``Base1``. The way around this is to use ``super``::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract owned {
|
||||
constructor() public { owner = msg.sender; }
|
||||
@ -188,7 +188,7 @@ equivalent to ``constructor() public {}``. For example:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
uint public a;
|
||||
@ -218,7 +218,7 @@ The constructors of all the base contracts will be called following the
|
||||
linearization rules explained below. If the base constructors have arguments,
|
||||
derived contracts need to specify all of them. This can be done in two ways::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract Base {
|
||||
uint x;
|
||||
@ -277,7 +277,7 @@ error "Linearization of inheritance graph impossible".
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract X {}
|
||||
contract A is X {}
|
||||
|
@ -22,7 +22,7 @@ Interfaces are denoted by their own keyword:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
interface Token {
|
||||
enum TokenType { Fungible, NonFungible }
|
||||
|
@ -47,7 +47,7 @@ more advanced example to implement a set).
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
library Set {
|
||||
// We define a new struct datatype that will be used to
|
||||
@ -121,7 +121,7 @@ custom types without the overhead of external function calls:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
library BigInt {
|
||||
struct bigint {
|
||||
|
@ -31,7 +31,7 @@ available without having to add further code.
|
||||
Let us rewrite the set example from the
|
||||
:ref:`libraries` in this way::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
// This is the same code as before, just without comments
|
||||
library Set {
|
||||
@ -81,7 +81,7 @@ Let us rewrite the set example from the
|
||||
|
||||
It is also possible to extend elementary types in that way::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
library Search {
|
||||
function indexOf(uint[] storage self, uint value)
|
||||
|
@ -53,7 +53,7 @@ return parameter list for functions.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f(uint a) private pure returns (uint b) { return a + 1; }
|
||||
@ -67,7 +67,7 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint private data;
|
||||
@ -111,7 +111,7 @@ when they are declared.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint public data = 42;
|
||||
@ -131,7 +131,7 @@ it evaluates to a state variable. If it is accessed externally
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint public data;
|
||||
@ -150,7 +150,7 @@ to write a function, for example:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract arrayExample {
|
||||
// public state variable
|
||||
@ -176,7 +176,7 @@ The next example is more complex:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Complex {
|
||||
struct Data {
|
||||
|
@ -404,4 +404,15 @@ Common Terms
|
||||
Code Examples
|
||||
-------------
|
||||
|
||||
* Ensure that all code examples begin with a ``pragma`` version that spans the largest where the contract code is valid. For example ``pragma solidity >=0.4.0 <0.6.0;``.
|
||||
A CI process tests all code block formatted code examples that begin with ``pragma solidity``, ``contract``, ``library``
|
||||
or ``interface`` using the ``./test/cmdlineTests.sh`` script when you create a PR. If you are adding new code examples,
|
||||
ensure they work and pass tests before creating the PR.
|
||||
|
||||
Ensure that all code examples begin with a ``pragma`` version that spans the largest where the contract code is valid.
|
||||
For example ``pragma solidity >=0.4.0 <0.7.0;``.
|
||||
|
||||
Running Documentation Tests
|
||||
---------------------------
|
||||
|
||||
Make sure your contributions pass our documentation tests by running ``./scripts/docs.sh`` that installs dependencies
|
||||
needed for documentation and checks for any problems such as broken links or syntax issues.
|
@ -37,7 +37,7 @@ Internal Function Calls
|
||||
Functions of the current contract can be called directly ("internally"), also recursively, as seen in
|
||||
this nonsensical example::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function g(uint a) public pure returns (uint ret) { return a + f(); }
|
||||
@ -75,7 +75,7 @@ When calling functions of other contracts, you can specify the amount of Wei or
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract InfoFeed {
|
||||
function info() public payable returns (uint ret) { return 42; }
|
||||
@ -122,7 +122,7 @@ parameters from the function declaration, but can be in arbitrary order.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
mapping(uint => uint) data;
|
||||
@ -145,7 +145,7 @@ Those parameters will still be present on the stack, but they are inaccessible.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
// omitted name for parameter
|
||||
@ -168,7 +168,7 @@ is compiled so recursive creation-dependencies are not possible.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract D {
|
||||
uint public x;
|
||||
@ -225,7 +225,7 @@ groupings of expressions.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >0.4.23 <0.6.0;
|
||||
pragma solidity >0.4.23 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint[] data;
|
||||
@ -270,7 +270,7 @@ because only a reference and not a copy is passed.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint[20] x;
|
||||
@ -316,7 +316,7 @@ the two variables have the same name but disjoint scopes.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
contract C {
|
||||
function minimalScoping() pure public {
|
||||
{
|
||||
@ -337,7 +337,7 @@ In any case, you will get a warning about the outer variable being shadowed.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
// This will report a warning
|
||||
contract C {
|
||||
function f() pure public returns (uint) {
|
||||
@ -357,7 +357,7 @@ In any case, you will get a warning about the outer variable being shadowed.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
// This will not compile
|
||||
contract C {
|
||||
function f() pure public returns (uint) {
|
||||
@ -404,7 +404,7 @@ a message string for ``require``, but not for ``assert``.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract Sharer {
|
||||
function sendHalf(address payable addr) public payable returns (uint balance) {
|
||||
@ -450,7 +450,7 @@ The following example shows how an error string can be used together with revert
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract VendingMachine {
|
||||
function buy(uint amount) public payable {
|
||||
|
@ -30,7 +30,7 @@ activate themselves.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract SimpleAuction {
|
||||
// Parameters of the auction. Times are either
|
||||
@ -194,7 +194,7 @@ high or low invalid bids.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >0.4.23 <0.6.0;
|
||||
pragma solidity >0.4.23 <0.7.0;
|
||||
|
||||
contract BlindAuction {
|
||||
struct Bid {
|
||||
|
@ -112,7 +112,7 @@ The full contract
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.24 <0.6.0;
|
||||
pragma solidity >=0.4.24 <0.7.0;
|
||||
|
||||
contract ReceiverPays {
|
||||
address owner = msg.sender;
|
||||
@ -286,7 +286,7 @@ The full contract
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.24 <0.6.0;
|
||||
pragma solidity >=0.4.24 <0.7.0;
|
||||
|
||||
contract SimplePaymentChannel {
|
||||
address payable public sender; // The account sending payments.
|
||||
|
@ -11,7 +11,7 @@ addresses match what you expect.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
library Balances {
|
||||
function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal {
|
||||
|
@ -6,7 +6,7 @@ Safe Remote Purchase
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract Purchase {
|
||||
uint public value;
|
||||
|
@ -32,7 +32,7 @@ of votes.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
/// @title Voting with delegation.
|
||||
contract Ballot {
|
||||
|
@ -17,7 +17,7 @@ Storage
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract SimpleStorage {
|
||||
uint storedData;
|
||||
@ -35,7 +35,7 @@ The first line simply tells that the source code is written for
|
||||
Solidity version 0.4.0 or anything newer that does not break functionality
|
||||
(up to, but not including, version 0.6.0). This is to ensure that the
|
||||
contract is not compilable with a new (breaking) compiler version, where it could behave differently.
|
||||
So-called pragmas are common instructions for compilers about how to treat the
|
||||
:ref:`Pragmas<pragma>` are common instructions for compilers about how to treat the
|
||||
source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_).
|
||||
|
||||
A contract in the sense of Solidity is a collection of code (its *functions*) and
|
||||
@ -81,7 +81,7 @@ registering with username and password — all you need is an Ethereum keypair.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
contract Coin {
|
||||
// The keyword "public" makes those variables
|
||||
|
@ -13,11 +13,11 @@ and :ref:`pragma directives<pragma>`.
|
||||
Pragmas
|
||||
=======
|
||||
|
||||
The ``pragma`` keyword can be used to enable certain compiler features
|
||||
The ``pragma`` keyword is used to enable certain compiler features
|
||||
or checks. A pragma directive is always local to a source file, so
|
||||
you have to add the pragma to all your files if you want enable it
|
||||
in all of your project. If you :ref:`import<import>` another file, the pragma
|
||||
from that file will not automatically apply to the importing file.
|
||||
from that file does not automatically apply to the importing file.
|
||||
|
||||
.. index:: ! pragma, version
|
||||
|
||||
@ -26,34 +26,34 @@ from that file will not automatically apply to the importing file.
|
||||
Version Pragma
|
||||
--------------
|
||||
|
||||
Source files can (and should) be annotated with a so-called version pragma to reject
|
||||
being compiled with future compiler versions that might introduce incompatible
|
||||
changes. We try to keep such changes to an absolute minimum and especially
|
||||
introduce changes in a way that changes in semantics will also require changes
|
||||
in the syntax, but this is of course not always possible. Because of that, it is always
|
||||
Source files can (and should) be annotated with a version pragma to reject
|
||||
compilation with future compiler versions that might introduce incompatible
|
||||
changes. We try to keep these to an absolute minimum and
|
||||
introduce them in a way that changes in semantics also require changes
|
||||
in the syntax, but this is not always possible. Because of this, it is always
|
||||
a good idea to read through the changelog at least for releases that contain
|
||||
breaking changes, those releases will always have versions of the form
|
||||
breaking changes. These releases always have versions of the form
|
||||
``0.x.0`` or ``x.0.0``.
|
||||
|
||||
The version pragma is used as follows::
|
||||
|
||||
pragma solidity ^0.5.2;
|
||||
|
||||
Such a source file will not compile with a compiler earlier than version 0.5.2
|
||||
and it will also not work on a compiler starting from version 0.6.0 (this
|
||||
second condition is added by using ``^``). The idea behind this is that
|
||||
there will be no breaking changes until version ``0.6.0``, so we can always
|
||||
be sure that our code will compile the way we intended it to. We do not fix
|
||||
the exact version of the compiler, so that bugfix releases are still possible.
|
||||
A source file with the line above does not compile with a compiler earlier than version 0.5.2,
|
||||
and it also does not work on a compiler starting from version 0.6.0 (this
|
||||
second condition is added by using ``^``). This is because
|
||||
there will be no breaking changes until version ``0.6.0``, so you can always
|
||||
be sure that your code compiles the way you intended. The exact version of the
|
||||
compiler is not fixed, so that bugfix releases are still possible.
|
||||
|
||||
It is possible to specify much more complex rules for the compiler version,
|
||||
the expression follows those used by `npm <https://docs.npmjs.com/misc/semver>`_.
|
||||
It is possible to specify more complex rules for the compiler version,
|
||||
these follow the same syntax used by `npm <https://docs.npmjs.com/misc/semver>`_.
|
||||
|
||||
.. note::
|
||||
Using the version pragma will *not* change the version of the compiler.
|
||||
It will also *not* enable or disable features of the compiler. It will just
|
||||
instruct the compiler to check whether its version matches the one
|
||||
required by the pragma. If it does not match, the compiler will issue
|
||||
Using the version pragma *does not* change the version of the compiler.
|
||||
It also *does not* enable or disable features of the compiler. It just
|
||||
instructs the compiler to check whether its version matches the one
|
||||
required by the pragma. If it does not match, the compiler issues
|
||||
an error.
|
||||
|
||||
.. index:: ! pragma, experimental
|
||||
@ -96,7 +96,7 @@ The component does not yet support all features of the Solidity language
|
||||
and likely outputs many warnings. In case it reports unsupported
|
||||
features, the analysis may not be fully sound.
|
||||
|
||||
.. index:: source file, ! import
|
||||
.. index:: source file, ! import, module
|
||||
|
||||
.. _import:
|
||||
|
||||
@ -106,8 +106,8 @@ Importing other Source Files
|
||||
Syntax and Semantics
|
||||
--------------------
|
||||
|
||||
Solidity supports import statements that are very similar to those available in JavaScript
|
||||
(from ES6 on), although Solidity does not know the concept of a "default export".
|
||||
Solidity supports import statements to help modularise your code that are similar to those available in JavaScript
|
||||
(from ES6 on). However, Solidity does not support the concept of a `default export <https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export#Description>`_.
|
||||
|
||||
At a global level, you can use import statements of the following form:
|
||||
|
||||
@ -117,29 +117,21 @@ At a global level, you can use import statements of the following form:
|
||||
|
||||
This statement imports all global symbols from "filename" (and symbols imported there) into the
|
||||
current global scope (different than in ES6 but backwards-compatible for Solidity).
|
||||
This simple form is not recommended for use, because it pollutes the namespace in an
|
||||
unpredictable way: If you add new top-level items inside "filename", they will automatically
|
||||
This form is not recommended for use, because it unpredictably pollutes the namespace.
|
||||
If you add new top-level items inside "filename", they automatically
|
||||
appear in all files that import like this from "filename". It is better to import specific
|
||||
symbols explicitly.
|
||||
|
||||
The following example creates a new global symbol ``symbolName`` whose members are all
|
||||
the global symbols from ``"filename"``.
|
||||
the global symbols from ``"filename"``:
|
||||
|
||||
::
|
||||
|
||||
import * as symbolName from "filename";
|
||||
|
||||
If there is a naming collision, you can also rename symbols while importing.
|
||||
This code
|
||||
creates new global symbols ``alias`` and ``symbol2`` which reference ``symbol1`` and ``symbol2`` from inside ``"filename"``, respectively.
|
||||
which results in all global symbols being available in the format ``symbolName.symbol``.
|
||||
|
||||
::
|
||||
|
||||
import {symbol1 as alias, symbol2} from "filename";
|
||||
|
||||
|
||||
|
||||
Another syntax is not part of ES6, but probably convenient:
|
||||
A variant of this syntax that is not part of ES6, but possibly useful is:
|
||||
|
||||
::
|
||||
|
||||
@ -147,31 +139,37 @@ Another syntax is not part of ES6, but probably convenient:
|
||||
|
||||
which is equivalent to ``import * as symbolName from "filename";``.
|
||||
|
||||
.. note::
|
||||
If you use `import "filename.sol" as moduleName;`, you access a contract called `C`
|
||||
from inside `"filename.sol"` as `moduleName.C` and not by using `C` directly.
|
||||
If there is a naming collision, you can rename symbols while importing. For example,
|
||||
the code below creates new global symbols ``alias`` and ``symbol2`` which reference
|
||||
``symbol1`` and ``symbol2`` from inside ``"filename"``, respectively.
|
||||
|
||||
::
|
||||
|
||||
import {symbol1 as alias, symbol2} from "filename";
|
||||
|
||||
Paths
|
||||
-----
|
||||
|
||||
In the above, ``filename`` is always treated as a path with ``/`` as directory separator,
|
||||
``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``,
|
||||
and ``.`` as the current and ``..`` as the parent directory. When ``.`` or ``..`` is followed by a character except ``/``,
|
||||
it is not considered as the current or the parent directory.
|
||||
All path names are treated as absolute paths unless they start with the current ``.`` or the parent directory ``..``.
|
||||
|
||||
To import a file ``x`` from the same directory as the current file, use ``import "./x" as x;``.
|
||||
If you use ``import "x" as x;`` instead, a different file could be referenced
|
||||
To import a file ``filename`` from the same directory as the current file, use ``import "./filename" as symbolName;``.
|
||||
If you use ``import "filename" as symbolName;`` instead, a different file could be referenced
|
||||
(in a global "include directory").
|
||||
|
||||
It depends on the compiler (see below) how to actually resolve the paths.
|
||||
It depends on the compiler (see :ref:`import-compiler`) how to actually resolve the paths.
|
||||
In general, the directory hierarchy does not need to strictly map onto your local
|
||||
filesystem, it can also map to resources discovered via e.g. ipfs, http or git.
|
||||
filesystem, and the path can also map to resources such as ipfs, http or git.
|
||||
|
||||
.. note::
|
||||
Always use relative imports like ``import "./filename.sol";`` and avoid
|
||||
using ``..`` in path specifiers. In the latter case, it is probably better to use
|
||||
global paths and set up remappings as explained below.
|
||||
|
||||
.. _import-compiler:
|
||||
|
||||
Use in Actual Compilers
|
||||
-----------------------
|
||||
|
||||
@ -280,7 +278,7 @@ for the two function parameters and two return variables.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
/** @title Shape calculator. */
|
||||
contract ShapeCalculator {
|
||||
|
@ -62,7 +62,7 @@ non-elementary type, the positions are found by adding an offset of ``keccak256(
|
||||
|
||||
So for the following contract snippet::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
struct s { uint a; uint b; }
|
||||
|
@ -55,7 +55,7 @@ complete contract):
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
|
||||
contract Fund {
|
||||
@ -78,7 +78,7 @@ as it uses ``call`` which forwards all remaining gas by default:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
|
||||
contract Fund {
|
||||
@ -97,7 +97,7 @@ outlined further below:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.11 <0.6.0;
|
||||
pragma solidity >=0.4.11 <0.7.0;
|
||||
|
||||
contract Fund {
|
||||
/// Mapping of ether shares of the contract.
|
||||
@ -183,7 +183,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
|
||||
contract TxUserWallet {
|
||||
@ -203,7 +203,7 @@ Now someone tricks you into sending ether to the address of this attack wallet:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity ^0.5.0;
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
|
||||
interface TxUserWallet {
|
||||
function transferTo(address payable dest, uint amount) external;
|
||||
|
@ -26,7 +26,7 @@ storage.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract SimpleStorage {
|
||||
uint storedData; // State variable
|
||||
@ -46,7 +46,7 @@ Functions are the executable units of code within a contract.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract SimpleAuction {
|
||||
function bid() public payable { // Function
|
||||
@ -69,7 +69,7 @@ Function modifiers can be used to amend the semantics of functions in a declarat
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract Purchase {
|
||||
address public seller;
|
||||
@ -96,7 +96,7 @@ Events are convenience interfaces with the EVM logging facilities.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.21 <0.6.0;
|
||||
pragma solidity >=0.4.21 <0.7.0;
|
||||
|
||||
contract SimpleAuction {
|
||||
event HighestBidIncreased(address bidder, uint amount); // Event
|
||||
@ -120,7 +120,7 @@ Structs are custom defined types that can group several variables (see
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Ballot {
|
||||
struct Voter { // Struct
|
||||
@ -141,7 +141,7 @@ Enums can be used to create custom types with a finite set of 'constant values'
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Purchase {
|
||||
enum State { Created, Locked, Inactive } // Enum
|
||||
|
@ -52,7 +52,7 @@ Surround top level declarations in solidity source with two blank lines.
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
// ...
|
||||
@ -70,7 +70,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
// ...
|
||||
@ -89,7 +89,7 @@ Blank lines may be omitted between groups of related one-liners (such as stub fu
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
function spam() public pure;
|
||||
@ -109,7 +109,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
function spam() public pure {
|
||||
@ -237,7 +237,7 @@ Import statements should always be placed at the top of the file.
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
import "./Owned.sol";
|
||||
|
||||
@ -251,7 +251,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
// ...
|
||||
@ -283,7 +283,7 @@ Within a grouping, place the ``view`` and ``pure`` functions last.
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
constructor() public {
|
||||
@ -315,7 +315,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract A {
|
||||
|
||||
@ -411,7 +411,7 @@ should:
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Coin {
|
||||
struct Bank {
|
||||
@ -422,7 +422,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract Coin
|
||||
{
|
||||
@ -723,7 +723,7 @@ manner as modifiers if the function declaration is long or hard to read.
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// Base contracts just to make this compile
|
||||
contract B {
|
||||
@ -755,7 +755,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// Base contracts just to make this compile
|
||||
contract B {
|
||||
@ -971,7 +971,7 @@ As shown in the example below, if the contract name is `Congress` and the librar
|
||||
|
||||
Yes::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// Owned.sol
|
||||
contract Owned {
|
||||
@ -1000,7 +1000,7 @@ Yes::
|
||||
|
||||
No::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// owned.sol
|
||||
contract owned {
|
||||
@ -1104,7 +1104,7 @@ multiline comment starting with `/**` and ending with `*/`.
|
||||
For example, the contract from `a simple smart contract <simple-smart-contract>`_ with the comments
|
||||
added looks like the one below::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
/// @author The Solidity Team
|
||||
/// @title A simple storage example
|
||||
|
@ -34,7 +34,7 @@ each ``_KeyType``, recursively. For example with a mapping:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract MappingExample {
|
||||
mapping(address => uint) public balances;
|
||||
|
@ -27,7 +27,7 @@ value it referred to previously.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract DeleteExample {
|
||||
uint data;
|
||||
|
@ -49,7 +49,7 @@ Data locations are not only relevant for persistency of data, but also for the s
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
contract C {
|
||||
uint[] x; // the data location of x is storage
|
||||
@ -146,7 +146,7 @@ or create a new memory array and copy every element.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f(uint len) public pure {
|
||||
@ -175,7 +175,7 @@ In the example below, the type of ``[1, 2, 3]`` is
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract C {
|
||||
function f() public pure {
|
||||
@ -190,7 +190,7 @@ Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays,
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.0 <0.6.0;
|
||||
pragma solidity >=0.4.0 <0.7.0;
|
||||
|
||||
// This will not compile.
|
||||
contract C {
|
||||
@ -248,7 +248,7 @@ Array Members
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract ArrayContract {
|
||||
uint[2**20] m_aLotOfIntegers;
|
||||
@ -347,7 +347,7 @@ shown in the following example:
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.11 <0.6.0;
|
||||
pragma solidity >=0.4.11 <0.7.0;
|
||||
|
||||
contract CrowdFunding {
|
||||
// Defines a new type with two fields.
|
||||
|
@ -59,15 +59,16 @@ This means that, for example ``~int256(0) == int256(-1)``.
|
||||
Shifts
|
||||
^^^^^^
|
||||
|
||||
The result of a shift operation has the type of the left operand. The
|
||||
expression ``x << y`` is equivalent to ``x * 2**y``, and, for positive integers,
|
||||
``x >> y`` is equivalent to ``x / 2**y``. For negative ``x``, ``x >> y``
|
||||
is equivalent to dividing by a power of ``2`` while rounding down (towards negative infinity).
|
||||
Shifting by a negative amount throws a runtime exception.
|
||||
The result of a shift operation has the type of the left operand, truncating the result to match the type.
|
||||
|
||||
- For positive and negative ``x`` values, ``x << y`` is equivalent to ``x * 2**y``.
|
||||
- For positive ``x`` values, ``x >> y`` is equivalent to ``x / 2**y``.
|
||||
- For negative ``x`` values, ``x >> y`` is equivalent to ``(x + 1) / 2**y - 1`` (which is the same as dividing ``x`` by ``2**y`` while rounding down towards negative infinity).
|
||||
- In all cases, shifting by a negative ``y`` throws a runtime exception.
|
||||
|
||||
.. warning::
|
||||
Before version ``0.5.0`` a right shift ``x >> y`` for negative ``x`` was equivalent to ``x / 2**y``,
|
||||
i.e. right shifts used rounding towards zero instead of rounding towards negative infinity.
|
||||
i.e., right shifts used rounding up (towards zero) instead of rounding down (towards negative infinity).
|
||||
|
||||
Addition, Subtraction and Multiplication
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -517,7 +518,7 @@ subsequent unsigned integer values starting from ``0``.
|
||||
|
||||
::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract test {
|
||||
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
|
||||
@ -621,7 +622,7 @@ Public (or external) functions have the following members:
|
||||
|
||||
Example that shows how to use the members::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
contract Example {
|
||||
function f() public payable returns (bytes4) {
|
||||
@ -634,7 +635,7 @@ Example that shows how to use the members::
|
||||
|
||||
Example that shows how to use internal function types::
|
||||
|
||||
pragma solidity >=0.4.16 <0.6.0;
|
||||
pragma solidity >=0.4.16 <0.7.0;
|
||||
|
||||
library ArrayUtils {
|
||||
// internal functions can be used in internal library functions because
|
||||
@ -685,7 +686,7 @@ Example that shows how to use internal function types::
|
||||
|
||||
Another example that uses external function types::
|
||||
|
||||
pragma solidity >=0.4.22 <0.6.0;
|
||||
pragma solidity >=0.4.22 <0.7.0;
|
||||
|
||||
contract Oracle {
|
||||
struct Request {
|
||||
|
@ -163,6 +163,10 @@ Mathematical and Cryptographic Functions
|
||||
``keccak256(bytes memory) returns (bytes32)``:
|
||||
compute the Keccak-256 hash of the input
|
||||
|
||||
.. note::
|
||||
|
||||
There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0.
|
||||
|
||||
``sha256(bytes memory) returns (bytes32)``:
|
||||
compute the SHA-256 hash of the input
|
||||
|
||||
@ -182,13 +186,16 @@ Mathematical and Cryptographic Functions
|
||||
|
||||
For further details, read `example usage <https://ethereum.stackexchange.com/q/1777/222>`_.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you use ``ecrecover``, be aware that a valid signature can be turned into a different valid signature without requiring
|
||||
knowledge of the corresponding private key. This is usually not a problem unless you require signatures to be unique or
|
||||
use them to identify items. OpenZeppelin have a `ECDSA helper library <https://docs.openzeppelin.org/docs/cryptography_ecdsa>`_ that you can use as a wrapper for ``ecrecover`` without this issue.
|
||||
|
||||
.. note::
|
||||
|
||||
When running ``sha256``, ``ripemd160`` or ``ecrecover`` on a *private blockchain*, you might encounter Out-of-Gas. This is because these functions are implemented as "precompiled contracts" and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net.
|
||||
|
||||
.. note::
|
||||
There used to be an alias for ``keccak256`` called ``sha3``, which was removed in version 0.5.0.
|
||||
|
||||
.. index:: balance, send, transfer, call, callcode, delegatecall, staticcall
|
||||
|
||||
.. _address_related:
|
||||
|
@ -45,6 +45,14 @@ struct ApplyRule
|
||||
{
|
||||
};
|
||||
template <class Method>
|
||||
struct ApplyRule<Method, 4>
|
||||
{
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
|
||||
{
|
||||
return Method::applySimple(_in[0], _in[1], _in[2], _in[3], _out);
|
||||
}
|
||||
};
|
||||
template <class Method>
|
||||
struct ApplyRule<Method, 3>
|
||||
{
|
||||
static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator<AssemblyItems> _out)
|
||||
@ -197,6 +205,32 @@ struct SwapComparison: SimplePeepholeOptimizerMethod<SwapComparison, 2>
|
||||
}
|
||||
};
|
||||
|
||||
struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod<IsZeroIsZeroJumpI, 4>
|
||||
{
|
||||
static size_t applySimple(
|
||||
AssemblyItem const& _iszero1,
|
||||
AssemblyItem const& _iszero2,
|
||||
AssemblyItem const& _pushTag,
|
||||
AssemblyItem const& _jumpi,
|
||||
std::back_insert_iterator<AssemblyItems> _out
|
||||
)
|
||||
{
|
||||
if (
|
||||
_iszero1 == Instruction::ISZERO &&
|
||||
_iszero2 == Instruction::ISZERO &&
|
||||
_pushTag.type() == PushTag &&
|
||||
_jumpi == Instruction::JUMPI
|
||||
)
|
||||
{
|
||||
*_out = _pushTag;
|
||||
*_out = _jumpi;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct JumpToNext: SimplePeepholeOptimizerMethod<JumpToNext, 3>
|
||||
{
|
||||
static size_t applySimple(
|
||||
@ -320,7 +354,12 @@ bool PeepholeOptimiser::optimise()
|
||||
{
|
||||
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
|
||||
while (state.i < m_items.size())
|
||||
applyMethods(state, PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), JumpToNext(), UnreachableCode(), TagConjunctions(), TruthyAnd(), Identity());
|
||||
applyMethods(
|
||||
state,
|
||||
PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(),
|
||||
IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(),
|
||||
TagConjunctions(), TruthyAnd(), Identity()
|
||||
);
|
||||
if (m_optimisedItems.size() < m_items.size() || (
|
||||
m_optimisedItems.size() == m_items.size() && (
|
||||
eth::bytesRequired(m_optimisedItems, 3) < eth::bytesRequired(m_items, 3) ||
|
||||
|
@ -146,10 +146,12 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2(
|
||||
{{Instruction::SHR, {0, X}}, [=]{ return X; }, false},
|
||||
{{Instruction::SHL, {X, 0}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::SHR, {X, 0}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::LT, {X, 0}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::GT, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}; }, false},
|
||||
{{Instruction::LT, {0, X}}, [=]() -> Pattern { return {Instruction::ISZERO, {{Instruction::ISZERO, {X}}}}; }, false},
|
||||
{{Instruction::GT, {X, ~u256(0)}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::LT, {~u256(0), X}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::GT, {0, X}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::LT, {X, 0}}, [=]{ return u256(0); }, true},
|
||||
{{Instruction::AND, {{Instruction::BYTE, {X, Y}}, {u256(0xff)}}}, [=]() -> Pattern { return {Instruction::BYTE, {X, Y}}; }, false},
|
||||
{{Instruction::BYTE, {X, 31}}, [=]() -> Pattern { return {Instruction::AND, {X, u256(0xff)}}; }, false}
|
||||
};
|
||||
@ -349,14 +351,26 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7(
|
||||
rules.push_back({
|
||||
// SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X)
|
||||
{Instruction::SHL, {{B}, {Instruction::SHL, {{A}, {X}}}}},
|
||||
[=]() -> Pattern { return {Instruction::SHL, {std::min(A.d() + B.d(), u256(256)), X}}; },
|
||||
[=]() -> Pattern {
|
||||
bigint sum = bigint(A.d()) + B.d();
|
||||
if (sum >= 256)
|
||||
return {Instruction::AND, {X, u256(0)}};
|
||||
else
|
||||
return {Instruction::SHL, {u256(sum), X}};
|
||||
},
|
||||
false
|
||||
});
|
||||
|
||||
rules.push_back({
|
||||
// SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X)
|
||||
{Instruction::SHR, {{B}, {Instruction::SHR, {{A}, {X}}}}},
|
||||
[=]() -> Pattern { return {Instruction::SHR, {std::min(A.d() + B.d(), u256(256)), X}}; },
|
||||
[=]() -> Pattern {
|
||||
bigint sum = bigint(A.d()) + B.d();
|
||||
if (sum >= 256)
|
||||
return {Instruction::AND, {X, u256(0)}};
|
||||
else
|
||||
return {Instruction::SHR, {u256(sum), X}};
|
||||
},
|
||||
false
|
||||
});
|
||||
|
||||
|
@ -118,6 +118,12 @@ bool ErrorReporter::checkForExcessiveErrors(Error::Type _type)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
|
||||
{
|
||||
error(_type, _location, _secondaryLocation, _description);
|
||||
BOOST_THROW_EXCEPTION(FatalError());
|
||||
}
|
||||
|
||||
void ErrorReporter::fatalError(Error::Type _type, SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
error(_type, _location, _description);
|
||||
@ -207,6 +213,15 @@ void ErrorReporter::typeError(SourceLocation const& _location, string const& _de
|
||||
);
|
||||
}
|
||||
|
||||
void ErrorReporter::fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondaryLocation, string const& _description)
|
||||
{
|
||||
fatalError(
|
||||
Error::Type::TypeError,
|
||||
_location,
|
||||
_secondaryLocation,
|
||||
_description
|
||||
);
|
||||
}
|
||||
|
||||
void ErrorReporter::fatalTypeError(SourceLocation const& _location, string const& _description)
|
||||
{
|
||||
|
@ -98,12 +98,13 @@ public:
|
||||
|
||||
auto filterEmpty = boost::adaptors::filtered([](std::string const& _s) { return !_s.empty(); });
|
||||
|
||||
std::string errorStr = dev::joinHumanReadable(descs | filterEmpty);
|
||||
std::string errorStr = dev::joinHumanReadable(descs | filterEmpty, " ");
|
||||
|
||||
error(Error::Type::TypeError, _location, errorStr);
|
||||
}
|
||||
|
||||
void fatalTypeError(SourceLocation const& _location, std::string const& _description);
|
||||
void fatalTypeError(SourceLocation const& _location, SecondarySourceLocation const& _secondLocation, std::string const& _description);
|
||||
|
||||
void docstringParsingError(std::string const& _description);
|
||||
|
||||
@ -118,12 +119,20 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void error(Error::Type _type,
|
||||
void error(
|
||||
Error::Type _type,
|
||||
SourceLocation const& _location,
|
||||
SecondarySourceLocation const& _secondaryLocation,
|
||||
std::string const& _description = std::string());
|
||||
|
||||
void fatalError(Error::Type _type,
|
||||
void fatalError(
|
||||
Error::Type _type,
|
||||
SourceLocation const& _location,
|
||||
SecondarySourceLocation const& _secondaryLocation,
|
||||
std::string const& _description = std::string());
|
||||
|
||||
void fatalError(
|
||||
Error::Type _type,
|
||||
SourceLocation const& _location = SourceLocation(),
|
||||
std::string const& _description = std::string());
|
||||
|
||||
|
@ -63,6 +63,10 @@ set(sources
|
||||
codegen/ExpressionCompiler.h
|
||||
codegen/LValue.cpp
|
||||
codegen/LValue.h
|
||||
codegen/MultiUseYulFunctionCollector.h
|
||||
codegen/MultiUseYulFunctionCollector.cpp
|
||||
codegen/YulUtilFunctions.h
|
||||
codegen/YulUtilFunctions.cpp
|
||||
formal/SMTChecker.cpp
|
||||
formal/SMTChecker.h
|
||||
formal/SMTLib2Interface.cpp
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/analysis/TypeChecker.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
@ -44,6 +45,7 @@ bool ContractLevelChecker::check(ContractDefinition const& _contract)
|
||||
checkExternalTypeClashes(_contract);
|
||||
checkHashCollisions(_contract);
|
||||
checkLibraryRequirements(_contract);
|
||||
checkBaseABICompatibility(_contract);
|
||||
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
}
|
||||
@ -460,3 +462,50 @@ void ContractLevelChecker::checkLibraryRequirements(ContractDefinition const& _c
|
||||
if (!var->isConstant())
|
||||
m_errorReporter.typeError(var->location(), "Library cannot have non-constant state variables");
|
||||
}
|
||||
|
||||
void ContractLevelChecker::checkBaseABICompatibility(ContractDefinition const& _contract)
|
||||
{
|
||||
if (_contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
|
||||
return;
|
||||
|
||||
if (_contract.isLibrary())
|
||||
{
|
||||
solAssert(
|
||||
_contract.baseContracts().empty() || m_errorReporter.hasErrors(),
|
||||
"Library is not allowed to inherit"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
SecondarySourceLocation errors;
|
||||
|
||||
// interfaceFunctionList contains all inherited functions as well
|
||||
for (auto const& func: _contract.interfaceFunctionList())
|
||||
{
|
||||
solAssert(func.second->hasDeclaration(), "Function has no declaration?!");
|
||||
|
||||
if (!func.second->declaration().sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2))
|
||||
continue;
|
||||
|
||||
auto const& currentLoc = func.second->declaration().location();
|
||||
|
||||
for (TypePointer const& paramType: func.second->parameterTypes() + func.second->parameterTypes())
|
||||
if (!TypeChecker::typeSupportedByOldABIEncoder(*paramType, false))
|
||||
{
|
||||
errors.append("Type only supported by the new experimental ABI encoder", currentLoc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!errors.infos.empty())
|
||||
m_errorReporter.fatalTypeError(
|
||||
_contract.location(),
|
||||
errors,
|
||||
std::string("Contract \"") +
|
||||
_contract.name() +
|
||||
"\" does not use the new experimental ABI encoder but wants to inherit from a contract " +
|
||||
"which uses types that require it. " +
|
||||
"Use \"pragma experimental ABIEncoderV2;\" for the inheriting contract as well to enable the feature."
|
||||
);
|
||||
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ private:
|
||||
void checkHashCollisions(ContractDefinition const& _contract);
|
||||
/// Checks that all requirements for a library are fulfilled if this is a library.
|
||||
void checkLibraryRequirements(ContractDefinition const& _contract);
|
||||
/// Checks base contracts for ABI compatibility
|
||||
void checkBaseABICompatibility(ContractDefinition const& _contract);
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
};
|
||||
|
@ -45,10 +45,7 @@ using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall)
|
||||
bool TypeChecker::typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall)
|
||||
{
|
||||
if (_isLibraryCall && _type.dataStoredIn(DataLocation::Storage))
|
||||
return true;
|
||||
@ -64,9 +61,6 @@ bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall)
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool TypeChecker::checkTypeRequirements(ASTNode const& _contract)
|
||||
{
|
||||
_contract.accept(*this);
|
||||
@ -94,6 +88,7 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
|
||||
for (auto const& n: _contract.subNodes())
|
||||
n->accept(*this);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -376,28 +371,6 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
||||
};
|
||||
for (ASTPointer<VariableDeclaration> const& var: _function.parameters())
|
||||
{
|
||||
TypePointer baseType = type(*var);
|
||||
if (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
||||
{
|
||||
baseType = arrayType->baseType();
|
||||
if (
|
||||
!m_scope->isInterface() &&
|
||||
baseType->dataStoredIn(DataLocation::CallData) &&
|
||||
baseType->isDynamicallyEncoded()
|
||||
)
|
||||
m_errorReporter.typeError(var->location(), "Calldata arrays with dynamically encoded base types are not yet supported.");
|
||||
}
|
||||
while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
|
||||
baseType = arrayType->baseType();
|
||||
|
||||
if (
|
||||
!m_scope->isInterface() &&
|
||||
baseType->dataStoredIn(DataLocation::CallData)
|
||||
)
|
||||
if (auto const* structType = dynamic_cast<StructType const*>(baseType.get()))
|
||||
if (structType->isDynamicallyEncoded())
|
||||
m_errorReporter.typeError(var->location(), "Dynamically encoded calldata structs are not yet supported.");
|
||||
|
||||
checkArgumentAndReturnParameter(*var);
|
||||
var->accept(*this);
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ public:
|
||||
/// (this can happen for variables with non-explicit types before their types are resolved)
|
||||
TypePointer const& type(VariableDeclaration const& _variable) const;
|
||||
|
||||
static bool typeSupportedByOldABIEncoder(Type const& _type, bool _isLibraryCall);
|
||||
|
||||
private:
|
||||
|
||||
bool visit(ContractDefinition const& _contract) override;
|
||||
|
@ -94,6 +94,12 @@ public:
|
||||
(*this)(_for.body);
|
||||
(*this)(_for.post);
|
||||
}
|
||||
void operator()(yul::Break const&)
|
||||
{
|
||||
}
|
||||
void operator()(yul::Continue const&)
|
||||
{
|
||||
}
|
||||
void operator()(yul::Block const& _block)
|
||||
{
|
||||
for (auto const& s: _block.statements)
|
||||
|
@ -429,7 +429,6 @@ private:
|
||||
std::vector<ASTPointer<ASTNode>> m_subNodes;
|
||||
ContractKind m_contractKind;
|
||||
|
||||
std::vector<ContractDefinition const*> m_linearizedBaseContracts;
|
||||
mutable std::unique_ptr<std::vector<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
|
||||
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
||||
mutable std::unique_ptr<std::vector<Declaration const*>> m_inheritableMembers;
|
||||
@ -589,6 +588,7 @@ public:
|
||||
}
|
||||
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& parameters() const { return m_parameters->parameters(); }
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
|
||||
ParameterList const& parameterList() const { return *m_parameters; }
|
||||
ASTPointer<ParameterList> const& returnParameterList() const { return m_returnParameters; }
|
||||
|
||||
@ -629,7 +629,6 @@ public:
|
||||
bool isFallback() const { return !m_isConstructor && name().empty(); }
|
||||
bool isPayable() const { return m_stateMutability == StateMutability::Payable; }
|
||||
std::vector<ASTPointer<ModifierInvocation>> const& modifiers() const { return m_functionModifiers; }
|
||||
std::vector<ASTPointer<VariableDeclaration>> const& returnParameters() const { return m_returnParameters->parameters(); }
|
||||
Block const& body() const { solAssert(m_body, ""); return *m_body; }
|
||||
bool isVisibleInContract() const override
|
||||
{
|
||||
|
@ -126,9 +126,15 @@ bool fitsPrecisionBase2(bigint const& _mantissa, uint32_t _expBase2)
|
||||
}
|
||||
|
||||
/// Checks whether _value fits into IntegerType _type.
|
||||
bool fitsIntegerType(bigint const& _value, IntegerType const& _type)
|
||||
BoolResult fitsIntegerType(bigint const& _value, IntegerType const& _type)
|
||||
{
|
||||
return (_type.minValue() <= _value) && (_value <= _type.maxValue());
|
||||
if (_value < 0 && !_type.isSigned())
|
||||
return BoolResult{std::string("Cannot implicitly convert signed literal to unsigned type.")};
|
||||
|
||||
if (_type.minValue() > _value || _value > _type.maxValue())
|
||||
return BoolResult{"Literal is too large to fit in " + _type.toString(false) + "."};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks whether _value fits into _bits bits when having 1 bit as the sign bit
|
||||
@ -722,7 +728,9 @@ BoolResult FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) con
|
||||
if (_convertTo.category() == category())
|
||||
{
|
||||
FixedPointType const& convertTo = dynamic_cast<FixedPointType const&>(_convertTo);
|
||||
if (convertTo.numBits() < m_totalBits || convertTo.fractionalDigits() < m_fractionalDigits)
|
||||
if (convertTo.fractionalDigits() < m_fractionalDigits)
|
||||
return BoolResult{std::string("Too many fractional digits.")};
|
||||
if (convertTo.numBits() < m_totalBits)
|
||||
return false;
|
||||
else
|
||||
return convertTo.maxIntegerValue() >= maxIntegerValue() && convertTo.minIntegerValue() <= minIntegerValue();
|
||||
|
@ -22,7 +22,6 @@
|
||||
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
|
||||
@ -54,8 +53,6 @@ string ABIFunctions::tupleEncoder(
|
||||
functionName += options.toFunctionNameSuffix();
|
||||
|
||||
return createExternallyUsedFunction(functionName, [&]() {
|
||||
solAssert(!_givenTypes.empty(), "");
|
||||
|
||||
// Note that the values are in reverse due to the difference in calling semantics.
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(headStart <valueParams>) -> tail {
|
||||
@ -183,15 +180,13 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
if (_fromMemory)
|
||||
functionName += "_fromMemory";
|
||||
|
||||
solAssert(!_types.empty(), "");
|
||||
|
||||
return createExternallyUsedFunction(functionName, [&]() {
|
||||
TypePointers decodingTypes;
|
||||
for (auto const& t: _types)
|
||||
decodingTypes.emplace_back(t->decodingType());
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(headStart, dataEnd) -> <valueReturnParams> {
|
||||
function <functionName>(headStart, dataEnd) <arrow> <valueReturnParams> {
|
||||
if slt(sub(dataEnd, headStart), <minimumSize>) { revert(0, 0) }
|
||||
<decodeElements>
|
||||
}
|
||||
@ -242,6 +237,7 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
|
||||
}
|
||||
templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
|
||||
templ("arrow", valueReturnParams.empty() ? "" : "->");
|
||||
templ("decodeElements", decodeElements);
|
||||
|
||||
return templ.render();
|
||||
@ -250,11 +246,9 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
|
||||
pair<string, set<string>> ABIFunctions::requestedFunctions()
|
||||
{
|
||||
string result;
|
||||
for (auto const& f: m_requestedFunctions)
|
||||
result += f.second;
|
||||
m_requestedFunctions.clear();
|
||||
return make_pair(result, std::move(m_externallyUsedFunctions));
|
||||
std::set<string> empty;
|
||||
swap(empty, m_externallyUsedFunctions);
|
||||
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
|
||||
}
|
||||
|
||||
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||
@ -271,6 +265,7 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||
return suffix;
|
||||
}
|
||||
|
||||
|
||||
string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
||||
{
|
||||
string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||
@ -401,7 +396,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to);
|
||||
body =
|
||||
Whiskers("converted := <shiftLeft>(<clean>(value))")
|
||||
("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
||||
("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
||||
("clean", cleanupFunction(_from))
|
||||
.render();
|
||||
}
|
||||
@ -477,7 +472,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
if (toCategory == Type::Category::Integer)
|
||||
body =
|
||||
Whiskers("converted := <convert>(<shift>(value))")
|
||||
("shift", shiftRightFunction(256 - from.numBytes() * 8))
|
||||
("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8))
|
||||
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
|
||||
.render();
|
||||
else if (toCategory == Type::Category::Address)
|
||||
@ -541,40 +536,6 @@ string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::combineExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "combine_external_function_id";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(addr, selector) -> combined {
|
||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shl32", shiftLeftFunction(32))
|
||||
("shl64", shiftLeftFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::splitExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "split_external_function_id";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(combined) -> addr, selector {
|
||||
combined := <shr64>(combined)
|
||||
selector := and(combined, 0xffffffff)
|
||||
addr := <shr32>(combined)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr32", shiftRightFunction(32))
|
||||
("shr64", shiftRightFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunction(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
@ -655,7 +616,7 @@ string ABIFunctions::abiEncodingFunction(
|
||||
else
|
||||
cleanupConvert = conversionFunction(_from, to) + "(value)";
|
||||
if (!_options.padded)
|
||||
cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")";
|
||||
cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")";
|
||||
templ("cleanupConvert", cleanupConvert);
|
||||
}
|
||||
return templ.render();
|
||||
@ -745,8 +706,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("copyFun", copyToMemoryFunction(true));
|
||||
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(true));
|
||||
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -816,16 +777,16 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||
templ("lengthFun", arrayLengthFunction(_from));
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataAreaFun", arrayDataAreaFunction(_from));
|
||||
templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
|
||||
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||
templ("nextArrayElement", nextArrayElementFunction(_from));
|
||||
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -859,10 +820,10 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray(
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("lengthFun", arrayLengthFunction(_from));
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("copyFun", copyToMemoryFunction(false));
|
||||
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(false));
|
||||
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -920,7 +881,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
|
||||
templ("lengthPaddedLong", _options.padded ? "i" : "length");
|
||||
templ("arrayDataSlot", arrayDataAreaFunction(_from));
|
||||
templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from));
|
||||
return templ.render();
|
||||
}
|
||||
else
|
||||
@ -961,9 +922,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||
templ("lengthFun", arrayLengthFunction(_from));
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataArea", arrayDataAreaFunction(_from));
|
||||
templ("dataArea", m_utils.arrayDataAreaFunction(_from));
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
// We use padded size because array elements are always padded.
|
||||
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
||||
@ -980,7 +941,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("encodeToMemoryFun", encodeToMemoryFun);
|
||||
std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
|
||||
for (size_t i = 0; i < itemsPerSlot; ++i)
|
||||
items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8);
|
||||
items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8);
|
||||
templ("items", items);
|
||||
return templ.render();
|
||||
}
|
||||
@ -1066,7 +1027,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
members.back()["retrieveValue"] = shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
||||
members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1209,7 +1170,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("combineExtFun", combineExternalFunctionIdFunction())
|
||||
("combineExtFun", m_utils.combineExternalFunctionIdFunction())
|
||||
.render();
|
||||
});
|
||||
else
|
||||
@ -1251,7 +1212,6 @@ string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bo
|
||||
if (structType->dataStoredIn(DataLocation::CallData))
|
||||
{
|
||||
solAssert(!_fromMemory, "");
|
||||
solUnimplementedAssert(!structType->isDynamicallyEncoded(), "Dynamically encoded calldata structs are not yet implemented.");
|
||||
return abiDecodingFunctionCalldataStruct(*structType);
|
||||
}
|
||||
else
|
||||
@ -1331,8 +1291,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
||||
templ("allocate", allocationFunction());
|
||||
templ("allocationSize", arrayAllocationSizeFunction(_type));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
||||
if (_type.isDynamicallySized())
|
||||
templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
|
||||
else
|
||||
@ -1357,13 +1317,10 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
|
||||
string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
{
|
||||
// This does not work with arrays of complex types - the array access
|
||||
// is not yet implemented in Solidity.
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
if (!_type.isDynamicallySized())
|
||||
solAssert(_type.length() < u256("0xffffffffffffffff"), "");
|
||||
if (_type.baseType()->isDynamicallyEncoded())
|
||||
solUnimplemented("Calldata arrays with non-value base types are not yet supported by Solidity.");
|
||||
solAssert(_type.baseType()->calldataEncodedSize() > 0, "");
|
||||
solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
|
||||
|
||||
string functionName =
|
||||
@ -1379,7 +1336,7 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
length := calldataload(offset)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
arrayPos := add(offset, 0x20)
|
||||
if gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
if gt(add(arrayPos, mul(length, <baseEncodedSize>)), end) { revert(0, 0) }
|
||||
}
|
||||
)";
|
||||
else
|
||||
@ -1394,7 +1351,8 @@ string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
|
||||
w("functionName", functionName);
|
||||
w("readableTypeName", _type.toString(true));
|
||||
w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
|
||||
w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length()));
|
||||
if (!_type.isDynamicallySized())
|
||||
w("length", toCompactHexWithPrefix(_type.length()));
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
@ -1426,9 +1384,9 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
|
||||
);
|
||||
templ("functionName", functionName);
|
||||
templ("load", _fromMemory ? "mload" : "calldataload");
|
||||
templ("allocate", allocationFunction());
|
||||
templ("allocationSize", arrayAllocationSizeFunction(_type));
|
||||
templ("copyToMemFun", copyToMemoryFunction(!_fromMemory));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
||||
templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -1480,7 +1438,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("allocate", allocationFunction());
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
|
||||
templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
|
||||
size_t headPos = 0;
|
||||
@ -1540,7 +1498,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromMemory ? "mload" : "calldataload")
|
||||
("splitExtFun", splitExternalFunctionIdFunction())
|
||||
("splitExtFun", m_utils.splitExternalFunctionIdFunction())
|
||||
.render();
|
||||
}
|
||||
else
|
||||
@ -1558,357 +1516,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||
{
|
||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_fromCalldata)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
calldatacopy(dst, src, length)
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
let i := 0
|
||||
for { } lt(i, length) { i := add(i, 32) }
|
||||
{
|
||||
mstore(add(dst, i), mload(add(src, i)))
|
||||
}
|
||||
if gt(i, length)
|
||||
{
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::leftAlignFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("leftAlign_") + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> aligned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)");
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (type.numBits() == 256)
|
||||
templ("body", "aligned := value");
|
||||
else
|
||||
templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::RationalNumber:
|
||||
solAssert(false, "Left align requested for rational number.");
|
||||
break;
|
||||
case Type::Category::Bool:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)");
|
||||
break;
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
solAssert(false, "Left align requested for non-value type.");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
templ("body", "aligned := value");
|
||||
break;
|
||||
case Type::Category::Contract:
|
||||
templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)");
|
||||
break;
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
unsigned storageBytes = dynamic_cast<EnumType const&>(_type).storageBytes();
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
solAssert(false, "Left align requested for inaccessible dynamic type.");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::shiftLeftFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
string functionName = "shift_left_" + to_string(_numBits);
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shl(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := mul(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string ABIFunctions::shiftRightFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
// Note that if this is extended with signed shifts,
|
||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shr(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := div(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string ABIFunctions::roundUpFunction()
|
||||
{
|
||||
string functionName = "round_up_to_mul_of_32";
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> result {
|
||||
result := and(add(value, 31), not(31))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(value) -> length {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
string body;
|
||||
if (!_type.isDynamicallySized())
|
||||
body = "length := " + toCompactHexWithPrefix(_type.length());
|
||||
else
|
||||
{
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
solAssert(false, "called regular array length function on calldata array");
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
body = "length := mload(value)";
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
// Retrieve length both for in-place strings and off-place strings:
|
||||
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
|
||||
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
|
||||
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
|
||||
body = R"(
|
||||
length := sload(value)
|
||||
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
|
||||
length := div(and(length, mask), 2)
|
||||
)";
|
||||
}
|
||||
else
|
||||
body = "length := sload(value)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
solAssert(!body.empty(), "");
|
||||
w("body", body);
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
string functionName = "array_allocation_size_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(length) -> size {
|
||||
// Make sure we can allocate memory without overflow
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
size := <allocationSize>
|
||||
<addLengthSlot>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
if (_type.isByteArray())
|
||||
// Round up
|
||||
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
|
||||
else
|
||||
w("allocationSize", "mul(length, 0x20)");
|
||||
if (_type.isDynamicallySized())
|
||||
w("addLengthSlot", "size := add(size, 0x20)");
|
||||
else
|
||||
w("addLengthSlot", "");
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_dataslot_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.dataStoredIn(DataLocation::Memory))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := memPtr
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else if (_type.dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
mstore(0, slot)
|
||||
dataSlot := keccak256(0, 0x20)
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
dataSlot := slot
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not used for calldata
|
||||
solAssert(false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.location() == DataLocation::Storage,
|
||||
""
|
||||
);
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.baseType()->storageBytes() > 16,
|
||||
""
|
||||
);
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.location() == DataLocation::Memory)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> nextPtr {
|
||||
nextPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else if (_type.location() == DataLocation::Storage)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> nextSlot {
|
||||
nextSlot := add(slot, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
solAssert(false, "");
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
|
||||
@ -1933,34 +1540,9 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::allocationFunction()
|
||||
{
|
||||
string functionName = "allocateMemory";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(size) -> memPtr {
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
let newFreePtr := add(memPtr, size)
|
||||
// protect against overflow
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
|
||||
mstore(<freeMemoryPointer>, newFreePtr)
|
||||
}
|
||||
)")
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
||||
{
|
||||
if (!m_requestedFunctions.count(_name))
|
||||
{
|
||||
auto fun = _creator();
|
||||
solAssert(!fun.empty(), "");
|
||||
m_requestedFunctions[_name] = fun;
|
||||
}
|
||||
return _name;
|
||||
return m_functionCollector->createFunction(_name, _creator);
|
||||
}
|
||||
|
||||
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
|
||||
|
@ -22,7 +22,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <functional>
|
||||
@ -30,8 +32,10 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class Type;
|
||||
class ArrayType;
|
||||
@ -40,17 +44,25 @@ class FunctionType;
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
using TypePointers = std::vector<TypePointer>;
|
||||
|
||||
///
|
||||
/// Class to generate encoding and decoding functions. Also maintains a collection
|
||||
/// of "functions to be generated" in order to avoid generating the same function
|
||||
/// multiple times.
|
||||
///
|
||||
/// Make sure to include the result of ``requestedFunctions()`` to a block that
|
||||
/// is visible from the code that was generated here, or use named labels.
|
||||
/**
|
||||
* Class to generate encoding and decoding functions. Also maintains a collection
|
||||
* of "functions to be generated" in order to avoid generating the same function
|
||||
* multiple times.
|
||||
*
|
||||
* Make sure to include the result of ``requestedFunctions()`` to a block that
|
||||
* is visible from the code that was generated here, or use named labels.
|
||||
*/
|
||||
class ABIFunctions
|
||||
{
|
||||
public:
|
||||
explicit ABIFunctions(langutil::EVMVersion _evmVersion = langutil::EVMVersion{}) : m_evmVersion(_evmVersion) {}
|
||||
explicit ABIFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_functionCollector(std::move(_functionCollector)),
|
||||
m_utils(_evmVersion, m_functionCollector)
|
||||
{}
|
||||
|
||||
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes
|
||||
/// into memory, converting the types to @a _targetTypes on the fly.
|
||||
@ -129,14 +141,6 @@ private:
|
||||
|
||||
std::string cleanupCombinedExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that combines the address and selector to a single value
|
||||
/// for use in the ABI.
|
||||
std::string combineExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that splits the address and selector from a single value
|
||||
/// for use in the ABI.
|
||||
std::string splitExternalFunctionIdFunction();
|
||||
|
||||
/// @returns the name of the ABI encoding function with the given type
|
||||
/// and queues the generation of the function to the requested functions.
|
||||
/// @param _fromStack if false, the input value was just loaded from storage
|
||||
@ -229,34 +233,6 @@ private:
|
||||
/// Part of @a abiDecodingFunction for array types.
|
||||
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
|
||||
|
||||
/// @returns a function that copies raw bytes of dynamic length from calldata
|
||||
/// or memory to memory.
|
||||
/// Pads with zeros and might write more than exactly length.
|
||||
std::string copyToMemoryFunction(bool _fromCalldata);
|
||||
|
||||
/// @returns the name of a function that takes a (cleaned) value of the given value type and
|
||||
/// left-aligns it, usually for use in non-padded encoding.
|
||||
std::string leftAlignFunction(Type const& _type);
|
||||
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
/// @returns the name of a function that rounds its input to the next multiple
|
||||
/// of 32 or the input if it is a multiple of 32.
|
||||
std::string roundUpFunction();
|
||||
|
||||
std::string arrayLengthFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that computes the number of bytes required
|
||||
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||
/// The function reverts for too large lengths.
|
||||
std::string arrayAllocationSizeFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that converts a storage slot number
|
||||
/// or a memory pointer to the slot number / memory pointer for the data position of an array
|
||||
/// which is stored in that slot / memory area.
|
||||
std::string arrayDataAreaFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
||||
/// Only works for memory arrays and storage arrays that store one item per slot.
|
||||
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function used during encoding that stores the length
|
||||
/// if the array is dynamically sized (and the options do not request in-place encoding).
|
||||
/// It returns the new encoding position.
|
||||
@ -264,12 +240,6 @@ private:
|
||||
/// does nothing and just returns the position again.
|
||||
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options);
|
||||
|
||||
/// @returns the name of a function that allocates memory.
|
||||
/// Modifies the "free memory pointer"
|
||||
/// Arguments: size
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
/// Helper function that uses @a _creator to create a function and add it to
|
||||
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
|
||||
/// cases.
|
||||
@ -283,10 +253,10 @@ private:
|
||||
/// @returns the size of the static part of the encoding of the given types.
|
||||
static size_t headSize(TypePointers const& _targetTypes);
|
||||
|
||||
/// Map from function name to code for a multi-use function.
|
||||
std::map<std::string, std::string> m_requestedFunctions;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
YulUtilFunctions m_utils;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1030,7 +1030,7 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDept
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) const
|
||||
void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const
|
||||
{
|
||||
/// Stack: reference [length] index
|
||||
DataLocation location = _arrayType.location();
|
||||
@ -1050,28 +1050,41 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
|
||||
// stack: <base_ref> <index>
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: <index> <base_ref>
|
||||
switch (location)
|
||||
{
|
||||
case DataLocation::Memory:
|
||||
case DataLocation::CallData:
|
||||
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
|
||||
m_context << u256(32) << Instruction::ADD;
|
||||
|
||||
if (!_arrayType.isByteArray())
|
||||
{
|
||||
m_context << Instruction::SWAP1;
|
||||
if (location == DataLocation::CallData)
|
||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||
{
|
||||
if (_arrayType.baseType()->isDynamicallyEncoded())
|
||||
m_context << u256(0x20);
|
||||
else
|
||||
m_context << _arrayType.baseType()->calldataEncodedSize();
|
||||
}
|
||||
else
|
||||
m_context << u256(_arrayType.memoryHeadSize());
|
||||
m_context << Instruction::MUL;
|
||||
}
|
||||
// stack: <base_ref> <index * size>
|
||||
|
||||
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
|
||||
m_context << u256(32) << Instruction::ADD;
|
||||
|
||||
if (_keepReference)
|
||||
m_context << Instruction::DUP2;
|
||||
|
||||
m_context << Instruction::ADD;
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
{
|
||||
if (_keepReference)
|
||||
m_context << Instruction::DUP2;
|
||||
else
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: [<base_ref>] <index> <base_ref>
|
||||
|
||||
eth::AssemblyItem endTag = m_context.newTag();
|
||||
if (_arrayType.isByteArray())
|
||||
{
|
||||
|
@ -97,11 +97,13 @@ public:
|
||||
/// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef.
|
||||
/// If @a _arrayType is a byte array, takes tight coding into account.
|
||||
void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const;
|
||||
/// Performs bounds checking and returns a reference on the stack.
|
||||
/// Checks whether the index is out of range and returns the absolute offset of the element reference[index]
|
||||
/// (i.e. reference + index * size_of_base_type).
|
||||
/// If @a _keepReference is true, the base reference to the beginning of the array is kept on the stack.
|
||||
/// Stack pre: reference [length] index
|
||||
/// Stack post (storage): storage_slot byte_offset
|
||||
/// Stack post: memory/calldata_offset
|
||||
void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true) const;
|
||||
/// Stack post (storage): [reference] storage_slot byte_offset
|
||||
/// Stack post: [reference] memory/calldata_offset
|
||||
void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const;
|
||||
|
||||
private:
|
||||
/// Adds the given number of bytes to a storage byte offset counter and also increments
|
||||
|
@ -97,6 +97,53 @@ void CompilerUtils::revertWithStringData(Type const& _argumentType)
|
||||
m_context << Instruction::REVERT;
|
||||
}
|
||||
|
||||
void CompilerUtils::accessCalldataTail(Type const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::CallData), "");
|
||||
|
||||
unsigned int baseEncodedSize = _type.calldataEncodedSize();
|
||||
solAssert(baseEncodedSize > 1, "");
|
||||
|
||||
// returns the absolute offset of the tail in "base_ref"
|
||||
m_context.appendInlineAssembly(Whiskers(R"({
|
||||
let rel_offset_of_tail := calldataload(ptr_to_tail)
|
||||
if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(<neededLength>, 1)))) { revert(0, 0) }
|
||||
base_ref := add(base_ref, rel_offset_of_tail)
|
||||
})")("neededLength", toCompactHexWithPrefix(baseEncodedSize)).render(), {"base_ref", "ptr_to_tail"});
|
||||
// stack layout: <absolute_offset_of_tail> <garbage>
|
||||
|
||||
if (!_type.isDynamicallySized())
|
||||
{
|
||||
m_context << Instruction::POP;
|
||||
// stack layout: <absolute_offset_of_tail>
|
||||
solAssert(
|
||||
_type.category() == Type::Category::Struct ||
|
||||
_type.category() == Type::Category::Array,
|
||||
"Invalid dynamically encoded base type on tail access."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const* arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||
solAssert(!!arrayType, "Invalid dynamically sized type.");
|
||||
unsigned int calldataStride = arrayType->calldataStride();
|
||||
solAssert(calldataStride > 0, "");
|
||||
|
||||
// returns the absolute offset of the tail in "base_ref"
|
||||
// and the length of the tail in "length"
|
||||
m_context.appendInlineAssembly(
|
||||
Whiskers(R"({
|
||||
length := calldataload(base_ref)
|
||||
base_ref := add(base_ref, 0x20)
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
if sgt(base_ref, sub(calldatasize(), mul(length, <calldataStride>))) { revert(0, 0) }
|
||||
})")("calldataStride", toCompactHexWithPrefix(calldataStride)).render(),
|
||||
{"base_ref", "length"}
|
||||
);
|
||||
// stack layout: <absolute_offset_of_tail> <length>
|
||||
}
|
||||
}
|
||||
|
||||
unsigned CompilerUtils::loadFromMemory(
|
||||
unsigned _offset,
|
||||
Type const& _type,
|
||||
|
@ -65,6 +65,13 @@ public:
|
||||
/// Stack post:
|
||||
void revertWithStringData(Type const& _argumentType);
|
||||
|
||||
/// Computes the absolute calldata offset of a tail given a base reference and the (absolute)
|
||||
/// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also
|
||||
/// returns the array length on the stack.
|
||||
/// Stack pre: base_ref tail_ptr
|
||||
/// Stack post: tail_ref [length]
|
||||
void accessCalldataTail(Type const& _type);
|
||||
|
||||
/// Loads data from memory to the stack.
|
||||
/// @param _offset offset in memory (or calldata)
|
||||
/// @param _type data type to load
|
||||
|
@ -1424,20 +1424,28 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
||||
}
|
||||
case DataLocation::CallData:
|
||||
{
|
||||
solUnimplementedAssert(!type.isDynamicallyEncoded(), "");
|
||||
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||
// For non-value types the calldata offset is returned directly.
|
||||
if (_memberAccess.annotation().type->isValueType())
|
||||
if (_memberAccess.annotation().type->isDynamicallyEncoded())
|
||||
{
|
||||
solAssert(_memberAccess.annotation().type->calldataEncodedSize(false) > 0, "");
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false);
|
||||
m_context << Instruction::DUP1;
|
||||
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||
CompilerUtils(m_context).accessCalldataTail(*_memberAccess.annotation().type);
|
||||
}
|
||||
else
|
||||
solAssert(
|
||||
_memberAccess.annotation().type->category() == Type::Category::Array ||
|
||||
_memberAccess.annotation().type->category() == Type::Category::Struct,
|
||||
""
|
||||
);
|
||||
{
|
||||
m_context << type.calldataOffsetOfMember(member) << Instruction::ADD;
|
||||
// For non-value types the calldata offset is returned directly.
|
||||
if (_memberAccess.annotation().type->isValueType())
|
||||
{
|
||||
solAssert(_memberAccess.annotation().type->calldataEncodedSize() > 0, "");
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(*_memberAccess.annotation().type, true, true, false);
|
||||
}
|
||||
else
|
||||
solAssert(
|
||||
_memberAccess.annotation().type->category() == Type::Category::Array ||
|
||||
_memberAccess.annotation().type->category() == Type::Category::Struct,
|
||||
""
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -1551,10 +1559,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
_indexAccess.indexExpression()->accept(*this);
|
||||
utils().convertType(*_indexAccess.indexExpression()->annotation().type, IntegerType::uint256(), true);
|
||||
// stack layout: <base_ref> [<length>] <index>
|
||||
ArrayUtils(m_context).accessIndex(arrayType);
|
||||
switch (arrayType.location())
|
||||
{
|
||||
case DataLocation::Storage:
|
||||
ArrayUtils(m_context).accessIndex(arrayType);
|
||||
if (arrayType.isByteArray())
|
||||
{
|
||||
solAssert(!arrayType.isString(), "Index access to string is not allowed.");
|
||||
@ -1564,18 +1572,36 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
setLValueToStorageItem(_indexAccess);
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
ArrayUtils(m_context).accessIndex(arrayType);
|
||||
setLValue<MemoryItem>(_indexAccess, *_indexAccess.annotation().type, !arrayType.isByteArray());
|
||||
break;
|
||||
case DataLocation::CallData:
|
||||
//@todo if we implement this, the value in calldata has to be added to the base offset
|
||||
solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented.");
|
||||
if (arrayType.baseType()->isValueType())
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(
|
||||
*arrayType.baseType(),
|
||||
true,
|
||||
!arrayType.isByteArray(),
|
||||
false
|
||||
);
|
||||
if (arrayType.baseType()->isDynamicallyEncoded())
|
||||
{
|
||||
// stack layout: <base_ref> <length> <index>
|
||||
ArrayUtils(m_context).accessIndex(arrayType, true, true);
|
||||
// stack layout: <base_ref> <ptr_to_tail>
|
||||
|
||||
CompilerUtils(m_context).accessCalldataTail(*arrayType.baseType());
|
||||
// stack layout: <tail_ref> [length]
|
||||
}
|
||||
else
|
||||
{
|
||||
ArrayUtils(m_context).accessIndex(arrayType, true);
|
||||
if (arrayType.baseType()->isValueType())
|
||||
CompilerUtils(m_context).loadFromMemoryDynamic(
|
||||
*arrayType.baseType(),
|
||||
true,
|
||||
!arrayType.isByteArray(),
|
||||
false
|
||||
);
|
||||
else
|
||||
solAssert(
|
||||
arrayType.baseType()->category() == Type::Category::Struct ||
|
||||
arrayType.baseType()->category() == Type::Category::Array,
|
||||
"Invalid statically sized non-value base type on array access."
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
52
libsolidity/codegen/MultiUseYulFunctionCollector.cpp
Normal file
52
libsolidity/codegen/MultiUseYulFunctionCollector.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Container of (unparsed) Yul functions identified by name which are meant to be generated
|
||||
* only once.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
string MultiUseYulFunctionCollector::requestedFunctions()
|
||||
{
|
||||
string result;
|
||||
for (auto const& f: m_requestedFunctions)
|
||||
result += f.second;
|
||||
m_requestedFunctions.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
string MultiUseYulFunctionCollector::createFunction(string const& _name, function<string ()> const& _creator)
|
||||
{
|
||||
if (!m_requestedFunctions.count(_name))
|
||||
{
|
||||
string fun = _creator();
|
||||
solAssert(!fun.empty(), "");
|
||||
solAssert(fun.find("function " + _name) != string::npos, "Function not properly named.");
|
||||
m_requestedFunctions[_name] = std::move(fun);
|
||||
}
|
||||
return _name;
|
||||
}
|
56
libsolidity/codegen/MultiUseYulFunctionCollector.h
Normal file
56
libsolidity/codegen/MultiUseYulFunctionCollector.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Container of (unparsed) Yul functions identified by name which are meant to be generated
|
||||
* only once.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
/**
|
||||
* Container of (unparsed) Yul functions identified by name which are meant to be generated
|
||||
* only once.
|
||||
*/
|
||||
class MultiUseYulFunctionCollector
|
||||
{
|
||||
public:
|
||||
/// Helper function that uses @a _creator to create a function and add it to
|
||||
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
|
||||
/// cases.
|
||||
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
||||
|
||||
/// @returns concatenation of all generated functions.
|
||||
/// Clears the internal list, i.e. calling it again will result in an
|
||||
/// empty return value.
|
||||
std::string requestedFunctions();
|
||||
|
||||
private:
|
||||
/// Map from function name to code for a multi-use function.
|
||||
std::map<std::string, std::string> m_requestedFunctions;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
438
libsolidity/codegen/YulUtilFunctions.cpp
Normal file
438
libsolidity/codegen/YulUtilFunctions.cpp
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "combine_external_function_id";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(addr, selector) -> combined {
|
||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shl32", shiftLeftFunction(32))
|
||||
("shl64", shiftLeftFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "split_external_function_id";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(combined) -> addr, selector {
|
||||
combined := <shr64>(combined)
|
||||
selector := and(combined, 0xffffffff)
|
||||
addr := <shr32>(combined)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr32", shiftRightFunction(32))
|
||||
("shr64", shiftRightFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||
{
|
||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_fromCalldata)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
calldatacopy(dst, src, length)
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
let i := 0
|
||||
for { } lt(i, length) { i := add(i, 32) }
|
||||
{
|
||||
mstore(add(dst, i), mload(add(src, i)))
|
||||
}
|
||||
if gt(i, length)
|
||||
{
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("leftAlign_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> aligned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)");
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (type.numBits() == 256)
|
||||
templ("body", "aligned := value");
|
||||
else
|
||||
templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::RationalNumber:
|
||||
solAssert(false, "Left align requested for rational number.");
|
||||
break;
|
||||
case Type::Category::Bool:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)");
|
||||
break;
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
solAssert(false, "Left align requested for non-value type.");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
templ("body", "aligned := value");
|
||||
break;
|
||||
case Type::Category::Contract:
|
||||
templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)");
|
||||
break;
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
unsigned storageBytes = dynamic_cast<EnumType const&>(_type).storageBytes();
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
solAssert(false, "Left align requested for inaccessible dynamic type.");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
string functionName = "shift_left_" + to_string(_numBits);
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shl(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := mul(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string YulUtilFunctions::shiftRightFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
// Note that if this is extended with signed shifts,
|
||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shr(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := div(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string YulUtilFunctions::roundUpFunction()
|
||||
{
|
||||
string functionName = "round_up_to_mul_of_32";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> result {
|
||||
result := and(add(value, 31), not(31))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(value) -> length {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
string body;
|
||||
if (!_type.isDynamicallySized())
|
||||
body = "length := " + toCompactHexWithPrefix(_type.length());
|
||||
else
|
||||
{
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
solAssert(false, "called regular array length function on calldata array");
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
body = "length := mload(value)";
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
// Retrieve length both for in-place strings and off-place strings:
|
||||
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
|
||||
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
|
||||
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
|
||||
body = R"(
|
||||
length := sload(value)
|
||||
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
|
||||
length := div(and(length, mask), 2)
|
||||
)";
|
||||
}
|
||||
else
|
||||
body = "length := sload(value)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
solAssert(!body.empty(), "");
|
||||
w("body", body);
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
string functionName = "array_allocation_size_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(length) -> size {
|
||||
// Make sure we can allocate memory without overflow
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
size := <allocationSize>
|
||||
<addLengthSlot>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
if (_type.isByteArray())
|
||||
// Round up
|
||||
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
|
||||
else
|
||||
w("allocationSize", "mul(length, 0x20)");
|
||||
if (_type.isDynamicallySized())
|
||||
w("addLengthSlot", "size := add(size, 0x20)");
|
||||
else
|
||||
w("addLengthSlot", "");
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_dataslot_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.dataStoredIn(DataLocation::Memory))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := memPtr
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else if (_type.dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
mstore(0, slot)
|
||||
dataSlot := keccak256(0, 0x20)
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
dataSlot := slot
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not used for calldata
|
||||
solAssert(false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.location() == DataLocation::Storage,
|
||||
""
|
||||
);
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.baseType()->storageBytes() > 16,
|
||||
""
|
||||
);
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.location() == DataLocation::Memory)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> nextPtr {
|
||||
nextPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else if (_type.location() == DataLocation::Storage)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> nextSlot {
|
||||
nextSlot := add(slot, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
solAssert(false, "");
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::allocationFunction()
|
||||
{
|
||||
string functionName = "allocateMemory";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(size) -> memPtr {
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
let newFreePtr := add(memPtr, size)
|
||||
// protect against overflow
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
|
||||
mstore(<freeMemoryPointer>, newFreePtr)
|
||||
}
|
||||
)")
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
100
libsolidity/codegen/YulUtilFunctions.h
Normal file
100
libsolidity/codegen/YulUtilFunctions.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class Type;
|
||||
class ArrayType;
|
||||
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
*/
|
||||
class YulUtilFunctions
|
||||
{
|
||||
public:
|
||||
explicit YulUtilFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_functionCollector(std::move(_functionCollector))
|
||||
{}
|
||||
|
||||
/// @returns a function that combines the address and selector to a single value
|
||||
/// for use in the ABI.
|
||||
std::string combineExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that splits the address and selector from a single value
|
||||
/// for use in the ABI.
|
||||
std::string splitExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that copies raw bytes of dynamic length from calldata
|
||||
/// or memory to memory.
|
||||
/// Pads with zeros and might write more than exactly length.
|
||||
std::string copyToMemoryFunction(bool _fromCalldata);
|
||||
|
||||
/// @returns the name of a function that takes a (cleaned) value of the given value type and
|
||||
/// left-aligns it, usually for use in non-padded encoding.
|
||||
std::string leftAlignFunction(Type const& _type);
|
||||
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
/// @returns the name of a function that rounds its input to the next multiple
|
||||
/// of 32 or the input if it is a multiple of 32.
|
||||
std::string roundUpFunction();
|
||||
|
||||
std::string arrayLengthFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that computes the number of bytes required
|
||||
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||
/// The function reverts for too large lengths.
|
||||
std::string arrayAllocationSizeFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that converts a storage slot number
|
||||
/// or a memory pointer to the slot number / memory pointer for the data position of an array
|
||||
/// which is stored in that slot / memory area.
|
||||
std::string arrayDataAreaFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
||||
/// Only works for memory arrays and storage arrays that store one item per slot.
|
||||
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that allocates memory.
|
||||
/// Modifies the "free memory pointer"
|
||||
/// Arguments: size
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
@ -374,6 +374,9 @@ void SMTChecker::checkOverflow(OverflowTarget& _target)
|
||||
|
||||
void SMTChecker::endVisit(UnaryOperation const& _op)
|
||||
{
|
||||
if (_op.annotation().type->category() == Type::Category::RationalNumber)
|
||||
return;
|
||||
|
||||
switch (_op.getOperator())
|
||||
{
|
||||
case Token::Not: // !
|
||||
@ -431,8 +434,21 @@ void SMTChecker::endVisit(UnaryOperation const& _op)
|
||||
}
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(UnaryOperation const& _op)
|
||||
{
|
||||
return !shortcutRationalNumber(_op);
|
||||
}
|
||||
|
||||
bool SMTChecker::visit(BinaryOperation const& _op)
|
||||
{
|
||||
return !shortcutRationalNumber(_op);
|
||||
}
|
||||
|
||||
void SMTChecker::endVisit(BinaryOperation const& _op)
|
||||
{
|
||||
if (_op.annotation().type->category() == Type::Category::RationalNumber)
|
||||
return;
|
||||
|
||||
if (TokenTraits::isArithmeticOp(_op.getOperator()))
|
||||
arithmeticOperation(_op);
|
||||
else if (TokenTraits::isCompareOp(_op.getOperator()))
|
||||
@ -535,13 +551,6 @@ void SMTChecker::visitGasLeft(FunctionCall const& _funCall)
|
||||
m_interface->addAssertion(symbolicVar->currentValue() <= symbolicVar->valueAtIndex(index - 1));
|
||||
}
|
||||
|
||||
void SMTChecker::eraseArrayKnowledge()
|
||||
{
|
||||
for (auto const& var: m_variables)
|
||||
if (var.first->annotation().type->category() == Type::Category::Mapping)
|
||||
newValue(*var.first);
|
||||
}
|
||||
|
||||
void SMTChecker::inlineFunctionCall(FunctionCall const& _funCall)
|
||||
{
|
||||
FunctionDefinition const* _funDef = nullptr;
|
||||
@ -698,17 +707,26 @@ void SMTChecker::endVisit(Literal const& _literal)
|
||||
solAssert(_literal.annotation().type, "Expected type for AST node");
|
||||
Type const& type = *_literal.annotation().type;
|
||||
if (isNumber(type.category()))
|
||||
|
||||
defineExpr(_literal, smt::Expression(type.literalValue(&_literal)));
|
||||
else if (isBool(type.category()))
|
||||
defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false));
|
||||
else
|
||||
{
|
||||
if (type.category() == Type::Category::StringLiteral)
|
||||
{
|
||||
auto stringType = make_shared<ArrayType>(DataLocation::Memory, true);
|
||||
auto stringLit = dynamic_cast<StringLiteralType const*>(_literal.annotation().type.get());
|
||||
solAssert(stringLit, "");
|
||||
auto result = newSymbolicVariable(*stringType, stringLit->richIdentifier(), *m_interface);
|
||||
m_expressions.emplace(&_literal, result.second);
|
||||
}
|
||||
m_errorReporter.warning(
|
||||
_literal.location(),
|
||||
"Assertion checker does not yet support the type of this literal (" +
|
||||
_literal.annotation().type->toString() +
|
||||
")."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void SMTChecker::endVisit(Return const& _return)
|
||||
@ -734,9 +752,9 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess)
|
||||
|
||||
auto const& exprType = _memberAccess.expression().annotation().type;
|
||||
solAssert(exprType, "");
|
||||
auto identifier = dynamic_cast<Identifier const*>(&_memberAccess.expression());
|
||||
if (exprType->category() == Type::Category::Magic)
|
||||
{
|
||||
auto identifier = dynamic_cast<Identifier const*>(&_memberAccess.expression());
|
||||
string accessedName;
|
||||
if (identifier)
|
||||
accessedName = identifier->name();
|
||||
@ -748,12 +766,23 @@ bool SMTChecker::visit(MemberAccess const& _memberAccess)
|
||||
defineGlobalVariable(accessedName + "." + _memberAccess.memberName(), _memberAccess);
|
||||
return false;
|
||||
}
|
||||
else if (exprType->category() == Type::Category::TypeType)
|
||||
{
|
||||
if (identifier && dynamic_cast<EnumDefinition const*>(identifier->annotation().referencedDeclaration))
|
||||
{
|
||||
auto enumType = dynamic_cast<EnumType const*>(accessType.get());
|
||||
solAssert(enumType, "");
|
||||
defineExpr(_memberAccess, enumType->memberValue(_memberAccess.memberName()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
m_errorReporter.warning(
|
||||
_memberAccess.location(),
|
||||
"Assertion checker does not yet support this expression."
|
||||
);
|
||||
|
||||
createExpr(_memberAccess);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -796,7 +825,6 @@ void SMTChecker::endVisit(IndexAccess const& _indexAccess)
|
||||
void SMTChecker::arrayAssignment()
|
||||
{
|
||||
m_arrayAssignmentHappened = true;
|
||||
eraseArrayKnowledge();
|
||||
}
|
||||
|
||||
void SMTChecker::arrayIndexAssignment(Assignment const& _assignment)
|
||||
@ -806,12 +834,48 @@ void SMTChecker::arrayIndexAssignment(Assignment const& _assignment)
|
||||
{
|
||||
auto const& varDecl = dynamic_cast<VariableDeclaration const&>(*id->annotation().referencedDeclaration);
|
||||
solAssert(knownVariable(varDecl), "");
|
||||
|
||||
if (varDecl.hasReferenceOrMappingType())
|
||||
resetVariables([&](VariableDeclaration const& _var) {
|
||||
if (_var == varDecl)
|
||||
return false;
|
||||
TypePointer prefix = _var.type();
|
||||
TypePointer originalType = typeWithoutPointer(varDecl.type());
|
||||
while (
|
||||
prefix->category() == Type::Category::Mapping ||
|
||||
prefix->category() == Type::Category::Array
|
||||
)
|
||||
{
|
||||
if (*originalType == *typeWithoutPointer(prefix))
|
||||
return true;
|
||||
if (prefix->category() == Type::Category::Mapping)
|
||||
{
|
||||
auto mapPrefix = dynamic_cast<MappingType const*>(prefix.get());
|
||||
solAssert(mapPrefix, "");
|
||||
prefix = mapPrefix->valueType();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto arrayPrefix = dynamic_cast<ArrayType const*>(prefix.get());
|
||||
solAssert(arrayPrefix, "");
|
||||
prefix = arrayPrefix->baseType();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
smt::Expression store = smt::Expression::store(
|
||||
m_variables[&varDecl]->currentValue(),
|
||||
expr(*indexAccess.indexExpression()),
|
||||
expr(_assignment.rightHandSide())
|
||||
);
|
||||
m_interface->addAssertion(newValue(varDecl) == store);
|
||||
// Update the SMT select value after the assignment,
|
||||
// necessary for sound models.
|
||||
defineExpr(indexAccess, smt::Expression::select(
|
||||
m_variables[&varDecl]->currentValue(),
|
||||
expr(*indexAccess.indexExpression())
|
||||
));
|
||||
}
|
||||
else if (dynamic_cast<IndexAccess const*>(&indexAccess.baseExpression()))
|
||||
m_errorReporter.warning(
|
||||
@ -860,6 +924,21 @@ void SMTChecker::defineGlobalFunction(string const& _name, Expression const& _ex
|
||||
}
|
||||
}
|
||||
|
||||
bool SMTChecker::shortcutRationalNumber(Expression const& _expr)
|
||||
{
|
||||
if (_expr.annotation().type->category() == Type::Category::RationalNumber)
|
||||
{
|
||||
auto rationalType = dynamic_cast<RationalNumberType const*>(_expr.annotation().type.get());
|
||||
solAssert(rationalType, "");
|
||||
if (rationalType->isNegative())
|
||||
defineExpr(_expr, smt::Expression(u2s(rationalType->literalValue(nullptr))));
|
||||
else
|
||||
defineExpr(_expr, smt::Expression(rationalType->literalValue(nullptr)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
|
||||
{
|
||||
switch (_op.getOperator())
|
||||
@ -1310,6 +1389,13 @@ void SMTChecker::resetVariables(function<bool(VariableDeclaration const&)> const
|
||||
});
|
||||
}
|
||||
|
||||
TypePointer SMTChecker::typeWithoutPointer(TypePointer const& _type)
|
||||
{
|
||||
if (auto refType = dynamic_cast<ReferenceType const*>(_type.get()))
|
||||
return ReferenceType::copyForLocationIfReference(refType->location(), _type);
|
||||
return _type;
|
||||
}
|
||||
|
||||
void SMTChecker::mergeVariables(vector<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse)
|
||||
{
|
||||
set<VariableDeclaration const*> uniqueVars(_variables.begin(), _variables.end());
|
||||
|
@ -71,7 +71,9 @@ private:
|
||||
void endVisit(VariableDeclarationStatement const& _node) override;
|
||||
void endVisit(Assignment const& _node) override;
|
||||
void endVisit(TupleExpression const& _node) override;
|
||||
bool visit(UnaryOperation const& _node) override;
|
||||
void endVisit(UnaryOperation const& _node) override;
|
||||
bool visit(BinaryOperation const& _node) override;
|
||||
void endVisit(BinaryOperation const& _node) override;
|
||||
void endVisit(FunctionCall const& _node) override;
|
||||
void endVisit(Identifier const& _node) override;
|
||||
@ -80,6 +82,9 @@ private:
|
||||
bool visit(MemberAccess const& _node) override;
|
||||
void endVisit(IndexAccess const& _node) override;
|
||||
|
||||
/// Do not visit subtree if node is a RationalNumber.
|
||||
/// Symbolic _expr is the rational literal.
|
||||
bool shortcutRationalNumber(Expression const& _expr);
|
||||
void arithmeticOperation(BinaryOperation const& _op);
|
||||
void compareOperation(BinaryOperation const& _op);
|
||||
void booleanOperation(BinaryOperation const& _op);
|
||||
@ -103,8 +108,6 @@ private:
|
||||
void arrayAssignment();
|
||||
/// Handles assignment to SMT array index.
|
||||
void arrayIndexAssignment(Assignment const& _assignment);
|
||||
/// Erases information about SMT arrays.
|
||||
void eraseArrayKnowledge();
|
||||
|
||||
/// Division expression in the given type. Requires special treatment because
|
||||
/// of rounding for signed division.
|
||||
@ -177,6 +180,9 @@ private:
|
||||
void resetStorageReferences();
|
||||
void resetVariables(std::vector<VariableDeclaration const*> _variables);
|
||||
void resetVariables(std::function<bool(VariableDeclaration const&)> const& _filter);
|
||||
/// @returns the type without storage pointer information if it has it.
|
||||
TypePointer typeWithoutPointer(TypePointer const& _type);
|
||||
|
||||
/// Given two different branches and the touched variables,
|
||||
/// merge the touched variables into after-branch ite variables
|
||||
/// using the branch condition as guard.
|
||||
|
@ -118,6 +118,7 @@ public:
|
||||
explicit Expression(bool _v): Expression(_v ? "true" : "false", Kind::Bool) {}
|
||||
Expression(size_t _number): Expression(std::to_string(_number), Kind::Int) {}
|
||||
Expression(u256 const& _number): Expression(_number.str(), Kind::Int) {}
|
||||
Expression(s256 const& _number): Expression(_number.str(), Kind::Int) {}
|
||||
Expression(bigint const& _number): Expression(_number.str(), Kind::Int) {}
|
||||
|
||||
Expression(Expression const&) = default;
|
||||
|
@ -57,8 +57,13 @@ smt::SortPointer dev::solidity::smtSort(Type const& _type)
|
||||
solAssert(mapType, "");
|
||||
return make_shared<smt::ArraySort>(smtSort(*mapType->keyType()), smtSort(*mapType->valueType()));
|
||||
}
|
||||
// TODO Solidity array
|
||||
return make_shared<smt::Sort>(smt::Kind::Int);
|
||||
else
|
||||
{
|
||||
solAssert(isArray(_type.category()), "");
|
||||
auto arrayType = dynamic_cast<ArrayType const*>(&_type);
|
||||
solAssert(arrayType, "");
|
||||
return make_shared<smt::ArraySort>(make_shared<smt::Sort>(smt::Kind::Int), smtSort(*arrayType->baseType()));
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Abstract case.
|
||||
@ -82,7 +87,7 @@ smt::Kind dev::solidity::smtKind(Type::Category _category)
|
||||
return smt::Kind::Bool;
|
||||
else if (isFunction(_category))
|
||||
return smt::Kind::Function;
|
||||
else if (isMapping(_category))
|
||||
else if (isMapping(_category) || isArray(_category))
|
||||
return smt::Kind::Array;
|
||||
// Abstract case.
|
||||
return smt::Kind::Int;
|
||||
@ -92,7 +97,8 @@ bool dev::solidity::isSupportedType(Type::Category _category)
|
||||
{
|
||||
return isNumber(_category) ||
|
||||
isBool(_category) ||
|
||||
isMapping(_category);
|
||||
isMapping(_category) ||
|
||||
isArray(_category);
|
||||
}
|
||||
|
||||
bool dev::solidity::isSupportedTypeDeclaration(Type::Category _category)
|
||||
@ -129,6 +135,8 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
|
||||
}
|
||||
else if (isAddress(_type.category()))
|
||||
var = make_shared<SymbolicAddressVariable>(_uniqueName, _solver);
|
||||
else if (isEnum(_type.category()))
|
||||
var = make_shared<SymbolicEnumVariable>(type, _uniqueName, _solver);
|
||||
else if (isRational(_type.category()))
|
||||
{
|
||||
auto rational = dynamic_cast<RationalNumberType const*>(&_type);
|
||||
@ -140,6 +148,8 @@ pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable(
|
||||
}
|
||||
else if (isMapping(_type.category()))
|
||||
var = make_shared<SymbolicMappingVariable>(type, _uniqueName, _solver);
|
||||
else if (isArray(_type.category()))
|
||||
var = make_shared<SymbolicArrayVariable>(type, _uniqueName, _solver);
|
||||
else
|
||||
solAssert(false, "");
|
||||
return make_pair(abstract, var);
|
||||
@ -175,12 +185,18 @@ bool dev::solidity::isAddress(Type::Category _category)
|
||||
return _category == Type::Category::Address;
|
||||
}
|
||||
|
||||
bool dev::solidity::isEnum(Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Enum;
|
||||
}
|
||||
|
||||
bool dev::solidity::isNumber(Type::Category _category)
|
||||
{
|
||||
return isInteger(_category) ||
|
||||
isRational(_category) ||
|
||||
isFixedBytes(_category) ||
|
||||
isAddress(_category);
|
||||
isAddress(_category) ||
|
||||
isEnum(_category);
|
||||
}
|
||||
|
||||
bool dev::solidity::isBool(Type::Category _category)
|
||||
@ -198,6 +214,11 @@ bool dev::solidity::isMapping(Type::Category _category)
|
||||
return _category == Type::Category::Mapping;
|
||||
}
|
||||
|
||||
bool dev::solidity::isArray(Type::Category _category)
|
||||
{
|
||||
return _category == Type::Category::Array;
|
||||
}
|
||||
|
||||
smt::Expression dev::solidity::minValue(IntegerType const& _type)
|
||||
{
|
||||
return smt::Expression(_type.minValue());
|
||||
@ -228,7 +249,14 @@ void dev::solidity::smt::setSymbolicUnknownValue(SymbolicVariable const& _variab
|
||||
|
||||
void dev::solidity::smt::setSymbolicUnknownValue(smt::Expression _expr, TypePointer const& _type, smt::SolverInterface& _interface)
|
||||
{
|
||||
if (isInteger(_type->category()))
|
||||
if (isEnum(_type->category()))
|
||||
{
|
||||
auto enumType = dynamic_cast<EnumType const*>(_type.get());
|
||||
solAssert(enumType, "");
|
||||
_interface.addAssertion(_expr >= 0);
|
||||
_interface.addAssertion(_expr < enumType->numberOfMembers());
|
||||
}
|
||||
else if (isInteger(_type->category()))
|
||||
{
|
||||
auto intType = dynamic_cast<IntegerType const*>(_type.get());
|
||||
solAssert(intType, "");
|
||||
|
@ -44,10 +44,12 @@ bool isInteger(Type::Category _category);
|
||||
bool isRational(Type::Category _category);
|
||||
bool isFixedBytes(Type::Category _category);
|
||||
bool isAddress(Type::Category _category);
|
||||
bool isEnum(Type::Category _category);
|
||||
bool isNumber(Type::Category _category);
|
||||
bool isBool(Type::Category _category);
|
||||
bool isFunction(Type::Category _category);
|
||||
bool isMapping(Type::Category _category);
|
||||
bool isArray(Type::Category _category);
|
||||
|
||||
/// Returns a new symbolic variable, according to _type.
|
||||
/// Also returns whether the type is abstract or not,
|
||||
|
@ -136,3 +136,23 @@ SymbolicMappingVariable::SymbolicMappingVariable(
|
||||
{
|
||||
solAssert(isMapping(m_type->category()), "");
|
||||
}
|
||||
|
||||
SymbolicArrayVariable::SymbolicArrayVariable(
|
||||
TypePointer _type,
|
||||
string const& _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), _uniqueName, _interface)
|
||||
{
|
||||
solAssert(isArray(m_type->category()), "");
|
||||
}
|
||||
|
||||
SymbolicEnumVariable::SymbolicEnumVariable(
|
||||
TypePointer _type,
|
||||
string const& _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
):
|
||||
SymbolicVariable(move(_type), _uniqueName, _interface)
|
||||
{
|
||||
solAssert(isEnum(m_type->category()), "");
|
||||
}
|
||||
|
@ -153,5 +153,31 @@ public:
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialization of SymbolicVariable for Array
|
||||
*/
|
||||
class SymbolicArrayVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicArrayVariable(
|
||||
TypePointer _type,
|
||||
std::string const& _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Specialization of SymbolicVariable for Enum
|
||||
*/
|
||||
class SymbolicEnumVariable: public SymbolicVariable
|
||||
{
|
||||
public:
|
||||
SymbolicEnumVariable(
|
||||
TypePointer _type,
|
||||
std::string const& _uniqueName,
|
||||
smt::SolverInterface& _interface
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,12 +26,31 @@ using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool anyDataStoredInStorage(TypePointers const& _pointers)
|
||||
{
|
||||
for (TypePointer const& pointer: _pointers)
|
||||
if (pointer->dataStoredIn(DataLocation::Storage))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
||||
{
|
||||
Json::Value abi(Json::arrayValue);
|
||||
|
||||
for (auto it: _contractDef.interfaceFunctions())
|
||||
{
|
||||
if (
|
||||
_contractDef.isLibrary() &&
|
||||
(it.second->stateMutability() > StateMutability::View ||
|
||||
anyDataStoredInStorage(it.second->parameterTypes() + it.second->returnParameterTypes()))
|
||||
)
|
||||
continue;
|
||||
|
||||
auto externalFunctionType = it.second->interfaceFunctionType();
|
||||
solAssert(!!externalFunctionType, "");
|
||||
Json::Value method;
|
||||
|
@ -127,16 +127,6 @@ bool hashMatchesContent(string const& _hash, string const& _content)
|
||||
}
|
||||
}
|
||||
|
||||
StringMap createSourceList(Json::Value const& _input)
|
||||
{
|
||||
StringMap sources;
|
||||
Json::Value const& jsonSources = _input["sources"];
|
||||
if (jsonSources.isObject())
|
||||
for (auto const& sourceName: jsonSources.getMemberNames())
|
||||
sources[sourceName] = jsonSources[sourceName]["content"].asString();
|
||||
return sources;
|
||||
}
|
||||
|
||||
bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact)
|
||||
{
|
||||
for (auto const& artifact: _outputSelection)
|
||||
@ -365,9 +355,9 @@ boost::optional<Json::Value> checkOutputSelection(Json::Value const& _outputSele
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boost::optional<Json::Value> StandardCompiler::parseOptimizerSettings(Json::Value const& _jsonInput)
|
||||
/// Validates the optimizer settings and returns them in a parsed object.
|
||||
/// On error returns the json-formatted error message.
|
||||
boost::variant<OptimiserSettings, Json::Value> parseOptimizerSettings(Json::Value const& _jsonInput)
|
||||
{
|
||||
if (auto result = checkOptimizerKeys(_jsonInput))
|
||||
return *result;
|
||||
@ -417,14 +407,14 @@ boost::optional<Json::Value> StandardCompiler::parseOptimizerSettings(Json::Valu
|
||||
return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet.");
|
||||
}
|
||||
}
|
||||
m_compilerStack.setOptimiserSettings(std::move(settings));
|
||||
return {};
|
||||
return std::move(settings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
boost::variant<StandardCompiler::InputsAndSettings, Json::Value> StandardCompiler::parseInput(Json::Value const& _input)
|
||||
{
|
||||
m_compilerStack.reset(false);
|
||||
InputsAndSettings ret;
|
||||
|
||||
if (!_input.isObject())
|
||||
return formatFatalError("JSONError", "Input is not a JSON object.");
|
||||
@ -443,7 +433,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
if (sources.empty())
|
||||
return formatFatalError("JSONError", "No input sources specified.");
|
||||
|
||||
Json::Value errors = Json::arrayValue;
|
||||
ret.errors = Json::arrayValue;
|
||||
|
||||
for (auto const& sourceName: sources.getMemberNames())
|
||||
{
|
||||
@ -459,14 +449,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
{
|
||||
string content = sources[sourceName]["content"].asString();
|
||||
if (!hash.empty() && !hashMatchesContent(hash, content))
|
||||
errors.append(formatError(
|
||||
ret.errors.append(formatError(
|
||||
false,
|
||||
"IOError",
|
||||
"general",
|
||||
"Mismatch between content and supplied hash for \"" + sourceName + "\""
|
||||
));
|
||||
else
|
||||
m_compilerStack.addSource(sourceName, content);
|
||||
ret.sources[sourceName] = content;
|
||||
}
|
||||
else if (sources[sourceName]["urls"].isArray())
|
||||
{
|
||||
@ -484,7 +474,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
if (result.success)
|
||||
{
|
||||
if (!hash.empty() && !hashMatchesContent(hash, result.responseOrErrorMessage))
|
||||
errors.append(formatError(
|
||||
ret.errors.append(formatError(
|
||||
false,
|
||||
"IOError",
|
||||
"general",
|
||||
@ -492,7 +482,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
));
|
||||
else
|
||||
{
|
||||
m_compilerStack.addSource(sourceName, result.responseOrErrorMessage);
|
||||
ret.sources[sourceName] = result.responseOrErrorMessage;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@ -504,7 +494,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
for (auto const& failure: failures)
|
||||
{
|
||||
/// If the import succeeded, let mark all the others as warnings, otherwise all of them are errors.
|
||||
errors.append(formatError(
|
||||
ret.errors.append(formatError(
|
||||
found ? true : false,
|
||||
"IOError",
|
||||
"general",
|
||||
@ -547,7 +537,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
"\"smtlib2Responses." + hashString + "\" must be a string."
|
||||
);
|
||||
|
||||
m_compilerStack.addSMTLib2Response(hash, smtlib2Responses[hashString].asString());
|
||||
ret.smtLib2Responses[hash] = smtlib2Responses[hashString].asString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -564,29 +554,31 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
boost::optional<langutil::EVMVersion> version = langutil::EVMVersion::fromString(settings["evmVersion"].asString());
|
||||
if (!version)
|
||||
return formatFatalError("JSONError", "Invalid EVM version requested.");
|
||||
m_compilerStack.setEVMVersion(*version);
|
||||
ret.evmVersion = *version;
|
||||
}
|
||||
|
||||
if (settings.isMember("remappings") && !settings["remappings"].isArray())
|
||||
return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings.");
|
||||
|
||||
vector<CompilerStack::Remapping> remappings;
|
||||
for (auto const& remapping: settings.get("remappings", Json::Value()))
|
||||
{
|
||||
if (!remapping.isString())
|
||||
return formatFatalError("JSONError", "\"settings.remappings\" must be an array of strings");
|
||||
if (auto r = CompilerStack::parseRemapping(remapping.asString()))
|
||||
remappings.emplace_back(std::move(*r));
|
||||
ret.remappings.emplace_back(std::move(*r));
|
||||
else
|
||||
return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\"");
|
||||
}
|
||||
m_compilerStack.setRemappings(remappings);
|
||||
|
||||
if (settings.isMember("optimizer"))
|
||||
if (auto result = parseOptimizerSettings(settings["optimizer"]))
|
||||
return *result;
|
||||
{
|
||||
auto optimiserSettings = parseOptimizerSettings(settings["optimizer"]);
|
||||
if (optimiserSettings.type() == typeid(Json::Value))
|
||||
return boost::get<Json::Value>(std::move(optimiserSettings)); // was an error
|
||||
else
|
||||
ret.optimiserSettings = boost::get<OptimiserSettings>(std::move(optimiserSettings));
|
||||
}
|
||||
|
||||
map<string, h160> libraries;
|
||||
Json::Value jsonLibraries = settings.get("libraries", Json::Value(Json::objectValue));
|
||||
if (!jsonLibraries.isObject())
|
||||
return formatFatalError("JSONError", "\"libraries\" is not a JSON object.");
|
||||
@ -616,7 +608,7 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
try
|
||||
{
|
||||
// @TODO use libraries only for the given source
|
||||
libraries[library] = h160(address);
|
||||
ret.libraries[library] = h160(address);
|
||||
}
|
||||
catch (dev::BadHexCharacter const&)
|
||||
{
|
||||
@ -627,32 +619,52 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
}
|
||||
}
|
||||
}
|
||||
m_compilerStack.setLibraries(libraries);
|
||||
|
||||
Json::Value metadataSettings = settings.get("metadata", Json::Value());
|
||||
|
||||
if (auto result = checkMetadataKeys(metadataSettings))
|
||||
return *result;
|
||||
|
||||
m_compilerStack.useMetadataLiteralSources(metadataSettings.get("useLiteralContent", Json::Value(false)).asBool());
|
||||
ret.metadataLiteralSources = metadataSettings.get("useLiteralContent", Json::Value(false)).asBool();
|
||||
|
||||
Json::Value outputSelection = settings.get("outputSelection", Json::Value());
|
||||
|
||||
if (auto jsonError = checkOutputSelection(outputSelection))
|
||||
return *jsonError;
|
||||
|
||||
m_compilerStack.setRequestedContractNames(requestedContractNames(outputSelection));
|
||||
ret.outputSelection = std::move(outputSelection);
|
||||
|
||||
bool const binariesRequested = isBinaryRequested(outputSelection);
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
Json::Value StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inputsAndSettings)
|
||||
{
|
||||
CompilerStack compilerStack(m_readFile);
|
||||
|
||||
StringMap sourceList = std::move(_inputsAndSettings.sources);
|
||||
for (auto const& source: sourceList)
|
||||
compilerStack.addSource(source.first, source.second);
|
||||
for (auto const& smtLib2Response: _inputsAndSettings.smtLib2Responses)
|
||||
compilerStack.addSMTLib2Response(smtLib2Response.first, smtLib2Response.second);
|
||||
compilerStack.setEVMVersion(_inputsAndSettings.evmVersion);
|
||||
compilerStack.setRemappings(_inputsAndSettings.remappings);
|
||||
compilerStack.setOptimiserSettings(std::move(_inputsAndSettings.optimiserSettings));
|
||||
compilerStack.setLibraries(_inputsAndSettings.libraries);
|
||||
compilerStack.useMetadataLiteralSources(_inputsAndSettings.metadataLiteralSources);
|
||||
compilerStack.setRequestedContractNames(requestedContractNames(_inputsAndSettings.outputSelection));
|
||||
|
||||
Json::Value errors = std::move(_inputsAndSettings.errors);
|
||||
|
||||
bool const binariesRequested = isBinaryRequested(_inputsAndSettings.outputSelection);
|
||||
|
||||
try
|
||||
{
|
||||
if (binariesRequested)
|
||||
m_compilerStack.compile();
|
||||
compilerStack.compile();
|
||||
else
|
||||
m_compilerStack.parseAndAnalyze();
|
||||
compilerStack.parseAndAnalyze();
|
||||
|
||||
for (auto const& error: m_compilerStack.errors())
|
||||
for (auto const& error: compilerStack.errors())
|
||||
{
|
||||
Error const& err = dynamic_cast<Error const&>(*error);
|
||||
|
||||
@ -735,8 +747,8 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
));
|
||||
}
|
||||
|
||||
bool const analysisSuccess = m_compilerStack.state() >= CompilerStack::State::AnalysisSuccessful;
|
||||
bool const compilationSuccess = m_compilerStack.state() == CompilerStack::State::CompilationSuccessful;
|
||||
bool const analysisSuccess = compilerStack.state() >= CompilerStack::State::AnalysisSuccessful;
|
||||
bool const compilationSuccess = compilerStack.state() == CompilerStack::State::CompilationSuccessful;
|
||||
|
||||
/// Inconsistent state - stop here to receive error reports from users
|
||||
if (((binariesRequested && !compilationSuccess) || !analysisSuccess) && errors.empty())
|
||||
@ -745,27 +757,27 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
Json::Value output = Json::objectValue;
|
||||
|
||||
if (errors.size() > 0)
|
||||
output["errors"] = errors;
|
||||
output["errors"] = std::move(errors);
|
||||
|
||||
if (!m_compilerStack.unhandledSMTLib2Queries().empty())
|
||||
for (string const& query: m_compilerStack.unhandledSMTLib2Queries())
|
||||
if (!compilerStack.unhandledSMTLib2Queries().empty())
|
||||
for (string const& query: compilerStack.unhandledSMTLib2Queries())
|
||||
output["auxiliaryInputRequested"]["smtlib2queries"]["0x" + keccak256(query).hex()] = query;
|
||||
|
||||
output["sources"] = Json::objectValue;
|
||||
unsigned sourceIndex = 0;
|
||||
for (string const& sourceName: analysisSuccess ? m_compilerStack.sourceNames() : vector<string>())
|
||||
for (string const& sourceName: analysisSuccess ? compilerStack.sourceNames() : vector<string>())
|
||||
{
|
||||
Json::Value sourceResult = Json::objectValue;
|
||||
sourceResult["id"] = sourceIndex++;
|
||||
if (isArtifactRequested(outputSelection, sourceName, "", "ast"))
|
||||
sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
|
||||
if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST"))
|
||||
sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "ast"))
|
||||
sourceResult["ast"] = ASTJsonConverter(false, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, sourceName, "", "legacyAST"))
|
||||
sourceResult["legacyAST"] = ASTJsonConverter(true, compilerStack.sourceIndices()).toJson(compilerStack.ast(sourceName));
|
||||
output["sources"][sourceName] = sourceResult;
|
||||
}
|
||||
|
||||
Json::Value contractsOutput = Json::objectValue;
|
||||
for (string const& contractName: analysisSuccess ? m_compilerStack.contractNames() : vector<string>())
|
||||
for (string const& contractName: analysisSuccess ? compilerStack.contractNames() : vector<string>())
|
||||
{
|
||||
size_t colon = contractName.rfind(':');
|
||||
solAssert(colon != string::npos, "");
|
||||
@ -774,47 +786,47 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
|
||||
|
||||
// ABI, documentation and metadata
|
||||
Json::Value contractData(Json::objectValue);
|
||||
if (isArtifactRequested(outputSelection, file, name, "abi"))
|
||||
contractData["abi"] = m_compilerStack.contractABI(contractName);
|
||||
if (isArtifactRequested(outputSelection, file, name, "metadata"))
|
||||
contractData["metadata"] = m_compilerStack.metadata(contractName);
|
||||
if (isArtifactRequested(outputSelection, file, name, "userdoc"))
|
||||
contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
|
||||
if (isArtifactRequested(outputSelection, file, name, "devdoc"))
|
||||
contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "abi"))
|
||||
contractData["abi"] = compilerStack.contractABI(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "metadata"))
|
||||
contractData["metadata"] = compilerStack.metadata(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "userdoc"))
|
||||
contractData["userdoc"] = compilerStack.natspecUser(contractName);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "devdoc"))
|
||||
contractData["devdoc"] = compilerStack.natspecDev(contractName);
|
||||
|
||||
// EVM
|
||||
Json::Value evmData(Json::objectValue);
|
||||
// @TODO: add ir
|
||||
if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.assembly"))
|
||||
evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
|
||||
if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly"))
|
||||
evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
|
||||
if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers"))
|
||||
evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(outputSelection, file, name, "evm.gasEstimates"))
|
||||
evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.assembly"))
|
||||
evmData["assembly"] = compilerStack.assemblyString(contractName, sourceList);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.legacyAssembly"))
|
||||
evmData["legacyAssembly"] = compilerStack.assemblyJSON(contractName, sourceList);
|
||||
if (isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.methodIdentifiers"))
|
||||
evmData["methodIdentifiers"] = compilerStack.methodIdentifiers(contractName);
|
||||
if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates"))
|
||||
evmData["gasEstimates"] = compilerStack.gasEstimates(contractName);
|
||||
|
||||
if (compilationSuccess && isArtifactRequested(
|
||||
outputSelection,
|
||||
_inputsAndSettings.outputSelection,
|
||||
file,
|
||||
name,
|
||||
{ "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
|
||||
))
|
||||
evmData["bytecode"] = collectEVMObject(
|
||||
m_compilerStack.object(contractName),
|
||||
m_compilerStack.sourceMapping(contractName)
|
||||
compilerStack.object(contractName),
|
||||
compilerStack.sourceMapping(contractName)
|
||||
);
|
||||
|
||||
if (compilationSuccess && isArtifactRequested(
|
||||
outputSelection,
|
||||
_inputsAndSettings.outputSelection,
|
||||
file,
|
||||
name,
|
||||
{ "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }
|
||||
))
|
||||
evmData["deployedBytecode"] = collectEVMObject(
|
||||
m_compilerStack.runtimeObject(contractName),
|
||||
m_compilerStack.runtimeSourceMapping(contractName)
|
||||
compilerStack.runtimeObject(contractName),
|
||||
compilerStack.runtimeSourceMapping(contractName)
|
||||
);
|
||||
|
||||
if (!evmData.empty())
|
||||
@ -837,7 +849,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
return compileInternal(_input);
|
||||
auto parsed = parseInput(_input);
|
||||
if (parsed.type() == typeid(InputsAndSettings))
|
||||
return compileSolidity(boost::get<InputsAndSettings>(std::move(parsed)));
|
||||
else
|
||||
return boost::get<Json::Value>(std::move(parsed));
|
||||
}
|
||||
catch (Json::LogicError const& _exception)
|
||||
{
|
||||
@ -849,11 +865,11 @@ Json::Value StandardCompiler::compile(Json::Value const& _input) noexcept
|
||||
}
|
||||
catch (Exception const& _exception)
|
||||
{
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal: " + boost::diagnostic_information(_exception));
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile: " + boost::diagnostic_information(_exception));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compileInternal");
|
||||
return formatFatalError("InternalCompilerError", "Internal exception in StandardCompiler::compile");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <libsolidity/interface/CompilerStack.h>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
@ -42,8 +43,8 @@ public:
|
||||
/// Creates a new StandardCompiler.
|
||||
/// @param _readFile callback to used to read files for import statements. Must return
|
||||
/// and must not emit exceptions.
|
||||
explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback())
|
||||
: m_compilerStack(_readFile), m_readFile(_readFile)
|
||||
explicit StandardCompiler(ReadCallback::Callback const& _readFile = ReadCallback::Callback()):
|
||||
m_readFile(_readFile)
|
||||
{
|
||||
}
|
||||
|
||||
@ -55,13 +56,25 @@ public:
|
||||
std::string compile(std::string const& _input) noexcept;
|
||||
|
||||
private:
|
||||
/// Validaes and applies the optimizer settings.
|
||||
/// On error returns the json-formatted error message.
|
||||
boost::optional<Json::Value> parseOptimizerSettings(Json::Value const& _settings);
|
||||
struct InputsAndSettings
|
||||
{
|
||||
Json::Value errors;
|
||||
std::map<std::string, std::string> sources;
|
||||
std::map<h256, std::string> smtLib2Responses;
|
||||
langutil::EVMVersion evmVersion;
|
||||
std::vector<CompilerStack::Remapping> remappings;
|
||||
OptimiserSettings optimiserSettings = OptimiserSettings::minimal();
|
||||
std::map<std::string, h160> libraries;
|
||||
bool metadataLiteralSources = false;
|
||||
Json::Value outputSelection;
|
||||
};
|
||||
|
||||
Json::Value compileInternal(Json::Value const& _input);
|
||||
/// Parses the input json (and potentially invokes the read callback) and either returns
|
||||
/// it in condensed form or an error as a json object.
|
||||
boost::variant<InputsAndSettings, Json::Value> parseInput(Json::Value const& _input);
|
||||
|
||||
Json::Value compileSolidity(InputsAndSettings _inputsAndSettings);
|
||||
|
||||
CompilerStack m_compilerStack;
|
||||
ReadCallback::Callback m_readFile;
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include <libyul/AsmScope.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/Utilities.h>
|
||||
#include <libyul/Exceptions.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
@ -33,6 +34,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -472,7 +474,7 @@ bool AsmAnalyzer::operator()(ForLoop const& _for)
|
||||
{
|
||||
solAssert(_for.condition, "");
|
||||
|
||||
Scope* originalScope = m_currentScope;
|
||||
Scope* outerScope = m_currentScope;
|
||||
|
||||
bool success = true;
|
||||
if (!(*this)(_for.pre))
|
||||
@ -485,18 +487,37 @@ bool AsmAnalyzer::operator()(ForLoop const& _for)
|
||||
if (!expectExpression(*_for.condition))
|
||||
success = false;
|
||||
m_stackHeight--;
|
||||
|
||||
// backup outer for-loop & create new state
|
||||
auto outerForLoop = m_currentForLoop;
|
||||
m_currentForLoop = &_for;
|
||||
|
||||
if (!(*this)(_for.body))
|
||||
success = false;
|
||||
|
||||
if (!(*this)(_for.post))
|
||||
success = false;
|
||||
|
||||
m_stackHeight -= scope(&_for.pre).numberOfVariables();
|
||||
m_info.stackHeightInfo[&_for] = m_stackHeight;
|
||||
m_currentScope = originalScope;
|
||||
m_currentScope = outerScope;
|
||||
m_currentForLoop = outerForLoop;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Break const& _break)
|
||||
{
|
||||
m_info.stackHeightInfo[&_break] = m_stackHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Continue const& _continue)
|
||||
{
|
||||
m_info.stackHeightInfo[&_continue] = m_stackHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AsmAnalyzer::operator()(Block const& _block)
|
||||
{
|
||||
bool success = true;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
namespace langutil
|
||||
@ -93,6 +94,8 @@ public:
|
||||
bool operator()(If const& _if);
|
||||
bool operator()(Switch const& _switch);
|
||||
bool operator()(ForLoop const& _forLoop);
|
||||
bool operator()(Break const&);
|
||||
bool operator()(Continue const&);
|
||||
bool operator()(Block const& _block);
|
||||
|
||||
private:
|
||||
@ -124,6 +127,7 @@ private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<Dialect> m_dialect;
|
||||
boost::optional<langutil::Error::Type> m_errorTypeForLoose;
|
||||
ForLoop const* m_currentForLoop = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -78,6 +78,10 @@ struct Case { langutil::SourceLocation location; std::unique_ptr<Literal> value;
|
||||
/// Switch statement
|
||||
struct Switch { langutil::SourceLocation location; std::unique_ptr<Expression> expression; std::vector<Case> cases; };
|
||||
struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr<Expression> condition; Block post; Block body; };
|
||||
/// Break statement (valid within for loop)
|
||||
struct Break { langutil::SourceLocation location; };
|
||||
/// Continue statement (valid within for loop)
|
||||
struct Continue { langutil::SourceLocation location; };
|
||||
|
||||
struct LocationExtractor: boost::static_visitor<langutil::SourceLocation>
|
||||
{
|
||||
|
@ -41,12 +41,14 @@ struct If;
|
||||
struct Switch;
|
||||
struct Case;
|
||||
struct ForLoop;
|
||||
struct Break;
|
||||
struct Continue;
|
||||
struct ExpressionStatement;
|
||||
struct Block;
|
||||
|
||||
struct TypedName;
|
||||
|
||||
using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>;
|
||||
using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
|
||||
using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Break, Continue, Block>;
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <liblangutil/Scanner.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <libdevcore/Common.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
@ -104,6 +105,32 @@ Statement Parser::parseStatement()
|
||||
}
|
||||
case Token::For:
|
||||
return parseForLoop();
|
||||
case Token::Break:
|
||||
if (m_insideForLoopBody)
|
||||
{
|
||||
auto stmt = Statement{ createWithLocation<Break>() };
|
||||
m_scanner->next();
|
||||
return stmt;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.syntaxError(location(), "Keyword break outside for-loop body is not allowed.");
|
||||
m_scanner->next();
|
||||
return {};
|
||||
}
|
||||
case Token::Continue:
|
||||
if (m_insideForLoopBody)
|
||||
{
|
||||
auto stmt = Statement{ createWithLocation<Continue>() };
|
||||
m_scanner->next();
|
||||
return stmt;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_errorReporter.syntaxError(location(), "Keyword continue outside for-loop body is not allowed.");
|
||||
m_scanner->next();
|
||||
return {};
|
||||
}
|
||||
case Token::Assign:
|
||||
{
|
||||
if (m_dialect->flavour != AsmFlavour::Loose)
|
||||
@ -243,13 +270,19 @@ Case Parser::parseCase()
|
||||
|
||||
ForLoop Parser::parseForLoop()
|
||||
{
|
||||
bool outerForLoopBody = m_insideForLoopBody;
|
||||
m_insideForLoopBody = false;
|
||||
|
||||
RecursionGuard recursionGuard(*this);
|
||||
ForLoop forLoop = createWithLocation<ForLoop>();
|
||||
expectToken(Token::For);
|
||||
forLoop.pre = parseBlock();
|
||||
forLoop.condition = make_unique<Expression>(parseExpression());
|
||||
forLoop.post = parseBlock();
|
||||
|
||||
m_insideForLoopBody = true;
|
||||
forLoop.body = parseBlock();
|
||||
m_insideForLoopBody = outerForLoopBody;
|
||||
forLoop.location.end = forLoop.body.location.end;
|
||||
return forLoop;
|
||||
}
|
||||
@ -455,6 +488,9 @@ VariableDeclaration Parser::parseVariableDeclaration()
|
||||
FunctionDefinition Parser::parseFunctionDefinition()
|
||||
{
|
||||
RecursionGuard recursionGuard(*this);
|
||||
auto outerForLoopBody = m_insideForLoopBody;
|
||||
m_insideForLoopBody = false;
|
||||
ScopeGuard restoreInsideForLoopBody{[&]() { m_insideForLoopBody = outerForLoopBody; }};
|
||||
FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
|
||||
expectToken(Token::Function);
|
||||
funDef.name = expectAsmIdentifier();
|
||||
|
@ -39,7 +39,7 @@ class Parser: public langutil::ParserBase
|
||||
{
|
||||
public:
|
||||
explicit Parser(langutil::ErrorReporter& _errorReporter, std::shared_ptr<Dialect> _dialect):
|
||||
ParserBase(_errorReporter), m_dialect(std::move(_dialect)) {}
|
||||
ParserBase(_errorReporter), m_dialect(std::move(_dialect)), m_insideForLoopBody{false} {}
|
||||
|
||||
/// Parses an inline assembly block starting with `{` and ending with `}`.
|
||||
/// @param _reuseScanner if true, do check for end of input after the `}`.
|
||||
@ -87,6 +87,7 @@ protected:
|
||||
|
||||
private:
|
||||
std::shared_ptr<Dialect> m_dialect;
|
||||
bool m_insideForLoopBody;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -224,6 +224,16 @@ string AsmPrinter::operator()(ForLoop const& _forLoop) const
|
||||
return out;
|
||||
}
|
||||
|
||||
string AsmPrinter::operator()(Break const&) const
|
||||
{
|
||||
return "break";
|
||||
}
|
||||
|
||||
string AsmPrinter::operator()(Continue const&) const
|
||||
{
|
||||
return "continue";
|
||||
}
|
||||
|
||||
string AsmPrinter::operator()(Block const& _block) const
|
||||
{
|
||||
if (_block.statements.empty())
|
||||
|
@ -50,6 +50,8 @@ public:
|
||||
std::string operator()(If const& _if) const;
|
||||
std::string operator()(Switch const& _switch) const;
|
||||
std::string operator()(ForLoop const& _forLoop) const;
|
||||
std::string operator()(Break const& _break) const;
|
||||
std::string operator()(Continue const& _continue) const;
|
||||
std::string operator()(Block const& _block) const;
|
||||
|
||||
private:
|
||||
|
@ -63,6 +63,8 @@ public:
|
||||
bool operator()(If const& _if);
|
||||
bool operator()(Switch const& _switch);
|
||||
bool operator()(ForLoop const& _forLoop);
|
||||
bool operator()(Break const&) { return true; }
|
||||
bool operator()(Continue const&) { return true; }
|
||||
bool operator()(Block const& _block);
|
||||
|
||||
private:
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <libyul/Exceptions.h>
|
||||
|
||||
#include <libdevcore/CommonData.h>
|
||||
#include <libdevcore/FixedHash.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
@ -31,12 +32,48 @@ using namespace yul;
|
||||
|
||||
u256 yul::valueOfNumberLiteral(Literal const& _literal)
|
||||
{
|
||||
assertThrow(_literal.kind == LiteralKind::Number, OptimizerException, "");
|
||||
yulAssert(_literal.kind == LiteralKind::Number, "Expected number literal!");
|
||||
|
||||
std::string const& literalString = _literal.value.str();
|
||||
assertThrow(isValidDecimal(literalString) || isValidHex(literalString), OptimizerException, "");
|
||||
yulAssert(isValidDecimal(literalString) || isValidHex(literalString), "Invalid number literal!");
|
||||
return u256(literalString);
|
||||
}
|
||||
|
||||
u256 yul::valueOfStringLiteral(Literal const& _literal)
|
||||
{
|
||||
yulAssert(_literal.kind == LiteralKind::String, "Expected string literal!");
|
||||
yulAssert(_literal.value.str().size() <= 32, "Literal string too long!");
|
||||
|
||||
return u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft));
|
||||
}
|
||||
|
||||
u256 yul::valueOfBoolLiteral(Literal const& _literal)
|
||||
{
|
||||
yulAssert(_literal.kind == LiteralKind::Boolean, "Expected bool literal!");
|
||||
|
||||
if (_literal.value == "true"_yulstring)
|
||||
return u256(1);
|
||||
else if (_literal.value == "false"_yulstring)
|
||||
return u256(0);
|
||||
|
||||
yulAssert(false, "Unexpected bool literal value!");
|
||||
}
|
||||
|
||||
u256 yul::valueOfLiteral(Literal const& _literal)
|
||||
{
|
||||
switch(_literal.kind)
|
||||
{
|
||||
case LiteralKind::Number:
|
||||
return valueOfNumberLiteral(_literal);
|
||||
case LiteralKind::Boolean:
|
||||
return valueOfBoolLiteral(_literal);
|
||||
case LiteralKind::String:
|
||||
return valueOfStringLiteral(_literal);
|
||||
default:
|
||||
yulAssert(false, "Unexpected literal kind!");
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
bool Less<Literal>::operator()(Literal const& _lhs, Literal const& _rhs) const
|
||||
{
|
||||
|
@ -27,6 +27,9 @@ namespace yul
|
||||
{
|
||||
|
||||
dev::u256 valueOfNumberLiteral(Literal const& _literal);
|
||||
dev::u256 valueOfStringLiteral(Literal const& _literal);
|
||||
dev::u256 valueOfBoolLiteral(Literal const& _literal);
|
||||
dev::u256 valueOfLiteral(Literal const& _literal);
|
||||
|
||||
/**
|
||||
* Linear order on Yul AST nodes.
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/Utilities.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
@ -68,7 +69,6 @@ void VariableReferenceCounter::operator()(ForLoop const& _forLoop)
|
||||
m_scope = originalScope;
|
||||
}
|
||||
|
||||
|
||||
void VariableReferenceCounter::operator()(Block const& _block)
|
||||
{
|
||||
Scope* originalScope = m_scope;
|
||||
@ -91,7 +91,6 @@ void VariableReferenceCounter::increaseRefIfFound(YulString _variableName)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
CodeTransform::CodeTransform(
|
||||
AbstractAssembly& _assembly,
|
||||
AsmAnalysisInfo& _analysisInfo,
|
||||
@ -396,20 +395,8 @@ void CodeTransform::operator()(Identifier const& _identifier)
|
||||
void CodeTransform::operator()(Literal const& _literal)
|
||||
{
|
||||
m_assembly.setSourceLocation(_literal.location);
|
||||
if (_literal.kind == LiteralKind::Number)
|
||||
m_assembly.appendConstant(u256(_literal.value.str()));
|
||||
else if (_literal.kind == LiteralKind::Boolean)
|
||||
{
|
||||
if (_literal.value == "true"_yulstring)
|
||||
m_assembly.appendConstant(u256(1));
|
||||
else
|
||||
m_assembly.appendConstant(u256(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(_literal.value.str().size() <= 32, "");
|
||||
m_assembly.appendConstant(u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft)));
|
||||
}
|
||||
m_assembly.appendConstant(valueOfLiteral(_literal));
|
||||
|
||||
checkStackHeight(&_literal);
|
||||
}
|
||||
|
||||
@ -616,11 +603,9 @@ void CodeTransform::operator()(ForLoop const& _forLoop)
|
||||
|
||||
visitStatements(_forLoop.pre.statements);
|
||||
|
||||
// TODO: When we implement break and continue, the labels and the stack heights at that point
|
||||
// have to be stored in a stack.
|
||||
AbstractAssembly::LabelID loopStart = m_assembly.newLabelId();
|
||||
AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
|
||||
AbstractAssembly::LabelID postPart = m_assembly.newLabelId();
|
||||
AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId();
|
||||
|
||||
m_assembly.setSourceLocation(_forLoop.location);
|
||||
m_assembly.appendLabel(loopStart);
|
||||
@ -630,6 +615,8 @@ void CodeTransform::operator()(ForLoop const& _forLoop)
|
||||
m_assembly.appendInstruction(solidity::Instruction::ISZERO);
|
||||
m_assembly.appendJumpToIf(loopEnd);
|
||||
|
||||
int const stackHeightBody = m_assembly.stackHeight();
|
||||
m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} });
|
||||
(*this)(_forLoop.body);
|
||||
|
||||
m_assembly.setSourceLocation(_forLoop.location);
|
||||
@ -642,7 +629,19 @@ void CodeTransform::operator()(ForLoop const& _forLoop)
|
||||
m_assembly.appendLabel(loopEnd);
|
||||
|
||||
finalizeBlock(_forLoop.pre, stackStartHeight);
|
||||
m_context->forLoopStack.pop();
|
||||
m_scope = originalScope;
|
||||
checkStackHeight(&_forLoop);
|
||||
}
|
||||
|
||||
void CodeTransform::operator()(Break const&)
|
||||
{
|
||||
yulAssert(false, "Code generation for break statement in Yul is not implemented yet.");
|
||||
}
|
||||
|
||||
void CodeTransform::operator()(Continue const&)
|
||||
{
|
||||
yulAssert(false, "Code generation for continue statement in Yul is not implemented yet.");
|
||||
}
|
||||
|
||||
void CodeTransform::operator()(Block const& _block)
|
||||
|
@ -30,6 +30,8 @@
|
||||
#include <boost/variant.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <stack>
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
class ErrorReporter;
|
||||
@ -57,6 +59,20 @@ struct CodeTransformContext
|
||||
std::map<Scope::Function const*, AbstractAssembly::LabelID> functionEntryIDs;
|
||||
std::map<Scope::Variable const*, int> variableStackHeights;
|
||||
std::map<Scope::Variable const*, unsigned> variableReferences;
|
||||
|
||||
struct JumpInfo
|
||||
{
|
||||
AbstractAssembly::LabelID label; ///< Jump's LabelID to jump to.
|
||||
int targetStackHeight; ///< Stack height after the jump.
|
||||
};
|
||||
|
||||
struct ForLoopLabels
|
||||
{
|
||||
JumpInfo post; ///< Jump info for jumping to post branch.
|
||||
JumpInfo done; ///< Jump info for jumping to done branch.
|
||||
};
|
||||
|
||||
std::stack<ForLoopLabels> forLoopStack;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -166,6 +182,8 @@ public:
|
||||
void operator()(Switch const& _switch);
|
||||
void operator()(FunctionDefinition const&);
|
||||
void operator()(ForLoop const&);
|
||||
void operator()(Break const&);
|
||||
void operator()(Continue const&);
|
||||
void operator()(Block const& _block);
|
||||
|
||||
private:
|
||||
|
@ -138,6 +138,15 @@ Statement ASTCopier::operator()(ForLoop const& _forLoop)
|
||||
translate(_forLoop.body)
|
||||
};
|
||||
}
|
||||
Statement ASTCopier::operator()(Break const& _break)
|
||||
{
|
||||
return Break{ _break };
|
||||
}
|
||||
|
||||
Statement ASTCopier::operator()(Continue const& _continue)
|
||||
{
|
||||
return Continue{ _continue };
|
||||
}
|
||||
|
||||
Statement ASTCopier::operator ()(Block const& _block)
|
||||
{
|
||||
|
@ -58,6 +58,8 @@ public:
|
||||
virtual Statement operator()(Switch const& _switch) = 0;
|
||||
virtual Statement operator()(FunctionDefinition const&) = 0;
|
||||
virtual Statement operator()(ForLoop const&) = 0;
|
||||
virtual Statement operator()(Break const&) = 0;
|
||||
virtual Statement operator()(Continue const&) = 0;
|
||||
virtual Statement operator()(Block const& _block) = 0;
|
||||
};
|
||||
|
||||
@ -83,6 +85,8 @@ public:
|
||||
Statement operator()(Switch const& _switch) override;
|
||||
Statement operator()(FunctionDefinition const&) override;
|
||||
Statement operator()(ForLoop const&) override;
|
||||
Statement operator()(Break const&) override;
|
||||
Statement operator()(Continue const&) override;
|
||||
Statement operator()(Block const& _block) override;
|
||||
|
||||
virtual Expression translate(Expression const& _expression);
|
||||
|
@ -161,6 +161,14 @@ void ASTModifier::operator()(ForLoop& _for)
|
||||
(*this)(_for.body);
|
||||
}
|
||||
|
||||
void ASTModifier::operator()(Break&)
|
||||
{
|
||||
}
|
||||
|
||||
void ASTModifier::operator()(Continue&)
|
||||
{
|
||||
}
|
||||
|
||||
void ASTModifier::operator()(Block& _block)
|
||||
{
|
||||
walkVector(_block.statements);
|
||||
|
@ -56,6 +56,8 @@ public:
|
||||
virtual void operator()(Switch const& _switch);
|
||||
virtual void operator()(FunctionDefinition const&);
|
||||
virtual void operator()(ForLoop const&);
|
||||
virtual void operator()(Break const&) {}
|
||||
virtual void operator()(Continue const&) {}
|
||||
virtual void operator()(Block const& _block);
|
||||
|
||||
virtual void visit(Statement const& _st);
|
||||
@ -91,6 +93,8 @@ public:
|
||||
virtual void operator()(Switch& _switch);
|
||||
virtual void operator()(FunctionDefinition&);
|
||||
virtual void operator()(ForLoop&);
|
||||
virtual void operator()(Break&);
|
||||
virtual void operator()(Continue&);
|
||||
virtual void operator()(Block& _block);
|
||||
|
||||
virtual void visit(Statement& _st);
|
||||
|
@ -130,6 +130,16 @@ void DataFlowAnalyzer::operator()(ForLoop& _for)
|
||||
popScope();
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Break&)
|
||||
{
|
||||
yulAssert(false, "Not implemented yet.");
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Continue&)
|
||||
{
|
||||
yulAssert(false, "Not implemented yet.");
|
||||
}
|
||||
|
||||
void DataFlowAnalyzer::operator()(Block& _block)
|
||||
{
|
||||
size_t numScopes = m_variableScopes.size();
|
||||
|
@ -53,6 +53,8 @@ public:
|
||||
void operator()(Switch& _switch) override;
|
||||
void operator()(FunctionDefinition&) override;
|
||||
void operator()(ForLoop&) override;
|
||||
void operator()(Break& _continue) override;
|
||||
void operator()(Continue& _continue) override;
|
||||
void operator()(Block& _block) override;
|
||||
|
||||
protected:
|
||||
|
@ -52,9 +52,16 @@ size_t CodeSize::codeSize(Block const& _block)
|
||||
return cs.m_size;
|
||||
}
|
||||
|
||||
size_t CodeSize::codeSizeIncludingFunctions(Block const& _block)
|
||||
{
|
||||
CodeSize cs(false);
|
||||
cs(_block);
|
||||
return cs.m_size;
|
||||
}
|
||||
|
||||
void CodeSize::visit(Statement const& _statement)
|
||||
{
|
||||
if (_statement.type() == typeid(FunctionDefinition))
|
||||
if (_statement.type() == typeid(FunctionDefinition) && m_ignoreFunctions)
|
||||
return;
|
||||
else if (!(
|
||||
_statement.type() == typeid(Block) ||
|
||||
|
@ -28,7 +28,7 @@ namespace yul
|
||||
/**
|
||||
* Metric for the size of code.
|
||||
* More specifically, the number of AST nodes.
|
||||
* Ignores function definitions while traversing the AST.
|
||||
* Ignores function definitions while traversing the AST by default.
|
||||
* If you want to know the size of a function, you have to invoke this on its body.
|
||||
*
|
||||
* As an exception, the following AST elements have a cost of zero:
|
||||
@ -44,14 +44,16 @@ public:
|
||||
static size_t codeSize(Statement const& _statement);
|
||||
static size_t codeSize(Expression const& _expression);
|
||||
static size_t codeSize(Block const& _block);
|
||||
static size_t codeSizeIncludingFunctions(Block const& _block);
|
||||
|
||||
private:
|
||||
CodeSize() {}
|
||||
CodeSize(bool _ignoreFunctions = true): m_ignoreFunctions(_ignoreFunctions) {}
|
||||
|
||||
void visit(Statement const& _statement) override;
|
||||
void visit(Expression const& _expression) override;
|
||||
|
||||
private:
|
||||
bool m_ignoreFunctions;
|
||||
size_t m_size = 0;
|
||||
};
|
||||
|
||||
|
@ -2,7 +2,19 @@ Note that the Yul optimiser is still in research phase. Because of that,
|
||||
the following description might not fully reflect the current or even
|
||||
planned state of the optimiser.
|
||||
|
||||
## Yul Optimiser
|
||||
Table of Contents:
|
||||
|
||||
- [Preprocessing](#preprocessing)
|
||||
- [Pseudo-SSA Transformation](#pseudo-ssa-transformation)
|
||||
- [Tools](#tools)
|
||||
- [Expression-Scale Simplifications](#expression-scale-simplifications)
|
||||
- [Statement-Scale Simplifications](#statement-scale-simplifications)
|
||||
- [Function Inlining](#function-inlining)
|
||||
- [Cleanup](#cleanup)
|
||||
- [Webassembly-sepcific](#webassembly-specific)
|
||||
|
||||
|
||||
# Yul Optimiser
|
||||
|
||||
The Yul optimiser consists of several stages and components that all transform
|
||||
the AST in a semantically equivalent way. The goal is to end up either with code
|
||||
@ -12,7 +24,22 @@ optimisation steps.
|
||||
The optimiser currently follows a purely greedy strategy and does not do any
|
||||
backtracking.
|
||||
|
||||
## Disambiguator
|
||||
All components of the optimiser are explained below, where
|
||||
the following transformation steps are the main components:
|
||||
|
||||
- [SSA Transform](#ssa-transform)
|
||||
- [Common Subexpression Eliminator](#common-subexpression-eliminator)
|
||||
- [Expression Simplifier](#expression-simplifier)
|
||||
- [Redundant Assign Eliminator](#redundant-assign-eliminator)
|
||||
- [Full Function Inliner](#full-function-inliner)
|
||||
|
||||
## Preprocessing
|
||||
|
||||
The preprocessing components perform transformations to get the program
|
||||
into a certain normal form that is easier to work with. This normal
|
||||
form is kept during the rest of the optimisation process.
|
||||
|
||||
### Disambiguator
|
||||
|
||||
The disambiguator takes an AST and returns a fresh copy where all identifiers have
|
||||
names unique to the input AST. This is a prerequisite for all other optimiser stages.
|
||||
@ -22,16 +49,17 @@ and we can basically ignore the result of the analysis phase.
|
||||
All subsequent stages have the property that all names stay unique. This means if
|
||||
a new identifier needs to be introduced, a new unique name is generated.
|
||||
|
||||
## Function Hoister
|
||||
### Function Hoister
|
||||
|
||||
The function hoister moves all function definitions to the end of the topmost block. This is
|
||||
a semantically equivalent transformation as long as it is performed after the
|
||||
disambiguation stage. The reason is that moving a definition to a higher-level block cannot decrease
|
||||
its visibility and it is impossible to reference variables defined in a different function.
|
||||
|
||||
The benefit of this stage is that function definitions can be looked up more easily.
|
||||
The benefit of this stage is that function definitions can be looked up more easily
|
||||
and functions can be optimised in isolation without having to traverse the AST.
|
||||
|
||||
## Function Grouper
|
||||
### Function Grouper
|
||||
|
||||
The function grouper has to be applied after the disambiguator and the function hoister.
|
||||
Its effect is that all topmost elements that are not function definitions are moved
|
||||
@ -41,22 +69,91 @@ After this step, a program has the following normal form:
|
||||
|
||||
{ I F... }
|
||||
|
||||
Where I is a block that does not contain any function definitions (not even recursively)
|
||||
Where I is a (potentially empty) block that does not contain any function definitions (not even recursively)
|
||||
and F is a list of function definitions such that no function contains a function definition.
|
||||
|
||||
## Functional Inliner
|
||||
The benefit of this stage is that we always know where the list of function begins.
|
||||
|
||||
The functional inliner depends on the disambiguator, the function hoister and function grouper.
|
||||
It performs function inlining such that the result of the inlining is an expression. This can
|
||||
only be done if the body of the function to be inlined has the form ``{ r := E }`` where ``r``
|
||||
is the single return value of the function, ``E`` is an expression and all arguments in the
|
||||
function call are so-called movable expressions. A movable expression is either a literal, a
|
||||
variable or a function call (or EVM opcode) which does not have side-effects and also does not
|
||||
depend on any side-effects.
|
||||
|
||||
As an example, neither ``mload`` nor ``mstore`` would be allowed.
|
||||
### For Loop Init Rewriter
|
||||
|
||||
## Expression Splitter
|
||||
This transformation moves the initialization part of a for-loop to before
|
||||
the loop:
|
||||
|
||||
for { Init... } C { Post... } {
|
||||
Body...
|
||||
}
|
||||
|
||||
is transformed to
|
||||
|
||||
{
|
||||
Init...
|
||||
for {} C { Post... } {
|
||||
Body...
|
||||
}
|
||||
}
|
||||
|
||||
This eases the rest of the optimisation process because we can ignore
|
||||
the complicated scoping rules of the for loop initialisation block.
|
||||
|
||||
## Pseudo-SSA Transformation
|
||||
|
||||
The purpose of this components is to get the program into a longer form,
|
||||
so that other components can more easily work with it. The final representation
|
||||
will be similar to a static-single-assignment (SSA) form, with the difference
|
||||
that it does not make use of explicit "phi" functions which combines the values
|
||||
from different branches of control flow because such a feature does not exist
|
||||
in the Yul language. Instead, assignments to existing variables are
|
||||
used.
|
||||
|
||||
An example transformation is the following:
|
||||
|
||||
{
|
||||
let a := calldataload(0)
|
||||
let b := calldataload(0x20)
|
||||
if gt(a, 0) {
|
||||
b := mul(b, 0x20)
|
||||
}
|
||||
a := add(a, 1)
|
||||
sstore(a, add(b, 0x20))
|
||||
}
|
||||
|
||||
When all the following transformation steps are applied, the program will look
|
||||
as follows:
|
||||
|
||||
{
|
||||
let _1 := 0
|
||||
let a_1 := calldataload(_1)
|
||||
let _2 := 0x20
|
||||
let b_1 := calldataload(_2)
|
||||
let b := b_1
|
||||
let _3 := 0
|
||||
let _4 := gt(a_1, _3)
|
||||
if _4 {
|
||||
let _5 := 0x20
|
||||
let b_2 := mul(b_1, _5)
|
||||
b := b_2
|
||||
}
|
||||
let a_2 := add(a_1, 1)
|
||||
let _6 := 0x20
|
||||
let _7 := add(b, _6)
|
||||
sstore(a_2, _7)
|
||||
}
|
||||
|
||||
Note that the only variable that is re-assigned in this snippet is ``b``.
|
||||
This re-assignment cannot be avoided because ``b`` has different values
|
||||
depending on the control flow. All other variables never change their
|
||||
value once they are defined. The advantage of this property is that
|
||||
variables can be freely moved around and references to them
|
||||
can be exchanged by their initial value (and vice-versa),
|
||||
as long as these values are still valid in the new context.
|
||||
|
||||
Of course, the code here is far from being optimised. To the contrary, it is much
|
||||
longer. The hope is that this code will be easier to work with and furthermore,
|
||||
there are optimiser steps that undo these changes and make the code more
|
||||
compact again at the end.
|
||||
|
||||
### Expression Splitter
|
||||
|
||||
The expression splitter turns expressions like ``add(mload(x), mul(mload(y), 0x20))``
|
||||
into a sequence of declarations of unique variables that are assigned sub-expressions
|
||||
@ -77,19 +174,351 @@ Note that this transformation does not change the order of opcodes or function c
|
||||
It is not applied to loop conditions, because the loop control flow does not allow
|
||||
this "outlining" of the inner expressions in all cases.
|
||||
|
||||
The final program should be in a form such that with the exception of loop conditions,
|
||||
function calls can only appear in the right-hand side of a variable declaration,
|
||||
assignments or expression statements and all arguments have to be constants or variables.
|
||||
The final program should be in a form such that (with the exception of loop conditions)
|
||||
function calls cannot appear nested inside expressions
|
||||
and all function call arguments have to be constants or variables.
|
||||
|
||||
The benefits of this form are that it is much easier to re-order the sequence of opcodes
|
||||
and it is also easier to perform function call inlining. The drawback is that
|
||||
such code is much harder to read for humans.
|
||||
and it is also easier to perform function call inlining. Furthermore, it is simpler
|
||||
to replace individual parts of expressions or re-organize the "expression tree".
|
||||
The drawback is that such code is much harder to read for humans.
|
||||
|
||||
## Expression Joiner
|
||||
### SSA Transform
|
||||
|
||||
This stage tries to replace repeated assignments to
|
||||
existing variables by declarations of new variables as much as
|
||||
possible.
|
||||
The reassignments are still there, but all references to the
|
||||
reassigned variables are replaced by the newly declared variables.
|
||||
|
||||
Example:
|
||||
|
||||
{
|
||||
let a := 1
|
||||
mstore(a, 2)
|
||||
a := 3
|
||||
}
|
||||
|
||||
is transformed to
|
||||
|
||||
{
|
||||
let a_1 := 1
|
||||
let a := a_1
|
||||
mstore(a_1, 2)
|
||||
let a_3 := 3
|
||||
a := a_3
|
||||
}
|
||||
|
||||
Exact semantics:
|
||||
|
||||
For any variable ``a`` that is assigned to somewhere in the code
|
||||
(variables that are declared with value and never re-assigned
|
||||
are not modified) perform the following transforms:
|
||||
- replace ``let a := v`` by ``let a_i := v let a := a_i``
|
||||
- replace ``a := v`` by ``let a_i := v a := a_i``
|
||||
where ``i`` is a number such that ``a_i`` is yet unused.
|
||||
|
||||
Furthermore, always record the current value of ``i`` used for ``a`` and replace each
|
||||
reference to ``a`` by ``a_i``.
|
||||
The current value mapping is cleared for a variable ``a`` at the end of each block
|
||||
in which it was assigned to and at the end of the for loop init block if it is assigned
|
||||
inside the for loop body or post block.
|
||||
|
||||
After this stage, the Redundant Assign Eliminator is recommended to remove the unnecessary
|
||||
intermediate assignments.
|
||||
|
||||
This stage provides best results if the Expression Splitter and the Common Subexpression Eliminator
|
||||
are run right before it, because then it does not generate excessive amounts of variables.
|
||||
On the other hand, the Common Subexpression Eliminator could be more efficient if run after the
|
||||
SSA transform.
|
||||
|
||||
### Redundant Assign Eliminator
|
||||
|
||||
The SSA transform always generates an assignment of the form ``a := a_i``, even though
|
||||
these might be unnecessary in many cases, like the following example:
|
||||
|
||||
{
|
||||
let a := 1
|
||||
a := mload(a)
|
||||
a := sload(a)
|
||||
sstore(a, 1)
|
||||
}
|
||||
|
||||
The SSA transform converts this snippet to the following:
|
||||
|
||||
{
|
||||
let a_1 := 1
|
||||
a := a_1
|
||||
let a_2 := mload(a_1)
|
||||
a := a_2
|
||||
let a_3 := sload(a_2)
|
||||
a := a_3
|
||||
sstore(a_3, 1)
|
||||
}
|
||||
|
||||
The Redundant Assign Eliminator removes all the three assignments to ``a``, because
|
||||
the value of ``a`` is not used and thus turn this
|
||||
snippet into strict SSA form:
|
||||
|
||||
{
|
||||
let a_1 := 1
|
||||
let a_2 := mload(a_1)
|
||||
let a_3 := sload(a_2)
|
||||
sstore(a_3, 1)
|
||||
}
|
||||
|
||||
Of course the intricate parts of determining whether an assignment is redundant or not
|
||||
are connected to joining control flow.
|
||||
|
||||
The component works as follows in detail:
|
||||
|
||||
The AST is traversed twice: in an information gathering step and in the
|
||||
actual removal step. During information gathering, we maintain a
|
||||
mapping from assignment statements to the three states
|
||||
"unused", "undecided" and "used" which signifies whether the assigned
|
||||
value will be used later by a reference to the variable.
|
||||
|
||||
When an assignment is visited, it is added to the mapping in the "undecided" state
|
||||
(see remark about for loops below) and every other assignment to the same variable
|
||||
that is still in the "undecided" state is changed to "unused".
|
||||
When a variable is referenced, the state of any assignment to that variable still
|
||||
in the "undecided" state is changed to "used".
|
||||
|
||||
At points where control flow splits, a copy
|
||||
of the mapping is handed over to each branch. At points where control flow
|
||||
joins, the two mappings coming from the two branches are combined in the following way:
|
||||
Statements that are only in one mapping or have the same state are used unchanged.
|
||||
Conflicting values are resolved in the following way:
|
||||
|
||||
- "unused", "undecided" -> "undecided"
|
||||
- "unused", "used" -> "used"
|
||||
- "undecided, "used" -> "used"
|
||||
|
||||
For for-loops, the condition, body and post-part are visited twice, taking
|
||||
the joining control-flow at the condition into account.
|
||||
In other words, we create three control flow paths: Zero runs of the loop,
|
||||
one run and two runs and then combine them at the end.
|
||||
|
||||
Simulating a third run or even more is unnecessary, which can be seen as follows:
|
||||
|
||||
A state of an assignment at the beginning of the iteration will deterministically
|
||||
result in a state of that assignment at the end of the iteration. Let this
|
||||
state mapping function be called `f`. The combination of the three different
|
||||
states `unused`, `undecided` and `used` as explained above is the `max`
|
||||
operation where `unused = 0`, `undecided = 1` and `used = 2`.
|
||||
|
||||
The proper way would be to compute
|
||||
|
||||
max(s, f(s), f(f(s)), f(f(f(s))), ...)
|
||||
|
||||
as state after the loop. Since `f` just has a range of three different values,
|
||||
iterating it has to reach a cycle after at most three iterations,
|
||||
and thus `f(f(f(s)))` has to equal one of `s`, `f(s)`, or `f(f(s))`
|
||||
and thus
|
||||
|
||||
max(s, f(s), f(f(s))) = max(s, f(s), f(f(s)), f(f(f(s))), ...).
|
||||
|
||||
In summary, running the loop at most twice is enough because there are only three
|
||||
different states.
|
||||
|
||||
For switch statements that have a "default"-case, there is no control-flow
|
||||
part that skips the switch.
|
||||
|
||||
When a variable goes out of scope, all statements still in the "undecided"
|
||||
state are changed to "unused", unless the variable is the return
|
||||
parameter of a function - there, the state changes to "used".
|
||||
|
||||
In the second traversal, all assignments that are in the "unused" state are removed.
|
||||
|
||||
This step is usually run right after the SSA transform to complete
|
||||
the generation of the pseudo-SSA.
|
||||
|
||||
## Tools
|
||||
|
||||
### Movability
|
||||
|
||||
Movability is a property of an expression. It roughly means that the expression
|
||||
is side-effect free and its evaluation only depends on the values of variables
|
||||
and the call-constant state of the environment. Most expressions are movable.
|
||||
The following parts make an expression non-movable:
|
||||
|
||||
- function calls (might be relaxed in the future if all statements in the function are movable)
|
||||
- opcodes that (can) have side-effects (like ``call`` or ``selfdestruct``)
|
||||
- opcodes that read or write memory, storage or external state information
|
||||
- opcodes that depend on the current PC, memory size or returndata size
|
||||
|
||||
### Dataflow Analyzer
|
||||
|
||||
The Dataflow Analyzer is not an optimizer step itself but is used as a tool
|
||||
by other components. While traversing the AST, it tracks the current value of
|
||||
each variable, as long as that value is a movable expression.
|
||||
It records the variables that are part of the expression
|
||||
that is currently assigned to each other variable. Upon each assignment to
|
||||
a variable ``a``, the current stored value of ``a`` is updated and
|
||||
all stored values of all variables ``b`` are cleared whenever ``a`` is part
|
||||
of the currently stored expression for ``b``.
|
||||
|
||||
At control-flow joins, knowledge about variables is cleared if they have or would be assigned
|
||||
in any of the control-flow paths. For instance, upon entering a
|
||||
for loop, all variables are cleared that will be assigned during the
|
||||
body or the post block.
|
||||
|
||||
## Expression-Scale Simplifications
|
||||
|
||||
These simplification passes change expressions and replace them by equivalent
|
||||
and hopefully simpler expressions.
|
||||
|
||||
### Common Subexpression Eliminator
|
||||
|
||||
This step uses the Dataflow Analyzer and replaces subexpressions that
|
||||
syntactically match the current value of a variable by a reference to
|
||||
that variable. This is an equivalence transform because such subexpressions have
|
||||
to be movable.
|
||||
|
||||
All subexpressions that are identifiers themselves are replaced by their
|
||||
current value if the value is an identifier.
|
||||
|
||||
The combination of the two rules above allow to compute a local value
|
||||
numbering, which means that if two variables have the same
|
||||
value, one of them will always be unused. The Unused Pruner or the
|
||||
Redundant Assign Eliminator will then be able to fully eliminate such
|
||||
variables.
|
||||
|
||||
This step is especially efficient if the expression splitter is run
|
||||
before. If the code is in pseudo-SSA form,
|
||||
the values of variables are available for a longer time and thus we
|
||||
have a higher chance of expressions to be replaceable.
|
||||
|
||||
The expression simplifier will be able to perform better replacements
|
||||
if the common subexpression eliminator was run right before it.
|
||||
|
||||
### Expression Simplifier
|
||||
|
||||
The Expression Simplifier uses the Dataflow Analyzer and makes use
|
||||
of a list of equivalence transforms on expressions like ``X + 0 -> X``
|
||||
to simplify the code.
|
||||
|
||||
It tries to match patterns like ``X + 0`` on each subexpression.
|
||||
During the matching procedure, it resolves variables to their currently
|
||||
assigned expressions to be able to match more deeply nested patterns
|
||||
even when the code is in pseudo-SSA form.
|
||||
|
||||
Some of the patterns like ``X - X -> 0`` can only be applied as long
|
||||
as the expression ``X`` is movable, because otherwise it would remove its potential side-effects.
|
||||
Since variable references are always movable, even if their current
|
||||
value might not be, the Expression Simplifier is again more powerful
|
||||
in split or pseudo-SSA form.
|
||||
|
||||
## Statement-Scale Simplifications
|
||||
|
||||
### Unused Pruner
|
||||
|
||||
This step removes the definitions of all functions that are never referenced.
|
||||
|
||||
It also removes the declaration of variables that are never referenced.
|
||||
If the declaration assigns a value that is not movable, the expression is retained,
|
||||
but its value is discarded.
|
||||
|
||||
All movable expression statements (expressions that are not assigned) are removed.
|
||||
|
||||
### Structural Simplifier
|
||||
|
||||
This is a general step that performs various kinds of simplifications on
|
||||
a structural level:
|
||||
|
||||
- replace if statement with empty body by ``pop(condition)``
|
||||
- replace if statement with true condition by its body
|
||||
- remove if statement with false condition
|
||||
- turn switch with single case into if
|
||||
- replace switch with only default case by ``pop(expression)`` and body
|
||||
- replace switch with literal expression by matching case body
|
||||
- replace for loop with false condition by its initialization part
|
||||
|
||||
This component uses the Dataflow Analyzer.
|
||||
|
||||
### Equivalent Function Combiner
|
||||
|
||||
If two functions are syntactically equivalent, while allowing variable
|
||||
renaming but not any re-ordering, then any reference to one of the
|
||||
functions is replaced by the other.
|
||||
|
||||
The actual removal of the function is performed by the Unused Pruner.
|
||||
|
||||
### Block Flattener
|
||||
|
||||
This stage eliminates nested blocks by inserting the statement in the
|
||||
inner block at the appropriate place in the outer block:
|
||||
|
||||
{
|
||||
let x := 2
|
||||
{
|
||||
let y := 3
|
||||
mstore(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
is transformed to
|
||||
|
||||
{
|
||||
let x := 2
|
||||
let y := 3
|
||||
mstore(x, y)
|
||||
}
|
||||
|
||||
As long as the code is disambiguated, this does not cause a problem because
|
||||
the scopes of variables can only grow.
|
||||
|
||||
## Function Inlining
|
||||
|
||||
### Functional Inliner
|
||||
|
||||
The functional inliner performs restricted function inlining. In particular,
|
||||
the result of this inlining is always a single expression. This can
|
||||
only be done if the function to be inlined has the form ``function f(...) -> r { r := E }`` where
|
||||
``E`` is an expression that does not reference ``r`` and all arguments in the
|
||||
function call are movable expressions. The function call is directly replaced
|
||||
by ``E``, substituting the function call arguments. Because this can cause the
|
||||
function call arguments to be duplicated, removed or re-ordered, they have
|
||||
to be movable.
|
||||
|
||||
### Full Function Inliner
|
||||
|
||||
The Full Function Inliner replaces certain calls of certain functions
|
||||
by the function's body. This is not very helpful in most cases, because
|
||||
it just increases the code size but does not have a benefit. Furthermore,
|
||||
code is usually very expensive and we would often rather have shorter
|
||||
code than more efficient code. In same cases, though, inlining a function
|
||||
can have positive effects on subsequent optimizer steps. This is the case
|
||||
if one of the function arguments is a constant, for example.
|
||||
|
||||
During inlining, a heuristic is used to tell if the function call
|
||||
should be inlined or not.
|
||||
The current heuristic does not inline into "large" functions unless
|
||||
the called function is tiny. Functions that are only used once
|
||||
are inlined, as well as medium-sized functions, while function
|
||||
calls with constant arguments allow slightly larger functions.
|
||||
|
||||
|
||||
In the future, we might want to have a backtracking component
|
||||
that, instead of inlining a function right away, only specializes it,
|
||||
which means that a copy of the function is generated where
|
||||
a certain parameter is always replaced by a constant. After that,
|
||||
we can run the optimizer on this specialized function. If it
|
||||
results in heavy gains, the specialized function is kept,
|
||||
otherwise the original function is used instead.
|
||||
|
||||
## Cleanup
|
||||
|
||||
The cleanup is performed at the end of the optimizer run. It tries
|
||||
to combine split expressions into deeply nested ones again and also
|
||||
improves the "compilability" for stack machines by eliminating
|
||||
variables as much as possible.
|
||||
|
||||
### Expression Joiner
|
||||
|
||||
This is the opposite operation of the expression splitter. It turns a sequence of
|
||||
variable declarations that have exactly one reference into a complex expression.
|
||||
This stage again fully preserves the order of function calls and opcode executions.
|
||||
This stage fully preserves the order of function calls and opcode executions.
|
||||
It does not make use of any information concerning the commutability of opcodes;
|
||||
if moving the value of a variable to its place of use would change the order
|
||||
of any function call or opcode execution, the transformation is not performed.
|
||||
@ -97,23 +526,69 @@ of any function call or opcode execution, the transformation is not performed.
|
||||
Note that the component will not move the assigned value of a variable assignment
|
||||
or a variable that is referenced more than once.
|
||||
|
||||
## Common Subexpression Eliminator
|
||||
The snippet ``let x := add(0, 2) let y := mul(x, mload(2))`` is not transformed,
|
||||
because it would cause the order of the call to the opcodes ``add`` and
|
||||
``mload`` to be swapped - even though this would not make a difference
|
||||
because ``add`` is movable.
|
||||
|
||||
This step replaces a subexpression by the value of a pre-existing variable
|
||||
that currently has the same value (only if the value is movable), based
|
||||
on a syntactic comparison.
|
||||
When reordering opcodes like that, variable references and literals are ignored.
|
||||
Because of that, the snippet ``let x := add(0, 2) let y := mul(x, 3)`` is
|
||||
transformed to ``let y := mul(add(0, 2), 3)``, even though the ``add`` opcode
|
||||
would be executed after the evaluation of the literal ``3``.
|
||||
|
||||
This can be used to compute a local value numbering, especially if the
|
||||
expression splitter is used before.
|
||||
### SSA Reverser
|
||||
|
||||
The expression simplifier will be able to perform better replacements
|
||||
if the common subexpression eliminator was run right before it.
|
||||
This is a tiny step that helps in reversing the effects of the SSA transform
|
||||
if it is combined with the Common Subexpression Eliminator and the
|
||||
Unused Pruner.
|
||||
|
||||
Prerequisites: Disambiguator
|
||||
The SSA form we generate is detrimental to code generation on the EVM and
|
||||
WebAssembly alike because it generates many local variables. It would
|
||||
be better to just re-use existing variables with assignments instead of
|
||||
fresh variable declarations.
|
||||
|
||||
## Full Function Inliner
|
||||
The SSA transform rewrites
|
||||
|
||||
## Rematerialisation
|
||||
a := E
|
||||
mstore(a, 1)
|
||||
|
||||
to
|
||||
|
||||
let a_1 := E
|
||||
a := a_1
|
||||
mstore(a_1, 1)
|
||||
|
||||
The problem is that instead of ``a``, the variable ``a_1`` is used
|
||||
whenever ``a`` was referenced. The SSA transform changes statements
|
||||
of this form by just swapping out the declaration and the assignment. The above
|
||||
snippet is turned into
|
||||
|
||||
a := E
|
||||
let a_1 := a
|
||||
mstore(a_1, 1)
|
||||
|
||||
This is a very simple equivalence transform, but when we now run the
|
||||
Common Subexpression Eliminator, it will replace all occurrences of ``a_1``
|
||||
by ``a`` (until ``a`` is re-assigned). The Unused Pruner will then
|
||||
eliminate the variable ``a_1`` altogether and thus fully reverse the
|
||||
SSA transform.
|
||||
|
||||
### Stack Compressor
|
||||
|
||||
One problem that makes code generation for the Ethereum Virtual Machine
|
||||
hard is the fact that there is a hard limit of 16 slots for reaching
|
||||
down the expression stack. This more or less translates to a limit
|
||||
of 16 local variables. The stack compressor takes Yul code and
|
||||
compiles it to EVM bytecode. Whenever the stack difference is too
|
||||
large, it records the function this happened in.
|
||||
|
||||
For each function that caused such a problem, the Rematerialiser
|
||||
is called with a special request to aggressively eliminate specific
|
||||
variables sorted by the cost of their values.
|
||||
|
||||
On failure, this procedure is repeated multiple times.
|
||||
|
||||
### Rematerialiser
|
||||
|
||||
The rematerialisation stage tries to replace variable references by the expression that
|
||||
was last assigned to the variable. This is of course only beneficial if this expression
|
||||
@ -123,31 +598,10 @@ point of use. The main benefit of this stage is that it can save stack slots if
|
||||
leads to a variable being eliminated completely (see below), but it can also
|
||||
save a DUP opcode on the EVM if the expression is very cheap.
|
||||
|
||||
The algorithm only allows movable expressions (see above for a definition) in this case.
|
||||
Expressions that contain other variables are also disallowed if one of those variables
|
||||
have been assigned to in the meantime. This is also not applied to variables where
|
||||
assignment and use span across loops and conditionals.
|
||||
|
||||
## Unused Definition Pruner
|
||||
|
||||
If a variable or function is not referenced, it is removed from the code.
|
||||
If there are two assignments to a variable where the first one is a movable expression
|
||||
and the variable is not used between the two assignments (and the second is not inside
|
||||
a loop or conditional, the first one is not inside), the first assignment is removed.
|
||||
|
||||
This step also removes movable expression statements.
|
||||
|
||||
|
||||
## Function Unifier
|
||||
|
||||
## Expression Simplifier
|
||||
|
||||
This step can only be applied for the EVM-flavoured dialect of Yul. It applies
|
||||
simple rules like ``x + 0 == x`` to simplify expressions.
|
||||
|
||||
## Ineffective Statement Remover
|
||||
|
||||
This step removes statements that have no side-effects.
|
||||
The Rematerialiser uses the Dataflow Analyzer to track the current values of variables,
|
||||
which are always movable.
|
||||
If the value is very cheap or the variable was explicitly requested to be eliminated,
|
||||
the variable reference is replaced by its current value.
|
||||
|
||||
## WebAssembly specific
|
||||
|
||||
|
@ -149,6 +149,16 @@ void RedundantAssignEliminator::operator()(ForLoop const& _forLoop)
|
||||
join(zeroRuns);
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(Break const&)
|
||||
{
|
||||
yulAssert(false, "Not implemented yet.");
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(Continue const&)
|
||||
{
|
||||
yulAssert(false, "Not implemented yet.");
|
||||
}
|
||||
|
||||
void RedundantAssignEliminator::operator()(Block const& _block)
|
||||
{
|
||||
// This will set all variables that are declared in this
|
||||
|
@ -112,6 +112,8 @@ public:
|
||||
void operator()(Switch const& _switch) override;
|
||||
void operator()(FunctionDefinition const&) override;
|
||||
void operator()(ForLoop const&) override;
|
||||
void operator()(Break const&) override;
|
||||
void operator()(Continue const&) override;
|
||||
void operator()(Block const& _block) override;
|
||||
|
||||
static void run(Dialect const& _dialect, Block& _ast);
|
||||
|
@ -21,10 +21,15 @@
|
||||
#include <libdevcore/CommonData.h>
|
||||
#include <libdevcore/Visitor.h>
|
||||
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
#include <boost/algorithm/cxx11/any_of.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
|
||||
using OptionalStatements = boost::optional<vector<Statement>>;
|
||||
|
||||
namespace {
|
||||
|
||||
ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression)
|
||||
@ -36,6 +41,86 @@ ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _
|
||||
}};
|
||||
}
|
||||
|
||||
void removeEmptyDefaultFromSwitch(Switch& _switchStmt)
|
||||
{
|
||||
boost::remove_erase_if(
|
||||
_switchStmt.cases,
|
||||
[](Case const& _case) { return !_case.value && _case.body.statements.empty(); }
|
||||
);
|
||||
}
|
||||
|
||||
void removeEmptyCasesFromSwitch(Switch& _switchStmt)
|
||||
{
|
||||
bool hasDefault = boost::algorithm::any_of(
|
||||
_switchStmt.cases,
|
||||
[](Case const& _case) { return !_case.value; }
|
||||
);
|
||||
|
||||
if (hasDefault)
|
||||
return;
|
||||
|
||||
boost::remove_erase_if(
|
||||
_switchStmt.cases,
|
||||
[](Case const& _case) { return _case.body.statements.empty(); }
|
||||
);
|
||||
}
|
||||
|
||||
OptionalStatements replaceConstArgSwitch(Switch& _switchStmt, u256 const& _constExprVal)
|
||||
{
|
||||
Block* matchingCaseBlock = nullptr;
|
||||
Case* defaultCase = nullptr;
|
||||
|
||||
for (auto& _case: _switchStmt.cases)
|
||||
{
|
||||
if (_case.value && valueOfLiteral(*_case.value) == _constExprVal)
|
||||
{
|
||||
matchingCaseBlock = &_case.body;
|
||||
break;
|
||||
}
|
||||
else if (!_case.value)
|
||||
defaultCase = &_case;
|
||||
}
|
||||
|
||||
if (!matchingCaseBlock && defaultCase)
|
||||
matchingCaseBlock = &defaultCase->body;
|
||||
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
|
||||
if (matchingCaseBlock)
|
||||
s->emplace_back(std::move(*matchingCaseBlock));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt)
|
||||
{
|
||||
yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
|
||||
|
||||
auto& switchCase = _switchStmt.cases.front();
|
||||
auto loc = locationOf(*_switchStmt.expression);
|
||||
if (switchCase.value)
|
||||
{
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
s->emplace_back(If{
|
||||
std::move(_switchStmt.location),
|
||||
make_unique<Expression>(FunctionalInstruction{
|
||||
std::move(loc),
|
||||
solidity::Instruction::EQ,
|
||||
{std::move(*switchCase.value), std::move(*_switchStmt.expression)}
|
||||
}),
|
||||
std::move(switchCase.body)
|
||||
});
|
||||
return s;
|
||||
}
|
||||
else
|
||||
{
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
|
||||
s->emplace_back(std::move(switchCase.body));
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void StructuralSimplifier::operator()(Block& _block)
|
||||
@ -45,6 +130,19 @@ void StructuralSimplifier::operator()(Block& _block)
|
||||
popScope();
|
||||
}
|
||||
|
||||
OptionalStatements StructuralSimplifier::reduceNoCaseSwitch(Switch& _switchStmt) const
|
||||
{
|
||||
yulAssert(_switchStmt.cases.empty(), "Expected no case!");
|
||||
|
||||
auto loc = locationOf(*_switchStmt.expression);
|
||||
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
|
||||
s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
boost::optional<dev::u256> StructuralSimplifier::hasLiteralValue(Expression const& _expression) const
|
||||
{
|
||||
Expression const* expr = &_expression;
|
||||
@ -59,7 +157,7 @@ boost::optional<dev::u256> StructuralSimplifier::hasLiteralValue(Expression cons
|
||||
if (expr && expr->type() == typeid(Literal))
|
||||
{
|
||||
Literal const& literal = boost::get<Literal>(*expr);
|
||||
return valueOfNumberLiteral(literal);
|
||||
return valueOfLiteral(literal);
|
||||
}
|
||||
|
||||
return boost::optional<u256>();
|
||||
@ -67,7 +165,6 @@ boost::optional<dev::u256> StructuralSimplifier::hasLiteralValue(Expression cons
|
||||
|
||||
void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
||||
{
|
||||
using OptionalStatements = boost::optional<vector<Statement>>;
|
||||
GenericFallbackReturnsVisitor<OptionalStatements, If, Switch, ForLoop> const visitor(
|
||||
[&](If& _ifStmt) -> OptionalStatements {
|
||||
if (_ifStmt.body.statements.empty())
|
||||
@ -83,58 +180,18 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
||||
return {};
|
||||
},
|
||||
[&](Switch& _switchStmt) -> OptionalStatements {
|
||||
if (_switchStmt.cases.size() == 1)
|
||||
{
|
||||
auto& switchCase = _switchStmt.cases.front();
|
||||
auto loc = locationOf(*_switchStmt.expression);
|
||||
if (switchCase.value)
|
||||
{
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
s->emplace_back(If{
|
||||
std::move(_switchStmt.location),
|
||||
make_unique<Expression>(FunctionalInstruction{
|
||||
std::move(loc),
|
||||
solidity::Instruction::EQ,
|
||||
{std::move(*switchCase.value), std::move(*_switchStmt.expression)}
|
||||
}), std::move(switchCase.body)});
|
||||
return s;
|
||||
}
|
||||
else
|
||||
{
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
|
||||
s->emplace_back(std::move(switchCase.body));
|
||||
return s;
|
||||
}
|
||||
}
|
||||
else if (boost::optional<u256> const constExprVal = hasLiteralValue(*_switchStmt.expression))
|
||||
{
|
||||
Block* matchingCaseBlock = nullptr;
|
||||
Case* defaultCase = nullptr;
|
||||
if (boost::optional<u256> const constExprVal = hasLiteralValue(*_switchStmt.expression))
|
||||
return replaceConstArgSwitch(_switchStmt, constExprVal.get());
|
||||
|
||||
for (auto& _case: _switchStmt.cases)
|
||||
{
|
||||
if (_case.value && valueOfNumberLiteral(*_case.value) == constExprVal)
|
||||
{
|
||||
matchingCaseBlock = &_case.body;
|
||||
break;
|
||||
}
|
||||
else if (!_case.value)
|
||||
defaultCase = &_case;
|
||||
}
|
||||
removeEmptyDefaultFromSwitch(_switchStmt);
|
||||
removeEmptyCasesFromSwitch(_switchStmt);
|
||||
|
||||
if (!matchingCaseBlock && defaultCase)
|
||||
matchingCaseBlock = &defaultCase->body;
|
||||
if (_switchStmt.cases.empty())
|
||||
return reduceNoCaseSwitch(_switchStmt);
|
||||
else if (_switchStmt.cases.size() == 1)
|
||||
return reduceSingleCaseSwitch(_switchStmt);
|
||||
|
||||
OptionalStatements s = vector<Statement>{};
|
||||
|
||||
if (matchingCaseBlock)
|
||||
s->emplace_back(std::move(*matchingCaseBlock));
|
||||
|
||||
return s;
|
||||
}
|
||||
else
|
||||
return {};
|
||||
return {};
|
||||
},
|
||||
[&](ForLoop& _forLoop) -> OptionalStatements {
|
||||
if (expressionAlwaysFalse(*_forLoop.condition))
|
||||
@ -148,10 +205,11 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements)
|
||||
_statements,
|
||||
[&](Statement& _stmt) -> OptionalStatements
|
||||
{
|
||||
visit(_stmt);
|
||||
OptionalStatements result = boost::apply_visitor(visitor, _stmt);
|
||||
if (result)
|
||||
simplify(*result);
|
||||
else
|
||||
visit(_stmt);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
@ -28,10 +28,13 @@ namespace yul
|
||||
* - replace if with empty body with pop(condition)
|
||||
* - replace if with true condition with its body
|
||||
* - remove if with false condition
|
||||
* - remove empty default switch case
|
||||
* - remove empty switch case if no default case exists
|
||||
* - replace switch with no cases with pop(expression)
|
||||
* - turn switch with single case into if
|
||||
* - replace switch with only default case with pop(expression) and body
|
||||
* - replace switch with const expr with matching case body
|
||||
* - remove for with false condition
|
||||
* - replace for with false condition by its initialization part
|
||||
*
|
||||
* Prerequisites: Disambiguator
|
||||
*
|
||||
@ -49,6 +52,7 @@ private:
|
||||
bool expressionAlwaysTrue(Expression const& _expression);
|
||||
bool expressionAlwaysFalse(Expression const& _expression);
|
||||
boost::optional<dev::u256> hasLiteralValue(Expression const& _expression) const;
|
||||
boost::optional<std::vector<Statement>> reduceNoCaseSwitch(Switch& _switchStmt) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include <libyul/optimiser/StructuralSimplifier.h>
|
||||
#include <libyul/optimiser/RedundantAssignEliminator.h>
|
||||
#include <libyul/optimiser/VarNameCleaner.h>
|
||||
#include <libyul/optimiser/Metrics.h>
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/AsmData.h>
|
||||
@ -80,8 +81,16 @@ void OptimiserSuite::run(
|
||||
|
||||
NameDispenser dispenser{*_dialect, ast};
|
||||
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
size_t codeSize = 0;
|
||||
for (size_t rounds = 0; rounds < 12; ++rounds)
|
||||
{
|
||||
{
|
||||
size_t newSize = CodeSize::codeSizeIncludingFunctions(ast);
|
||||
if (newSize == codeSize)
|
||||
break;
|
||||
codeSize = newSize;
|
||||
}
|
||||
|
||||
{
|
||||
// Turn into SSA and simplify
|
||||
ExpressionSplitter{*_dialect, dispenser}(ast);
|
||||
|
@ -55,6 +55,8 @@ public:
|
||||
bool statementEqual(Switch const& _lhs, Switch const& _rhs);
|
||||
bool switchCaseEqual(Case const& _lhs, Case const& _rhs);
|
||||
bool statementEqual(ForLoop const& _lhs, ForLoop const& _rhs);
|
||||
bool statementEqual(Break const&, Break const&) { return true; }
|
||||
bool statementEqual(Continue const&, Continue const&) { return true; }
|
||||
bool statementEqual(Block const& _lhs, Block const& _rhs);
|
||||
private:
|
||||
bool statementEqual(Instruction const& _lhs, Instruction const& _rhs);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user