mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #2947 from ethereum/develop
Merge develop into release for 0.4.17.
This commit is contained in:
commit
bdeb9e52a2
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "deps"]
|
|
||||||
path = deps
|
|
||||||
url = https://github.com/ethereum/cpp-dependencies
|
|
@ -174,7 +174,6 @@ cache:
|
|||||||
ccache: true
|
ccache: true
|
||||||
directories:
|
directories:
|
||||||
- boost_1_57_0
|
- boost_1_57_0
|
||||||
- build
|
|
||||||
- $HOME/.local
|
- $HOME/.local
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@ -221,7 +220,7 @@ deploy:
|
|||||||
branch:
|
branch:
|
||||||
- develop
|
- develop
|
||||||
- release
|
- release
|
||||||
- /^v[0-9]/
|
- /^v\d/
|
||||||
# This is the deploy target for the native build (Linux and macOS)
|
# This is the deploy target for the native build (Linux and macOS)
|
||||||
# which generates ZIPs per commit and the source tarball.
|
# which generates ZIPs per commit and the source tarball.
|
||||||
#
|
#
|
||||||
|
@ -8,14 +8,17 @@ include(EthPolicy)
|
|||||||
eth_policy()
|
eth_policy()
|
||||||
|
|
||||||
# project name and version should be set after cmake_policy CMP0048
|
# project name and version should be set after cmake_policy CMP0048
|
||||||
set(PROJECT_VERSION "0.4.16")
|
set(PROJECT_VERSION "0.4.17")
|
||||||
project(solidity VERSION ${PROJECT_VERSION})
|
project(solidity VERSION ${PROJECT_VERSION})
|
||||||
|
|
||||||
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
|
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
|
||||||
|
|
||||||
|
# Setup cccache.
|
||||||
|
include(EthCcache)
|
||||||
|
|
||||||
# Let's find our dependencies
|
# Let's find our dependencies
|
||||||
include(EthDependencies)
|
include(EthDependencies)
|
||||||
include(deps/jsoncpp.cmake)
|
include(jsoncpp)
|
||||||
|
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
|
|
||||||
|
25
Changelog.md
25
Changelog.md
@ -1,3 +1,28 @@
|
|||||||
|
### 0.4.17 (2017-09-21)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* Assembly Parser: Support multiple assignment (``x, y := f()``).
|
||||||
|
* Code Generator: Keep a single copy of encoding functions when using the experimental "ABIEncoderV2".
|
||||||
|
* Code Generator: Partial support for passing ``structs`` as arguments and return parameters (requires ``pragma experimental ABIEncoderV2;`` for now).
|
||||||
|
* General: Support ``pragma experimental "v0.5.0";`` to activate upcoming breaking changes.
|
||||||
|
* General: Added ``.selector`` member on external function types to retrieve their signature.
|
||||||
|
* Optimizer: Add new optimization step to remove unused ``JUMPDEST``s.
|
||||||
|
* Static Analyzer: Warn when using deprecated builtins ``sha3`` and ``suicide``
|
||||||
|
(replaced by ``keccak256`` and ``selfdestruct``, introduced in 0.4.2 and 0.2.0, respectively).
|
||||||
|
* Syntax Checker: Warn if no visibility is specified on contract functions.
|
||||||
|
* Type Checker: Display helpful warning for unused function arguments/return parameters.
|
||||||
|
* Type Checker: Do not show the same error multiple times for events.
|
||||||
|
* Type Checker: Greatly reduce the number of duplicate errors shown for duplicate constructors and functions.
|
||||||
|
* Type Checker: Warn on using literals as tight packing parameters in ``keccak256``, ``sha3``, ``sha256`` and ``ripemd160``.
|
||||||
|
* Type Checker: Enforce ``view`` and ``pure``.
|
||||||
|
* Type Checker: Enforce ``view`` / ``constant`` with error as experimental 0.5.0 feature.
|
||||||
|
* Type Checker: Enforce fallback functions to be ``external`` as experimental 0.5.0 feature.
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* ABI JSON: Include all overloaded events.
|
||||||
|
* Parser: Crash fix related to parseTypeName.
|
||||||
|
* Type Checker: Allow constant byte arrays.
|
||||||
|
|
||||||
### 0.4.16 (2017-08-24)
|
### 0.4.16 (2017-08-24)
|
||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# The Solidity Contract-Oriented Programming Language
|
# The Solidity Contract-Oriented Programming Language
|
||||||
[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[![Join the chat at https://gitter.im/ethereum/solidity](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/solidity?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/ethereum/solidity.svg?branch=develop)](https://travis-ci.org/ethereum/solidity)
|
||||||
|
|
||||||
## Useful links
|
## Useful links
|
||||||
To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
|
To get started you can find an introduction to the language in the [Solidity documentation](https://solidity.readthedocs.org). In the documentation, you can find [code examples](https://solidity.readthedocs.io/en/latest/solidity-by-example.html) as well as [a reference](https://solidity.readthedocs.io/en/latest/solidity-in-depth.html) of the syntax and details on how to write smart contracts.
|
||||||
|
48
circle.yml
48
circle.yml
@ -1,10 +1,50 @@
|
|||||||
version: 2
|
version: 2
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
branches:
|
|
||||||
ignore:
|
|
||||||
- /.*/
|
|
||||||
docker:
|
docker:
|
||||||
- image: trzeci/emscripten:sdk-tag-1.37.18-64bit
|
- image: trzeci/emscripten:sdk-tag-1.37.21-64bit
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Install external tests deps
|
||||||
|
command: |
|
||||||
|
apt-get -qq update
|
||||||
|
apt-get -qy install netcat curl
|
||||||
|
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.4/install.sh | NVM_DIR=/usr/local/nvm bash
|
||||||
|
- run:
|
||||||
|
name: Test external tests deps
|
||||||
|
command: |
|
||||||
|
export NVM_DIR="/usr/local/nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||||
|
nvm --version
|
||||||
|
nvm install 6
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
- run:
|
||||||
|
name: Init submodules
|
||||||
|
command: |
|
||||||
|
git submodule update --init
|
||||||
|
- restore_cache:
|
||||||
|
name: Restore Boost build
|
||||||
|
key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }}
|
||||||
|
- run:
|
||||||
|
name: Bootstrap Boost
|
||||||
|
command: |
|
||||||
|
scripts/travis-emscripten/install_deps.sh
|
||||||
|
- run:
|
||||||
|
name: Build
|
||||||
|
command: |
|
||||||
|
scripts/travis-emscripten/build_emscripten.sh
|
||||||
|
- save_cache:
|
||||||
|
name: Save Boost build
|
||||||
|
key: *boost-cache-key
|
||||||
|
paths:
|
||||||
|
- boost_1_57_0
|
||||||
|
- run:
|
||||||
|
name: Test
|
||||||
|
command: |
|
||||||
|
. /usr/local/nvm/nvm.sh
|
||||||
|
scripts/test_emscripten.sh
|
||||||
|
- store_artifacts:
|
||||||
|
path: build/solc/soljson.js
|
||||||
|
destination: soljson.js
|
||||||
|
15
cmake/EthCcache.cmake
Normal file
15
cmake/EthCcache.cmake
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Setup ccache.
|
||||||
|
#
|
||||||
|
# The ccache is auto-enabled if the tool is found.
|
||||||
|
# To disable set -DCCACHE=OFF option.
|
||||||
|
if(NOT DEFINED CMAKE_CXX_COMPILER_LAUNCHER)
|
||||||
|
find_program(CCACHE ccache DOC "ccache tool path; set to OFF to disable")
|
||||||
|
if(CCACHE)
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
|
||||||
|
if(COMMAND cotire)
|
||||||
|
# Change ccache config to meet cotire requirements.
|
||||||
|
set(ENV{CCACHE_SLOPPINESS} pch_defines,time_macros)
|
||||||
|
endif()
|
||||||
|
message(STATUS "[ccache] Enabled: ${CCACHE}")
|
||||||
|
endif()
|
||||||
|
endif()
|
@ -14,14 +14,6 @@
|
|||||||
#
|
#
|
||||||
# These settings then end up spanning all POSIX platforms (Linux, OS X, BSD, etc)
|
# These settings then end up spanning all POSIX platforms (Linux, OS X, BSD, etc)
|
||||||
|
|
||||||
# Use ccache if available
|
|
||||||
find_program(CCACHE_FOUND ccache)
|
|
||||||
if(CCACHE_FOUND)
|
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
|
|
||||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache)
|
|
||||||
message("Using ccache")
|
|
||||||
endif(CCACHE_FOUND)
|
|
||||||
|
|
||||||
include(CheckCXXCompilerFlag)
|
include(CheckCXXCompilerFlag)
|
||||||
|
|
||||||
check_cxx_compiler_flag(-fstack-protector-strong have_stack_protector_strong)
|
check_cxx_compiler_flag(-fstack-protector-strong have_stack_protector_strong)
|
||||||
|
51
cmake/jsoncpp.cmake
Normal file
51
cmake/jsoncpp.cmake
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
if (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
||||||
|
set(JSONCPP_CMAKE_COMMAND emcmake cmake)
|
||||||
|
else()
|
||||||
|
set(JSONCPP_CMAKE_COMMAND ${CMAKE_COMMAND})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Disable implicit fallthrough warning in jsoncpp for gcc >= 7 until the upstream handles it properly
|
||||||
|
if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
|
||||||
|
set(JSONCCP_EXTRA_FLAGS -Wno-implicit-fallthrough)
|
||||||
|
else()
|
||||||
|
set(JSONCCP_EXTRA_FLAGS "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(prefix "${CMAKE_BINARY_DIR}/deps")
|
||||||
|
set(JSONCPP_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}jsoncpp${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||||
|
set(JSONCPP_INCLUDE_DIR "${prefix}/include")
|
||||||
|
|
||||||
|
set(byproducts "")
|
||||||
|
if(CMAKE_VERSION VERSION_GREATER 3.1)
|
||||||
|
set(byproducts BUILD_BYPRODUCTS "${JSONCPP_LIBRARY}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
ExternalProject_Add(jsoncpp-project
|
||||||
|
PREFIX "${prefix}"
|
||||||
|
DOWNLOAD_DIR "${CMAKE_SOURCE_DIR}/deps/downloads"
|
||||||
|
DOWNLOAD_NAME jsoncpp-1.7.7.tar.gz
|
||||||
|
URL https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz
|
||||||
|
URL_HASH SHA256=087640ebcf7fbcfe8e2717a0b9528fff89c52fcf69fa2a18cc2b538008098f97
|
||||||
|
CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND}
|
||||||
|
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||||
|
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
|
||||||
|
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
|
||||||
|
# Build static lib but suitable to be included in a shared lib.
|
||||||
|
-DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS}
|
||||||
|
-DJSONCPP_WITH_TESTS=OFF
|
||||||
|
-DJSONCPP_WITH_PKGCONFIG_SUPPORT=OFF
|
||||||
|
-DCMAKE_CXX_FLAGS=${JSONCCP_EXTRA_FLAGS}
|
||||||
|
# Overwrite build and install commands to force Release build on MSVC.
|
||||||
|
BUILD_COMMAND cmake --build <BINARY_DIR> --config Release
|
||||||
|
INSTALL_COMMAND cmake --build <BINARY_DIR> --config Release --target install
|
||||||
|
${byproducts}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create jsoncpp imported library
|
||||||
|
add_library(jsoncpp STATIC IMPORTED)
|
||||||
|
file(MAKE_DIRECTORY ${JSONCPP_INCLUDE_DIR}) # Must exist.
|
||||||
|
set_property(TARGET jsoncpp PROPERTY IMPORTED_LOCATION ${JSONCPP_LIBRARY})
|
||||||
|
set_property(TARGET jsoncpp PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${JSONCPP_INCLUDE_DIR})
|
||||||
|
add_dependencies(jsoncpp jsoncpp-project)
|
1
deps
1
deps
@ -1 +0,0 @@
|
|||||||
Subproject commit e5c8316db8d3daa0abc3b5af8545ce330057608c
|
|
@ -17,6 +17,8 @@ We assume the interface functions of a contract are strongly typed, known at com
|
|||||||
|
|
||||||
This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.
|
This specification does not address contracts whose interface is dynamic or otherwise known only at run-time. Should these cases become important they can be adequately handled as facilities built within the Ethereum ecosystem.
|
||||||
|
|
||||||
|
.. _abi_function_selector:
|
||||||
|
|
||||||
Function Selector
|
Function Selector
|
||||||
=================
|
=================
|
||||||
|
|
||||||
@ -34,45 +36,47 @@ Types
|
|||||||
|
|
||||||
The following elementary types exist:
|
The following elementary types exist:
|
||||||
|
|
||||||
- `uint<M>`: unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. e.g. `uint32`, `uint8`, `uint256`.
|
- ``uint<M>``: unsigned integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``. e.g. ``uint32``, ``uint8``, ``uint256``.
|
||||||
|
|
||||||
- `int<M>`: two's complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`.
|
- ``int<M>``: two's complement signed integer type of ``M`` bits, ``0 < M <= 256``, ``M % 8 == 0``.
|
||||||
|
|
||||||
- `address`: equivalent to `uint160`, except for the assumed interpretation and language typing.
|
- ``address``: equivalent to ``uint160``, except for the assumed interpretation and language typing.
|
||||||
|
|
||||||
- `uint`, `int`: synonyms for `uint256`, `int256` respectively (not to be used for computing the function selector).
|
- ``uint``, ``int``: synonyms for ``uint256``, ``int256`` respectively (this shorthand not to be used for computing the function selector).
|
||||||
|
|
||||||
- `bool`: equivalent to `uint8` restricted to the values 0 and 1
|
- ``bool``: equivalent to ``uint8`` restricted to the values 0 and 1
|
||||||
|
|
||||||
- `fixed<M>x<N>`: signed fixed-point decimal number of `M` bits, `8 <= M <= 256`, `M % 8 ==0`, and `0 < N <= 80`, which denotes the value `v` as `v / (10 ** N)`.
|
- ``fixed<M>x<N>``: signed fixed-point decimal number of ``M`` bits, ``8 <= M <= 256``, ``M % 8 ==0``, and ``0 < N <= 80``, which denotes the value ``v`` as ``v / (10 ** N)``.
|
||||||
|
|
||||||
- `ufixed<M>x<N>`: unsigned variant of `fixed<M>x<N>`.
|
- ``ufixed<M>x<N>``: unsigned variant of ``fixed<M>x<N>``.
|
||||||
|
|
||||||
- `fixed`, `ufixed`: synonyms for `fixed128x19`, `ufixed128x19` respectively (not to be used for computing the function selector).
|
- ``fixed``, ``ufixed``: synonyms for ``fixed128x19``, ``ufixed128x19`` respectively (this shorthand not to be used for computing the function selector).
|
||||||
|
|
||||||
- `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`.
|
- ``bytes<M>``: binary type of ``M`` bytes, ``0 < M <= 32``.
|
||||||
|
|
||||||
- `function`: equivalent to `bytes24`: an address, followed by a function selector
|
- ``function``: equivalent to ``bytes24``: an address, followed by a function selector
|
||||||
|
|
||||||
The following (fixed-size) array type exists:
|
The following (fixed-size) array type exists:
|
||||||
|
|
||||||
- `<type>[M]`: a fixed-length array of the given fixed-length type.
|
- ``<type>[M]``: a fixed-length array of ``M`` elements, ``M > 0``, of the given type.
|
||||||
|
|
||||||
The following non-fixed-size types exist:
|
The following non-fixed-size types exist:
|
||||||
|
|
||||||
- `bytes`: dynamic sized byte sequence.
|
- ``bytes``: dynamic sized byte sequence.
|
||||||
|
|
||||||
- `string`: dynamic sized unicode string assumed to be UTF-8 encoded.
|
- ``string``: dynamic sized unicode string assumed to be UTF-8 encoded.
|
||||||
|
|
||||||
- `<type>[]`: a variable-length array of the given fixed-length type.
|
- ``<type>[]``: a variable-length array of elements of the given type.
|
||||||
|
|
||||||
Types can be combined to anonymous structs by enclosing a finite non-negative number
|
Types can be combined to a tuple by enclosing a finite non-negative number
|
||||||
of them inside parentheses, separated by commas:
|
of them inside parentheses, separated by commas:
|
||||||
|
|
||||||
- `(T1,T2,...,Tn)`: anonymous struct (ordered tuple) consisting of the types `T1`, ..., `Tn`, `n >= 0`
|
- ``(T1,T2,...,Tn)``: tuple consisting of the types ``T1``, ..., ``Tn``, ``n >= 0``
|
||||||
|
|
||||||
It is possible to form structs of structs, arrays of structs and so on.
|
It is possible to form tuples of tuples, arrays of tuples and so on.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Solidity supports all the types presented above with the same names with the exception of tuples. The ABI tuple type is utilised for encoding Solidity ``structs``.
|
||||||
|
|
||||||
Formal Specification of the Encoding
|
Formal Specification of the Encoding
|
||||||
====================================
|
====================================
|
||||||
@ -82,98 +86,99 @@ properties, which are especially useful if some arguments are nested arrays:
|
|||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
|
|
||||||
1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve `a_i[k][l][r]`. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case.
|
1. The number of reads necessary to access a value is at most the depth of the value inside the argument array structure, i.e. four reads are needed to retrieve ``a_i[k][l][r]``. In a previous version of the ABI, the number of reads scaled linearly with the total number of dynamic parameters in the worst case.
|
||||||
|
|
||||||
2. The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative "addresses"
|
2. The data of a variable or array element is not interleaved with other data and it is relocatable, i.e. it only uses relative "addresses"
|
||||||
|
|
||||||
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
|
We distinguish static and dynamic types. Static types are encoded in-place and dynamic types are encoded at a separately allocated location after the current block.
|
||||||
|
|
||||||
**Definition:** The following types are called "dynamic":
|
**Definition:** The following types are called "dynamic":
|
||||||
* `bytes`
|
|
||||||
* `string`
|
* ``bytes``
|
||||||
* `T[]` for any `T`
|
* ``string``
|
||||||
* `T[k]` for any dynamic `T` and any `k > 0`
|
* ``T[]`` for any ``T``
|
||||||
* `(T1,...,Tk)` if any `Ti` is dynamic for `1 <= i <= k`
|
* ``T[k]`` for any dynamic ``T`` and any ``k > 0``
|
||||||
|
* ``(T1,...,Tk)`` if any ``Ti`` is dynamic for ``1 <= i <= k``
|
||||||
|
|
||||||
All other types are called "static".
|
All other types are called "static".
|
||||||
|
|
||||||
**Definition:** `len(a)` is the number of bytes in a binary string `a`.
|
**Definition:** ``len(a)`` is the number of bytes in a binary string ``a``.
|
||||||
The type of `len(a)` is assumed to be `uint256`.
|
The type of ``len(a)`` is assumed to be ``uint256``.
|
||||||
|
|
||||||
We define `enc`, the actual encoding, as a mapping of values of the ABI types to binary strings such
|
We define ``enc``, the actual encoding, as a mapping of values of the ABI types to binary strings such
|
||||||
that `len(enc(X))` depends on the value of `X` if and only if the type of `X` is dynamic.
|
that ``len(enc(X))`` depends on the value of ``X`` if and only if the type of ``X`` is dynamic.
|
||||||
|
|
||||||
**Definition:** For any ABI value `X`, we recursively define `enc(X)`, depending
|
**Definition:** For any ABI value ``X``, we recursively define ``enc(X)``, depending
|
||||||
on the type of `X` being
|
on the type of ``X`` being
|
||||||
|
|
||||||
- `(T1,...,Tk)` for `k >= 0` and any types `T1`, ..., `Tk`
|
- ``(T1,...,Tk)`` for ``k >= 0`` and any types ``T1``, ..., ``Tk``
|
||||||
|
|
||||||
`enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))`
|
``enc(X) = head(X(1)) ... head(X(k-1)) tail(X(0)) ... tail(X(k-1))``
|
||||||
|
|
||||||
where `X(i)` is the `ith` component of the value, and
|
where ``X(i)`` is the ``ith`` component of the value, and
|
||||||
`head` and `tail` are defined for `Ti` being a static type as
|
``head`` and ``tail`` are defined for ``Ti`` being a static type as
|
||||||
|
|
||||||
`head(X(i)) = enc(X(i))` and `tail(X(i)) = ""` (the empty string)
|
``head(X(i)) = enc(X(i))`` and ``tail(X(i)) = ""`` (the empty string)
|
||||||
|
|
||||||
and as
|
and as
|
||||||
|
|
||||||
`head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))`
|
``head(X(i)) = enc(len(head(X(0)) ... head(X(k-1)) tail(X(0)) ... tail(X(i-1))))``
|
||||||
`tail(X(i)) = enc(X(i))`
|
``tail(X(i)) = enc(X(i))``
|
||||||
|
|
||||||
otherwise, i.e. if `Ti` is a dynamic type.
|
otherwise, i.e. if ``Ti`` is a dynamic type.
|
||||||
|
|
||||||
Note that in the dynamic case, `head(X(i))` is well-defined since the lengths of
|
Note that in the dynamic case, ``head(X(i))`` is well-defined since the lengths of
|
||||||
the head parts only depend on the types and not the values. Its value is the offset
|
the head parts only depend on the types and not the values. Its value is the offset
|
||||||
of the beginning of `tail(X(i))` relative to the start of `enc(X)`.
|
of the beginning of ``tail(X(i))`` relative to the start of ``enc(X)``.
|
||||||
|
|
||||||
- `T[k]` for any `T` and `k`:
|
- ``T[k]`` for any ``T`` and ``k``:
|
||||||
|
|
||||||
`enc(X) = enc((X[0], ..., X[k-1]))`
|
``enc(X) = enc((X[0], ..., X[k-1]))``
|
||||||
|
|
||||||
i.e. it is encoded as if it were an anonymous struct with `k` elements
|
i.e. it is encoded as if it were a tuple with ``k`` elements
|
||||||
of the same type.
|
of the same type.
|
||||||
|
|
||||||
- `T[]` where `X` has `k` elements (`k` is assumed to be of type `uint256`):
|
- ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``):
|
||||||
|
|
||||||
`enc(X) = enc(k) enc([X[1], ..., X[k]])`
|
``enc(X) = enc(k) enc([X[1], ..., X[k]])``
|
||||||
|
|
||||||
i.e. it is encoded as if it were an array of static size `k`, prefixed with
|
i.e. it is encoded as if it were an array of static size ``k``, prefixed with
|
||||||
the number of elements.
|
the number of elements.
|
||||||
|
|
||||||
- `bytes`, of length `k` (which is assumed to be of type `uint256`):
|
- ``bytes``, of length ``k`` (which is assumed to be of type ``uint256``):
|
||||||
|
|
||||||
`enc(X) = enc(k) pad_right(X)`, i.e. the number of bytes is encoded as a
|
``enc(X) = enc(k) pad_right(X)``, i.e. the number of bytes is encoded as a
|
||||||
`uint256` followed by the actual value of `X` as a byte sequence, followed by
|
``uint256`` followed by the actual value of ``X`` as a byte sequence, followed by
|
||||||
the minimum number of zero-bytes such that `len(enc(X))` is a multiple of 32.
|
the minimum number of zero-bytes such that ``len(enc(X))`` is a multiple of 32.
|
||||||
|
|
||||||
- `string`:
|
- ``string``:
|
||||||
|
|
||||||
`enc(X) = enc(enc_utf8(X))`, i.e. `X` is utf-8 encoded and this value is interpreted as of `bytes` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
|
``enc(X) = enc(enc_utf8(X))``, i.e. ``X`` is utf-8 encoded and this value is interpreted as of ``bytes`` type and encoded further. Note that the length used in this subsequent encoding is the number of bytes of the utf-8 encoded string, not its number of characters.
|
||||||
|
|
||||||
- `uint<M>`: `enc(X)` is the big-endian encoding of `X`, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes.
|
- ``uint<M>``: ``enc(X)`` is the big-endian encoding of ``X``, padded on the higher-order (left) side with zero-bytes such that the length is a multiple of 32 bytes.
|
||||||
- `address`: as in the `uint160` case
|
- ``address``: as in the ``uint160`` case
|
||||||
- `int<M>`: `enc(X)` is the big-endian two's complement encoding of `X`, padded on the higher-oder (left) side with `0xff` for negative `X` and with zero bytes for positive `X` such that the length is a multiple of 32 bytes.
|
- ``int<M>``: ``enc(X)`` is the big-endian two's complement encoding of ``X``, padded on the higher-oder (left) side with ``0xff`` for negative ``X`` and with zero bytes for positive ``X`` such that the length is a multiple of 32 bytes.
|
||||||
- `bool`: as in the `uint8` case, where `1` is used for `true` and `0` for `false`
|
- ``bool``: as in the ``uint8`` case, where ``1`` is used for ``true`` and ``0`` for ``false``
|
||||||
- `fixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `int256`.
|
- ``fixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``int256``.
|
||||||
- `fixed`: as in the `fixed128x19` case
|
- ``fixed``: as in the ``fixed128x19`` case
|
||||||
- `ufixed<M>x<N>`: `enc(X)` is `enc(X * 10**N)` where `X * 10**N` is interpreted as a `uint256`.
|
- ``ufixed<M>x<N>``: ``enc(X)`` is ``enc(X * 10**N)`` where ``X * 10**N`` is interpreted as a ``uint256``.
|
||||||
- `ufixed`: as in the `ufixed128x19` case
|
- ``ufixed``: as in the ``ufixed128x19`` case
|
||||||
- `bytes<M>`: `enc(X)` is the sequence of bytes in `X` padded with zero-bytes to a length of 32.
|
- ``bytes<M>``: ``enc(X)`` is the sequence of bytes in ``X`` padded with zero-bytes to a length of 32.
|
||||||
|
|
||||||
Note that for any `X`, `len(enc(X))` is a multiple of 32.
|
Note that for any ``X``, ``len(enc(X))`` is a multiple of 32.
|
||||||
|
|
||||||
Function Selector and Argument Encoding
|
Function Selector and Argument Encoding
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
All in all, a call to the function `f` with parameters `a_1, ..., a_n` is encoded as
|
All in all, a call to the function ``f`` with parameters ``a_1, ..., a_n`` is encoded as
|
||||||
|
|
||||||
`function_selector(f) enc((a_1, ..., a_n))`
|
``function_selector(f) enc((a_1, ..., a_n))``
|
||||||
|
|
||||||
and the return values `v_1, ..., v_k` of `f` are encoded as
|
and the return values ``v_1, ..., v_k`` of ``f`` are encoded as
|
||||||
|
|
||||||
`enc((v_1, ..., v_k))`
|
``enc((v_1, ..., v_k))``
|
||||||
|
|
||||||
i.e. the values are combined into an anonymous struct and encoded.
|
i.e. the values are combined into a tuple and encoded.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
========
|
========
|
||||||
@ -191,39 +196,40 @@ Given the contract:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Thus for our `Foo` example if we wanted to call `baz` with the parameters `69` and `true`, we would pass 68 bytes total, which can be broken down into:
|
Thus for our ``Foo`` example if we wanted to call ``baz`` with the parameters ``69`` and ``true``, we would pass 68 bytes total, which can be broken down into:
|
||||||
|
|
||||||
- `0xcdcd77c0`: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature `baz(uint32,bool)`.
|
- ``0xcdcd77c0``: the Method ID. This is derived as the first 4 bytes of the Keccak hash of the ASCII form of the signature ``baz(uint32,bool)``.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000045`: the first parameter, a uint32 value `69` padded to 32 bytes
|
- ``0x0000000000000000000000000000000000000000000000000000000000000045``: the first parameter, a uint32 value ``69`` padded to 32 bytes
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter - boolean `true`, padded to 32 bytes
|
- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the second parameter - boolean ``true``, padded to 32 bytes
|
||||||
|
|
||||||
In total::
|
In total::
|
||||||
|
|
||||||
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
|
0xcdcd77c000000000000000000000000000000000000000000000000000000000000000450000000000000000000000000000000000000000000000000000000000000001
|
||||||
|
|
||||||
It returns a single `bool`. If, for example, it were to return `false`, its output would be the single byte array `0x0000000000000000000000000000000000000000000000000000000000000000`, a single bool.
|
It returns a single ``bool``. If, for example, it were to return ``false``, its output would be the single byte array ``0x0000000000000000000000000000000000000000000000000000000000000000``, a single bool.
|
||||||
|
|
||||||
If we wanted to call `bar` with the argument `["abc", "def"]`, we would pass 68 bytes total, broken down into:
|
If we wanted to call ``bar`` with the argument ``["abc", "def"]``, we would pass 68 bytes total, broken down into:
|
||||||
|
|
||||||
- `0xfce353f6`: the Method ID. This is derived from the signature `bar(bytes3[2])`.
|
- ``0xfce353f6``: the Method ID. This is derived from the signature ``bar(bytes3[2])``.
|
||||||
- `0x6162630000000000000000000000000000000000000000000000000000000000`: the first part of the first parameter, a `bytes3` value `"abc"` (left-aligned).
|
- ``0x6162630000000000000000000000000000000000000000000000000000000000``: the first part of the first parameter, a ``bytes3`` value ``"abc"`` (left-aligned).
|
||||||
- `0x6465660000000000000000000000000000000000000000000000000000000000`: the second part of the first parameter, a `bytes3` value `"def"` (left-aligned).
|
- ``0x6465660000000000000000000000000000000000000000000000000000000000``: the second part of the first parameter, a ``bytes3`` value ``"def"`` (left-aligned).
|
||||||
|
|
||||||
In total::
|
In total::
|
||||||
|
|
||||||
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
|
0xfce353f661626300000000000000000000000000000000000000000000000000000000006465660000000000000000000000000000000000000000000000000000000000
|
||||||
|
|
||||||
If we wanted to call `sam` with the arguments `"dave"`, `true` and `[1,2,3]`, we would pass 292 bytes total, broken down into:
|
If we wanted to call ``sam`` with the arguments ``"dave"``, ``true`` and ``[1,2,3]``, we would pass 292 bytes total, broken down into:
|
||||||
- `0xa5643bf2`: the Method ID. This is derived from the signature `sam(bytes,bool,uint256[])`. Note that `uint` is replaced with its canonical representation `uint256`.
|
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000060`: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, `0x60`.
|
- ``0xa5643bf2``: the Method ID. This is derived from the signature ``sam(bytes,bool,uint256[])``. Note that ``uint`` is replaced with its canonical representation ``uint256``.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000001`: the second parameter: boolean true.
|
- ``0x0000000000000000000000000000000000000000000000000000000000000060``: the location of the data part of the first parameter (dynamic type), measured in bytes from the start of the arguments block. In this case, ``0x60``.
|
||||||
- `0x00000000000000000000000000000000000000000000000000000000000000a0`: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, `0xa0`.
|
- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the second parameter: boolean true.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000004`: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4.
|
- ``0x00000000000000000000000000000000000000000000000000000000000000a0``: the location of the data part of the third parameter (dynamic type), measured in bytes. In this case, ``0xa0``.
|
||||||
- `0x6461766500000000000000000000000000000000000000000000000000000000`: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of `"dave"`, padded on the right to 32 bytes.
|
- ``0x0000000000000000000000000000000000000000000000000000000000000004``: the data part of the first argument, it starts with the length of the byte array in elements, in this case, 4.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000003`: the data part of the third argument, it starts with the length of the array in elements, in this case, 3.
|
- ``0x6461766500000000000000000000000000000000000000000000000000000000``: the contents of the first argument: the UTF-8 (equal to ASCII in this case) encoding of ``"dave"``, padded on the right to 32 bytes.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000001`: the first entry of the third parameter.
|
- ``0x0000000000000000000000000000000000000000000000000000000000000003``: the data part of the third argument, it starts with the length of the array in elements, in this case, 3.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000002`: the second entry of the third parameter.
|
- ``0x0000000000000000000000000000000000000000000000000000000000000001``: the first entry of the third parameter.
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000003`: the third entry of the third parameter.
|
- ``0x0000000000000000000000000000000000000000000000000000000000000002``: the second entry of the third parameter.
|
||||||
|
- ``0x0000000000000000000000000000000000000000000000000000000000000003``: the third entry of the third parameter.
|
||||||
|
|
||||||
In total::
|
In total::
|
||||||
|
|
||||||
@ -232,26 +238,26 @@ In total::
|
|||||||
Use of Dynamic Types
|
Use of Dynamic Types
|
||||||
====================
|
====================
|
||||||
|
|
||||||
A call to a function with the signature `f(uint,uint32[],bytes10,bytes)` with values `(0x123, [0x456, 0x789], "1234567890", "Hello, world!")` is encoded in the following way:
|
A call to a function with the signature ``f(uint,uint32[],bytes10,bytes)`` with values ``(0x123, [0x456, 0x789], "1234567890", "Hello, world!")`` is encoded in the following way:
|
||||||
|
|
||||||
We take the first four bytes of `sha3("f(uint256,uint32[],bytes10,bytes)")`, i.e. `0x8be65246`.
|
We take the first four bytes of ``sha3("f(uint256,uint32[],bytes10,bytes)")``, i.e. ``0x8be65246``.
|
||||||
Then we encode the head parts of all four arguments. For the static types `uint256` and `bytes10`, these are directly the values we want to pass, whereas for the dynamic types `uint32[]` and `bytes`, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
|
Then we encode the head parts of all four arguments. For the static types ``uint256`` and ``bytes10``, these are directly the values we want to pass, whereas for the dynamic types ``uint32[]`` and ``bytes``, we use the offset in bytes to the start of their data area, measured from the start of the value encoding (i.e. not counting the first four bytes containing the hash of the function signature). These are:
|
||||||
|
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000123` (`0x123` padded to 32 bytes)
|
- ``0x0000000000000000000000000000000000000000000000000000000000000123`` (``0x123`` padded to 32 bytes)
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000080` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)
|
- ``0x0000000000000000000000000000000000000000000000000000000000000080`` (offset to start of data part of second parameter, 4*32 bytes, exactly the size of the head part)
|
||||||
- `0x3132333435363738393000000000000000000000000000000000000000000000` (`"1234567890"` padded to 32 bytes on the right)
|
- ``0x3132333435363738393000000000000000000000000000000000000000000000`` (``"1234567890"`` padded to 32 bytes on the right)
|
||||||
- `0x00000000000000000000000000000000000000000000000000000000000000e0` (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4\*32 + 3\*32 (see below))
|
- ``0x00000000000000000000000000000000000000000000000000000000000000e0`` (offset to start of data part of fourth parameter = offset to start of data part of first dynamic parameter + size of data part of first dynamic parameter = 4\*32 + 3\*32 (see below))
|
||||||
|
|
||||||
After this, the data part of the first dynamic argument, `[0x456, 0x789]` follows:
|
After this, the data part of the first dynamic argument, ``[0x456, 0x789]`` follows:
|
||||||
|
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000002` (number of elements of the array, 2)
|
- ``0x0000000000000000000000000000000000000000000000000000000000000002`` (number of elements of the array, 2)
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000456` (first element)
|
- ``0x0000000000000000000000000000000000000000000000000000000000000456`` (first element)
|
||||||
- `0x0000000000000000000000000000000000000000000000000000000000000789` (second element)
|
- ``0x0000000000000000000000000000000000000000000000000000000000000789`` (second element)
|
||||||
|
|
||||||
Finally, we encode the data part of the second dynamic argument, `"Hello, world!"`:
|
Finally, we encode the data part of the second dynamic argument, ``"Hello, world!"``:
|
||||||
|
|
||||||
- `0x000000000000000000000000000000000000000000000000000000000000000d` (number of elements (bytes in this case): 13)
|
- ``0x000000000000000000000000000000000000000000000000000000000000000d`` (number of elements (bytes in this case): 13)
|
||||||
- `0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000` (`"Hello, world!"` padded to 32 bytes on the right)
|
- ``0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000`` (``"Hello, world!"`` padded to 32 bytes on the right)
|
||||||
|
|
||||||
All together, the encoding is (newline after function selector and each 32-bytes for clarity):
|
All together, the encoding is (newline after function selector and each 32-bytes for clarity):
|
||||||
|
|
||||||
@ -277,41 +283,48 @@ Given an event name and series of event parameters, we split them into two sub-s
|
|||||||
|
|
||||||
In effect, a log entry using this ABI is described as:
|
In effect, a log entry using this ABI is described as:
|
||||||
|
|
||||||
- `address`: the address of the contract (intrinsically provided by Ethereum);
|
- ``address``: the address of the contract (intrinsically provided by Ethereum);
|
||||||
- `topics[0]`: `keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")` (`canonical_type_of` is a function that simply returns the canonical type of a given argument, e.g. for `uint indexed foo`, it would return `uint256`). If the event is declared as `anonymous` the `topics[0]` is not generated;
|
- ``topics[0]``: ``keccak(EVENT_NAME+"("+EVENT_ARGS.map(canonical_type_of).join(",")+")")`` (``canonical_type_of`` is a function that simply returns the canonical type of a given argument, e.g. for ``uint indexed foo``, it would return ``uint256``). If the event is declared as ``anonymous`` the ``topics[0]`` is not generated;
|
||||||
- `topics[n]`: `EVENT_INDEXED_ARGS[n - 1]` (`EVENT_INDEXED_ARGS` is the series of `EVENT_ARGS` that are indexed);
|
- ``topics[n]``: ``EVENT_INDEXED_ARGS[n - 1]`` (``EVENT_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are indexed);
|
||||||
- `data`: `abi_serialise(EVENT_NON_INDEXED_ARGS)` (`EVENT_NON_INDEXED_ARGS` is the series of `EVENT_ARGS` that are not indexed, `abi_serialise` is the ABI serialisation function used for returning a series of typed values from a function, as described above).
|
- ``data``: ``abi_serialise(EVENT_NON_INDEXED_ARGS)`` (``EVENT_NON_INDEXED_ARGS`` is the series of ``EVENT_ARGS`` that are not indexed, ``abi_serialise`` is the ABI serialisation function used for returning a series of typed values from a function, as described above).
|
||||||
|
|
||||||
JSON
|
JSON
|
||||||
====
|
====
|
||||||
|
|
||||||
The JSON format for a contract's interface is given by an array of function and/or event descriptions. A function description is a JSON object with the fields:
|
The JSON format for a contract's interface is given by an array of function and/or event descriptions.
|
||||||
|
A function description is a JSON object with the fields:
|
||||||
|
|
||||||
- `type`: `"function"`, `"constructor"`, or `"fallback"` (the :ref:`unnamed "default" function <fallback-function>`);
|
- ``type``: ``"function"``, ``"constructor"``, or ``"fallback"`` (the :ref:`unnamed "default" function <fallback-function>`);
|
||||||
- `name`: the name of the function;
|
- ``name``: the name of the function;
|
||||||
- `inputs`: an array of objects, each of which contains:
|
- ``inputs``: an array of objects, each of which contains:
|
||||||
* `name`: the name of the parameter;
|
|
||||||
* `type`: the canonical type of the parameter.
|
|
||||||
- `outputs`: an array of objects similar to `inputs`, can be omitted if function doesn't return anything;
|
|
||||||
- `constant`: `true` if function is :ref:`specified to not modify blockchain state <view-functions>`);
|
|
||||||
- `payable`: `true` if function accepts ether, defaults to `false`;
|
|
||||||
- `stateMutability`: a string with one of the following values: `pure` (:ref:`specified to not read blockchain state <pure-functions>`), `view` (same as `constant` above), `nonpayable` and `payable` (same as `payable` above).
|
|
||||||
|
|
||||||
`type` can be omitted, defaulting to `"function"`.
|
* ``name``: the name of the parameter;
|
||||||
|
* ``type``: the canonical type of the parameter (more below).
|
||||||
|
* ``components``: used for tuple types (more below).
|
||||||
|
|
||||||
Constructor and fallback function never have `name` or `outputs`. Fallback function doesn't have `inputs` either.
|
- ``outputs``: an array of objects similar to ``inputs``, can be omitted if function doesn't return anything;
|
||||||
|
- ``payable``: ``true`` if function accepts ether, defaults to ``false``;
|
||||||
|
- ``stateMutability``: a string with one of the following values: ``pure`` (:ref:`specified to not read blockchain state <pure-functions>`), ``view`` (:ref:`specified to not modify the blockchain state <view-functions>`), ``nonpayable`` and ``payable`` (same as ``payable`` above).
|
||||||
|
- ``constant``: ``true`` if function is either ``pure`` or ``view``
|
||||||
|
|
||||||
|
``type`` can be omitted, defaulting to ``"function"``.
|
||||||
|
|
||||||
|
Constructor and fallback function never have ``name`` or ``outputs``. Fallback function doesn't have ``inputs`` either.
|
||||||
|
|
||||||
Sending non-zero ether to non-payable function will throw. Don't do it.
|
Sending non-zero ether to non-payable function will throw. Don't do it.
|
||||||
|
|
||||||
An event description is a JSON object with fairly similar fields:
|
An event description is a JSON object with fairly similar fields:
|
||||||
|
|
||||||
- `type`: always `"event"`
|
- ``type``: always ``"event"``
|
||||||
- `name`: the name of the event;
|
- ``name``: the name of the event;
|
||||||
- `inputs`: an array of objects, each of which contains:
|
- ``inputs``: an array of objects, each of which contains:
|
||||||
* `name`: the name of the parameter;
|
|
||||||
* `type`: the canonical type of the parameter.
|
* ``name``: the name of the parameter;
|
||||||
* `indexed`: `true` if the field is part of the log's topics, `false` if it one of the log's data segment.
|
* ``type``: the canonical type of the parameter (more below).
|
||||||
- `anonymous`: `true` if the event was declared as `anonymous`.
|
* ``components``: used for tuple types (more below).
|
||||||
|
* ``indexed``: ``true`` if the field is part of the log's topics, ``false`` if it one of the log's data segment.
|
||||||
|
|
||||||
|
- ``anonymous``: ``true`` if the event was declared as ``anonymous``.
|
||||||
|
|
||||||
For example,
|
For example,
|
||||||
|
|
||||||
@ -345,3 +358,87 @@ would result in the JSON:
|
|||||||
"name":"foo",
|
"name":"foo",
|
||||||
"outputs": []
|
"outputs": []
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
Handling tuple types
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Despite that names are intentionally not part of the ABI encoding they do make a lot of sense to be included
|
||||||
|
in the JSON to enable displaying it to the end user. The structure is nested in the following way:
|
||||||
|
|
||||||
|
An object with members ``name``, ``type`` and potentially ``components`` describes a typed variable.
|
||||||
|
The canonical type is determined until a tuple type is reached and the string description up
|
||||||
|
to that point is stored in ``type`` prefix with the word ``tuple``, i.e. it will be ``tuple`` followed by
|
||||||
|
a sequence of ``[]`` and ``[k]`` with
|
||||||
|
integers ``k``. The components of the tuple are then stored in the member ``components``,
|
||||||
|
which is of array type and has the same structure as the top-level object except that
|
||||||
|
``indexed`` is not allowed there.
|
||||||
|
|
||||||
|
As an example, the code
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
contract Test {
|
||||||
|
struct S { uint a; uint[] b; T[] c; }
|
||||||
|
struct T { uint x; uint y; }
|
||||||
|
function f(S s, T t, uint a) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
would result in the JSON:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "f",
|
||||||
|
"type": "function",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "s",
|
||||||
|
"type": "tuple",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "b",
|
||||||
|
"type": "uint256[]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "c",
|
||||||
|
"type": "tuple[]",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "x",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "t",
|
||||||
|
"type": "tuple",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "x",
|
||||||
|
"type": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "y",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "a",
|
||||||
|
"type": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
@ -9,9 +9,10 @@ This assembly language can also be used as "inline assembly" inside Solidity
|
|||||||
source code. We start with describing how to use inline assembly and how it
|
source code. We start with describing how to use inline assembly and how it
|
||||||
differs from standalone assembly and then specify assembly itself.
|
differs from standalone assembly and then specify assembly itself.
|
||||||
|
|
||||||
TODO: Write about how scoping rules of inline assembly are a bit different
|
.. note::
|
||||||
and the complications that arise when for example using internal functions
|
TODO: Write about how scoping rules of inline assembly are a bit different
|
||||||
of libraries. Furthermore, write about the symbols defined by the compiler.
|
and the complications that arise when for example using internal functions
|
||||||
|
of libraries. Furthermore, write about the symbols defined by the compiler.
|
||||||
|
|
||||||
.. _inline-assembly:
|
.. _inline-assembly:
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ you really know what you are doing.
|
|||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
pragma solidity ^0.4.0;
|
pragma solidity ^0.4.12;
|
||||||
|
|
||||||
library VectorSum {
|
library VectorSum {
|
||||||
// This function is less efficient because the optimizer currently fails to
|
// This function is less efficient because the optimizer currently fails to
|
||||||
@ -1005,7 +1006,7 @@ that modifies the stack and with every label that is annotated with a stack
|
|||||||
adjustment. Every time a new
|
adjustment. Every time a new
|
||||||
local variable is introduced, it is registered together with the current
|
local variable is introduced, it is registered together with the current
|
||||||
stack height. If a variable is accessed (either for copying its value or for
|
stack height. If a variable is accessed (either for copying its value or for
|
||||||
assignment), the appropriate DUP or SWAP instruction is selected depending
|
assignment), the appropriate ``DUP`` or ``SWAP`` instruction is selected depending
|
||||||
on the difference between the current stack height and the
|
on the difference between the current stack height and the
|
||||||
stack height at the point the variable was introduced.
|
stack height at the point the variable was introduced.
|
||||||
|
|
||||||
|
@ -358,6 +358,10 @@
|
|||||||
"bugs": [],
|
"bugs": [],
|
||||||
"released": "2017-08-24"
|
"released": "2017-08-24"
|
||||||
},
|
},
|
||||||
|
"0.4.17": {
|
||||||
|
"bugs": [],
|
||||||
|
"released": "2017-09-21"
|
||||||
|
},
|
||||||
"0.4.2": {
|
"0.4.2": {
|
||||||
"bugs": [
|
"bugs": [
|
||||||
"DelegateCallReturnValue",
|
"DelegateCallReturnValue",
|
||||||
|
@ -16,51 +16,23 @@ inaccessible.
|
|||||||
Creating Contracts
|
Creating Contracts
|
||||||
******************
|
******************
|
||||||
|
|
||||||
Contracts can be created "from outside" or from Solidity contracts.
|
Contracts can be created "from outside" via Ethereum transactions or from within Solidity contracts.
|
||||||
|
|
||||||
|
IDEs, such as `Remix <https://remix.ethereum.org/>`_, make the creation process seamless using UI elements.
|
||||||
|
|
||||||
|
Creating contracts programatically on Ethereum is best done via using the JavaScript API `web3.js <https://github.com/etherem/web3.js>`_.
|
||||||
|
As of today it has a method called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_
|
||||||
|
to facilitate contract creation.
|
||||||
|
|
||||||
When a contract is created, its constructor (a function with the same
|
When a contract is created, its constructor (a function with the same
|
||||||
name as the contract) is executed once.
|
name as the contract) is executed once.
|
||||||
|
|
||||||
A constructor is optional. Only one constructor is allowed, and this means
|
A constructor is optional. Only one constructor is allowed, and this means
|
||||||
overloading is not supported.
|
overloading is not supported.
|
||||||
|
|
||||||
From ``web3.js``, i.e. the JavaScript
|
|
||||||
API, this is done as follows::
|
|
||||||
|
|
||||||
// Need to specify some source including contract name for the data param below
|
|
||||||
var source = "contract CONTRACT_NAME { function CONTRACT_NAME(uint a, uint b) {} }";
|
|
||||||
|
|
||||||
// The json abi array generated by the compiler
|
|
||||||
var abiArray = [
|
|
||||||
{
|
|
||||||
"inputs":[
|
|
||||||
{"name":"x","type":"uint256"},
|
|
||||||
{"name":"y","type":"uint256"}
|
|
||||||
],
|
|
||||||
"type":"constructor"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"constant":true,
|
|
||||||
"inputs":[],
|
|
||||||
"name":"x",
|
|
||||||
"outputs":[{"name":"","type":"bytes32"}],
|
|
||||||
"type":"function"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
var MyContract_ = web3.eth.contract(source);
|
|
||||||
MyContract = web3.eth.contract(MyContract_.CONTRACT_NAME.info.abiDefinition);
|
|
||||||
// deploy new contract
|
|
||||||
var contractInstance = MyContract.new(
|
|
||||||
10,
|
|
||||||
11,
|
|
||||||
{from: myAccount, gas: 1000000}
|
|
||||||
);
|
|
||||||
|
|
||||||
.. index:: constructor;arguments
|
.. index:: constructor;arguments
|
||||||
|
|
||||||
Internally, constructor arguments are passed after the code of
|
Internally, constructor arguments are passed :ref:`ABI encoded <ABI>` after the code of
|
||||||
the contract itself, but you do not have to care about this
|
the contract itself, but you do not have to care about this if you use ``web3.js``.
|
||||||
if you use ``web3.js``.
|
|
||||||
|
|
||||||
If a contract wants to create another contract, the source code
|
If a contract wants to create another contract, the source code
|
||||||
(and the binary) of the created contract has to be known to the creator.
|
(and the binary) of the created contract has to be known to the creator.
|
||||||
@ -469,9 +441,20 @@ View Functions
|
|||||||
|
|
||||||
Functions can be declared ``view`` in which case they promise not to modify the state.
|
Functions can be declared ``view`` in which case they promise not to modify the state.
|
||||||
|
|
||||||
|
The following statements are considered modifying the state:
|
||||||
|
|
||||||
|
#. Writing to state variables.
|
||||||
|
#. :ref:`Emitting events. <events>`.
|
||||||
|
#. :ref:`Creating other contracts <creating-contracts>`.
|
||||||
|
#. Using ``selfdestruct``.
|
||||||
|
#. Sending Ether via calls.
|
||||||
|
#. Calling any function not marked ``view`` or ``pure``.
|
||||||
|
#. Using low-level calls.
|
||||||
|
#. Using inline assembly that contains certain opcodes.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
pragma solidity ^0.4.0;
|
pragma solidity ^0.4.16;
|
||||||
|
|
||||||
contract C {
|
contract C {
|
||||||
function f(uint a, uint b) view returns (uint) {
|
function f(uint a, uint b) view returns (uint) {
|
||||||
@ -496,9 +479,17 @@ Pure Functions
|
|||||||
|
|
||||||
Functions can be declared ``pure`` in which case they promise not to read from or modify the state.
|
Functions can be declared ``pure`` in which case they promise not to read from or modify the state.
|
||||||
|
|
||||||
|
In addition to the list of state modifying statements explained above, the following are considered reading from the state:
|
||||||
|
|
||||||
|
#. Reading from state variables.
|
||||||
|
#. Accessing ``this.balance`` or ``<address>.balance``.
|
||||||
|
#. Accessing any of the members of ``block``, ``tx``, ``msg`` (with the exception of ``msg.sig`` and ``msg.data``).
|
||||||
|
#. Calling any function not marked ``pure``.
|
||||||
|
#. Using inline assembly that contains certain opcodes.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
pragma solidity ^0.4.0;
|
pragma solidity ^0.4.16;
|
||||||
|
|
||||||
contract C {
|
contract C {
|
||||||
function f(uint a, uint b) pure returns (uint) {
|
function f(uint a, uint b) pure returns (uint) {
|
||||||
@ -524,9 +515,11 @@ functions match the given function identifier (or if no data was supplied at
|
|||||||
all).
|
all).
|
||||||
|
|
||||||
Furthermore, this function is executed whenever the contract receives plain
|
Furthermore, this function is executed whenever the contract receives plain
|
||||||
Ether (without data). In such a context, there is usually very little gas available to
|
Ether (without data). Additionally, in order to receive Ether, the fallback function
|
||||||
the function call (to be precise, 2300 gas), so it is important to make fallback functions as cheap as
|
must be marked ``payable``. If no such function exists, the contract cannot receive
|
||||||
possible.
|
Ether through regular transactions.
|
||||||
|
|
||||||
|
In such a context, there is usually very little gas available to the function call (to be precise, 2300 gas), so it is important to make fallback functions as cheap as possible. Note that the gas required by a transaction (as opposed to an internal call) that invokes the fallback function is much higher, because each transaction charges an additional amount of 21000 gas or more for things like signature checking.
|
||||||
|
|
||||||
In particular, the following operations will consume more gas than the stipend provided to a fallback function:
|
In particular, the following operations will consume more gas than the stipend provided to a fallback function:
|
||||||
|
|
||||||
@ -537,6 +530,10 @@ In particular, the following operations will consume more gas than the stipend p
|
|||||||
|
|
||||||
Please ensure you test your fallback function thoroughly to ensure the execution cost is less than 2300 gas before deploying a contract.
|
Please ensure you test your fallback function thoroughly to ensure the execution cost is less than 2300 gas before deploying a contract.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve
|
||||||
|
any payload supplied with the call.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
|
Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``)
|
||||||
but do not define a fallback function
|
but do not define a fallback function
|
||||||
@ -544,6 +541,14 @@ Please ensure you test your fallback function thoroughly to ensure the execution
|
|||||||
before Solidity v0.4.0). So if you want your contract to receive Ether,
|
before Solidity v0.4.0). So if you want your contract to receive Ether,
|
||||||
you have to implement a fallback function.
|
you have to implement a fallback function.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
A contract without a payable fallback function can receive Ether as a recipient of a `coinbase transaction` (aka `miner block reward`)
|
||||||
|
or as a destination of a ``selfdestruct``.
|
||||||
|
|
||||||
|
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
|
||||||
|
|
||||||
|
It also means that ``this.balance`` can be higher than the sum of some manual accounting implemented in a contract (i.e. having a counter updated in the fallback function).
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
pragma solidity ^0.4.0;
|
pragma solidity ^0.4.0;
|
||||||
@ -1100,7 +1105,7 @@ are all compiled as calls (``DELEGATECALL``) to an external
|
|||||||
contract/library. If you use libraries, take care that an
|
contract/library. If you use libraries, take care that an
|
||||||
actual external function call is performed.
|
actual external function call is performed.
|
||||||
``msg.sender``, ``msg.value`` and ``this`` will retain their values
|
``msg.sender``, ``msg.value`` and ``this`` will retain their values
|
||||||
in this call, though (prior to Homestead, because of the use of `CALLCODE`, ``msg.sender`` and
|
in this call, though (prior to Homestead, because of the use of ``CALLCODE``, ``msg.sender`` and
|
||||||
``msg.value`` changed, though).
|
``msg.value`` changed, though).
|
||||||
|
|
||||||
The following example shows how to use memory types and
|
The following example shows how to use memory types and
|
||||||
|
@ -66,14 +66,19 @@ Running the compiler tests
|
|||||||
Solidity includes different types of tests. They are included in the application
|
Solidity includes different types of tests. They are included in the application
|
||||||
called ``soltest``. Some of them require the ``cpp-ethereum`` client in testing mode.
|
called ``soltest``. Some of them require the ``cpp-ethereum`` client in testing mode.
|
||||||
|
|
||||||
To run ``cpp-ethereum`` in testing mode: ``eth --test -d /tmp/testeth``.
|
To run a subset of the tests that do not require ``cpp-ethereum``, use ``./build/test/soltest -- --no-ipc``.
|
||||||
|
|
||||||
To run the tests: ``soltest -- --ipcpath /tmp/testeth/geth.ipc``.
|
For all other tests, you need to install `cpp-ethereum <https://github.com/ethereum/cpp-ethereum/releases/download/solidityTester/eth>`_ and run it in testing mode: ``eth --test -d /tmp/testeth``.
|
||||||
|
|
||||||
|
Then you run the actual tests: ``./build/test/soltest -- --ipcpath /tmp/testeth/geth.ipc``.
|
||||||
|
|
||||||
To run a subset of tests, filters can be used:
|
To run a subset of tests, filters can be used:
|
||||||
``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``.
|
``soltest -t TestSuite/TestName -- --ipcpath /tmp/testeth/geth.ipc``, where ``TestName`` can be a wildcard ``*``.
|
||||||
|
|
||||||
Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests.
|
Alternatively, there is a testing script at ``scripts/test.sh`` which executes all tests and runs
|
||||||
|
``cpp-ethereum`` automatically if it is in the path (but does not download it).
|
||||||
|
|
||||||
|
Travis CI even runs some additional tests (including ``solc-js`` and testing third party Solidity frameworks) that require compiling the Emscripten target.
|
||||||
|
|
||||||
Whiskers
|
Whiskers
|
||||||
========
|
========
|
||||||
|
@ -237,16 +237,17 @@ creation-dependencies are not possible.
|
|||||||
D newD = new D(arg);
|
D newD = new D(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAndEndowD(uint arg, uint amount) {
|
function createAndEndowD(uint arg, uint amount) payable {
|
||||||
// Send ether along with the creation
|
// Send ether along with the creation
|
||||||
D newD = (new D).value(amount)(arg);
|
D newD = (new D).value(amount)(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
As seen in the example, it is possible to forward Ether to the creation using the ``.value()`` option,
|
As seen in the example, it is possible to forward Ether while creating
|
||||||
but it is not possible to limit the amount of gas. If the creation fails
|
an instance of ``D`` using the ``.value()`` option, but it is not possible
|
||||||
(due to out-of-stack, not enough balance or other problems), an exception
|
to limit the amount of gas.
|
||||||
is thrown.
|
If the creation fails (due to out-of-stack, not enough balance or other problems),
|
||||||
|
an exception is thrown.
|
||||||
|
|
||||||
Order of Evaluation of Expressions
|
Order of Evaluation of Expressions
|
||||||
==================================
|
==================================
|
||||||
@ -389,6 +390,9 @@ There are two other ways to trigger exceptions: The ``revert`` function can be u
|
|||||||
revert the current call. In the future it might be possible to also include details about the error
|
revert the current call. In the future it might be possible to also include details about the error
|
||||||
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``.
|
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future.
|
||||||
|
|
||||||
When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send``
|
When exceptions happen in a sub-call, they "bubble up" (i.e. exceptions are rethrown) automatically. Exceptions to this rule are ``send``
|
||||||
and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case
|
and the low-level functions ``call``, ``delegatecall`` and ``callcode`` -- those return ``false`` in case
|
||||||
of an exception instead of "bubbling up".
|
of an exception instead of "bubbling up".
|
||||||
|
@ -103,11 +103,6 @@ This is a limitation of the EVM and will be solved with the next protocol update
|
|||||||
|
|
||||||
Returning variably-sized data as part of an external transaction or call is fine.
|
Returning variably-sized data as part of an external transaction or call is fine.
|
||||||
|
|
||||||
How do you represent ``double``/``float`` in Solidity?
|
|
||||||
======================================================
|
|
||||||
|
|
||||||
This is not yet possible.
|
|
||||||
|
|
||||||
Is it possible to in-line initialize an array like so: ``string[] myarray = ["a", "b"];``
|
Is it possible to in-line initialize an array like so: ``string[] myarray = ["a", "b"];``
|
||||||
=========================================================================================
|
=========================================================================================
|
||||||
|
|
||||||
@ -125,24 +120,6 @@ Example::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Are timestamps (``now,`` ``block.timestamp``) reliable?
|
|
||||||
=======================================================
|
|
||||||
|
|
||||||
This depends on what you mean by "reliable".
|
|
||||||
In general, they are supplied by miners and are therefore vulnerable.
|
|
||||||
|
|
||||||
Unless someone really messes up the blockchain or the clock on
|
|
||||||
your computer, you can make the following assumptions:
|
|
||||||
|
|
||||||
You publish a transaction at a time X, this transaction contains same
|
|
||||||
code that calls ``now`` and is included in a block whose timestamp is Y
|
|
||||||
and this block is included into the canonical chain (published) at a time Z.
|
|
||||||
|
|
||||||
The value of ``now`` will be identical to Y and X <= Y <= Z.
|
|
||||||
|
|
||||||
Never use ``now`` or ``block.hash`` as a source of randomness, unless you know
|
|
||||||
what you are doing!
|
|
||||||
|
|
||||||
Can a contract function return a ``struct``?
|
Can a contract function return a ``struct``?
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
@ -155,37 +132,6 @@ Enums are not supported by the ABI, they are just supported by Solidity.
|
|||||||
You have to do the mapping yourself for now, we might provide some help
|
You have to do the mapping yourself for now, we might provide some help
|
||||||
later.
|
later.
|
||||||
|
|
||||||
What is the deal with ``function () { ... }`` inside Solidity contracts? How can a function not have a name?
|
|
||||||
============================================================================================================
|
|
||||||
|
|
||||||
This function is called "fallback function" and it
|
|
||||||
is called when someone just sent Ether to the contract without
|
|
||||||
providing any data or if someone messed up the types so that they tried to
|
|
||||||
call a function that does not exist.
|
|
||||||
|
|
||||||
The default behaviour (if no fallback function is explicitly given) in
|
|
||||||
these situations is to throw an exception.
|
|
||||||
|
|
||||||
If the contract is meant to receive Ether with simple transfers, you
|
|
||||||
should implement the fallback function as
|
|
||||||
|
|
||||||
``function() payable { }``
|
|
||||||
|
|
||||||
Another use of the fallback function is to e.g. register that your
|
|
||||||
contract received ether by using an event.
|
|
||||||
|
|
||||||
*Attention*: If you implement the fallback function take care that it uses as
|
|
||||||
little gas as possible, because ``send()`` will only supply a limited amount.
|
|
||||||
|
|
||||||
Is it possible to pass arguments to the fallback function?
|
|
||||||
==========================================================
|
|
||||||
|
|
||||||
The fallback function cannot take parameters.
|
|
||||||
|
|
||||||
Under special circumstances, you can send data. If you take care
|
|
||||||
that none of the other functions is invoked, you can access the data
|
|
||||||
by ``msg.data``.
|
|
||||||
|
|
||||||
Can state variables be initialized in-line?
|
Can state variables be initialized in-line?
|
||||||
===========================================
|
===========================================
|
||||||
|
|
||||||
@ -230,13 +176,6 @@ Better use ``for (uint i = 0; i < a.length...``
|
|||||||
|
|
||||||
See `struct_and_for_loop_tester.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/65_struct_and_for_loop_tester.sol>`_.
|
See `struct_and_for_loop_tester.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/65_struct_and_for_loop_tester.sol>`_.
|
||||||
|
|
||||||
What character set does Solidity use?
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
Solidity is character set agnostic concerning strings in the source code, although
|
|
||||||
UTF-8 is recommended. Identifiers (variables, functions, ...) can only use
|
|
||||||
ASCII.
|
|
||||||
|
|
||||||
What are some examples of basic string manipulation (``substring``, ``indexOf``, ``charAt``, etc)?
|
What are some examples of basic string manipulation (``substring``, ``indexOf``, ``charAt``, etc)?
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
|
|
||||||
@ -441,23 +380,6 @@ The correct way to do this is the following::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
What is the difference between ``bytes`` and ``byte[]``?
|
|
||||||
========================================================
|
|
||||||
|
|
||||||
``bytes`` is usually more efficient: When used as arguments to functions (i.e. in
|
|
||||||
CALLDATA) or in memory, every single element of a ``byte[]`` is padded to 32
|
|
||||||
bytes which wastes 31 bytes per element.
|
|
||||||
|
|
||||||
Is it possible to send a value while calling an overloaded function?
|
|
||||||
====================================================================
|
|
||||||
|
|
||||||
It's a known missing feature. https://www.pivotaltracker.com/story/show/92020468
|
|
||||||
as part of https://www.pivotaltracker.com/n/projects/1189488
|
|
||||||
|
|
||||||
Best solution currently see is to introduce a special case for gas and value and
|
|
||||||
just re-check whether they are present at the point of overload resolution.
|
|
||||||
|
|
||||||
|
|
||||||
******************
|
******************
|
||||||
Advanced Questions
|
Advanced Questions
|
||||||
******************
|
******************
|
||||||
@ -503,23 +425,6 @@ Note2: Optimizing storage access can pull the gas costs down considerably, becau
|
|||||||
currently do not work across loops and also have a problem with bounds checking.
|
currently do not work across loops and also have a problem with bounds checking.
|
||||||
You might get much better results in the future, though.
|
You might get much better results in the future, though.
|
||||||
|
|
||||||
What does ``p.recipient.call.value(p.amount)(p.data)`` do?
|
|
||||||
==========================================================
|
|
||||||
|
|
||||||
Every external function call in Solidity can be modified in two ways:
|
|
||||||
|
|
||||||
1. You can add Ether together with the call
|
|
||||||
2. You can limit the amount of gas available to the call
|
|
||||||
|
|
||||||
This is done by "calling a function on the function":
|
|
||||||
|
|
||||||
``f.gas(2).value(20)()`` calls the modified function ``f`` and thereby sending 20
|
|
||||||
Wei and limiting the gas to 2 (so this function call will most likely go out of
|
|
||||||
gas and return your 20 Wei).
|
|
||||||
|
|
||||||
In the above example, the low-level function ``call`` is used to invoke another
|
|
||||||
contract with ``p.data`` as payload and ``p.amount`` Wei is sent with that call.
|
|
||||||
|
|
||||||
What happens to a ``struct``'s mapping when copying over a ``struct``?
|
What happens to a ``struct``'s mapping when copying over a ``struct``?
|
||||||
======================================================================
|
======================================================================
|
||||||
|
|
||||||
|
@ -144,6 +144,7 @@ Contents
|
|||||||
solidity-in-depth.rst
|
solidity-in-depth.rst
|
||||||
security-considerations.rst
|
security-considerations.rst
|
||||||
using-the-compiler.rst
|
using-the-compiler.rst
|
||||||
|
metadata.rst
|
||||||
abi-spec.rst
|
abi-spec.rst
|
||||||
style-guide.rst
|
style-guide.rst
|
||||||
common-patterns.rst
|
common-patterns.rst
|
||||||
|
@ -99,7 +99,7 @@ Arch Linux also has packages, albeit limited to the latest development version:
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
pacman -S solidity-git
|
pacman -S solidity
|
||||||
|
|
||||||
Homebrew is missing pre-built bottles at the time of writing,
|
Homebrew is missing pre-built bottles at the time of writing,
|
||||||
following a Jenkins to TravisCI migration, but Homebrew
|
following a Jenkins to TravisCI migration, but Homebrew
|
||||||
|
@ -57,6 +57,14 @@ and overwrite your number, but the number will still be stored in the history
|
|||||||
of the blockchain. Later, we will see how you can impose access restrictions
|
of the blockchain. Later, we will see how you can impose access restrictions
|
||||||
so that only you can alter the number.
|
so that only you can alter the number.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
All identifiers (contract names, function names and variable names) are restricted to
|
||||||
|
the ASCII character set. It is possible to store UTF-8 encoded data in string variables.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Be careful with using Unicode text as similarly looking (or even identical) characters can
|
||||||
|
have different code points and as such will be encoded as a different byte array.
|
||||||
|
|
||||||
.. index:: ! subcurrency
|
.. index:: ! subcurrency
|
||||||
|
|
||||||
Subcurrency Example
|
Subcurrency Example
|
||||||
|
144
docs/metadata.rst
Normal file
144
docs/metadata.rst
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
#################
|
||||||
|
Contract Metadata
|
||||||
|
#################
|
||||||
|
|
||||||
|
.. index:: metadata, contract verification
|
||||||
|
|
||||||
|
The Solidity compiler automatically generates a JSON file, the
|
||||||
|
contract metadata, that contains information about the current contract.
|
||||||
|
It can be used to query the compiler version, the sources used, the ABI
|
||||||
|
and NatSpec documentation in order to more safely interact with the contract
|
||||||
|
and to verify its source code.
|
||||||
|
|
||||||
|
The compiler appends a Swarm hash of the metadata file to the end of the
|
||||||
|
bytecode (for details, see below) of each contract, so that you can retrieve
|
||||||
|
the file in an authenticated way without having to resort to a centralized
|
||||||
|
data provider.
|
||||||
|
|
||||||
|
Of course, you have to publish the metadata file to Swarm (or some other service)
|
||||||
|
so that others can access it. The file can be output by using ``solc --metadata``
|
||||||
|
and the file will be called ``ContractName_meta.json``.
|
||||||
|
It will contain Swarm references to the source code, so you have to upload
|
||||||
|
all source files and the metadata file.
|
||||||
|
|
||||||
|
The metadata file has the following format. The example below is presented in a
|
||||||
|
human-readable way. Properly formatted metadata should use quotes correctly,
|
||||||
|
reduce whitespace to a minimum and sort the keys of all objects to arrive at a
|
||||||
|
unique formatting.
|
||||||
|
Comments are of course also not permitted and used here only for explanatory purposes.
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
{
|
||||||
|
// Required: The version of the metadata format
|
||||||
|
version: "1",
|
||||||
|
// Required: Source code language, basically selects a "sub-version"
|
||||||
|
// of the specification
|
||||||
|
language: "Solidity",
|
||||||
|
// Required: Details about the compiler, contents are specific
|
||||||
|
// to the language.
|
||||||
|
compiler: {
|
||||||
|
// Required for Solidity: Version of the compiler
|
||||||
|
version: "0.4.6+commit.2dabbdf0.Emscripten.clang",
|
||||||
|
// Optional: Hash of the compiler binary which produced this output
|
||||||
|
keccak256: "0x123..."
|
||||||
|
},
|
||||||
|
// Required: Compilation source files/source units, keys are file names
|
||||||
|
sources:
|
||||||
|
{
|
||||||
|
"myFile.sol": {
|
||||||
|
// Required: keccak256 hash of the source file
|
||||||
|
"keccak256": "0x123...",
|
||||||
|
// Required (unless "content" is used, see below): Sorted URL(s)
|
||||||
|
// to the source file, protocol is more or less arbitrary, but a
|
||||||
|
// Swarm URL is recommended
|
||||||
|
"urls": [ "bzzr://56ab..." ]
|
||||||
|
},
|
||||||
|
"mortal": {
|
||||||
|
// Required: keccak256 hash of the source file
|
||||||
|
"keccak256": "0x234...",
|
||||||
|
// Required (unless "url" is used): literal contents of the source file
|
||||||
|
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Required: Compiler settings
|
||||||
|
settings:
|
||||||
|
{
|
||||||
|
// Required for Solidity: Sorted list of remappings
|
||||||
|
remappings: [ ":g/dir" ],
|
||||||
|
// Optional: Optimizer settings (enabled defaults to false)
|
||||||
|
optimizer: {
|
||||||
|
enabled: true,
|
||||||
|
runs: 500
|
||||||
|
},
|
||||||
|
// Required for Solidity: File and name of the contract or library this
|
||||||
|
// metadata is created for.
|
||||||
|
compilationTarget: {
|
||||||
|
"myFile.sol": "MyContract"
|
||||||
|
},
|
||||||
|
// Required for Solidity: Addresses for libraries used
|
||||||
|
libraries: {
|
||||||
|
"MyLib": "0x123123..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Required: Generated information about the contract.
|
||||||
|
output:
|
||||||
|
{
|
||||||
|
// Required: ABI definition of the contract
|
||||||
|
abi: [ ... ],
|
||||||
|
// Required: NatSpec user documentation of the contract
|
||||||
|
userdoc: [ ... ],
|
||||||
|
// Required: NatSpec developer documentation of the contract
|
||||||
|
devdoc: [ ... ],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Note the ABI definition above has no fixed order. It can change with compiler versions.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Since the bytecode of the resulting contract contains the metadata hash, any change to
|
||||||
|
the metadata will result in a change of the bytecode. Furthermore, since the metadata
|
||||||
|
includes a hash of all the sources used, a single whitespace change in any of the source
|
||||||
|
codes will result in a different metadata, and subsequently a different bytecode.
|
||||||
|
|
||||||
|
Encoding of the Metadata Hash in the Bytecode
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Because we might support other ways to retrieve the metadata file in the future,
|
||||||
|
the mapping ``{"bzzr0": <Swarm hash>}`` is stored
|
||||||
|
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the beginning of that
|
||||||
|
encoding is not easy to find, its length is added in a two-byte big-endian
|
||||||
|
encoding. The current version of the Solidity compiler thus adds the following
|
||||||
|
to the end of the deployed bytecode::
|
||||||
|
|
||||||
|
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
|
||||||
|
|
||||||
|
So in order to retrieve the data, the end of the deployed bytecode can be checked
|
||||||
|
to match that pattern and use the Swarm hash to retrieve the file.
|
||||||
|
|
||||||
|
Usage for Automatic Interface Generation and NatSpec
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
The metadata is used in the following way: A component that wants to interact
|
||||||
|
with a contract (e.g. Mist) retrieves the code of the contract, from that
|
||||||
|
the Swarm hash of a file which is then retrieved.
|
||||||
|
That file is JSON-decoded into a structure like above.
|
||||||
|
|
||||||
|
The component can then use the ABI to automatically generate a rudimentary
|
||||||
|
user interface for the contract.
|
||||||
|
|
||||||
|
Furthermore, Mist can use the userdoc to display a confirmation message to the user
|
||||||
|
whenever they interact with the contract.
|
||||||
|
|
||||||
|
Usage for Source Code Verification
|
||||||
|
==================================
|
||||||
|
|
||||||
|
In order to verify the compilation, sources can be retrieved from Swarm
|
||||||
|
via the link in the metadata file.
|
||||||
|
The compiler of the correct version (which is checked to be part of the "official" compilers)
|
||||||
|
is invoked on that input with the specified settings. The resulting
|
||||||
|
bytecode is compared to the data of the creation transaction or ``CREATE`` opcode data.
|
||||||
|
This automatically verifies the metadata since its hash is part of the bytecode.
|
||||||
|
Excess data corresponds to the constructor input data, which should be decoded
|
||||||
|
according to the interface and presented to the user.
|
@ -84,10 +84,8 @@ Layout of Call Data
|
|||||||
*******************
|
*******************
|
||||||
|
|
||||||
When a Solidity contract is deployed and when it is called from an
|
When a Solidity contract is deployed and when it is called from an
|
||||||
account, the input data is assumed to be in the format in `the ABI
|
account, the input data is assumed to be in the format in :ref:`the ABI
|
||||||
specification
|
specification <ABI>`. The ABI specification requires arguments to be padded to multiples of 32
|
||||||
<https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI>`_. The
|
|
||||||
ABI specification requires arguments to be padded to multiples of 32
|
|
||||||
bytes. The internal function calls use a different convention.
|
bytes. The internal function calls use a different convention.
|
||||||
|
|
||||||
|
|
||||||
@ -145,13 +143,13 @@ Different types have different rules for cleaning up invalid values:
|
|||||||
Internals - The Optimizer
|
Internals - The Optimizer
|
||||||
*************************
|
*************************
|
||||||
|
|
||||||
The Solidity optimizer operates on assembly, so it can be and also is used by other languages. It splits the sequence of instructions into basic blocks at JUMPs and JUMPDESTs. Inside these blocks, the instructions are analysed and every modification to the stack, to memory or storage is recorded as an expression which consists of an instruction and a list of arguments which are essentially pointers to other expressions. The main idea is now to find expressions that are always equal (on every input) and combine them into an expression class. The optimizer first tries to find each new expression in a list of already known expressions. If this does not work, the expression is simplified according to rules like ``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is done recursively, we can also apply the latter rule if the second factor is a more complex expression where we know that it will always evaluate to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different: If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we actually do not know what is stored at x after we wrote to y. On the other hand, if a simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x.
|
The Solidity optimizer operates on assembly, so it can be and also is used by other languages. It splits the sequence of instructions into basic blocks at ``JUMPs`` and ``JUMPDESTs``. Inside these blocks, the instructions are analysed and every modification to the stack, to memory or storage is recorded as an expression which consists of an instruction and a list of arguments which are essentially pointers to other expressions. The main idea is now to find expressions that are always equal (on every input) and combine them into an expression class. The optimizer first tries to find each new expression in a list of already known expressions. If this does not work, the expression is simplified according to rules like ``constant + constant = sum_of_constants`` or ``X * 1 = X``. Since this is done recursively, we can also apply the latter rule if the second factor is a more complex expression where we know that it will always evaluate to one. Modifications to storage and memory locations have to erase knowledge about storage and memory locations which are not known to be different: If we first write to location x and then to location y and both are input variables, the second could overwrite the first, so we actually do not know what is stored at x after we wrote to y. On the other hand, if a simplification of the expression x - y evaluates to a non-zero constant, we know that we can keep our knowledge about what is stored at x.
|
||||||
|
|
||||||
At the end of this process, we know which expressions have to be on the stack in the end and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all JUMP and JUMPI instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown JUMP. If a JUMPI is found whose condition evaluates to a constant, it is transformed to an unconditional jump.
|
At the end of this process, we know which expressions have to be on the stack in the end and have a list of modifications to memory and storage. This information is stored together with the basic blocks and is used to link them. Furthermore, knowledge about the stack, storage and memory configuration is forwarded to the next block(s). If we know the targets of all ``JUMP`` and ``JUMPI`` instructions, we can build a complete control flow graph of the program. If there is only one target we do not know (this can happen as in principle, jump targets can be computed from inputs), we have to erase all knowledge about the input state of a block as it can be the target of the unknown ``JUMP``. If a ``JUMPI`` is found whose condition evaluates to a constant, it is transformed to an unconditional jump.
|
||||||
|
|
||||||
As the last step, the code in each block is completely re-generated. A dependency graph is created from the expressions on the stack at the end of the block and every operation that is not part of this graph is essentially dropped. Now code is generated that applies the modifications to memory and storage in the order they were made in the original code (dropping modifications which were found not to be needed) and finally, generates all values that are required to be on the stack in the correct place.
|
As the last step, the code in each block is completely re-generated. A dependency graph is created from the expressions on the stack at the end of the block and every operation that is not part of this graph is essentially dropped. Now code is generated that applies the modifications to memory and storage in the order they were made in the original code (dropping modifications which were found not to be needed) and finally, generates all values that are required to be on the stack in the correct place.
|
||||||
|
|
||||||
These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a JUMPI and during the analysis, the condition evaluates to a constant, the JUMPI is replaced depending on the value of the constant, and thus code like
|
These steps are applied to each basic block and the newly generated code is used as replacement if it is smaller. If a basic block is split at a ``JUMPI`` and during the analysis, the condition evaluates to a constant, the ``JUMPI`` is replaced depending on the value of the constant, and thus code like
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@ -223,156 +221,12 @@ This means the following source mappings represent the same information:
|
|||||||
|
|
||||||
``1:2:1;:9;2::2;;``
|
``1:2:1;:9;2::2;;``
|
||||||
|
|
||||||
*****************
|
|
||||||
Contract Metadata
|
|
||||||
*****************
|
|
||||||
|
|
||||||
The Solidity compiler automatically generates a JSON file, the
|
|
||||||
contract metadata, that contains information about the current contract.
|
|
||||||
It can be used to query the compiler version, the sources used, the ABI
|
|
||||||
and NatSpec documentation in order to more safely interact with the contract
|
|
||||||
and to verify its source code.
|
|
||||||
|
|
||||||
The compiler appends a Swarm hash of the metadata file to the end of the
|
|
||||||
bytecode (for details, see below) of each contract, so that you can retrieve
|
|
||||||
the file in an authenticated way without having to resort to a centralized
|
|
||||||
data provider.
|
|
||||||
|
|
||||||
Of course, you have to publish the metadata file to Swarm (or some other service)
|
|
||||||
so that others can access it. The file can be output by using ``solc --metadata``
|
|
||||||
and the file will be called ``ContractName_meta.json``.
|
|
||||||
It will contain Swarm references to the source code, so you have to upload
|
|
||||||
all source files and the metadata file.
|
|
||||||
|
|
||||||
The metadata file has the following format. The example below is presented in a
|
|
||||||
human-readable way. Properly formatted metadata should use quotes correctly,
|
|
||||||
reduce whitespace to a minimum and sort the keys of all objects to arrive at a
|
|
||||||
unique formatting.
|
|
||||||
Comments are of course also not permitted and used here only for explanatory purposes.
|
|
||||||
|
|
||||||
.. code-block:: none
|
|
||||||
|
|
||||||
{
|
|
||||||
// Required: The version of the metadata format
|
|
||||||
version: "1",
|
|
||||||
// Required: Source code language, basically selects a "sub-version"
|
|
||||||
// of the specification
|
|
||||||
language: "Solidity",
|
|
||||||
// Required: Details about the compiler, contents are specific
|
|
||||||
// to the language.
|
|
||||||
compiler: {
|
|
||||||
// Required for Solidity: Version of the compiler
|
|
||||||
version: "0.4.6+commit.2dabbdf0.Emscripten.clang",
|
|
||||||
// Optional: Hash of the compiler binary which produced this output
|
|
||||||
keccak256: "0x123..."
|
|
||||||
},
|
|
||||||
// Required: Compilation source files/source units, keys are file names
|
|
||||||
sources:
|
|
||||||
{
|
|
||||||
"myFile.sol": {
|
|
||||||
// Required: keccak256 hash of the source file
|
|
||||||
"keccak256": "0x123...",
|
|
||||||
// Required (unless "content" is used, see below): Sorted URL(s)
|
|
||||||
// to the source file, protocol is more or less arbitrary, but a
|
|
||||||
// Swarm URL is recommended
|
|
||||||
"urls": [ "bzzr://56ab..." ]
|
|
||||||
},
|
|
||||||
"mortal": {
|
|
||||||
// Required: keccak256 hash of the source file
|
|
||||||
"keccak256": "0x234...",
|
|
||||||
// Required (unless "url" is used): literal contents of the source file
|
|
||||||
"content": "contract mortal is owned { function kill() { if (msg.sender == owner) selfdestruct(owner); } }"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Required: Compiler settings
|
|
||||||
settings:
|
|
||||||
{
|
|
||||||
// Required for Solidity: Sorted list of remappings
|
|
||||||
remappings: [ ":g/dir" ],
|
|
||||||
// Optional: Optimizer settings (enabled defaults to false)
|
|
||||||
optimizer: {
|
|
||||||
enabled: true,
|
|
||||||
runs: 500
|
|
||||||
},
|
|
||||||
// Required for Solidity: File and name of the contract or library this
|
|
||||||
// metadata is created for.
|
|
||||||
compilationTarget: {
|
|
||||||
"myFile.sol": "MyContract"
|
|
||||||
},
|
|
||||||
// Required for Solidity: Addresses for libraries used
|
|
||||||
libraries: {
|
|
||||||
"MyLib": "0x123123..."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Required: Generated information about the contract.
|
|
||||||
output:
|
|
||||||
{
|
|
||||||
// Required: ABI definition of the contract
|
|
||||||
abi: [ ... ],
|
|
||||||
// Required: NatSpec user documentation of the contract
|
|
||||||
userdoc: [ ... ],
|
|
||||||
// Required: NatSpec developer documentation of the contract
|
|
||||||
devdoc: [ ... ],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Note the ABI definition above has no fixed order. It can change with compiler versions.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Since the bytecode of the resulting contract contains the metadata hash, any change to
|
|
||||||
the metadata will result in a change of the bytecode. Furthermore, since the metadata
|
|
||||||
includes a hash of all the sources used, a single whitespace change in any of the source
|
|
||||||
codes will result in a different metadata, and subsequently a different bytecode.
|
|
||||||
|
|
||||||
Encoding of the Metadata Hash in the Bytecode
|
|
||||||
=============================================
|
|
||||||
|
|
||||||
Because we might support other ways to retrieve the metadata file in the future,
|
|
||||||
the mapping ``{"bzzr0": <Swarm hash>}`` is stored
|
|
||||||
[CBOR](https://tools.ietf.org/html/rfc7049)-encoded. Since the beginning of that
|
|
||||||
encoding is not easy to find, its length is added in a two-byte big-endian
|
|
||||||
encoding. The current version of the Solidity compiler thus adds the following
|
|
||||||
to the end of the deployed bytecode::
|
|
||||||
|
|
||||||
0xa1 0x65 'b' 'z' 'z' 'r' '0' 0x58 0x20 <32 bytes swarm hash> 0x00 0x29
|
|
||||||
|
|
||||||
So in order to retrieve the data, the end of the deployed bytecode can be checked
|
|
||||||
to match that pattern and use the Swarm hash to retrieve the file.
|
|
||||||
|
|
||||||
Usage for Automatic Interface Generation and NatSpec
|
|
||||||
====================================================
|
|
||||||
|
|
||||||
The metadata is used in the following way: A component that wants to interact
|
|
||||||
with a contract (e.g. Mist) retrieves the code of the contract, from that
|
|
||||||
the Swarm hash of a file which is then retrieved.
|
|
||||||
That file is JSON-decoded into a structure like above.
|
|
||||||
|
|
||||||
The component can then use the ABI to automatically generate a rudimentary
|
|
||||||
user interface for the contract.
|
|
||||||
|
|
||||||
Furthermore, Mist can use the userdoc to display a confirmation message to the user
|
|
||||||
whenever they interact with the contract.
|
|
||||||
|
|
||||||
Usage for Source Code Verification
|
|
||||||
==================================
|
|
||||||
|
|
||||||
In order to verify the compilation, sources can be retrieved from Swarm
|
|
||||||
via the link in the metadata file.
|
|
||||||
The compiler of the correct version (which is checked to be part of the "official" compilers)
|
|
||||||
is invoked on that input with the specified settings. The resulting
|
|
||||||
bytecode is compared to the data of the creation transaction or CREATE opcode data.
|
|
||||||
This automatically verifies the metadata since its hash is part of the bytecode.
|
|
||||||
Excess data corresponds to the constructor input data, which should be decoded
|
|
||||||
according to the interface and presented to the user.
|
|
||||||
|
|
||||||
|
|
||||||
***************
|
***************
|
||||||
Tips and Tricks
|
Tips and Tricks
|
||||||
***************
|
***************
|
||||||
|
|
||||||
* Use ``delete`` on arrays to delete all its elements.
|
* Use ``delete`` on arrays to delete all its elements.
|
||||||
* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple SSTORE operations might be combined into a single (SSTORE costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
|
* Use shorter types for struct elements and sort them such that short types are grouped together. This can lower the gas costs as multiple ``SSTORE`` operations might be combined into a single (``SSTORE`` costs 5000 or 20000 gas, so this is what you want to optimise). Use the gas price estimator (with optimiser enabled) to check!
|
||||||
* Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you automatically.
|
* Make your state variables public - the compiler will create :ref:`getters <visibility-and-getters>` for you automatically.
|
||||||
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
|
* If you end up checking conditions on input or state a lot at the beginning of your functions, try using :ref:`modifiers`.
|
||||||
* If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``.
|
* If your contract has a function called ``send`` but you want to use the built-in send-function, use ``address(contractVariable).send(amount)``.
|
||||||
@ -469,7 +323,7 @@ Global Variables
|
|||||||
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
|
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
|
||||||
- ``revert()``: abort execution and revert state changes
|
- ``revert()``: abort execution and revert state changes
|
||||||
- ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
|
- ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
|
||||||
- ``sha3(...) returns (bytes32)``: an alias to `keccak256`
|
- ``sha3(...) returns (bytes32)``: an alias to ``keccak256``
|
||||||
- ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments
|
- ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments
|
||||||
- ``ripemd160(...) returns (bytes20)``: compute the RIPEMD-160 hash of the (tightly packed) arguments
|
- ``ripemd160(...) returns (bytes20)``: compute the RIPEMD-160 hash of the (tightly packed) arguments
|
||||||
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error
|
- ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover address associated with the public key from elliptic curve signature, return zero on error
|
||||||
@ -478,7 +332,7 @@ Global Variables
|
|||||||
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address``
|
- ``this`` (current contract's type): the current contract, explicitly convertible to ``address``
|
||||||
- ``super``: the contract one level higher in the inheritance hierarchy
|
- ``super``: the contract one level higher in the inheritance hierarchy
|
||||||
- ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address
|
- ``selfdestruct(address recipient)``: destroy the current contract, sending its funds to the given address
|
||||||
- ``suicide(address recipieint)``: an alias to `selfdestruct``
|
- ``suicide(address recipieint)``: an alias to ``selfdestruct``
|
||||||
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
|
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
|
||||||
- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
|
- ``<address>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
|
||||||
- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
- ``<address>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
|
||||||
@ -519,7 +373,7 @@ Reserved Keywords
|
|||||||
These keywords are reserved in Solidity. They might become part of the syntax in the future:
|
These keywords are reserved in Solidity. They might become part of the syntax in the future:
|
||||||
|
|
||||||
``abstract``, ``after``, ``case``, ``catch``, ``default``, ``final``, ``in``, ``inline``, ``let``, ``match``, ``null``,
|
``abstract``, ``after``, ``case``, ``catch``, ``default``, ``final``, ``in``, ``inline``, ``let``, ``match``, ``null``,
|
||||||
``of``, ``pure``, ``relocatable``, ``static``, ``switch``, ``try``, ``type``, ``typeof``, ``view``.
|
``of``, ``relocatable``, ``static``, ``switch``, ``try``, ``type``, ``typeof``.
|
||||||
|
|
||||||
Language Grammar
|
Language Grammar
|
||||||
================
|
================
|
||||||
|
@ -535,6 +535,9 @@ Safe Remote Purchase
|
|||||||
enum State { Created, Locked, Inactive }
|
enum State { Created, Locked, Inactive }
|
||||||
State public state;
|
State public state;
|
||||||
|
|
||||||
|
// Ensure that `msg.value` is an even number.
|
||||||
|
// Division will truncate if it is an odd number.
|
||||||
|
// Check via multiplication that it wasn't an odd number.
|
||||||
function Purchase() payable {
|
function Purchase() payable {
|
||||||
seller = msg.sender;
|
seller = msg.sender;
|
||||||
value = msg.value / 2;
|
value = msg.value / 2;
|
||||||
|
@ -54,7 +54,7 @@ Operators:
|
|||||||
* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation)
|
* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation)
|
||||||
* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation), ``<<`` (left shift), ``>>`` (right shift)
|
* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder), ``**`` (exponentiation), ``<<`` (left shift), ``>>`` (right shift)
|
||||||
|
|
||||||
Division always truncates (it is just compiled to the DIV opcode of the EVM), but it does not truncate if both
|
Division always truncates (it is just compiled to the ``DIV`` opcode of the EVM), but it does not truncate if both
|
||||||
operators are :ref:`literals<rational_literals>` (or literal expressions).
|
operators are :ref:`literals<rational_literals>` (or literal expressions).
|
||||||
|
|
||||||
Division by zero and modulus with zero throws a runtime exception.
|
Division by zero and modulus with zero throws a runtime exception.
|
||||||
@ -70,6 +70,30 @@ sign extends. Shifting by a negative amount throws a runtime exception.
|
|||||||
are going to be rounded towards zero (truncated). In other programming languages the shift right of negative values
|
are going to be rounded towards zero (truncated). In other programming languages the shift right of negative values
|
||||||
works like division with rounding down (towards negative infinity).
|
works like division with rounding down (towards negative infinity).
|
||||||
|
|
||||||
|
.. index:: ! ufixed, ! fixed, ! fixed point number
|
||||||
|
|
||||||
|
Fixed Point Numbers
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Fixed point numbers are not fully supported by Solidity yet. They can be declared, but
|
||||||
|
cannot be assigned to or from.
|
||||||
|
|
||||||
|
``fixed`` / ``ufixed``: Signed and unsigned fixed point number of various sizes. Keywords ``ufixedMxN`` and ``fixedMxN``, where ``M`` represent the number of bits taken by
|
||||||
|
the type and ``N`` represent how many decimal points are available. ``M`` must be divisible by 8 and goes from 8 to 256 bits. ``N`` must be between 0 and 80, inclusive.
|
||||||
|
``ufixed`` and ``fixed`` are aliases for ``ufixed128x19`` and ``fixed128x19``, respectively.
|
||||||
|
|
||||||
|
Operators:
|
||||||
|
|
||||||
|
* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``)
|
||||||
|
* Arithmetic operators: ``+``, ``-``, unary ``-``, unary ``+``, ``*``, ``/``, ``%`` (remainder)
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The main difference between floating point (``float`` and ``double`` in many languages, more precisely IEEE 754 numbers) and fixed point numbers is
|
||||||
|
that the number of bits used for the integer and the fractional part (the part after the decimal dot) is flexible in the former, while it is strictly
|
||||||
|
defined in the latter. Generally, in floating point almost the entire space is used to represent the number, while only a small number of bits define
|
||||||
|
where the decimal point is.
|
||||||
|
|
||||||
.. index:: address, balance, send, call, callcode, delegatecall, transfer
|
.. index:: address, balance, send, call, callcode, delegatecall, transfer
|
||||||
|
|
||||||
.. _address:
|
.. _address:
|
||||||
@ -127,6 +151,24 @@ the function ``call`` is provided which takes an arbitrary number of arguments o
|
|||||||
|
|
||||||
``call`` returns a boolean indicating whether the invoked function terminated (``true``) or caused an EVM exception (``false``). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance).
|
``call`` returns a boolean indicating whether the invoked function terminated (``true``) or caused an EVM exception (``false``). It is not possible to access the actual data returned (for this we would need to know the encoding and size in advance).
|
||||||
|
|
||||||
|
It is possible to adjust the supplied gas with the ``.gas()`` modifier::
|
||||||
|
|
||||||
|
namReg.call.gas(1000000)("register", "MyName");
|
||||||
|
|
||||||
|
Similarly, the supplied Ether value can be controlled too::
|
||||||
|
|
||||||
|
nameReg.call.value(1 ether)("register", "MyName");
|
||||||
|
|
||||||
|
Lastly, these modifiers can be combined. Their order does not matter::
|
||||||
|
|
||||||
|
nameReg.call.gas(1000000).value(1 ether)("register", "MyName");
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
It is not yet possible to use the gas or value modifiers on overloaded functions.
|
||||||
|
|
||||||
|
A workaround is to introduce a special case for gas and value and just re-check
|
||||||
|
whether they are present at the point of overload resolution.
|
||||||
|
|
||||||
In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values.
|
In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used. Prior to homestead, only a limited variant called ``callcode`` was available that did not provide access to the original ``msg.sender`` and ``msg.value`` values.
|
||||||
|
|
||||||
All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
|
All three functions ``call``, ``delegatecall`` and ``callcode`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
|
||||||
@ -169,6 +211,10 @@ Members:
|
|||||||
|
|
||||||
* ``.length`` yields the fixed length of the byte array (read-only).
|
* ``.length`` yields the fixed length of the byte array (read-only).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
It is possible to use an array of bytes as ``byte[]``, but it is wasting a lot of space, 31 bytes every element,
|
||||||
|
to be exact, when passing in calls. It is better to use ``bytes``.
|
||||||
|
|
||||||
Dynamically-sized byte array
|
Dynamically-sized byte array
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
@ -181,15 +227,6 @@ As a rule of thumb, use ``bytes`` for arbitrary-length raw byte data and ``strin
|
|||||||
for arbitrary-length string (UTF-8) data. If you can limit the length to a certain
|
for arbitrary-length string (UTF-8) data. If you can limit the length to a certain
|
||||||
number of bytes, always use one of ``bytes1`` to ``bytes32`` because they are much cheaper.
|
number of bytes, always use one of ``bytes1`` to ``bytes32`` because they are much cheaper.
|
||||||
|
|
||||||
.. index:: ! ufixed, ! fixed, ! fixed point number
|
|
||||||
|
|
||||||
Fixed Point Numbers
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
Fixed point numbers are not fully supported by Solidity yet. They can be declared, but
|
|
||||||
cannot be assigned to or from.
|
|
||||||
|
|
||||||
.. index:: address, literal;address
|
.. index:: address, literal;address
|
||||||
|
|
||||||
.. _address_literals:
|
.. _address_literals:
|
||||||
@ -363,6 +400,17 @@ Note that public functions of the current contract can be used both as an
|
|||||||
internal and as an external function. To use ``f`` as an internal function,
|
internal and as an external function. To use ``f`` as an internal function,
|
||||||
just use ``f``, if you want to use its external form, use ``this.f``.
|
just use ``f``, if you want to use its external form, use ``this.f``.
|
||||||
|
|
||||||
|
Additionally, public (or external) functions also have a special member called ``selector``,
|
||||||
|
which returns the :ref:`ABI function selector <abi_function_selector>`::
|
||||||
|
|
||||||
|
pragma solidity ^0.4.0;
|
||||||
|
|
||||||
|
contract Selector {
|
||||||
|
function f() returns (bytes4) {
|
||||||
|
return this.f.selector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Example that shows how to use internal function types::
|
Example that shows how to use internal function types::
|
||||||
|
|
||||||
pragma solidity ^0.4.5;
|
pragma solidity ^0.4.5;
|
||||||
@ -467,10 +515,10 @@ context, there is always a default, but it can be overridden by appending
|
|||||||
either ``storage`` or ``memory`` to the type. The default for function parameters (including return parameters) is ``memory``, the default for local variables is ``storage`` and the location is forced
|
either ``storage`` or ``memory`` to the type. The default for function parameters (including return parameters) is ``memory``, the default for local variables is ``storage`` and the location is forced
|
||||||
to ``storage`` for state variables (obviously).
|
to ``storage`` for state variables (obviously).
|
||||||
|
|
||||||
There is also a third data location, "calldata", which is a non-modifiable,
|
There is also a third data location, ``calldata``, which is a non-modifiable,
|
||||||
non-persistent area where function arguments are stored. Function parameters
|
non-persistent area where function arguments are stored. Function parameters
|
||||||
(not return parameters) of external functions are forced to "calldata" and
|
(not return parameters) of external functions are forced to ``calldata`` and
|
||||||
behave mostly like memory.
|
behave mostly like ``memory``.
|
||||||
|
|
||||||
Data locations are important because they change how assignments behave:
|
Data locations are important because they change how assignments behave:
|
||||||
assignments between storage and memory and also to a state variable (even from other state variables)
|
assignments between storage and memory and also to a state variable (even from other state variables)
|
||||||
|
@ -72,6 +72,18 @@ Block and Transaction Properties
|
|||||||
``msg.value`` can change for every **external** function call.
|
``msg.value`` can change for every **external** function call.
|
||||||
This includes calls to library functions.
|
This includes calls to library functions.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Do not rely on ``block.timestamp``, ``now`` and ``block.blockhash`` as a source of randomness,
|
||||||
|
unless you know what you are doing.
|
||||||
|
|
||||||
|
Both the timestamp and the block hash can be influenced by miners to some degree.
|
||||||
|
Bad actors in the mining community can for example run a casino payout function on a chosen hash
|
||||||
|
and just retry a different hash if they did not receive any money.
|
||||||
|
|
||||||
|
The current block timestamp must be strictly larger than the timestamp of the last block,
|
||||||
|
but the only guarantee is that it will be somewhere between the timestamps of two
|
||||||
|
consecutive blocks in the canonical chain.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
If you want to implement access restrictions in library functions using
|
If you want to implement access restrictions in library functions using
|
||||||
``msg.sender``, you have to manually supply the value of
|
``msg.sender``, you have to manually supply the value of
|
||||||
|
100
libdevcore/ABI.h
100
libdevcore/ABI.h
@ -1,100 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of solidity.
|
|
||||||
|
|
||||||
solidity is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
solidity is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
/** @file ABI.h
|
|
||||||
* @author Gav Wood <i@gavwood.com>
|
|
||||||
* @date 2014
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <libdevcore/Common.h>
|
|
||||||
#include <libdevcore/FixedHash.h>
|
|
||||||
#include <libdevcore/CommonData.h>
|
|
||||||
#include <libdevcore/SHA3.h>
|
|
||||||
|
|
||||||
namespace dev
|
|
||||||
{
|
|
||||||
namespace eth
|
|
||||||
{
|
|
||||||
|
|
||||||
inline string32 toString32(std::string const& _s)
|
|
||||||
{
|
|
||||||
string32 ret;
|
|
||||||
for (unsigned i = 0; i < 32; ++i)
|
|
||||||
ret[i] = i < _s.size() ? _s[i] : 0;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T> struct ABISerialiser {};
|
|
||||||
template <unsigned N> struct ABISerialiser<FixedHash<N>> { static bytes serialise(FixedHash<N> const& _t) { static_assert(N <= 32, "Cannot serialise hash > 32 bytes."); static_assert(N > 0, "Cannot serialise zero-length hash."); return bytes(32 - N, 0) + _t.asBytes(); } };
|
|
||||||
template <> struct ABISerialiser<u256> { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } };
|
|
||||||
template <> struct ABISerialiser<u160> { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } };
|
|
||||||
template <> struct ABISerialiser<string32> { static bytes serialise(string32 const& _t) { bytes ret; bytesConstRef((byte const*)_t.data(), 32).populate(bytesRef(&ret)); return ret; } };
|
|
||||||
template <> struct ABISerialiser<std::string>
|
|
||||||
{
|
|
||||||
static bytes serialise(std::string const& _t)
|
|
||||||
{
|
|
||||||
bytes ret = h256(u256(32)).asBytes() + h256(u256(_t.size())).asBytes();
|
|
||||||
ret.resize(ret.size() + (_t.size() + 31) / 32 * 32);
|
|
||||||
bytesConstRef(&_t).populate(bytesRef(&ret).cropped(64));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
inline bytes abiInAux() { return {}; }
|
|
||||||
template <class T, class ... U> bytes abiInAux(T const& _t, U const& ... _u)
|
|
||||||
{
|
|
||||||
return ABISerialiser<T>::serialise(_t) + abiInAux(_u ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ... T> bytes abiIn(std::string _id, T const& ... _t)
|
|
||||||
{
|
|
||||||
return keccak256(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T> struct ABIDeserialiser {};
|
|
||||||
template <unsigned N> struct ABIDeserialiser<FixedHash<N>> { static FixedHash<N> deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash<N> ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } };
|
|
||||||
template <> struct ABIDeserialiser<u256> { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian<u256>(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } };
|
|
||||||
template <> struct ABIDeserialiser<u160> { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian<u160>(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } };
|
|
||||||
template <> struct ABIDeserialiser<string32> { static string32 deserialise(bytesConstRef& io_t) { string32 ret; io_t.cropped(0, 32).populate(bytesRef((byte*)ret.data(), 32)); io_t = io_t.cropped(32); return ret; } };
|
|
||||||
template <> struct ABIDeserialiser<std::string>
|
|
||||||
{
|
|
||||||
static std::string deserialise(bytesConstRef& io_t)
|
|
||||||
{
|
|
||||||
unsigned o = (uint16_t)u256(h256(io_t.cropped(0, 32)));
|
|
||||||
unsigned s = (uint16_t)u256(h256(io_t.cropped(o, 32)));
|
|
||||||
std::string ret;
|
|
||||||
ret.resize(s);
|
|
||||||
io_t.cropped(o + 32, s).populate(bytesRef((byte*)ret.data(), s));
|
|
||||||
io_t = io_t.cropped(32);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class T> T abiOut(bytes const& _data)
|
|
||||||
{
|
|
||||||
bytesConstRef o(&_data);
|
|
||||||
return ABIDeserialiser<T>::deserialise(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class T> T abiOut(bytesConstRef& _data)
|
|
||||||
{
|
|
||||||
return ABIDeserialiser<T>::deserialise(_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,13 +37,7 @@
|
|||||||
#pragma warning(disable:3682) //call through incomplete class
|
#pragma warning(disable:3682) //call through incomplete class
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <map>
|
#include <libdevcore/vector_ref.h>
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include <set>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
@ -67,14 +61,13 @@
|
|||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#endif // defined(__GNUC__)
|
#endif // defined(__GNUC__)
|
||||||
|
|
||||||
#include "vector_ref.h"
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using byte = uint8_t;
|
using byte = uint8_t;
|
||||||
|
|
||||||
// Quote a given token stream to turn it into a string.
|
|
||||||
#define DEV_QUOTED_HELPER(s) #s
|
|
||||||
#define DEV_QUOTED(s) DEV_QUOTED_HELPER(s)
|
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -85,32 +78,15 @@ using bytesConstRef = vector_ref<byte const>;
|
|||||||
|
|
||||||
// Numeric types.
|
// Numeric types.
|
||||||
using bigint = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<>>;
|
using bigint = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<>>;
|
||||||
using u64 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<64, 64, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
|
||||||
using u128 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<128, 128, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
|
||||||
using u256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
using u256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
||||||
using s256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>;
|
using s256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>;
|
||||||
using u160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
using u160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
||||||
using s160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>;
|
|
||||||
using u512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 512, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
|
||||||
using s512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 512, boost::multiprecision::signed_magnitude, boost::multiprecision::unchecked, void>>;
|
|
||||||
using u256s = std::vector<u256>;
|
|
||||||
using u160s = std::vector<u160>;
|
|
||||||
using u256Set = std::set<u256>;
|
|
||||||
using u160Set = std::set<u160>;
|
|
||||||
|
|
||||||
// Map types.
|
// Map types.
|
||||||
using StringMap = std::map<std::string, std::string>;
|
using StringMap = std::map<std::string, std::string>;
|
||||||
|
|
||||||
// Hash types.
|
|
||||||
using StringHashMap = std::unordered_map<std::string, std::string>;
|
|
||||||
|
|
||||||
// String types.
|
// String types.
|
||||||
using strings = std::vector<std::string>;
|
using strings = std::vector<std::string>;
|
||||||
// Fixed-length string types.
|
|
||||||
using string32 = std::array<char, 32>;
|
|
||||||
|
|
||||||
// Null/Invalid values for convenience.
|
|
||||||
static const bytes NullBytes;
|
|
||||||
|
|
||||||
/// Interprets @a _u as a two's complement signed number and returns the resulting s256.
|
/// Interprets @a _u as a two's complement signed number and returns the resulting s256.
|
||||||
inline s256 u2s(u256 _u)
|
inline s256 u2s(u256 _u)
|
||||||
@ -143,16 +119,6 @@ inline std::ostream& operator<<(std::ostream& os, bytes const& _bytes)
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <size_t n> inline u256 exp10()
|
|
||||||
{
|
|
||||||
return exp10<n - 1>() * u256(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <> inline u256 exp10<0>()
|
|
||||||
{
|
|
||||||
return u256(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// RAII utility class whose destructor calls a given function.
|
/// RAII utility class whose destructor calls a given function.
|
||||||
class ScopeGuard
|
class ScopeGuard
|
||||||
{
|
{
|
||||||
@ -164,12 +130,4 @@ private:
|
|||||||
std::function<void(void)> m_f;
|
std::function<void(void)> m_f;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class WithExisting: int
|
|
||||||
{
|
|
||||||
Trust = 0,
|
|
||||||
Verify,
|
|
||||||
Rescue,
|
|
||||||
Kill
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -28,34 +28,6 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace dev;
|
using namespace dev;
|
||||||
|
|
||||||
std::string dev::escaped(std::string const& _s, bool _all)
|
|
||||||
{
|
|
||||||
static const map<char, char> prettyEscapes{{'\r', 'r'}, {'\n', 'n'}, {'\t', 't'}, {'\v', 'v'}};
|
|
||||||
std::string ret;
|
|
||||||
ret.reserve(_s.size() + 2);
|
|
||||||
ret.push_back('"');
|
|
||||||
for (auto i: _s)
|
|
||||||
if (i == '"' && !_all)
|
|
||||||
ret += "\\\"";
|
|
||||||
else if (i == '\\' && !_all)
|
|
||||||
ret += "\\\\";
|
|
||||||
else if (prettyEscapes.count(i) && !_all)
|
|
||||||
{
|
|
||||||
ret += '\\';
|
|
||||||
ret += prettyEscapes.find(i)->second;
|
|
||||||
}
|
|
||||||
else if (i < ' ' || _all)
|
|
||||||
{
|
|
||||||
ret += "\\x";
|
|
||||||
ret.push_back("0123456789abcdef"[(uint8_t)i / 16]);
|
|
||||||
ret.push_back("0123456789abcdef"[(uint8_t)i % 16]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ret.push_back(i);
|
|
||||||
ret.push_back('"');
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int dev::fromHex(char _i, WhenError _throw)
|
int dev::fromHex(char _i, WhenError _throw)
|
||||||
{
|
{
|
||||||
if (_i >= '0' && _i <= '9')
|
if (_i >= '0' && _i <= '9')
|
||||||
|
@ -26,11 +26,10 @@
|
|||||||
#include <libdevcore/Common.h>
|
#include <libdevcore/Common.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <algorithm>
|
|
||||||
#include <unordered_set>
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
|
@ -35,6 +35,9 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace dev;
|
using namespace dev;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
template <typename _T>
|
template <typename _T>
|
||||||
inline _T contentsGeneric(std::string const& _file)
|
inline _T contentsGeneric(std::string const& _file)
|
||||||
{
|
{
|
||||||
@ -56,6 +59,8 @@ inline _T contentsGeneric(std::string const& _file)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
string dev::contentsString(string const& _file)
|
string dev::contentsString(string const& _file)
|
||||||
{
|
{
|
||||||
return contentsGeneric<string>(_file);
|
return contentsGeneric<string>(_file);
|
||||||
|
@ -23,20 +23,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libdevcore/CommonData.h>
|
||||||
|
|
||||||
|
#include <boost/functional/hash.hpp>
|
||||||
|
#include <boost/io/ios_state.hpp>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/functional/hash.hpp>
|
|
||||||
#include <boost/io/ios_state.hpp>
|
|
||||||
#include "CommonData.h"
|
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Compile-time calculation of Log2 of constant values.
|
|
||||||
template <unsigned N> struct StaticLog2 { enum { result = 1 + StaticLog2<N/2>::result }; };
|
|
||||||
template <> struct StaticLog2<1> { enum { result = 0 }; };
|
|
||||||
|
|
||||||
/// Fixed-size raw-byte array container type, with an API optimised for storing hashes.
|
/// Fixed-size raw-byte array container type, with an API optimised for storing hashes.
|
||||||
/// Transparently converts to/from the corresponding arithmetic type; this will
|
/// Transparently converts to/from the corresponding arithmetic type; this will
|
||||||
/// assume the data contained in the hash is big-endian.
|
/// assume the data contained in the hash is big-endian.
|
||||||
@ -50,9 +48,6 @@ public:
|
|||||||
/// The size of the container.
|
/// The size of the container.
|
||||||
enum { size = N };
|
enum { size = N };
|
||||||
|
|
||||||
/// A dummy flag to avoid accidental construction from pointer.
|
|
||||||
enum ConstructFromPointerType { ConstructFromPointer };
|
|
||||||
|
|
||||||
/// Method to convert from a string.
|
/// Method to convert from a string.
|
||||||
enum ConstructFromStringType { FromHex, FromBinary };
|
enum ConstructFromStringType { FromHex, FromBinary };
|
||||||
|
|
||||||
@ -77,9 +72,6 @@ public:
|
|||||||
/// Explicitly construct, copying from a byte array.
|
/// Explicitly construct, copying from a byte array.
|
||||||
explicit FixedHash(bytesConstRef _b, ConstructFromHashType _t = FailIfDifferent) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min<unsigned>(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min<unsigned>(_b.size(), N); for (unsigned i = 0; i < c; ++i) m_data[_t == AlignRight ? N - 1 - i : i] = _b[_t == AlignRight ? _b.size() - 1 - i : i]; } } }
|
explicit FixedHash(bytesConstRef _b, ConstructFromHashType _t = FailIfDifferent) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min<unsigned>(_b.size(), N)); else { m_data.fill(0); if (_t != FailIfDifferent) { auto c = std::min<unsigned>(_b.size(), N); for (unsigned i = 0; i < c; ++i) m_data[_t == AlignRight ? N - 1 - i : i] = _b[_t == AlignRight ? _b.size() - 1 - i : i]; } } }
|
||||||
|
|
||||||
/// Explicitly construct, copying from a bytes in memory with given pointer.
|
|
||||||
explicit FixedHash(byte const* _bs, ConstructFromPointerType) { memcpy(m_data.data(), _bs, N); }
|
|
||||||
|
|
||||||
/// Explicitly construct, copying from a string.
|
/// Explicitly construct, copying from a string.
|
||||||
explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex, ConstructFromHashType _ht = FailIfDifferent): FixedHash(_t == FromHex ? fromHex(_s, WhenError::Throw) : dev::asBytes(_s), _ht) {}
|
explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex, ConstructFromHashType _ht = FailIfDifferent): FixedHash(_t == FromHex ? fromHex(_s, WhenError::Throw) : dev::asBytes(_s), _ht) {}
|
||||||
|
|
||||||
@ -92,37 +84,16 @@ public:
|
|||||||
// The obvious comparison operators.
|
// The obvious comparison operators.
|
||||||
bool operator==(FixedHash const& _c) const { return m_data == _c.m_data; }
|
bool operator==(FixedHash const& _c) const { return m_data == _c.m_data; }
|
||||||
bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; }
|
bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; }
|
||||||
|
/// Required to sort objects of this type or use them as map keys.
|
||||||
bool operator<(FixedHash const& _c) const { for (unsigned i = 0; i < N; ++i) if (m_data[i] < _c.m_data[i]) return true; else if (m_data[i] > _c.m_data[i]) return false; return false; }
|
bool operator<(FixedHash const& _c) const { for (unsigned i = 0; i < N; ++i) if (m_data[i] < _c.m_data[i]) return true; else if (m_data[i] > _c.m_data[i]) return false; return false; }
|
||||||
bool operator>=(FixedHash const& _c) const { return !operator<(_c); }
|
|
||||||
bool operator<=(FixedHash const& _c) const { return operator==(_c) || operator<(_c); }
|
|
||||||
bool operator>(FixedHash const& _c) const { return !operator<=(_c); }
|
|
||||||
|
|
||||||
// The obvious binary operators.
|
|
||||||
FixedHash& operator^=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; }
|
|
||||||
FixedHash operator^(FixedHash const& _c) const { return FixedHash(*this) ^= _c; }
|
|
||||||
FixedHash& operator|=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] |= _c.m_data[i]; return *this; }
|
|
||||||
FixedHash operator|(FixedHash const& _c) const { return FixedHash(*this) |= _c; }
|
|
||||||
FixedHash& operator&=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] &= _c.m_data[i]; return *this; }
|
|
||||||
FixedHash operator&(FixedHash const& _c) const { return FixedHash(*this) &= _c; }
|
|
||||||
FixedHash operator~() const { FixedHash ret; for (unsigned i = 0; i < N; ++i) ret[i] = ~m_data[i]; return ret; }
|
FixedHash operator~() const { FixedHash ret; for (unsigned i = 0; i < N; ++i) ret[i] = ~m_data[i]; return ret; }
|
||||||
|
|
||||||
// Big-endian increment.
|
|
||||||
FixedHash& operator++() { for (unsigned i = size; i > 0 && !++m_data[--i]; ) {} return *this; }
|
|
||||||
|
|
||||||
/// @returns true if all one-bits in @a _c are set in this object.
|
|
||||||
bool contains(FixedHash const& _c) const { return (*this & _c) == _c; }
|
|
||||||
|
|
||||||
/// @returns a particular byte from the hash.
|
/// @returns a particular byte from the hash.
|
||||||
byte& operator[](unsigned _i) { return m_data[_i]; }
|
byte& operator[](unsigned _i) { return m_data[_i]; }
|
||||||
/// @returns a particular byte from the hash.
|
/// @returns a particular byte from the hash.
|
||||||
byte operator[](unsigned _i) const { return m_data[_i]; }
|
byte operator[](unsigned _i) const { return m_data[_i]; }
|
||||||
|
|
||||||
/// @returns an abridged version of the hash as a user-readable hex string.
|
|
||||||
std::string abridged() const { return toHex(ref().cropped(0, 4)) + "\342\200\246"; }
|
|
||||||
|
|
||||||
/// @returns a version of the hash as a user-readable hex string that leaves out the middle part.
|
|
||||||
std::string abridgedMiddle() const { return toHex(ref().cropped(0, 4)) + "\342\200\246" + toHex(ref().cropped(N - 4)); }
|
|
||||||
|
|
||||||
/// @returns the hash as a user-readable hex string.
|
/// @returns the hash as a user-readable hex string.
|
||||||
std::string hex() const { return toHex(ref()); }
|
std::string hex() const { return toHex(ref()); }
|
||||||
|
|
||||||
@ -147,54 +118,17 @@ public:
|
|||||||
/// @returns a constant reference to the object's data as an STL array.
|
/// @returns a constant reference to the object's data as an STL array.
|
||||||
std::array<byte, N> const& asArray() const { return m_data; }
|
std::array<byte, N> const& asArray() const { return m_data; }
|
||||||
|
|
||||||
struct hash
|
|
||||||
{
|
|
||||||
/// Make a hash of the object's data.
|
|
||||||
size_t operator()(FixedHash const& _value) const { return boost::hash_range(_value.m_data.cbegin(), _value.m_data.cend()); }
|
|
||||||
};
|
|
||||||
|
|
||||||
template <unsigned P, unsigned M> inline FixedHash& shiftBloom(FixedHash<M> const& _h)
|
|
||||||
{
|
|
||||||
return (*this |= _h.template bloomPart<P, N>());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <unsigned P, unsigned M> inline bool containsBloom(FixedHash<M> const& _h)
|
|
||||||
{
|
|
||||||
return contains(_h.template bloomPart<P, N>());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <unsigned P, unsigned M> inline FixedHash<M> bloomPart() const
|
|
||||||
{
|
|
||||||
unsigned const c_bloomBits = M * 8;
|
|
||||||
unsigned const c_mask = c_bloomBits - 1;
|
|
||||||
unsigned const c_bloomBytes = (StaticLog2<c_bloomBits>::result + 7) / 8;
|
|
||||||
|
|
||||||
static_assert((M & (M - 1)) == 0, "M must be power-of-two");
|
|
||||||
static_assert(P * c_bloomBytes <= N, "out of range");
|
|
||||||
|
|
||||||
FixedHash<M> ret;
|
|
||||||
byte const* p = data();
|
|
||||||
for (unsigned i = 0; i < P; ++i)
|
|
||||||
{
|
|
||||||
unsigned index = 0;
|
|
||||||
for (unsigned j = 0; j < c_bloomBytes; ++j, ++p)
|
|
||||||
index = (index << 8) | *p;
|
|
||||||
index &= c_mask;
|
|
||||||
ret[M - 1 - index / 8] |= (1 << (index % 8));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the index of the first bit set to one, or size() * 8 if no bits are set.
|
/// Returns the index of the first bit set to one, or size() * 8 if no bits are set.
|
||||||
inline unsigned firstBitSet() const
|
inline unsigned firstBitSet() const
|
||||||
{
|
{
|
||||||
unsigned ret = 0;
|
unsigned ret = 0;
|
||||||
for (auto d: m_data)
|
for (auto d: m_data)
|
||||||
if (d)
|
if (d)
|
||||||
|
{
|
||||||
for (;; ++ret, d <<= 1)
|
for (;; ++ret, d <<= 1)
|
||||||
if (d & 0x80)
|
if (d & 0x80)
|
||||||
return ret;
|
return ret;
|
||||||
else {}
|
}
|
||||||
else
|
else
|
||||||
ret += 8;
|
ret += 8;
|
||||||
return ret;
|
return ret;
|
||||||
@ -206,21 +140,6 @@ private:
|
|||||||
std::array<byte, N> m_data; ///< The binary data.
|
std::array<byte, N> m_data; ///< The binary data.
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Fast equality operator for h256.
|
|
||||||
template<> inline bool FixedHash<32>::operator==(FixedHash<32> const& _other) const
|
|
||||||
{
|
|
||||||
const uint64_t* hash1 = (const uint64_t*)data();
|
|
||||||
const uint64_t* hash2 = (const uint64_t*)_other.data();
|
|
||||||
return (hash1[0] == hash2[0]) && (hash1[1] == hash2[1]) && (hash1[2] == hash2[2]) && (hash1[3] == hash2[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fast std::hash compatible hash function object for h256.
|
|
||||||
template<> inline size_t FixedHash<32>::hash::operator()(FixedHash<32> const& value) const
|
|
||||||
{
|
|
||||||
uint64_t const* data = reinterpret_cast<uint64_t const*>(value.data());
|
|
||||||
return boost::hash_range(data, data + 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stream I/O for the FixedHash class.
|
/// Stream I/O for the FixedHash class.
|
||||||
template <unsigned N>
|
template <unsigned N>
|
||||||
inline std::ostream& operator<<(std::ostream& _out, FixedHash<N> const& _h)
|
inline std::ostream& operator<<(std::ostream& _out, FixedHash<N> const& _h)
|
||||||
@ -234,56 +153,7 @@ inline std::ostream& operator<<(std::ostream& _out, FixedHash<N> const& _h)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Common types of FixedHash.
|
// Common types of FixedHash.
|
||||||
using h2048 = FixedHash<256>;
|
|
||||||
using h1024 = FixedHash<128>;
|
|
||||||
using h520 = FixedHash<65>;
|
|
||||||
using h512 = FixedHash<64>;
|
|
||||||
using h256 = FixedHash<32>;
|
using h256 = FixedHash<32>;
|
||||||
using h160 = FixedHash<20>;
|
using h160 = FixedHash<20>;
|
||||||
using h128 = FixedHash<16>;
|
|
||||||
using h64 = FixedHash<8>;
|
|
||||||
using h512s = std::vector<h512>;
|
|
||||||
using h256s = std::vector<h256>;
|
|
||||||
using h160s = std::vector<h160>;
|
|
||||||
using h256Set = std::set<h256>;
|
|
||||||
using h160Set = std::set<h160>;
|
|
||||||
using h256Hash = std::unordered_set<h256>;
|
|
||||||
using h160Hash = std::unordered_set<h160>;
|
|
||||||
|
|
||||||
/// Convert the given value into h160 (160-bit unsigned integer) using the right 20 bytes.
|
|
||||||
inline h160 right160(h256 const& _t)
|
|
||||||
{
|
|
||||||
h160 ret;
|
|
||||||
memcpy(ret.data(), _t.data() + 12, 20);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert the given value into h160 (160-bit unsigned integer) using the left 20 bytes.
|
|
||||||
inline h160 left160(h256 const& _t)
|
|
||||||
{
|
|
||||||
h160 ret;
|
|
||||||
memcpy(&ret[0], _t.data(), 20);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string toString(h256s const& _bs)
|
|
||||||
{
|
|
||||||
std::ostringstream out;
|
|
||||||
out << "[ ";
|
|
||||||
for (auto i: _bs)
|
|
||||||
out << i.abridged() << ", ";
|
|
||||||
out << "]";
|
|
||||||
return out.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace std
|
|
||||||
{
|
|
||||||
/// Forward std::hash<dev::FixedHash> to dev::FixedHash::hash.
|
|
||||||
template<> struct hash<dev::h64>: dev::h64::hash {};
|
|
||||||
template<> struct hash<dev::h128>: dev::h128::hash {};
|
|
||||||
template<> struct hash<dev::h160>: dev::h160::hash {};
|
|
||||||
template<> struct hash<dev::h256>: dev::h256::hash {};
|
|
||||||
template<> struct hash<dev::h512>: dev::h512::hash {};
|
|
||||||
}
|
|
||||||
|
@ -97,10 +97,9 @@ static const uint64_t RC[24] = \
|
|||||||
static inline void keccakf(void* state) {
|
static inline void keccakf(void* state) {
|
||||||
uint64_t* a = (uint64_t*)state;
|
uint64_t* a = (uint64_t*)state;
|
||||||
uint64_t b[5] = {0};
|
uint64_t b[5] = {0};
|
||||||
uint64_t t = 0;
|
|
||||||
uint8_t x, y;
|
|
||||||
|
|
||||||
for (int i = 0; i < 24; i++) {
|
for (int i = 0; i < 24; i++) {
|
||||||
|
uint8_t x, y;
|
||||||
// Theta
|
// Theta
|
||||||
FOR5(x, 1,
|
FOR5(x, 1,
|
||||||
b[x] = 0;
|
b[x] = 0;
|
||||||
@ -110,7 +109,7 @@ static inline void keccakf(void* state) {
|
|||||||
FOR5(y, 5,
|
FOR5(y, 5,
|
||||||
a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); ))
|
a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); ))
|
||||||
// Rho and pi
|
// Rho and pi
|
||||||
t = a[1];
|
uint64_t t = a[1];
|
||||||
x = 0;
|
x = 0;
|
||||||
REPEAT24(b[0] = a[pi[x]];
|
REPEAT24(b[0] = a[pi[x]];
|
||||||
a[pi[x]] = rol(t, rho[x]);
|
a[pi[x]] = rol(t, rho[x]);
|
||||||
|
@ -23,8 +23,9 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libdevcore/FixedHash.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "FixedHash.h"
|
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
@ -47,10 +48,4 @@ inline h256 keccak256(std::string const& _input) { return keccak256(bytesConstRe
|
|||||||
/// Calculate Keccak-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash.
|
/// Calculate Keccak-256 hash of the given input (presented as a FixedHash), returns a 256-bit hash.
|
||||||
template<unsigned N> inline h256 keccak256(FixedHash<N> const& _input) { return keccak256(_input.ref()); }
|
template<unsigned N> inline h256 keccak256(FixedHash<N> const& _input) { return keccak256(_input.ref()); }
|
||||||
|
|
||||||
/// Calculate Keccak-256 hash of the given input, possibly interpreting it as nibbles, and return the hash as a string filled with binary data.
|
|
||||||
inline std::string keccak256(std::string const& _input, bool _isNibbles) { return asString((_isNibbles ? keccak256(fromHex(_input)) : keccak256(bytesConstRef(&_input))).asBytes()); }
|
|
||||||
|
|
||||||
/// Calculate Keccak-256 MAC
|
|
||||||
inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,8 @@
|
|||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace dev;
|
using namespace dev;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
bytes toLittleEndian(size_t _size)
|
bytes toLittleEndian(size_t _size)
|
||||||
{
|
{
|
||||||
@ -59,6 +61,8 @@ h256 swarmHashIntermediate(string const& _input, size_t _offset, size_t _length)
|
|||||||
return swarmHashSimple(ref, _length);
|
return swarmHashSimple(ref, _length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
h256 dev::swarmHash(string const& _input)
|
h256 dev::swarmHash(string const& _input)
|
||||||
{
|
{
|
||||||
return swarmHashIntermediate(_input, 0, _input.size());
|
return swarmHashIntermediate(_input, 0, _input.size());
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
|
|
||||||
/// Compute the "swarm hash" of @a _data
|
/// Compute the "swarm hash" of @a _input
|
||||||
h256 swarmHash(std::string const& _data);
|
h256 swarmHash(std::string const& _input);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ public:
|
|||||||
using value_type = _T;
|
using value_type = _T;
|
||||||
using element_type = _T;
|
using element_type = _T;
|
||||||
using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type;
|
using mutable_value_type = typename std::conditional<std::is_const<_T>::value, typename std::remove_const<_T>::type, _T>::type;
|
||||||
|
using string_type = typename std::conditional<std::is_const<_T>::value, std::string const, std::string>::type;
|
||||||
|
using vector_type = typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const, std::vector<_T>>::type;
|
||||||
|
|
||||||
static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data.");
|
static_assert(std::is_pod<value_type>::value, "vector_ref can only be used with PODs due to its low-level treatment of data.");
|
||||||
|
|
||||||
@ -30,18 +32,13 @@ public:
|
|||||||
/// Creates a new vector_ref to point to @a _count elements starting at @a _data.
|
/// Creates a new vector_ref to point to @a _count elements starting at @a _data.
|
||||||
vector_ref(_T* _data, size_t _count): m_data(_data), m_count(_count) {}
|
vector_ref(_T* _data, size_t _count): m_data(_data), m_count(_count) {}
|
||||||
/// Creates a new vector_ref pointing to the data part of a string (given as pointer).
|
/// Creates a new vector_ref pointing to the data part of a string (given as pointer).
|
||||||
vector_ref(typename std::conditional<std::is_const<_T>::value, std::string const*, std::string*>::type _data): m_data(reinterpret_cast<_T*>(_data->data())), m_count(_data->size() / sizeof(_T)) {}
|
vector_ref(string_type* _data): m_data(reinterpret_cast<_T*>(_data->data())), m_count(_data->size() / sizeof(_T)) {}
|
||||||
/// Creates a new vector_ref pointing to the data part of a vector (given as pointer).
|
|
||||||
vector_ref(typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const*, std::vector<_T>*>::type _data): m_data(_data->data()), m_count(_data->size()) {}
|
|
||||||
/// Creates a new vector_ref pointing to the data part of a string (given as reference).
|
/// Creates a new vector_ref pointing to the data part of a string (given as reference).
|
||||||
vector_ref(typename std::conditional<std::is_const<_T>::value, std::string const&, std::string&>::type _data): m_data(reinterpret_cast<_T*>(_data.data())), m_count(_data.size() / sizeof(_T)) {}
|
vector_ref(string_type& _data): vector_ref(&_data) {}
|
||||||
#if DEV_LDB
|
/// Creates a new vector_ref pointing to the data part of a vector (given as pointer).
|
||||||
vector_ref(ldb::Slice const& _s): m_data(reinterpret_cast<_T*>(_s.data())), m_count(_s.size() / sizeof(_T)) {}
|
vector_ref(vector_type* _data): m_data(_data->data()), m_count(_data->size()) {}
|
||||||
#endif
|
|
||||||
explicit operator bool() const { return m_data && m_count; }
|
explicit operator bool() const { return m_data && m_count; }
|
||||||
|
|
||||||
bool contentsEqual(std::vector<mutable_value_type> const& _c) const { if (!m_data || m_count == 0) return _c.empty(); else return _c.size() == m_count && !memcmp(_c.data(), m_data, m_count * sizeof(_T)); }
|
|
||||||
std::vector<mutable_value_type> toVector() const { return std::vector<mutable_value_type>(m_data, m_data + m_count); }
|
|
||||||
std::vector<unsigned char> toBytes() const { return std::vector<unsigned char>(reinterpret_cast<unsigned char const*>(m_data), reinterpret_cast<unsigned char const*>(m_data) + m_count * sizeof(_T)); }
|
std::vector<unsigned char> toBytes() const { return std::vector<unsigned char>(reinterpret_cast<unsigned char const*>(m_data), reinterpret_cast<unsigned char const*>(m_data) + m_count * sizeof(_T)); }
|
||||||
std::string toString() const { return std::string((char const*)m_data, ((char const*)m_data) + m_count * sizeof(_T)); }
|
std::string toString() const { return std::string((char const*)m_data, ((char const*)m_data) + m_count * sizeof(_T)); }
|
||||||
|
|
||||||
@ -50,25 +47,14 @@ public:
|
|||||||
|
|
||||||
_T* data() const { return m_data; }
|
_T* data() const { return m_data; }
|
||||||
/// @returns the number of elements referenced (not necessarily number of bytes).
|
/// @returns the number of elements referenced (not necessarily number of bytes).
|
||||||
size_t count() const { return m_count; }
|
|
||||||
/// @returns the number of elements referenced (not necessarily number of bytes).
|
|
||||||
size_t size() const { return m_count; }
|
size_t size() const { return m_count; }
|
||||||
bool empty() const { return !m_count; }
|
bool empty() const { return !m_count; }
|
||||||
/// @returns a new vector_ref pointing at the next chunk of @a size() elements.
|
|
||||||
vector_ref<_T> next() const { if (!m_data) return *this; else return vector_ref<_T>(m_data + m_count, m_count); }
|
|
||||||
/// @returns a new vector_ref which is a shifted and shortened view of the original data.
|
/// @returns a new vector_ref which is a shifted and shortened view of the original data.
|
||||||
/// If this goes out of bounds in any way, returns an empty vector_ref.
|
/// If this goes out of bounds in any way, returns an empty vector_ref.
|
||||||
/// If @a _count is ~size_t(0), extends the view to the end of the data.
|
/// If @a _count is ~size_t(0), extends the view to the end of the data.
|
||||||
vector_ref<_T> cropped(size_t _begin, size_t _count) const { if (m_data && _begin <= m_count && _count <= m_count && _begin + _count <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); }
|
vector_ref<_T> cropped(size_t _begin, size_t _count) const { if (m_data && _begin <= m_count && _count <= m_count && _begin + _count <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); }
|
||||||
/// @returns a new vector_ref which is a shifted view of the original data (not going beyond it).
|
/// @returns a new vector_ref which is a shifted view of the original data (not going beyond it).
|
||||||
vector_ref<_T> cropped(size_t _begin) const { if (m_data && _begin <= m_count) return vector_ref<_T>(m_data + _begin, m_count - _begin); else return vector_ref<_T>(); }
|
vector_ref<_T> cropped(size_t _begin) const { if (m_data && _begin <= m_count) return vector_ref<_T>(m_data + _begin, m_count - _begin); else return vector_ref<_T>(); }
|
||||||
void retarget(_T* _d, size_t _s) { m_data = _d; m_count = _s; }
|
|
||||||
void retarget(std::vector<_T> const& _t) { m_data = _t.data(); m_count = _t.size(); }
|
|
||||||
template <class T> bool overlapsWith(vector_ref<T> _t) const { void const* f1 = data(); void const* t1 = data() + size(); void const* f2 = _t.data(); void const* t2 = _t.data() + _t.size(); return f1 < t2 && t1 > f2; }
|
|
||||||
/// Copies the contents of this vector_ref to the contents of @a _t, up to the max size of @a _t.
|
|
||||||
void copyTo(vector_ref<typename std::remove_const<_T>::type> _t) const { if (overlapsWith(_t)) memmove(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); else memcpy(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); }
|
|
||||||
/// Copies the contents of this vector_ref to the contents of @a _t, and zeros further trailing elements in @a _t.
|
|
||||||
void populate(vector_ref<typename std::remove_const<_T>::type> _t) const { copyTo(_t); memset(_t.data() + m_count, 0, std::max(_t.size(), m_count) - m_count); }
|
|
||||||
|
|
||||||
_T* begin() { return m_data; }
|
_T* begin() { return m_data; }
|
||||||
_T* end() { return m_data + m_count; }
|
_T* end() { return m_data + m_count; }
|
||||||
@ -81,20 +67,11 @@ public:
|
|||||||
bool operator==(vector_ref<_T> const& _cmp) const { return m_data == _cmp.m_data && m_count == _cmp.m_count; }
|
bool operator==(vector_ref<_T> const& _cmp) const { return m_data == _cmp.m_data && m_count == _cmp.m_count; }
|
||||||
bool operator!=(vector_ref<_T> const& _cmp) const { return !operator==(_cmp); }
|
bool operator!=(vector_ref<_T> const& _cmp) const { return !operator==(_cmp); }
|
||||||
|
|
||||||
#if DEV_LDB
|
|
||||||
operator ldb::Slice() const { return ldb::Slice((char const*)m_data, m_count * sizeof(_T)); }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void reset() { m_data = nullptr; m_count = 0; }
|
void reset() { m_data = nullptr; m_count = 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
_T* m_data;
|
_T* m_data = nullptr;
|
||||||
size_t m_count;
|
size_t m_count = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class _T> vector_ref<_T const> ref(_T const& _t) { return vector_ref<_T const>(&_t, 1); }
|
|
||||||
template<class _T> vector_ref<_T> ref(_T& _t) { return vector_ref<_T>(&_t, 1); }
|
|
||||||
template<class _T> vector_ref<_T const> ref(std::vector<_T> const& _t) { return vector_ref<_T const>(&_t); }
|
|
||||||
template<class _T> vector_ref<_T> ref(std::vector<_T>& _t) { return vector_ref<_T>(&_t); }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <libevmasm/CommonSubexpressionEliminator.h>
|
#include <libevmasm/CommonSubexpressionEliminator.h>
|
||||||
#include <libevmasm/ControlFlowGraph.h>
|
#include <libevmasm/ControlFlowGraph.h>
|
||||||
#include <libevmasm/PeepholeOptimiser.h>
|
#include <libevmasm/PeepholeOptimiser.h>
|
||||||
|
#include <libevmasm/JumpdestRemover.h>
|
||||||
#include <libevmasm/BlockDeduplicator.h>
|
#include <libevmasm/BlockDeduplicator.h>
|
||||||
#include <libevmasm/ConstantOptimiser.h>
|
#include <libevmasm/ConstantOptimiser.h>
|
||||||
#include <libevmasm/GasMeter.h>
|
#include <libevmasm/GasMeter.h>
|
||||||
@ -48,6 +49,8 @@ void Assembly::append(Assembly const& _a)
|
|||||||
}
|
}
|
||||||
m_deposit = newDeposit;
|
m_deposit = newDeposit;
|
||||||
m_usedTags += _a.m_usedTags;
|
m_usedTags += _a.m_usedTags;
|
||||||
|
// This does not transfer the names of named tags on purpose. The tags themselves are
|
||||||
|
// transferred, but their names are only available inside the assembly.
|
||||||
for (auto const& i: _a.m_data)
|
for (auto const& i: _a.m_data)
|
||||||
m_data.insert(i);
|
m_data.insert(i);
|
||||||
for (auto const& i: _a.m_strings)
|
for (auto const& i: _a.m_strings)
|
||||||
@ -180,7 +183,7 @@ private:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
|
void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
|
||||||
{
|
{
|
||||||
Functionalizer f(_out, _prefix, _sourceCodes);
|
Functionalizer f(_out, _prefix, _sourceCodes);
|
||||||
|
|
||||||
@ -198,18 +201,23 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con
|
|||||||
for (size_t i = 0; i < m_subs.size(); ++i)
|
for (size_t i = 0; i < m_subs.size(); ++i)
|
||||||
{
|
{
|
||||||
_out << endl << _prefix << "sub_" << i << ": assembly {\n";
|
_out << endl << _prefix << "sub_" << i << ": assembly {\n";
|
||||||
m_subs[i]->streamAsm(_out, _prefix + " ", _sourceCodes);
|
m_subs[i]->assemblyStream(_out, _prefix + " ", _sourceCodes);
|
||||||
_out << _prefix << "}" << endl;
|
_out << _prefix << "}" << endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_auxiliaryData.size() > 0)
|
if (m_auxiliaryData.size() > 0)
|
||||||
_out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl;
|
_out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl;
|
||||||
|
|
||||||
return _out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType) const
|
string Assembly::assemblyString(StringMap const& _sourceCodes) const
|
||||||
|
{
|
||||||
|
ostringstream tmp;
|
||||||
|
assemblyStream(tmp, "", _sourceCodes);
|
||||||
|
return tmp.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType)
|
||||||
{
|
{
|
||||||
Json::Value value;
|
Json::Value value;
|
||||||
value["name"] = _name;
|
value["name"] = _name;
|
||||||
@ -222,14 +230,14 @@ Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
string toStringInHex(u256 _value)
|
string Assembly::toStringInHex(u256 _value)
|
||||||
{
|
{
|
||||||
std::stringstream hexStr;
|
std::stringstream hexStr;
|
||||||
hexStr << hex << _value;
|
hexStr << hex << _value;
|
||||||
return hexStr.str();
|
return hexStr.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes) const
|
Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const
|
||||||
{
|
{
|
||||||
Json::Value root;
|
Json::Value root;
|
||||||
|
|
||||||
@ -300,32 +308,19 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes
|
|||||||
{
|
{
|
||||||
std::stringstream hexStr;
|
std::stringstream hexStr;
|
||||||
hexStr << hex << i;
|
hexStr << hex << i;
|
||||||
data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true);
|
data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceCodes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_auxiliaryData.size() > 0)
|
if (m_auxiliaryData.size() > 0)
|
||||||
root[".auxdata"] = toHex(m_auxiliaryData);
|
root[".auxdata"] = toHex(m_auxiliaryData);
|
||||||
|
|
||||||
_out << root;
|
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value Assembly::stream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes, bool _inJsonFormat) const
|
|
||||||
{
|
|
||||||
if (_inJsonFormat)
|
|
||||||
return streamAsmJson(_out, _sourceCodes);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
streamAsm(_out, _prefix, _sourceCodes);
|
|
||||||
return Json::Value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
||||||
{
|
{
|
||||||
assertThrow(m_deposit >= 0, AssemblyException, "");
|
assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow.");
|
||||||
m_deposit += _i.deposit();
|
m_deposit += _i.deposit();
|
||||||
m_items.push_back(_i);
|
m_items.push_back(_i);
|
||||||
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())
|
if (m_items.back().location().isEmpty() && !m_currentSourceLocation.isEmpty())
|
||||||
@ -333,6 +328,14 @@ AssemblyItem const& Assembly::append(AssemblyItem const& _i)
|
|||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssemblyItem Assembly::namedTag(string const& _name)
|
||||||
|
{
|
||||||
|
assertThrow(!_name.empty(), AssemblyException, "Empty named tag.");
|
||||||
|
if (!m_namedTags.count(_name))
|
||||||
|
m_namedTags[_name] = size_t(newTag().data());
|
||||||
|
return AssemblyItem(Tag, m_namedTags.at(_name));
|
||||||
|
}
|
||||||
|
|
||||||
AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier)
|
AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier)
|
||||||
{
|
{
|
||||||
h256 h(dev::keccak256(_identifier));
|
h256 h(dev::keccak256(_identifier));
|
||||||
@ -349,6 +352,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
|||||||
{
|
{
|
||||||
OptimiserSettings settings;
|
OptimiserSettings settings;
|
||||||
settings.isCreation = _isCreation;
|
settings.isCreation = _isCreation;
|
||||||
|
settings.runJumpdestRemover = true;
|
||||||
settings.runPeephole = true;
|
settings.runPeephole = true;
|
||||||
if (_enable)
|
if (_enable)
|
||||||
{
|
{
|
||||||
@ -357,18 +361,21 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs)
|
|||||||
settings.runConstantOptimiser = true;
|
settings.runConstantOptimiser = true;
|
||||||
}
|
}
|
||||||
settings.expectedExecutionsPerDeployment = _runs;
|
settings.expectedExecutionsPerDeployment = _runs;
|
||||||
optimiseInternal(settings);
|
optimise(settings);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Assembly& Assembly::optimise(OptimiserSettings _settings)
|
Assembly& Assembly::optimise(OptimiserSettings const& _settings)
|
||||||
{
|
{
|
||||||
optimiseInternal(_settings);
|
optimiseInternal(_settings, {});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
map<u256, u256> Assembly::optimiseInternal(OptimiserSettings _settings)
|
map<u256, u256> Assembly::optimiseInternal(
|
||||||
|
OptimiserSettings const& _settings,
|
||||||
|
std::set<size_t> const& _tagsReferencedFromOutside
|
||||||
|
)
|
||||||
{
|
{
|
||||||
// Run optimisation for sub-assemblies.
|
// Run optimisation for sub-assemblies.
|
||||||
for (size_t subId = 0; subId < m_subs.size(); ++subId)
|
for (size_t subId = 0; subId < m_subs.size(); ++subId)
|
||||||
@ -376,7 +383,10 @@ map<u256, u256> Assembly::optimiseInternal(OptimiserSettings _settings)
|
|||||||
OptimiserSettings settings = _settings;
|
OptimiserSettings settings = _settings;
|
||||||
// Disable creation mode for sub-assemblies.
|
// Disable creation mode for sub-assemblies.
|
||||||
settings.isCreation = false;
|
settings.isCreation = false;
|
||||||
map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(settings);
|
map<u256, u256> subTagReplacements = m_subs[subId]->optimiseInternal(
|
||||||
|
settings,
|
||||||
|
JumpdestRemover::referencedTags(m_items, subId)
|
||||||
|
);
|
||||||
// Apply the replacements (can be empty).
|
// Apply the replacements (can be empty).
|
||||||
BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId);
|
BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId);
|
||||||
}
|
}
|
||||||
@ -387,6 +397,13 @@ map<u256, u256> Assembly::optimiseInternal(OptimiserSettings _settings)
|
|||||||
{
|
{
|
||||||
count = 0;
|
count = 0;
|
||||||
|
|
||||||
|
if (_settings.runJumpdestRemover)
|
||||||
|
{
|
||||||
|
JumpdestRemover jumpdestOpt(m_items);
|
||||||
|
if (jumpdestOpt.optimise(_tagsReferencedFromOutside))
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
if (_settings.runPeephole)
|
if (_settings.runPeephole)
|
||||||
{
|
{
|
||||||
PeepholeOptimiser peepOpt(m_items);
|
PeepholeOptimiser peepOpt(m_items);
|
||||||
@ -473,8 +490,9 @@ LinkerObject const& Assembly::assemble() const
|
|||||||
for (auto const& sub: m_subs)
|
for (auto const& sub: m_subs)
|
||||||
{
|
{
|
||||||
sub->assemble();
|
sub->assemble();
|
||||||
if (!sub->m_tagPositionsInBytecode.empty())
|
for (size_t tagPos: sub->m_tagPositionsInBytecode)
|
||||||
subTagSize = max(subTagSize, *max_element(sub->m_tagPositionsInBytecode.begin(), sub->m_tagPositionsInBytecode.end()));
|
if (tagPos != size_t(-1) && tagPos > subTagSize)
|
||||||
|
subTagSize = tagPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkerObject& ret = m_assembledObject;
|
LinkerObject& ret = m_assembledObject;
|
||||||
@ -570,9 +588,10 @@ LinkerObject const& Assembly::assemble() const
|
|||||||
ret.bytecode.resize(ret.bytecode.size() + 20);
|
ret.bytecode.resize(ret.bytecode.size() + 20);
|
||||||
break;
|
break;
|
||||||
case Tag:
|
case Tag:
|
||||||
assertThrow(i.data() != 0, AssemblyException, "");
|
assertThrow(i.data() != 0, AssemblyException, "Invalid tag position.");
|
||||||
assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag.");
|
assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag.");
|
||||||
assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large.");
|
assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large.");
|
||||||
|
assertThrow(m_tagPositionsInBytecode[size_t(i.data())] == size_t(-1), AssemblyException, "Duplicate tag position.");
|
||||||
m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size();
|
m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size();
|
||||||
ret.bytecode.push_back((byte)Instruction::JUMPDEST);
|
ret.bytecode.push_back((byte)Instruction::JUMPDEST);
|
||||||
break;
|
break;
|
||||||
|
@ -47,6 +47,8 @@ public:
|
|||||||
|
|
||||||
AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); }
|
AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); }
|
||||||
AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); }
|
AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); }
|
||||||
|
/// Returns a tag identified by the given name. Creates it if it does not yet exist.
|
||||||
|
AssemblyItem namedTag(std::string const& _name);
|
||||||
AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); }
|
AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); }
|
||||||
AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); }
|
AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); }
|
||||||
Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); }
|
Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); }
|
||||||
@ -100,6 +102,7 @@ public:
|
|||||||
struct OptimiserSettings
|
struct OptimiserSettings
|
||||||
{
|
{
|
||||||
bool isCreation = false;
|
bool isCreation = false;
|
||||||
|
bool runJumpdestRemover = false;
|
||||||
bool runPeephole = false;
|
bool runPeephole = false;
|
||||||
bool runDeduplicate = false;
|
bool runDeduplicate = false;
|
||||||
bool runCSE = false;
|
bool runCSE = false;
|
||||||
@ -110,7 +113,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Execute optimisation passes as defined by @a _settings and return the optimised assembly.
|
/// Execute optimisation passes as defined by @a _settings and return the optimised assembly.
|
||||||
Assembly& optimise(OptimiserSettings _settings);
|
Assembly& optimise(OptimiserSettings const& _settings);
|
||||||
|
|
||||||
/// Modify (if @a _enable is set) and return the current assembly such that creation and
|
/// Modify (if @a _enable is set) and return the current assembly such that creation and
|
||||||
/// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly.
|
/// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly.
|
||||||
@ -119,28 +122,37 @@ public:
|
|||||||
/// If @a _enable is not set, will perform some simple peephole optimizations.
|
/// If @a _enable is not set, will perform some simple peephole optimizations.
|
||||||
Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200);
|
Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200);
|
||||||
|
|
||||||
Json::Value stream(
|
/// Create a text representation of the assembly.
|
||||||
|
std::string assemblyString(
|
||||||
|
StringMap const& _sourceCodes = StringMap()
|
||||||
|
) const;
|
||||||
|
void assemblyStream(
|
||||||
std::ostream& _out,
|
std::ostream& _out,
|
||||||
std::string const& _prefix = "",
|
std::string const& _prefix = "",
|
||||||
const StringMap &_sourceCodes = StringMap(),
|
StringMap const& _sourceCodes = StringMap()
|
||||||
bool _inJsonFormat = false
|
) const;
|
||||||
|
|
||||||
|
/// Create a JSON representation of the assembly.
|
||||||
|
Json::Value assemblyJSON(
|
||||||
|
StringMap const& _sourceCodes = StringMap()
|
||||||
) const;
|
) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Does the same operations as @a optimise, but should only be applied to a sub and
|
/// Does the same operations as @a optimise, but should only be applied to a sub and
|
||||||
/// returns the replaced tags.
|
/// returns the replaced tags. Also takes an argument containing the tags of this assembly
|
||||||
std::map<u256, u256> optimiseInternal(OptimiserSettings _settings);
|
/// that are referenced in a super-assembly.
|
||||||
|
std::map<u256, u256> optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> const& _tagsReferencedFromOutside);
|
||||||
|
|
||||||
unsigned bytesRequired(unsigned subTagSize) const;
|
unsigned bytesRequired(unsigned subTagSize) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Json::Value streamAsmJson(std::ostream& _out, StringMap const& _sourceCodes) const;
|
static Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string());
|
||||||
std::ostream& streamAsm(std::ostream& _out, std::string const& _prefix, StringMap const& _sourceCodes) const;
|
static std::string toStringInHex(u256 _value);
|
||||||
Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string()) const;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// 0 is reserved for exception
|
/// 0 is reserved for exception
|
||||||
unsigned m_usedTags = 1;
|
unsigned m_usedTags = 1;
|
||||||
|
std::map<std::string, size_t> m_namedTags;
|
||||||
AssemblyItems m_items;
|
AssemblyItems m_items;
|
||||||
std::map<h256, bytes> m_data;
|
std::map<h256, bytes> m_data;
|
||||||
/// Data that is appended to the very end of the contract.
|
/// Data that is appended to the very end of the contract.
|
||||||
@ -159,7 +171,7 @@ protected:
|
|||||||
|
|
||||||
inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a)
|
inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a)
|
||||||
{
|
{
|
||||||
_a.stream(_out);
|
_a.assemblyStream(_out);
|
||||||
return _out;
|
return _out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,18 +59,18 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
|
|||||||
case Tag: // 1 byte for the JUMPDEST
|
case Tag: // 1 byte for the JUMPDEST
|
||||||
return 1;
|
return 1;
|
||||||
case PushString:
|
case PushString:
|
||||||
return 33;
|
return 1 + 32;
|
||||||
case Push:
|
case Push:
|
||||||
return 1 + max<unsigned>(1, dev::bytesRequired(data()));
|
return 1 + max<unsigned>(1, dev::bytesRequired(data()));
|
||||||
case PushSubSize:
|
case PushSubSize:
|
||||||
case PushProgramSize:
|
case PushProgramSize:
|
||||||
return 4; // worst case: a 16MB program
|
return 1 + 4; // worst case: a 16MB program
|
||||||
case PushTag:
|
case PushTag:
|
||||||
case PushData:
|
case PushData:
|
||||||
case PushSub:
|
case PushSub:
|
||||||
return 1 + _addressLength;
|
return 1 + _addressLength;
|
||||||
case PushLibraryAddress:
|
case PushLibraryAddress:
|
||||||
return 21;
|
return 1 + 20;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -249,8 +249,11 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item)
|
|||||||
_out << " PushProgramSize";
|
_out << " PushProgramSize";
|
||||||
break;
|
break;
|
||||||
case PushLibraryAddress:
|
case PushLibraryAddress:
|
||||||
_out << " PushLibraryAddress " << hex << h256(_item.data()).abridgedMiddle() << dec;
|
{
|
||||||
|
string hash(h256((_item.data())).hex());
|
||||||
|
_out << " PushLibraryAddress " << hash.substr(0, 8) + "..." + hash.substr(hash.length() - 8);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case UndefinedItem:
|
case UndefinedItem:
|
||||||
_out << " ???";
|
_out << " ???";
|
||||||
break;
|
break;
|
||||||
|
@ -22,10 +22,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libevmasm/BlockDeduplicator.h>
|
#include <libevmasm/BlockDeduplicator.h>
|
||||||
#include <functional>
|
|
||||||
#include <libevmasm/AssemblyItem.h>
|
#include <libevmasm/AssemblyItem.h>
|
||||||
#include <libevmasm/SemanticInformation.h>
|
#include <libevmasm/SemanticInformation.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace dev;
|
using namespace dev;
|
||||||
using namespace dev::eth;
|
using namespace dev::eth;
|
||||||
|
@ -91,7 +91,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces all constants i by the code given in @a _replacement[i].
|
/// Replaces all constants i by the code given in @a _replacement[i].
|
||||||
static void replaceConstants(AssemblyItems& _items, std::map<u256, AssemblyItems> const& _replacement);
|
static void replaceConstants(AssemblyItems& _items, std::map<u256, AssemblyItems> const& _replacements);
|
||||||
|
|
||||||
Params m_params;
|
Params m_params;
|
||||||
u256 const& m_value;
|
u256 const& m_value;
|
||||||
|
@ -23,11 +23,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libdevcore/Common.h>
|
||||||
|
#include <libevmasm/AssemblyItem.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <libdevcore/Common.h>
|
#include <set>
|
||||||
#include <libevmasm/AssemblyItem.h>
|
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
|
@ -189,9 +189,9 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
|
|||||||
return gas;
|
return gas;
|
||||||
}
|
}
|
||||||
|
|
||||||
GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _position)
|
GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _value)
|
||||||
{
|
{
|
||||||
u256 const* value = m_state->expressionClasses().knownConstant(_position);
|
u256 const* value = m_state->expressionClasses().knownConstant(_value);
|
||||||
if (!value)
|
if (!value)
|
||||||
return GasConsumption::infinite();
|
return GasConsumption::infinite();
|
||||||
return GasConsumption(_multiplier * ((*value + 31) / 32));
|
return GasConsumption(_multiplier * ((*value + 31) / 32));
|
||||||
|
@ -87,13 +87,6 @@ enum class Instruction: uint8_t
|
|||||||
DIFFICULTY, ///< get the block's difficulty
|
DIFFICULTY, ///< get the block's difficulty
|
||||||
GASLIMIT, ///< get the block's gas limit
|
GASLIMIT, ///< get the block's gas limit
|
||||||
|
|
||||||
JUMPTO = 0x4a, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
|
|
||||||
JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
|
|
||||||
JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
|
|
||||||
JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
|
|
||||||
JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
|
|
||||||
RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
|
|
||||||
|
|
||||||
POP = 0x50, ///< remove item from stack
|
POP = 0x50, ///< remove item from stack
|
||||||
MLOAD, ///< load word from memory
|
MLOAD, ///< load word from memory
|
||||||
MSTORE, ///< save word to memory
|
MSTORE, ///< save word to memory
|
||||||
@ -106,8 +99,6 @@ enum class Instruction: uint8_t
|
|||||||
MSIZE, ///< get the size of active memory
|
MSIZE, ///< get the size of active memory
|
||||||
GAS, ///< get the amount of available gas
|
GAS, ///< get the amount of available gas
|
||||||
JUMPDEST, ///< set a potential jump destination
|
JUMPDEST, ///< set a potential jump destination
|
||||||
BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
|
|
||||||
BEGINDATA, ///< begine the data section -- not part of Instructions.cpp
|
|
||||||
|
|
||||||
PUSH1 = 0x60, ///< place 1 byte item on stack
|
PUSH1 = 0x60, ///< place 1 byte item on stack
|
||||||
PUSH2, ///< place 2 byte item on stack
|
PUSH2, ///< place 2 byte item on stack
|
||||||
@ -182,6 +173,17 @@ enum class Instruction: uint8_t
|
|||||||
LOG3, ///< Makes a log entry; 3 topics.
|
LOG3, ///< Makes a log entry; 3 topics.
|
||||||
LOG4, ///< Makes a log entry; 4 topics.
|
LOG4, ///< Makes a log entry; 4 topics.
|
||||||
|
|
||||||
|
JUMPTO = 0xb0, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
|
||||||
|
JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp
|
||||||
|
JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp
|
||||||
|
JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
|
||||||
|
JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp
|
||||||
|
BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp
|
||||||
|
BEGINDATA, ///< begin the data section -- not part of Instructions.cpp
|
||||||
|
RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp
|
||||||
|
PUTLOCAL, ///< pop top of stack to local variable -- not part of Instructions.cpp
|
||||||
|
GETLOCAL, ///< push local variable to top of stack -- not part of Instructions.cpp
|
||||||
|
|
||||||
CREATE = 0xf0, ///< create a new account with associated code
|
CREATE = 0xf0, ///< create a new account with associated code
|
||||||
CALL, ///< message-call into an account
|
CALL, ///< message-call into an account
|
||||||
CALLCODE, ///< message-call with another account's code only
|
CALLCODE, ///< message-call with another account's code only
|
||||||
|
68
libevmasm/JumpdestRemover.cpp
Normal file
68
libevmasm/JumpdestRemover.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author Alex Beregszaszi
|
||||||
|
* Removes unused JUMPDESTs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "JumpdestRemover.h"
|
||||||
|
|
||||||
|
#include <libsolidity/interface/Exceptions.h>
|
||||||
|
|
||||||
|
#include <libevmasm/AssemblyItem.h>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dev::eth;
|
||||||
|
using namespace dev;
|
||||||
|
|
||||||
|
|
||||||
|
bool JumpdestRemover::optimise(set<size_t> const& _tagsReferencedFromOutside)
|
||||||
|
{
|
||||||
|
set<size_t> references{referencedTags(m_items, -1)};
|
||||||
|
references.insert(_tagsReferencedFromOutside.begin(), _tagsReferencedFromOutside.end());
|
||||||
|
|
||||||
|
size_t initialSize = m_items.size();
|
||||||
|
/// Remove tags which are never referenced.
|
||||||
|
auto pend = remove_if(
|
||||||
|
m_items.begin(),
|
||||||
|
m_items.end(),
|
||||||
|
[&](AssemblyItem const& _item)
|
||||||
|
{
|
||||||
|
if (_item.type() != Tag)
|
||||||
|
return false;
|
||||||
|
auto asmIdAndTag = _item.splitForeignPushTag();
|
||||||
|
solAssert(asmIdAndTag.first == size_t(-1), "Sub-assembly tag used as label.");
|
||||||
|
size_t tag = asmIdAndTag.second;
|
||||||
|
return !references.count(tag);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
m_items.erase(pend, m_items.end());
|
||||||
|
return m_items.size() != initialSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
set<size_t> JumpdestRemover::referencedTags(AssemblyItems const& _items, size_t _subId)
|
||||||
|
{
|
||||||
|
set<size_t> ret;
|
||||||
|
for (auto const& item: _items)
|
||||||
|
if (item.type() == PushTag)
|
||||||
|
{
|
||||||
|
auto subAndTag = item.splitForeignPushTag();
|
||||||
|
if (subAndTag.first == _subId)
|
||||||
|
ret.insert(subAndTag.second);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
50
libevmasm/JumpdestRemover.h
Normal file
50
libevmasm/JumpdestRemover.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @author Alex Beregszaszi
|
||||||
|
* Removes unused JUMPDESTs.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace dev
|
||||||
|
{
|
||||||
|
namespace eth
|
||||||
|
{
|
||||||
|
class AssemblyItem;
|
||||||
|
using AssemblyItems = std::vector<AssemblyItem>;
|
||||||
|
|
||||||
|
class JumpdestRemover
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit JumpdestRemover(AssemblyItems& _items): m_items(_items) {}
|
||||||
|
|
||||||
|
bool optimise(std::set<size_t> const& _tagsReferencedFromOutside);
|
||||||
|
|
||||||
|
/// @returns a set of all tags from the given sub-assembly that are referenced
|
||||||
|
/// from the given list of items.
|
||||||
|
static std::set<size_t> referencedTags(AssemblyItems const& _items, size_t _subId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AssemblyItems& m_items;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -38,7 +38,7 @@ void LinkerObject::link(map<string, h160> const& _libraryAddresses)
|
|||||||
std::map<size_t, std::string> remainingRefs;
|
std::map<size_t, std::string> remainingRefs;
|
||||||
for (auto const& linkRef: linkReferences)
|
for (auto const& linkRef: linkReferences)
|
||||||
if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses))
|
if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses))
|
||||||
address->ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
|
copy(address->data(), address->data() + 20, bytecode.begin() + linkRef.first);
|
||||||
else
|
else
|
||||||
remainingRefs.insert(linkRef);
|
remainingRefs.insert(linkRef);
|
||||||
linkReferences.swap(remainingRefs);
|
linkReferences.swap(remainingRefs);
|
||||||
|
@ -30,6 +30,9 @@ using namespace dev;
|
|||||||
|
|
||||||
// TODO: Extend this to use the tools from ExpressionClasses.cpp
|
// TODO: Extend this to use the tools from ExpressionClasses.cpp
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
struct OptimiserState
|
struct OptimiserState
|
||||||
{
|
{
|
||||||
AssemblyItems const& items;
|
AssemblyItems const& items;
|
||||||
@ -246,6 +249,8 @@ void applyMethods(OptimiserState& _state, Method, OtherMethods... _other)
|
|||||||
applyMethods(_state, _other...);
|
applyMethods(_state, _other...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool PeepholeOptimiser::optimise()
|
bool PeepholeOptimiser::optimise()
|
||||||
{
|
{
|
||||||
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
|
OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)};
|
||||||
|
@ -188,3 +188,56 @@ bool SemanticInformation::invalidatesStorage(Instruction _instruction)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::invalidInPureFunctions(Instruction _instruction)
|
||||||
|
{
|
||||||
|
switch (_instruction)
|
||||||
|
{
|
||||||
|
case Instruction::ADDRESS:
|
||||||
|
case Instruction::BALANCE:
|
||||||
|
case Instruction::ORIGIN:
|
||||||
|
case Instruction::CALLER:
|
||||||
|
case Instruction::CALLVALUE:
|
||||||
|
case Instruction::GASPRICE:
|
||||||
|
case Instruction::EXTCODESIZE:
|
||||||
|
case Instruction::EXTCODECOPY:
|
||||||
|
case Instruction::BLOCKHASH:
|
||||||
|
case Instruction::COINBASE:
|
||||||
|
case Instruction::TIMESTAMP:
|
||||||
|
case Instruction::NUMBER:
|
||||||
|
case Instruction::DIFFICULTY:
|
||||||
|
case Instruction::GASLIMIT:
|
||||||
|
case Instruction::STATICCALL:
|
||||||
|
case Instruction::SLOAD:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return invalidInViewFunctions(_instruction);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SemanticInformation::invalidInViewFunctions(Instruction _instruction)
|
||||||
|
{
|
||||||
|
switch (_instruction)
|
||||||
|
{
|
||||||
|
case Instruction::SSTORE:
|
||||||
|
case Instruction::JUMP:
|
||||||
|
case Instruction::JUMPI:
|
||||||
|
case Instruction::GAS:
|
||||||
|
case Instruction::LOG0:
|
||||||
|
case Instruction::LOG1:
|
||||||
|
case Instruction::LOG2:
|
||||||
|
case Instruction::LOG3:
|
||||||
|
case Instruction::LOG4:
|
||||||
|
case Instruction::CREATE:
|
||||||
|
case Instruction::CALL:
|
||||||
|
case Instruction::CALLCODE:
|
||||||
|
case Instruction::DELEGATECALL:
|
||||||
|
case Instruction::CREATE2:
|
||||||
|
case Instruction::SELFDESTRUCT:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@ -53,6 +53,8 @@ struct SemanticInformation
|
|||||||
static bool invalidatesMemory(solidity::Instruction _instruction);
|
static bool invalidatesMemory(solidity::Instruction _instruction);
|
||||||
/// @returns true if the given instruction modifies storage (even indirectly).
|
/// @returns true if the given instruction modifies storage (even indirectly).
|
||||||
static bool invalidatesStorage(solidity::Instruction _instruction);
|
static bool invalidatesStorage(solidity::Instruction _instruction);
|
||||||
|
static bool invalidInPureFunctions(solidity::Instruction _instruction);
|
||||||
|
static bool invalidInViewFunctions(solidity::Instruction _instruction);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,8 @@ public:
|
|||||||
virtual void appendLabelReference(LabelID _labelId) = 0;
|
virtual void appendLabelReference(LabelID _labelId) = 0;
|
||||||
/// Generate a new unique label.
|
/// Generate a new unique label.
|
||||||
virtual LabelID newLabelId() = 0;
|
virtual LabelID newLabelId() = 0;
|
||||||
|
/// Returns a label identified by the given name. Creates it if it does not yet exist.
|
||||||
|
virtual LabelID namedLabel(std::string const& _name) = 0;
|
||||||
/// Append a reference to a to-be-linked symobl.
|
/// Append a reference to a to-be-linked symobl.
|
||||||
/// Currently, we assume that the value is always a 20 byte number.
|
/// Currently, we assume that the value is always a 20 byte number.
|
||||||
virtual void appendLinkerSymbol(std::string const& _name) = 0;
|
virtual void appendLinkerSymbol(std::string const& _name) = 0;
|
||||||
|
@ -77,6 +77,14 @@ EVMAssembly::LabelID EVMAssembly::newLabelId()
|
|||||||
return m_nextLabelId++;
|
return m_nextLabelId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AbstractAssembly::LabelID EVMAssembly::namedLabel(string const& _name)
|
||||||
|
{
|
||||||
|
solAssert(!_name.empty(), "");
|
||||||
|
if (!m_namedLabels.count(_name))
|
||||||
|
m_namedLabels[_name] = newLabelId();
|
||||||
|
return m_namedLabels[_name];
|
||||||
|
}
|
||||||
|
|
||||||
void EVMAssembly::appendLinkerSymbol(string const&)
|
void EVMAssembly::appendLinkerSymbol(string const&)
|
||||||
{
|
{
|
||||||
solAssert(false, "Linker symbols not yet implemented.");
|
solAssert(false, "Linker symbols not yet implemented.");
|
||||||
|
@ -52,6 +52,8 @@ public:
|
|||||||
virtual void appendLabelReference(LabelID _labelId) override;
|
virtual void appendLabelReference(LabelID _labelId) override;
|
||||||
/// Generate a new unique label.
|
/// Generate a new unique label.
|
||||||
virtual LabelID newLabelId() override;
|
virtual LabelID newLabelId() override;
|
||||||
|
/// Returns a label identified by the given name. Creates it if it does not yet exist.
|
||||||
|
virtual LabelID namedLabel(std::string const& _name) override;
|
||||||
/// Append a reference to a to-be-linked symobl.
|
/// Append a reference to a to-be-linked symobl.
|
||||||
/// Currently, we assume that the value is always a 20 byte number.
|
/// Currently, we assume that the value is always a 20 byte number.
|
||||||
virtual void appendLinkerSymbol(std::string const& _name) override;
|
virtual void appendLinkerSymbol(std::string const& _name) override;
|
||||||
@ -85,6 +87,7 @@ private:
|
|||||||
LabelID m_nextLabelId = 0;
|
LabelID m_nextLabelId = 0;
|
||||||
int m_stackHeight = 0;
|
int m_stackHeight = 0;
|
||||||
bytes m_bytecode;
|
bytes m_bytecode;
|
||||||
|
std::map<std::string, LabelID> m_namedLabels;
|
||||||
std::map<LabelID, size_t> m_labelPositions;
|
std::map<LabelID, size_t> m_labelPositions;
|
||||||
std::map<size_t, LabelID> m_labelReferences;
|
std::map<size_t, LabelID> m_labelReferences;
|
||||||
std::vector<size_t> m_assemblySizePositions;
|
std::vector<size_t> m_assemblySizePositions;
|
||||||
|
@ -60,9 +60,12 @@ void CodeTransform::operator()(VariableDeclaration const& _varDecl)
|
|||||||
|
|
||||||
void CodeTransform::operator()(Assignment const& _assignment)
|
void CodeTransform::operator()(Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
visitExpression(*_assignment.value);
|
int height = m_assembly.stackHeight();
|
||||||
|
boost::apply_visitor(*this, *_assignment.value);
|
||||||
|
expectDeposit(_assignment.variableNames.size(), height);
|
||||||
|
|
||||||
m_assembly.setSourceLocation(_assignment.location);
|
m_assembly.setSourceLocation(_assignment.location);
|
||||||
generateAssignment(_assignment.variableName);
|
generateMultiAssignment(_assignment.variableNames);
|
||||||
checkStackHeight(&_assignment);
|
checkStackHeight(&_assignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,10 +111,10 @@ void CodeTransform::operator()(FunctionCall const& _call)
|
|||||||
visitExpression(arg);
|
visitExpression(arg);
|
||||||
m_assembly.setSourceLocation(_call.location);
|
m_assembly.setSourceLocation(_call.location);
|
||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
m_assembly.appendJumpsub(functionEntryID(*function), function->arguments.size(), function->returns.size());
|
m_assembly.appendJumpsub(functionEntryID(_call.functionName.name, *function), function->arguments.size(), function->returns.size());
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_assembly.appendJumpTo(functionEntryID(*function), function->returns.size() - function->arguments.size() - 1);
|
m_assembly.appendJumpTo(functionEntryID(_call.functionName.name, *function), function->returns.size() - function->arguments.size() - 1);
|
||||||
m_assembly.appendLabel(returnLabel);
|
m_assembly.appendLabel(returnLabel);
|
||||||
m_stackAdjustment--;
|
m_stackAdjustment--;
|
||||||
}
|
}
|
||||||
@ -286,12 +289,12 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
|||||||
if (m_evm15)
|
if (m_evm15)
|
||||||
{
|
{
|
||||||
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
|
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
|
||||||
m_assembly.appendBeginsub(functionEntryID(function), _function.arguments.size());
|
m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.arguments.size());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
|
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore + height);
|
||||||
m_assembly.appendLabel(functionEntryID(function));
|
m_assembly.appendLabel(functionEntryID(_function.name, function));
|
||||||
}
|
}
|
||||||
m_stackAdjustment += localStackAdjustment;
|
m_stackAdjustment += localStackAdjustment;
|
||||||
|
|
||||||
@ -303,8 +306,16 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
|
|||||||
m_assembly.appendConstant(u256(0));
|
m_assembly.appendConstant(u256(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeTransform(m_assembly, m_info, m_julia, m_evm15, m_identifierAccess, localStackAdjustment, m_context)
|
CodeTransform(
|
||||||
(_function.body);
|
m_assembly,
|
||||||
|
m_info,
|
||||||
|
m_julia,
|
||||||
|
m_evm15,
|
||||||
|
m_identifierAccess,
|
||||||
|
m_useNamedLabelsForFunctions,
|
||||||
|
localStackAdjustment,
|
||||||
|
m_context
|
||||||
|
)(_function.body);
|
||||||
|
|
||||||
{
|
{
|
||||||
// The stack layout here is:
|
// The stack layout here is:
|
||||||
@ -421,10 +432,16 @@ AbstractAssembly::LabelID CodeTransform::labelID(Scope::Label const& _label)
|
|||||||
return m_context->labelIDs[&_label];
|
return m_context->labelIDs[&_label];
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _function)
|
AbstractAssembly::LabelID CodeTransform::functionEntryID(string const& _name, Scope::Function const& _function)
|
||||||
{
|
{
|
||||||
if (!m_context->functionEntryIDs.count(&_function))
|
if (!m_context->functionEntryIDs.count(&_function))
|
||||||
m_context->functionEntryIDs[&_function] = m_assembly.newLabelId();
|
{
|
||||||
|
AbstractAssembly::LabelID id =
|
||||||
|
m_useNamedLabelsForFunctions ?
|
||||||
|
m_assembly.namedLabel(_name) :
|
||||||
|
m_assembly.newLabelId();
|
||||||
|
m_context->functionEntryIDs[&_function] = id;
|
||||||
|
}
|
||||||
return m_context->functionEntryIDs[&_function];
|
return m_context->functionEntryIDs[&_function];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,6 +472,13 @@ void CodeTransform::finalizeBlock(Block const& _block, int blockStartStackHeight
|
|||||||
checkStackHeight(&_block);
|
checkStackHeight(&_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CodeTransform::generateMultiAssignment(vector<Identifier> const& _variableNames)
|
||||||
|
{
|
||||||
|
solAssert(m_scope, "");
|
||||||
|
for (auto const& variableName: _variableNames | boost::adaptors::reversed)
|
||||||
|
generateAssignment(variableName);
|
||||||
|
}
|
||||||
|
|
||||||
void CodeTransform::generateAssignment(Identifier const& _variableName)
|
void CodeTransform::generateAssignment(Identifier const& _variableName)
|
||||||
{
|
{
|
||||||
solAssert(m_scope, "");
|
solAssert(m_scope, "");
|
||||||
|
@ -50,13 +50,15 @@ public:
|
|||||||
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
|
solidity::assembly::AsmAnalysisInfo& _analysisInfo,
|
||||||
bool _julia = false,
|
bool _julia = false,
|
||||||
bool _evm15 = false,
|
bool _evm15 = false,
|
||||||
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess()
|
ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess(),
|
||||||
|
bool _useNamedLabelsForFunctions = false
|
||||||
): CodeTransform(
|
): CodeTransform(
|
||||||
_assembly,
|
_assembly,
|
||||||
_analysisInfo,
|
_analysisInfo,
|
||||||
_julia,
|
_julia,
|
||||||
_evm15,
|
_evm15,
|
||||||
_identifierAccess,
|
_identifierAccess,
|
||||||
|
_useNamedLabelsForFunctions,
|
||||||
_assembly.stackHeight(),
|
_assembly.stackHeight(),
|
||||||
std::make_shared<Context>()
|
std::make_shared<Context>()
|
||||||
)
|
)
|
||||||
@ -78,6 +80,7 @@ protected:
|
|||||||
bool _julia,
|
bool _julia,
|
||||||
bool _evm15,
|
bool _evm15,
|
||||||
ExternalIdentifierAccess const& _identifierAccess,
|
ExternalIdentifierAccess const& _identifierAccess,
|
||||||
|
bool _useNamedLabelsForFunctions,
|
||||||
int _stackAdjustment,
|
int _stackAdjustment,
|
||||||
std::shared_ptr<Context> _context
|
std::shared_ptr<Context> _context
|
||||||
):
|
):
|
||||||
@ -85,6 +88,7 @@ protected:
|
|||||||
m_info(_analysisInfo),
|
m_info(_analysisInfo),
|
||||||
m_julia(_julia),
|
m_julia(_julia),
|
||||||
m_evm15(_evm15),
|
m_evm15(_evm15),
|
||||||
|
m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions),
|
||||||
m_identifierAccess(_identifierAccess),
|
m_identifierAccess(_identifierAccess),
|
||||||
m_stackAdjustment(_stackAdjustment),
|
m_stackAdjustment(_stackAdjustment),
|
||||||
m_context(_context)
|
m_context(_context)
|
||||||
@ -110,7 +114,7 @@ private:
|
|||||||
/// @returns the label ID corresponding to the given label, allocating a new one if
|
/// @returns the label ID corresponding to the given label, allocating a new one if
|
||||||
/// necessary.
|
/// necessary.
|
||||||
AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label);
|
AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label);
|
||||||
AbstractAssembly::LabelID functionEntryID(solidity::assembly::Scope::Function const& _function);
|
AbstractAssembly::LabelID functionEntryID(std::string const& _name, solidity::assembly::Scope::Function const& _function);
|
||||||
/// Generates code for an expression that is supposed to return a single value.
|
/// Generates code for an expression that is supposed to return a single value.
|
||||||
void visitExpression(solidity::assembly::Statement const& _expression);
|
void visitExpression(solidity::assembly::Statement const& _expression);
|
||||||
|
|
||||||
@ -120,6 +124,7 @@ private:
|
|||||||
/// to @a _blackStartStackHeight.
|
/// to @a _blackStartStackHeight.
|
||||||
void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
|
void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
|
||||||
|
|
||||||
|
void generateMultiAssignment(std::vector<solidity::assembly::Identifier> const& _variableNames);
|
||||||
void generateAssignment(solidity::assembly::Identifier const& _variableName);
|
void generateAssignment(solidity::assembly::Identifier const& _variableName);
|
||||||
|
|
||||||
/// Determines the stack height difference to the given variables. Throws
|
/// Determines the stack height difference to the given variables. Throws
|
||||||
@ -136,6 +141,7 @@ private:
|
|||||||
solidity::assembly::Scope* m_scope = nullptr;
|
solidity::assembly::Scope* m_scope = nullptr;
|
||||||
bool m_julia = false;
|
bool m_julia = false;
|
||||||
bool m_evm15 = false;
|
bool m_evm15 = false;
|
||||||
|
bool m_useNamedLabelsForFunctions = false;
|
||||||
ExternalIdentifierAccess m_identifierAccess;
|
ExternalIdentifierAccess m_identifierAccess;
|
||||||
/// Adjustment between the stack height as determined during the analysis phase
|
/// Adjustment between the stack height as determined during the analysis phase
|
||||||
/// and the stack height in the assembly. This is caused by an initial stack being present
|
/// and the stack height in the assembly. This is caused by an initial stack being present
|
||||||
|
@ -72,14 +72,13 @@ std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::v
|
|||||||
{
|
{
|
||||||
CompilerState cs;
|
CompilerState cs;
|
||||||
cs.populateStandard();
|
cs.populateStandard();
|
||||||
stringstream ret;
|
|
||||||
auto assembly = CodeFragment::compile(_src, cs).assembly(cs);
|
auto assembly = CodeFragment::compile(_src, cs).assembly(cs);
|
||||||
if (_opt)
|
if (_opt)
|
||||||
assembly = assembly.optimise(true);
|
assembly = assembly.optimise(true);
|
||||||
assembly.stream(ret);
|
string ret = assembly.assemblyString();
|
||||||
for (auto i: cs.treesToKill)
|
for (auto i: cs.treesToKill)
|
||||||
killBigints(i);
|
killBigints(i);
|
||||||
return ret.str();
|
return ret;
|
||||||
}
|
}
|
||||||
catch (Exception const& _e)
|
catch (Exception const& _e)
|
||||||
{
|
{
|
||||||
|
@ -38,30 +38,30 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
|
|||||||
return !m_errorOccured;
|
return !m_errorOccured;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocStringAnalyser::visit(ContractDefinition const& _node)
|
bool DocStringAnalyser::visit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
static const set<string> validTags = set<string>{"author", "title", "dev", "notice"};
|
static const set<string> validTags = set<string>{"author", "title", "dev", "notice"};
|
||||||
parseDocStrings(_node, _node.annotation(), validTags, "contracts");
|
parseDocStrings(_contract, _contract.annotation(), validTags, "contracts");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocStringAnalyser::visit(FunctionDefinition const& _node)
|
bool DocStringAnalyser::visit(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
handleCallable(_node, _node, _node.annotation());
|
handleCallable(_function, _function, _function.annotation());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocStringAnalyser::visit(ModifierDefinition const& _node)
|
bool DocStringAnalyser::visit(ModifierDefinition const& _modifier)
|
||||||
{
|
{
|
||||||
handleCallable(_node, _node, _node.annotation());
|
handleCallable(_modifier, _modifier, _modifier.annotation());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocStringAnalyser::visit(EventDefinition const& _node)
|
bool DocStringAnalyser::visit(EventDefinition const& _event)
|
||||||
{
|
{
|
||||||
handleCallable(_node, _node, _node.annotation());
|
handleCallable(_event, _event, _event.annotation());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ void DocStringAnalyser::handleCallable(
|
|||||||
DocumentedAnnotation& _annotation
|
DocumentedAnnotation& _annotation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param", "why3"};
|
static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||||
|
|
||||||
set<string> validParams;
|
set<string> validParams;
|
||||||
|
@ -43,13 +43,13 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
|
|||||||
make_shared<MagicVariableDeclaration>("selfdestruct",
|
make_shared<MagicVariableDeclaration>("selfdestruct",
|
||||||
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
|
||||||
make_shared<MagicVariableDeclaration>("addmod",
|
make_shared<MagicVariableDeclaration>("addmod",
|
||||||
make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod)),
|
make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::AddMod, false, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("mulmod",
|
make_shared<MagicVariableDeclaration>("mulmod",
|
||||||
make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod)),
|
make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("sha3",
|
make_shared<MagicVariableDeclaration>("sha3",
|
||||||
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)),
|
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("keccak256",
|
make_shared<MagicVariableDeclaration>("keccak256",
|
||||||
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true)),
|
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("log0",
|
make_shared<MagicVariableDeclaration>("log0",
|
||||||
make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
|
make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)),
|
||||||
make_shared<MagicVariableDeclaration>("log1",
|
make_shared<MagicVariableDeclaration>("log1",
|
||||||
@ -61,17 +61,17 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
|
|||||||
make_shared<MagicVariableDeclaration>("log4",
|
make_shared<MagicVariableDeclaration>("log4",
|
||||||
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)),
|
make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log4)),
|
||||||
make_shared<MagicVariableDeclaration>("sha256",
|
make_shared<MagicVariableDeclaration>("sha256",
|
||||||
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true)),
|
make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("ecrecover",
|
make_shared<MagicVariableDeclaration>("ecrecover",
|
||||||
make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover)),
|
make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("ripemd160",
|
make_shared<MagicVariableDeclaration>("ripemd160",
|
||||||
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true)),
|
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("assert",
|
make_shared<MagicVariableDeclaration>("assert",
|
||||||
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert)),
|
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("require",
|
make_shared<MagicVariableDeclaration>("require",
|
||||||
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require)),
|
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
|
||||||
make_shared<MagicVariableDeclaration>("revert",
|
make_shared<MagicVariableDeclaration>("revert",
|
||||||
make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert))})
|
make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure))})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool visit(SourceUnit& _sourceUnit) override;
|
bool visit(SourceUnit& _sourceUnit) override;
|
||||||
void endVisit(SourceUnit& _sourceUnit) override;
|
void endVisit(SourceUnit& _sourceUnit) override;
|
||||||
bool visit(ImportDirective& _declaration) override;
|
bool visit(ImportDirective& _import) override;
|
||||||
bool visit(ContractDefinition& _contract) override;
|
bool visit(ContractDefinition& _contract) override;
|
||||||
void endVisit(ContractDefinition& _contract) override;
|
void endVisit(ContractDefinition& _contract) override;
|
||||||
bool visit(StructDefinition& _struct) override;
|
bool visit(StructDefinition& _struct) override;
|
||||||
|
@ -50,8 +50,8 @@ private:
|
|||||||
virtual bool visit(ContractDefinition const& _contract) override;
|
virtual bool visit(ContractDefinition const& _contract) override;
|
||||||
virtual void endVisit(ContractDefinition const& _contract) override;
|
virtual void endVisit(ContractDefinition const& _contract) override;
|
||||||
|
|
||||||
virtual bool visit(VariableDeclaration const& _declaration) override;
|
virtual bool visit(VariableDeclaration const& _variable) override;
|
||||||
virtual void endVisit(VariableDeclaration const& _declaration) override;
|
virtual void endVisit(VariableDeclaration const& _variable) override;
|
||||||
|
|
||||||
virtual bool visit(Identifier const& _identifier) override;
|
virtual bool visit(Identifier const& _identifier) override;
|
||||||
|
|
||||||
|
@ -57,8 +57,6 @@ bool StaticAnalyzer::visit(FunctionDefinition const& _function)
|
|||||||
solAssert(m_localVarUseCount.empty(), "");
|
solAssert(m_localVarUseCount.empty(), "");
|
||||||
m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
|
m_nonPayablePublic = _function.isPublic() && !_function.isPayable();
|
||||||
m_constructor = _function.isConstructor();
|
m_constructor = _function.isConstructor();
|
||||||
if (_function.stateMutability() == StateMutability::Pure)
|
|
||||||
m_errorReporter.warning(_function.location(), "Function is marked pure. Be careful, pureness is not enforced yet.");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +67,16 @@ void StaticAnalyzer::endVisit(FunctionDefinition const&)
|
|||||||
m_constructor = false;
|
m_constructor = false;
|
||||||
for (auto const& var: m_localVarUseCount)
|
for (auto const& var: m_localVarUseCount)
|
||||||
if (var.second == 0)
|
if (var.second == 0)
|
||||||
m_errorReporter.warning(var.first->location(), "Unused local variable");
|
{
|
||||||
|
if (var.first->isCallableParameter())
|
||||||
|
m_errorReporter.warning(
|
||||||
|
var.first->location(),
|
||||||
|
"Unused function parameter. Remove or comment out the variable name to silence this warning."
|
||||||
|
);
|
||||||
|
else
|
||||||
|
m_errorReporter.warning(var.first->location(), "Unused local variable.");
|
||||||
|
}
|
||||||
|
|
||||||
m_localVarUseCount.clear();
|
m_localVarUseCount.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ namespace solidity
|
|||||||
/**
|
/**
|
||||||
* The module that performs static analysis on the AST.
|
* The module that performs static analysis on the AST.
|
||||||
* In this context, static analysis is anything that can produce warnings which can help
|
* In this context, static analysis is anything that can produce warnings which can help
|
||||||
* programmers write cleaner code. For every warning generated eher, it has to be possible to write
|
* programmers write cleaner code. For every warning generated here, it has to be possible to write
|
||||||
* equivalent code that does generate the warning.
|
* equivalent code that does not generate the warning.
|
||||||
*/
|
*/
|
||||||
class StaticAnalyzer: private ASTConstVisitor
|
class StaticAnalyzer: private ASTConstVisitor
|
||||||
{
|
{
|
||||||
|
@ -138,7 +138,7 @@ bool SyntaxChecker::visit(WhileStatement const&)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SyntaxChecker::endVisit(WhileStatement const& )
|
void SyntaxChecker::endVisit(WhileStatement const&)
|
||||||
{
|
{
|
||||||
m_inLoopDepth--;
|
m_inLoopDepth--;
|
||||||
}
|
}
|
||||||
@ -193,6 +193,18 @@ bool SyntaxChecker::visit(PlaceholderStatement const&)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SyntaxChecker::visit(FunctionDefinition const& _function)
|
||||||
|
{
|
||||||
|
if (_function.noVisibilitySpecified())
|
||||||
|
m_errorReporter.warning(
|
||||||
|
_function.location(),
|
||||||
|
"No visibility specified. Defaulting to \"" +
|
||||||
|
Declaration::visibilityToString(_function.visibility()) +
|
||||||
|
"\"."
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool SyntaxChecker::visit(FunctionTypeName const& _node)
|
bool SyntaxChecker::visit(FunctionTypeName const& _node)
|
||||||
{
|
{
|
||||||
for (auto const& decl: _node.parameterTypeList()->parameters())
|
for (auto const& decl: _node.parameterTypeList()->parameters())
|
||||||
|
@ -66,6 +66,7 @@ private:
|
|||||||
|
|
||||||
virtual bool visit(PlaceholderStatement const& _placeholderStatement) override;
|
virtual bool visit(PlaceholderStatement const& _placeholderStatement) override;
|
||||||
|
|
||||||
|
virtual bool visit(FunctionDefinition const& _function) override;
|
||||||
virtual bool visit(FunctionTypeName const& _node) override;
|
virtual bool visit(FunctionTypeName const& _node) override;
|
||||||
|
|
||||||
ErrorReporter& m_errorReporter;
|
ErrorReporter& m_errorReporter;
|
||||||
|
@ -120,6 +120,11 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
|
|||||||
m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters.");
|
m_errorReporter.typeError(fallbackFunction->parameterList().location(), "Fallback function cannot take parameters.");
|
||||||
if (!fallbackFunction->returnParameters().empty())
|
if (!fallbackFunction->returnParameters().empty())
|
||||||
m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values.");
|
m_errorReporter.typeError(fallbackFunction->returnParameterList()->location(), "Fallback function cannot return values.");
|
||||||
|
if (
|
||||||
|
_contract.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050) &&
|
||||||
|
fallbackFunction->visibility() != FunctionDefinition::Visibility::External
|
||||||
|
)
|
||||||
|
m_errorReporter.typeError(fallbackFunction->location(), "Fallback function must be defined as \"external\".");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,29 +169,53 @@ void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _con
|
|||||||
for (; it != functions[_contract.name()].end(); ++it)
|
for (; it != functions[_contract.name()].end(); ++it)
|
||||||
ssl.append("Another declaration is here:", (*it)->location());
|
ssl.append("Another declaration is here:", (*it)->location());
|
||||||
|
|
||||||
|
string msg = "More than one constructor defined.";
|
||||||
|
size_t occurrences = ssl.infos.size();
|
||||||
|
if (occurrences > 32)
|
||||||
|
{
|
||||||
|
ssl.infos.resize(32);
|
||||||
|
msg += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences.";
|
||||||
|
}
|
||||||
|
|
||||||
m_errorReporter.declarationError(
|
m_errorReporter.declarationError(
|
||||||
functions[_contract.name()].front()->location(),
|
functions[_contract.name()].front()->location(),
|
||||||
ssl,
|
ssl,
|
||||||
"More than one constructor defined."
|
msg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (auto const& it: functions)
|
for (auto const& it: functions)
|
||||||
{
|
{
|
||||||
vector<FunctionDefinition const*> const& overloads = it.second;
|
vector<FunctionDefinition const*> const& overloads = it.second;
|
||||||
for (size_t i = 0; i < overloads.size(); ++i)
|
set<size_t> reported;
|
||||||
|
for (size_t i = 0; i < overloads.size() && !reported.count(i); ++i)
|
||||||
|
{
|
||||||
|
SecondarySourceLocation ssl;
|
||||||
|
|
||||||
for (size_t j = i + 1; j < overloads.size(); ++j)
|
for (size_t j = i + 1; j < overloads.size(); ++j)
|
||||||
if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
|
if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j])))
|
||||||
{
|
{
|
||||||
|
ssl.append("Other declaration is here:", overloads[j]->location());
|
||||||
|
reported.insert(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ssl.infos.size() > 0)
|
||||||
|
{
|
||||||
|
string msg = "Function with same name and arguments defined twice.";
|
||||||
|
size_t occurrences = ssl.infos.size();
|
||||||
|
if (occurrences > 32)
|
||||||
|
{
|
||||||
|
ssl.infos.resize(32);
|
||||||
|
msg += " Truncated from " + boost::lexical_cast<string>(occurrences) + " to the first 32 occurrences.";
|
||||||
|
}
|
||||||
|
|
||||||
m_errorReporter.declarationError(
|
m_errorReporter.declarationError(
|
||||||
overloads[j]->location(),
|
overloads[i]->location(),
|
||||||
SecondarySourceLocation().append(
|
ssl,
|
||||||
"Other declaration is here:",
|
msg
|
||||||
overloads[i]->location()
|
|
||||||
),
|
|
||||||
"Function with same name and arguments defined twice."
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _contract)
|
void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _contract)
|
||||||
@ -315,6 +344,9 @@ void TypeChecker::checkFunctionOverride(FunctionDefinition const& function, Func
|
|||||||
if (!functionType.hasEqualArgumentTypes(superType))
|
if (!functionType.hasEqualArgumentTypes(superType))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!function.annotation().superFunction)
|
||||||
|
function.annotation().superFunction = &super;
|
||||||
|
|
||||||
if (function.visibility() != super.visibility())
|
if (function.visibility() != super.visibility())
|
||||||
overrideError(function, super, "Overriding function visibility differs.");
|
overrideError(function, super, "Overriding function visibility differs.");
|
||||||
|
|
||||||
@ -519,7 +551,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
|
|||||||
if (!type(*var)->canLiveOutsideStorage())
|
if (!type(*var)->canLiveOutsideStorage())
|
||||||
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
||||||
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
|
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
|
||||||
m_errorReporter.fatalTypeError(var->location(), "Internal type is not allowed for public or external functions.");
|
m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions.");
|
||||||
|
|
||||||
var->accept(*this);
|
var->accept(*this);
|
||||||
}
|
}
|
||||||
@ -591,7 +623,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|||||||
{
|
{
|
||||||
bool allowed = false;
|
bool allowed = false;
|
||||||
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get()))
|
if (auto arrayType = dynamic_cast<ArrayType const*>(_variable.type().get()))
|
||||||
allowed = arrayType->isString();
|
allowed = arrayType->isByteArray();
|
||||||
if (!allowed)
|
if (!allowed)
|
||||||
m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented.");
|
m_errorReporter.typeError(_variable.location(), "Constants of non-value type not yet implemented.");
|
||||||
}
|
}
|
||||||
@ -614,7 +646,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|||||||
_variable.visibility() >= VariableDeclaration::Visibility::Public &&
|
_variable.visibility() >= VariableDeclaration::Visibility::Public &&
|
||||||
!FunctionType(_variable).interfaceFunctionType()
|
!FunctionType(_variable).interfaceFunctionType()
|
||||||
)
|
)
|
||||||
m_errorReporter.typeError(_variable.location(), "Internal type is not allowed for public state variables.");
|
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
|
||||||
|
|
||||||
if (varType->category() == Type::Category::Array)
|
if (varType->category() == Type::Category::Array)
|
||||||
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
|
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
|
||||||
@ -623,7 +655,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
|
|||||||
(arrayType->location() == DataLocation::CallData)) &&
|
(arrayType->location() == DataLocation::CallData)) &&
|
||||||
!arrayType->validForCalldata()
|
!arrayType->validForCalldata()
|
||||||
)
|
)
|
||||||
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded as calldata.");
|
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -698,15 +730,15 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
|
|||||||
{
|
{
|
||||||
if (var->isIndexed())
|
if (var->isIndexed())
|
||||||
numIndexed++;
|
numIndexed++;
|
||||||
|
if (!type(*var)->canLiveOutsideStorage())
|
||||||
|
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
||||||
|
if (!type(*var)->interfaceType(false))
|
||||||
|
m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
|
||||||
|
}
|
||||||
if (_eventDef.isAnonymous() && numIndexed > 4)
|
if (_eventDef.isAnonymous() && numIndexed > 4)
|
||||||
m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
|
m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
|
||||||
else if (!_eventDef.isAnonymous() && numIndexed > 3)
|
else if (!_eventDef.isAnonymous() && numIndexed > 3)
|
||||||
m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
|
m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
|
||||||
if (!type(*var)->canLiveOutsideStorage())
|
|
||||||
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
|
|
||||||
if (!type(*var)->interfaceType(false))
|
|
||||||
m_errorReporter.typeError(var->location(), "Internal type is not allowed as event parameter type.");
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1445,7 +1477,37 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|||||||
else
|
else
|
||||||
_functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes());
|
_functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes());
|
||||||
|
|
||||||
|
if (auto functionName = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
||||||
|
{
|
||||||
|
if (functionName->name() == "sha3" && functionType->kind() == FunctionType::Kind::SHA3)
|
||||||
|
m_errorReporter.warning(_functionCall.location(), "\"sha3\" has been deprecated in favour of \"keccak256\"");
|
||||||
|
else if (functionName->name() == "suicide" && functionType->kind() == FunctionType::Kind::Selfdestruct)
|
||||||
|
m_errorReporter.warning(_functionCall.location(), "\"suicide\" has been deprecated in favour of \"selfdestruct\"");
|
||||||
|
}
|
||||||
|
|
||||||
TypePointers parameterTypes = functionType->parameterTypes();
|
TypePointers parameterTypes = functionType->parameterTypes();
|
||||||
|
|
||||||
|
if (!functionType->padArguments())
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < arguments.size(); ++i)
|
||||||
|
{
|
||||||
|
auto const& argType = type(*arguments[i]);
|
||||||
|
if (auto literal = dynamic_cast<RationalNumberType const*>(argType.get()))
|
||||||
|
{
|
||||||
|
/* If no mobile type is available an error will be raised elsewhere. */
|
||||||
|
if (literal->mobileType())
|
||||||
|
m_errorReporter.warning(
|
||||||
|
_functionCall.location(),
|
||||||
|
"The type of \"" +
|
||||||
|
argType->toString() +
|
||||||
|
"\" was inferred as " +
|
||||||
|
literal->mobileType()->toString() +
|
||||||
|
". This is probably not desired. Use an explicit type to silence this warning."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
|
if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size())
|
||||||
{
|
{
|
||||||
string msg =
|
string msg =
|
||||||
@ -1561,14 +1623,16 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
|||||||
if (contract->contractKind() == ContractDefinition::ContractKind::Interface)
|
if (contract->contractKind() == ContractDefinition::ContractKind::Interface)
|
||||||
m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface.");
|
m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface.");
|
||||||
if (!contract->annotation().unimplementedFunctions.empty())
|
if (!contract->annotation().unimplementedFunctions.empty())
|
||||||
|
{
|
||||||
|
SecondarySourceLocation ssl;
|
||||||
|
for (auto function: contract->annotation().unimplementedFunctions)
|
||||||
|
ssl.append("Missing implementation:", function->location());
|
||||||
m_errorReporter.typeError(
|
m_errorReporter.typeError(
|
||||||
_newExpression.location(),
|
_newExpression.location(),
|
||||||
SecondarySourceLocation().append(
|
ssl,
|
||||||
"Missing implementation:",
|
|
||||||
contract->annotation().unimplementedFunctions.front()->location()
|
|
||||||
),
|
|
||||||
"Trying to create an instance of an abstract contract."
|
"Trying to create an instance of an abstract contract."
|
||||||
);
|
);
|
||||||
|
}
|
||||||
if (!contract->constructorIsPublic())
|
if (!contract->constructorIsPublic())
|
||||||
m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
|
m_errorReporter.typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
|
||||||
|
|
||||||
@ -1604,7 +1668,9 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
|
|||||||
TypePointers{type},
|
TypePointers{type},
|
||||||
strings(),
|
strings(),
|
||||||
strings(),
|
strings(),
|
||||||
FunctionType::Kind::ObjectCreation
|
FunctionType::Kind::ObjectCreation,
|
||||||
|
false,
|
||||||
|
StateMutability::Pure
|
||||||
);
|
);
|
||||||
_newExpression.annotation().isPure = true;
|
_newExpression.annotation().isPure = true;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,7 @@ private:
|
|||||||
void checkContractDuplicateFunctions(ContractDefinition const& _contract);
|
void checkContractDuplicateFunctions(ContractDefinition const& _contract);
|
||||||
void checkContractIllegalOverrides(ContractDefinition const& _contract);
|
void checkContractIllegalOverrides(ContractDefinition const& _contract);
|
||||||
/// Reports a type error with an appropiate message if overriden function signature differs.
|
/// Reports a type error with an appropiate message if overriden function signature differs.
|
||||||
|
/// Also stores the direct super function in the AST annotations.
|
||||||
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
|
void checkFunctionOverride(FunctionDefinition const& function, FunctionDefinition const& super);
|
||||||
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
|
void overrideError(FunctionDefinition const& function, FunctionDefinition const& super, std::string message);
|
||||||
void checkContractAbstractFunctions(ContractDefinition const& _contract);
|
void checkContractAbstractFunctions(ContractDefinition const& _contract);
|
||||||
|
325
libsolidity/analysis/ViewPureChecker.cpp
Normal file
325
libsolidity/analysis/ViewPureChecker.cpp
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <libsolidity/analysis/ViewPureChecker.h>
|
||||||
|
|
||||||
|
#include <libevmasm/SemanticInformation.h>
|
||||||
|
|
||||||
|
#include <libsolidity/inlineasm/AsmData.h>
|
||||||
|
#include <libsolidity/ast/ExperimentalFeatures.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace dev;
|
||||||
|
using namespace dev::solidity;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
class AssemblyViewPureChecker: public boost::static_visitor<void>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit AssemblyViewPureChecker(std::function<void(StateMutability, SourceLocation const&)> _reportMutability):
|
||||||
|
m_reportMutability(_reportMutability) {}
|
||||||
|
|
||||||
|
void operator()(assembly::Label const&) { }
|
||||||
|
void operator()(assembly::Instruction const& _instruction)
|
||||||
|
{
|
||||||
|
if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction))
|
||||||
|
m_reportMutability(StateMutability::NonPayable, _instruction.location);
|
||||||
|
else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction))
|
||||||
|
m_reportMutability(StateMutability::View, _instruction.location);
|
||||||
|
}
|
||||||
|
void operator()(assembly::Literal const&) {}
|
||||||
|
void operator()(assembly::Identifier const&) {}
|
||||||
|
void operator()(assembly::FunctionalInstruction const& _instr)
|
||||||
|
{
|
||||||
|
(*this)(_instr.instruction);
|
||||||
|
for (auto const& arg: _instr.arguments)
|
||||||
|
boost::apply_visitor(*this, arg);
|
||||||
|
}
|
||||||
|
void operator()(assembly::StackAssignment const&) {}
|
||||||
|
void operator()(assembly::Assignment const& _assignment)
|
||||||
|
{
|
||||||
|
boost::apply_visitor(*this, *_assignment.value);
|
||||||
|
}
|
||||||
|
void operator()(assembly::VariableDeclaration const& _varDecl)
|
||||||
|
{
|
||||||
|
if (_varDecl.value)
|
||||||
|
boost::apply_visitor(*this, *_varDecl.value);
|
||||||
|
}
|
||||||
|
void operator()(assembly::FunctionDefinition const& _funDef)
|
||||||
|
{
|
||||||
|
(*this)(_funDef.body);
|
||||||
|
}
|
||||||
|
void operator()(assembly::FunctionCall const& _funCall)
|
||||||
|
{
|
||||||
|
for (auto const& arg: _funCall.arguments)
|
||||||
|
boost::apply_visitor(*this, arg);
|
||||||
|
}
|
||||||
|
void operator()(assembly::Switch const& _switch)
|
||||||
|
{
|
||||||
|
boost::apply_visitor(*this, *_switch.expression);
|
||||||
|
for (auto const& _case: _switch.cases)
|
||||||
|
{
|
||||||
|
if (_case.value)
|
||||||
|
(*this)(*_case.value);
|
||||||
|
(*this)(_case.body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void operator()(assembly::ForLoop const& _for)
|
||||||
|
{
|
||||||
|
(*this)(_for.pre);
|
||||||
|
boost::apply_visitor(*this, *_for.condition);
|
||||||
|
(*this)(_for.body);
|
||||||
|
(*this)(_for.post);
|
||||||
|
}
|
||||||
|
void operator()(assembly::Block const& _block)
|
||||||
|
{
|
||||||
|
for (auto const& s: _block.statements)
|
||||||
|
boost::apply_visitor(*this, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewPureChecker::check()
|
||||||
|
{
|
||||||
|
// The bool means "enforce view with errors".
|
||||||
|
map<ContractDefinition const*, bool> contracts;
|
||||||
|
|
||||||
|
for (auto const& node: m_ast)
|
||||||
|
{
|
||||||
|
SourceUnit const* source = dynamic_cast<SourceUnit const*>(node.get());
|
||||||
|
solAssert(source, "");
|
||||||
|
bool enforceView = source->annotation().experimentalFeatures.count(ExperimentalFeature::V050);
|
||||||
|
for (ContractDefinition const* c: source->filteredNodes<ContractDefinition>(source->nodes()))
|
||||||
|
contracts[c] = enforceView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check modifiers first to infer their state mutability.
|
||||||
|
for (auto const& contract: contracts)
|
||||||
|
{
|
||||||
|
m_enforceViewWithError = contract.second;
|
||||||
|
for (ModifierDefinition const* mod: contract.first->functionModifiers())
|
||||||
|
mod->accept(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const& contract: contracts)
|
||||||
|
{
|
||||||
|
m_enforceViewWithError = contract.second;
|
||||||
|
contract.first->accept(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !m_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool ViewPureChecker::visit(FunctionDefinition const& _funDef)
|
||||||
|
{
|
||||||
|
solAssert(!m_currentFunction, "");
|
||||||
|
m_currentFunction = &_funDef;
|
||||||
|
m_currentBestMutability = StateMutability::Pure;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
|
||||||
|
{
|
||||||
|
solAssert(m_currentFunction == &_funDef, "");
|
||||||
|
if (
|
||||||
|
m_currentBestMutability < _funDef.stateMutability() &&
|
||||||
|
_funDef.stateMutability() != StateMutability::Payable &&
|
||||||
|
_funDef.isImplemented() &&
|
||||||
|
!_funDef.isConstructor() &&
|
||||||
|
!_funDef.isFallback() &&
|
||||||
|
!_funDef.annotation().superFunction
|
||||||
|
)
|
||||||
|
m_errorReporter.warning(
|
||||||
|
_funDef.location(),
|
||||||
|
"Function state mutability can be restricted to " + stateMutabilityToString(m_currentBestMutability)
|
||||||
|
);
|
||||||
|
m_currentFunction = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ViewPureChecker::visit(ModifierDefinition const&)
|
||||||
|
{
|
||||||
|
solAssert(m_currentFunction == nullptr, "");
|
||||||
|
m_currentBestMutability = StateMutability::Pure;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef)
|
||||||
|
{
|
||||||
|
solAssert(m_currentFunction == nullptr, "");
|
||||||
|
m_inferredMutability[&_modifierDef] = m_currentBestMutability;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(Identifier const& _identifier)
|
||||||
|
{
|
||||||
|
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
|
||||||
|
solAssert(declaration, "");
|
||||||
|
|
||||||
|
StateMutability mutability = StateMutability::Pure;
|
||||||
|
|
||||||
|
bool writes = _identifier.annotation().lValueRequested;
|
||||||
|
if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
|
||||||
|
{
|
||||||
|
if (varDecl->isStateVariable() && !varDecl->isConstant())
|
||||||
|
mutability = writes ? StateMutability::NonPayable : StateMutability::View;
|
||||||
|
}
|
||||||
|
else if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
|
||||||
|
{
|
||||||
|
switch (magicVar->type()->category())
|
||||||
|
{
|
||||||
|
case Type::Category::Contract:
|
||||||
|
solAssert(_identifier.name() == "this" || _identifier.name() == "super", "");
|
||||||
|
if (!dynamic_cast<ContractType const&>(*magicVar->type()).isSuper())
|
||||||
|
// reads the address
|
||||||
|
mutability = StateMutability::View;
|
||||||
|
break;
|
||||||
|
case Type::Category::Integer:
|
||||||
|
solAssert(_identifier.name() == "now", "");
|
||||||
|
mutability = StateMutability::View;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reportMutability(mutability, _identifier.location());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
|
||||||
|
{
|
||||||
|
AssemblyViewPureChecker{
|
||||||
|
[=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
|
||||||
|
}(_inlineAssembly.operations());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::reportMutability(StateMutability _mutability, SourceLocation const& _location)
|
||||||
|
{
|
||||||
|
if (m_currentFunction && m_currentFunction->stateMutability() < _mutability)
|
||||||
|
{
|
||||||
|
string text;
|
||||||
|
if (_mutability == StateMutability::View)
|
||||||
|
text =
|
||||||
|
"Function declared as pure, but this expression (potentially) reads from the "
|
||||||
|
"environment or state and thus requires \"view\".";
|
||||||
|
else if (_mutability == StateMutability::NonPayable)
|
||||||
|
text =
|
||||||
|
"Function declared as " +
|
||||||
|
stateMutabilityToString(m_currentFunction->stateMutability()) +
|
||||||
|
", but this expression (potentially) modifies the state and thus "
|
||||||
|
"requires non-payable (the default) or payable.";
|
||||||
|
else
|
||||||
|
solAssert(false, "");
|
||||||
|
|
||||||
|
solAssert(
|
||||||
|
m_currentFunction->stateMutability() == StateMutability::View ||
|
||||||
|
m_currentFunction->stateMutability() == StateMutability::Pure,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
if (!m_enforceViewWithError && m_currentFunction->stateMutability() == StateMutability::View)
|
||||||
|
m_errorReporter.warning(_location, text);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_errors = true;
|
||||||
|
m_errorReporter.typeError(_location, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_mutability > m_currentBestMutability)
|
||||||
|
m_currentBestMutability = _mutability;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
|
||||||
|
{
|
||||||
|
if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StateMutability mut = dynamic_cast<FunctionType const&>(*_functionCall.expression().annotation().type).stateMutability();
|
||||||
|
// We only require "nonpayable" to call a payble function.
|
||||||
|
if (mut == StateMutability::Payable)
|
||||||
|
mut = StateMutability::NonPayable;
|
||||||
|
reportMutability(mut, _functionCall.location());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
|
||||||
|
{
|
||||||
|
StateMutability mutability = StateMutability::Pure;
|
||||||
|
bool writes = _memberAccess.annotation().lValueRequested;
|
||||||
|
|
||||||
|
ASTString const& member = _memberAccess.memberName();
|
||||||
|
switch (_memberAccess.expression().annotation().type->category())
|
||||||
|
{
|
||||||
|
case Type::Category::Contract:
|
||||||
|
case Type::Category::Integer:
|
||||||
|
if (member == "balance" && !_memberAccess.annotation().referencedDeclaration)
|
||||||
|
mutability = StateMutability::View;
|
||||||
|
break;
|
||||||
|
case Type::Category::Magic:
|
||||||
|
// we can ignore the kind of magic and only look at the name of the member
|
||||||
|
if (member != "data" && member != "sig" && member != "blockhash")
|
||||||
|
mutability = StateMutability::View;
|
||||||
|
break;
|
||||||
|
case Type::Category::Struct:
|
||||||
|
{
|
||||||
|
if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage))
|
||||||
|
mutability = writes ? StateMutability::NonPayable : StateMutability::View;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Type::Category::Array:
|
||||||
|
{
|
||||||
|
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
|
||||||
|
if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage))
|
||||||
|
mutability = writes ? StateMutability::NonPayable : StateMutability::View;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reportMutability(mutability, _memberAccess.location());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
|
||||||
|
{
|
||||||
|
if (!_indexAccess.indexExpression())
|
||||||
|
solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool writes = _indexAccess.annotation().lValueRequested;
|
||||||
|
if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
|
||||||
|
reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
|
||||||
|
{
|
||||||
|
solAssert(_modifier.name(), "");
|
||||||
|
if (ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration))
|
||||||
|
{
|
||||||
|
solAssert(m_inferredMutability.count(mod), "");
|
||||||
|
reportMutability(m_inferredMutability.at(mod), _modifier.location());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(dynamic_cast<ContractDefinition const*>(_modifier.name()->annotation().referencedDeclaration), "");
|
||||||
|
}
|
||||||
|
|
80
libsolidity/analysis/ViewPureChecker.h
Normal file
80
libsolidity/analysis/ViewPureChecker.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libsolidity/ast/ASTEnums.h>
|
||||||
|
#include <libsolidity/ast/ASTForward.h>
|
||||||
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
|
|
||||||
|
#include <libsolidity/interface/ErrorReporter.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace dev
|
||||||
|
{
|
||||||
|
namespace solidity
|
||||||
|
{
|
||||||
|
|
||||||
|
class ASTNode;
|
||||||
|
class FunctionDefinition;
|
||||||
|
class ModifierDefinition;
|
||||||
|
class Identifier;
|
||||||
|
class MemberAccess;
|
||||||
|
class IndexAccess;
|
||||||
|
class ModifierInvocation;
|
||||||
|
class FunctionCall;
|
||||||
|
class InlineAssembly;
|
||||||
|
|
||||||
|
class ViewPureChecker: private ASTConstVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ViewPureChecker(std::vector<std::shared_ptr<ASTNode>> const& _ast, ErrorReporter& _errorReporter):
|
||||||
|
m_ast(_ast), m_errorReporter(_errorReporter) {}
|
||||||
|
|
||||||
|
bool check();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
virtual bool visit(FunctionDefinition const& _funDef) override;
|
||||||
|
virtual void endVisit(FunctionDefinition const& _funDef) override;
|
||||||
|
virtual bool visit(ModifierDefinition const& _modifierDef) override;
|
||||||
|
virtual void endVisit(ModifierDefinition const& _modifierDef) override;
|
||||||
|
virtual void endVisit(Identifier const& _identifier) override;
|
||||||
|
virtual void endVisit(MemberAccess const& _memberAccess) override;
|
||||||
|
virtual void endVisit(IndexAccess const& _indexAccess) override;
|
||||||
|
virtual void endVisit(ModifierInvocation const& _modifier) override;
|
||||||
|
virtual void endVisit(FunctionCall const& _functionCall) override;
|
||||||
|
virtual void endVisit(InlineAssembly const& _inlineAssembly) override;
|
||||||
|
|
||||||
|
/// Called when an element of mutability @a _mutability is encountered.
|
||||||
|
/// Creates appropriate warnings and errors and sets @a m_currentBestMutability.
|
||||||
|
void reportMutability(StateMutability _mutability, SourceLocation const& _location);
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<ASTNode>> const& m_ast;
|
||||||
|
ErrorReporter& m_errorReporter;
|
||||||
|
|
||||||
|
bool m_errors = false;
|
||||||
|
bool m_enforceViewWithError = false;
|
||||||
|
StateMutability m_currentBestMutability = StateMutability::Payable;
|
||||||
|
FunctionDefinition const* m_currentFunction = nullptr;
|
||||||
|
std::map<ModifierDefinition const*, StateMutability> m_inferredMutability;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -176,12 +176,20 @@ vector<EventDefinition const*> const& ContractDefinition::interfaceEvents() cons
|
|||||||
m_interfaceEvents.reset(new vector<EventDefinition const*>());
|
m_interfaceEvents.reset(new vector<EventDefinition const*>());
|
||||||
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)
|
||||||
for (EventDefinition const* e: contract->events())
|
for (EventDefinition const* e: contract->events())
|
||||||
if (eventsSeen.count(e->name()) == 0)
|
|
||||||
{
|
{
|
||||||
eventsSeen.insert(e->name());
|
/// NOTE: this requires the "internal" version of an Event,
|
||||||
|
/// though here internal strictly refers to visibility,
|
||||||
|
/// and not to function encoding (jump vs. call)
|
||||||
|
auto const& function = e->functionType(true);
|
||||||
|
solAssert(function, "");
|
||||||
|
string eventSignature = function->externalSignature();
|
||||||
|
if (eventsSeen.count(eventSignature) == 0)
|
||||||
|
{
|
||||||
|
eventsSeen.insert(eventSignature);
|
||||||
m_interfaceEvents->push_back(e);
|
m_interfaceEvents->push_back(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return *m_interfaceEvents;
|
return *m_interfaceEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,26 +226,6 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::inter
|
|||||||
return *m_interfaceFunctionList;
|
return *m_interfaceFunctionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value const& ContractDefinition::devDocumentation() const
|
|
||||||
{
|
|
||||||
return m_devDocumentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
Json::Value const& ContractDefinition::userDocumentation() const
|
|
||||||
{
|
|
||||||
return m_userDocumentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContractDefinition::setDevDocumentation(Json::Value const& _devDocumentation)
|
|
||||||
{
|
|
||||||
m_devDocumentation = _devDocumentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentation)
|
|
||||||
{
|
|
||||||
m_userDocumentation = _userDocumentation;
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
|
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
|
||||||
{
|
{
|
||||||
if (!m_inheritableMembers)
|
if (!m_inheritableMembers)
|
||||||
|
@ -180,6 +180,7 @@ public:
|
|||||||
|
|
||||||
/// @returns the declared name.
|
/// @returns the declared name.
|
||||||
ASTString const& name() const { return *m_name; }
|
ASTString const& name() const { return *m_name; }
|
||||||
|
bool noVisibilitySpecified() const { return m_visibility == Visibility::Default; }
|
||||||
Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; }
|
Visibility visibility() const { return m_visibility == Visibility::Default ? defaultVisibility() : m_visibility; }
|
||||||
bool isPublic() const { return visibility() >= Visibility::Public; }
|
bool isPublic() const { return visibility() >= Visibility::Public; }
|
||||||
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
|
virtual bool isVisibleInContract() const { return visibility() != Visibility::External; }
|
||||||
@ -392,12 +393,6 @@ public:
|
|||||||
/// Returns the fallback function or nullptr if no fallback function was specified.
|
/// Returns the fallback function or nullptr if no fallback function was specified.
|
||||||
FunctionDefinition const* fallbackFunction() const;
|
FunctionDefinition const* fallbackFunction() const;
|
||||||
|
|
||||||
Json::Value const& userDocumentation() const;
|
|
||||||
void setUserDocumentation(Json::Value const& _userDocumentation);
|
|
||||||
|
|
||||||
Json::Value const& devDocumentation() const;
|
|
||||||
void setDevDocumentation(Json::Value const& _devDocumentation);
|
|
||||||
|
|
||||||
virtual TypePointer type() const override;
|
virtual TypePointer type() const override;
|
||||||
|
|
||||||
virtual ContractDefinitionAnnotation& annotation() const override;
|
virtual ContractDefinitionAnnotation& annotation() const override;
|
||||||
@ -409,10 +404,6 @@ private:
|
|||||||
std::vector<ASTPointer<ASTNode>> m_subNodes;
|
std::vector<ASTPointer<ASTNode>> m_subNodes;
|
||||||
ContractKind m_contractKind;
|
ContractKind m_contractKind;
|
||||||
|
|
||||||
// parsed Natspec documentation of the contract.
|
|
||||||
Json::Value m_userDocumentation;
|
|
||||||
Json::Value m_devDocumentation;
|
|
||||||
|
|
||||||
std::vector<ContractDefinition const*> m_linearizedBaseContracts;
|
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<std::pair<FixedHash<4>, FunctionTypePointer>>> m_interfaceFunctionList;
|
||||||
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
mutable std::unique_ptr<std::vector<EventDefinition const*>> m_interfaceEvents;
|
||||||
|
@ -94,6 +94,9 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
|
|||||||
|
|
||||||
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||||
{
|
{
|
||||||
|
/// The function this function overrides, if any. This is always the closest
|
||||||
|
/// in the linearized inheritance hierarchy.
|
||||||
|
FunctionDefinition const* superFunction = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation
|
||||||
|
@ -129,7 +129,7 @@ string ASTJsonConverter::sourceLocationToString(SourceLocation const& _location)
|
|||||||
return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex);
|
return std::to_string(_location.start) + ":" + std::to_string(length) + ":" + std::to_string(sourceIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePath) const
|
string ASTJsonConverter::namePathToString(std::vector<ASTString> const& _namePath)
|
||||||
{
|
{
|
||||||
return boost::algorithm::join(_namePath, ".");
|
return boost::algorithm::join(_namePath, ".");
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ void ASTJsonConverter::appendExpressionAttributes(
|
|||||||
_attributes += exprAttributes;
|
_attributes += exprAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info)
|
Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair<assembly::Identifier const* ,InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) const
|
||||||
{
|
{
|
||||||
Json::Value tuple(Json::objectValue);
|
Json::Value tuple(Json::objectValue);
|
||||||
tuple["src"] = sourceLocationToString(_info.first->location);
|
tuple["src"] = sourceLocationToString(_info.first->location);
|
||||||
@ -328,6 +328,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
|
|||||||
make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
|
make_pair(m_legacy ? "constant" : "isDeclaredConst", _node.stateMutability() <= StateMutability::View),
|
||||||
make_pair("payable", _node.isPayable()),
|
make_pair("payable", _node.isPayable()),
|
||||||
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
|
make_pair("stateMutability", stateMutabilityToString(_node.stateMutability())),
|
||||||
|
make_pair("superFunction", idOrNull(_node.annotation().superFunction)),
|
||||||
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
make_pair("visibility", Declaration::visibilityToString(_node.visibility())),
|
||||||
make_pair("parameters", toJson(_node.parameterList())),
|
make_pair("parameters", toJson(_node.parameterList())),
|
||||||
make_pair("isConstructor", _node.isConstructor()),
|
make_pair("isConstructor", _node.isConstructor()),
|
||||||
|
@ -120,7 +120,7 @@ private:
|
|||||||
std::vector<std::pair<std::string, Json::Value>>&& _attributes
|
std::vector<std::pair<std::string, Json::Value>>&& _attributes
|
||||||
);
|
);
|
||||||
std::string sourceLocationToString(SourceLocation const& _location) const;
|
std::string sourceLocationToString(SourceLocation const& _location) const;
|
||||||
std::string namePathToString(std::vector<ASTString> const& _namePath) const;
|
static std::string namePathToString(std::vector<ASTString> const& _namePath);
|
||||||
static Json::Value idOrNull(ASTNode const* _pt)
|
static Json::Value idOrNull(ASTNode const* _pt)
|
||||||
{
|
{
|
||||||
return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue;
|
return _pt ? Json::Value(nodeId(*_pt)) : Json::nullValue;
|
||||||
@ -129,13 +129,13 @@ private:
|
|||||||
{
|
{
|
||||||
return _node ? toJson(*_node) : Json::nullValue;
|
return _node ? toJson(*_node) : Json::nullValue;
|
||||||
}
|
}
|
||||||
Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info);
|
Json::Value inlineAssemblyIdentifierToJson(std::pair<assembly::Identifier const* , InlineAssemblyAnnotation::ExternalIdentifierInfo> _info) const;
|
||||||
std::string location(VariableDeclaration::Location _location);
|
static std::string location(VariableDeclaration::Location _location);
|
||||||
std::string contractKind(ContractDefinition::ContractKind _kind);
|
static std::string contractKind(ContractDefinition::ContractKind _kind);
|
||||||
std::string functionCallKind(FunctionCallKind _kind);
|
static std::string functionCallKind(FunctionCallKind _kind);
|
||||||
std::string literalTokenKind(Token::Value _token);
|
static std::string literalTokenKind(Token::Value _token);
|
||||||
std::string type(Expression const& _expression);
|
static std::string type(Expression const& _expression);
|
||||||
std::string type(VariableDeclaration const& _varDecl);
|
static std::string type(VariableDeclaration const& _varDecl);
|
||||||
static int nodeId(ASTNode const& _node)
|
static int nodeId(ASTNode const& _node)
|
||||||
{
|
{
|
||||||
return _node.id();
|
return _node.id();
|
||||||
@ -151,8 +151,8 @@ private:
|
|||||||
}
|
}
|
||||||
return tmp;
|
return tmp;
|
||||||
}
|
}
|
||||||
Json::Value typePointerToJson(TypePointer _tp);
|
static Json::Value typePointerToJson(TypePointer _tp);
|
||||||
Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
|
static Json::Value typePointerToJson(std::shared_ptr<std::vector<TypePointer>> _tps);
|
||||||
void appendExpressionAttributes(
|
void appendExpressionAttributes(
|
||||||
std::vector<std::pair<std::string, Json::Value>> &_attributes,
|
std::vector<std::pair<std::string, Json::Value>> &_attributes,
|
||||||
ExpressionAnnotation const& _annotation
|
ExpressionAnnotation const& _annotation
|
||||||
|
@ -21,9 +21,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <libsolidity/ast/ASTPrinter.h>
|
#include <libsolidity/ast/ASTPrinter.h>
|
||||||
#include <boost/algorithm/string/join.hpp>
|
|
||||||
#include <libsolidity/ast/AST.h>
|
#include <libsolidity/ast/AST.h>
|
||||||
|
|
||||||
|
#include <json/json.h>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/join.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
@ -579,8 +582,11 @@ void ASTPrinter::printSourcePart(ASTNode const& _node)
|
|||||||
if (!m_source.empty())
|
if (!m_source.empty())
|
||||||
{
|
{
|
||||||
SourceLocation const& location(_node.location());
|
SourceLocation const& location(_node.location());
|
||||||
*m_ostream << indentation() << " Source: "
|
*m_ostream <<
|
||||||
<< escaped(m_source.substr(location.start, location.end - location.start), false) << endl;
|
indentation() <<
|
||||||
|
" Source: " <<
|
||||||
|
Json::valueToQuotedString(m_source.substr(location.start, location.end - location.start).c_str()) <<
|
||||||
|
endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ enum class ExperimentalFeature
|
|||||||
{
|
{
|
||||||
SMTChecker,
|
SMTChecker,
|
||||||
ABIEncoderV2, // new ABI encoder that makes use of JULIA
|
ABIEncoderV2, // new ABI encoder that makes use of JULIA
|
||||||
|
V050, // v0.5.0 breaking changes
|
||||||
Test,
|
Test,
|
||||||
TestOnlyAnalysis
|
TestOnlyAnalysis
|
||||||
};
|
};
|
||||||
@ -45,6 +46,7 @@ static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames
|
|||||||
{
|
{
|
||||||
{ "SMTChecker", ExperimentalFeature::SMTChecker },
|
{ "SMTChecker", ExperimentalFeature::SMTChecker },
|
||||||
{ "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 },
|
{ "ABIEncoderV2", ExperimentalFeature::ABIEncoderV2 },
|
||||||
|
{ "v0.5.0", ExperimentalFeature::V050 },
|
||||||
{ "__test", ExperimentalFeature::Test },
|
{ "__test", ExperimentalFeature::Test },
|
||||||
{ "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis },
|
{ "__testOnlyAnalysis", ExperimentalFeature::TestOnlyAnalysis },
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/range/adaptor/reversed.hpp>
|
#include <boost/range/adaptor/reversed.hpp>
|
||||||
|
#include <boost/range/algorithm/copy.hpp>
|
||||||
#include <boost/range/adaptor/sliced.hpp>
|
#include <boost/range/adaptor/sliced.hpp>
|
||||||
#include <boost/range/adaptor/transformed.hpp>
|
#include <boost/range/adaptor/transformed.hpp>
|
||||||
|
|
||||||
@ -304,6 +305,9 @@ MemberList::MemberMap Type::boundFunctions(Type const& _type, ContractDefinition
|
|||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountType)
|
bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountType)
|
||||||
{
|
{
|
||||||
// Disable >>> here.
|
// Disable >>> here.
|
||||||
@ -317,6 +321,8 @@ bool isValidShiftAndAmountType(Token::Value _operator, Type const& _shiftAmountT
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
|
IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
|
||||||
m_bits(_bits), m_modifier(_modifier)
|
m_bits(_bits), m_modifier(_modifier)
|
||||||
{
|
{
|
||||||
@ -1465,7 +1471,7 @@ string ArrayType::toString(bool _short) const
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
string ArrayType::canonicalName(bool _addDataLocation) const
|
string ArrayType::canonicalName() const
|
||||||
{
|
{
|
||||||
string ret;
|
string ret;
|
||||||
if (isString())
|
if (isString())
|
||||||
@ -1474,16 +1480,29 @@ string ArrayType::canonicalName(bool _addDataLocation) const
|
|||||||
ret = "bytes";
|
ret = "bytes";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ret = baseType()->canonicalName(false) + "[";
|
ret = baseType()->canonicalName() + "[";
|
||||||
if (!isDynamicallySized())
|
if (!isDynamicallySized())
|
||||||
ret += length().str();
|
ret += length().str();
|
||||||
ret += "]";
|
ret += "]";
|
||||||
}
|
}
|
||||||
if (_addDataLocation && location() == DataLocation::Storage)
|
|
||||||
ret += " storage";
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string ArrayType::signatureInExternalFunction(bool _structsByName) const
|
||||||
|
{
|
||||||
|
if (isByteArray())
|
||||||
|
return canonicalName();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
solAssert(baseType(), "");
|
||||||
|
return
|
||||||
|
baseType()->signatureInExternalFunction(_structsByName) +
|
||||||
|
"[" +
|
||||||
|
(isDynamicallySized() ? "" : length().str()) +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
|
MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
@ -1592,7 +1611,7 @@ string ContractType::toString(bool) const
|
|||||||
m_contract.name();
|
m_contract.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
string ContractType::canonicalName(bool) const
|
string ContractType::canonicalName() const
|
||||||
{
|
{
|
||||||
return m_contract.annotation().canonicalName;
|
return m_contract.annotation().canonicalName;
|
||||||
}
|
}
|
||||||
@ -1716,15 +1735,22 @@ unsigned StructType::calldataEncodedSize(bool _padded) const
|
|||||||
|
|
||||||
bool StructType::isDynamicallyEncoded() const
|
bool StructType::isDynamicallyEncoded() const
|
||||||
{
|
{
|
||||||
solAssert(false, "Structs are not yet supported in the ABI.");
|
solAssert(!recursive(), "");
|
||||||
|
for (auto t: memoryMemberTypes())
|
||||||
|
{
|
||||||
|
solAssert(t, "Parameter should have external type.");
|
||||||
|
t = t->interfaceType(false);
|
||||||
|
if (t->isDynamicallyEncoded())
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
u256 StructType::memorySize() const
|
u256 StructType::memorySize() const
|
||||||
{
|
{
|
||||||
u256 size;
|
u256 size;
|
||||||
for (auto const& member: members(nullptr))
|
for (auto const& t: memoryMemberTypes())
|
||||||
if (member.type->canLiveOutsideStorage())
|
size += t->memoryHeadSize();
|
||||||
size += member.type->memoryHeadSize();
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1762,10 +1788,33 @@ MemberList::MemberMap StructType::nativeMembers(ContractDefinition const*) const
|
|||||||
|
|
||||||
TypePointer StructType::interfaceType(bool _inLibrary) const
|
TypePointer StructType::interfaceType(bool _inLibrary) const
|
||||||
{
|
{
|
||||||
|
if (!canBeUsedExternally(_inLibrary))
|
||||||
|
return TypePointer();
|
||||||
|
|
||||||
|
// Has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary)
|
||||||
if (_inLibrary && location() == DataLocation::Storage)
|
if (_inLibrary && location() == DataLocation::Storage)
|
||||||
return shared_from_this();
|
return shared_from_this();
|
||||||
else
|
else
|
||||||
return TypePointer();
|
return copyForLocation(DataLocation::Memory, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StructType::canBeUsedExternally(bool _inLibrary) const
|
||||||
|
{
|
||||||
|
if (_inLibrary && location() == DataLocation::Storage)
|
||||||
|
return true;
|
||||||
|
else if (recursive())
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Check that all members have interface types.
|
||||||
|
// We pass "false" to canBeUsedExternally (_inLibrary), because this struct will be
|
||||||
|
// passed by value and thus the encoding does not differ, but it will disallow
|
||||||
|
// mappings.
|
||||||
|
for (auto const& var: m_struct.members())
|
||||||
|
if (!var->annotation().type->canBeUsedExternally(false))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const
|
TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer) const
|
||||||
@ -1775,12 +1824,27 @@ TypePointer StructType::copyForLocation(DataLocation _location, bool _isPointer)
|
|||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
string StructType::canonicalName(bool _addDataLocation) const
|
string StructType::signatureInExternalFunction(bool _structsByName) const
|
||||||
{
|
{
|
||||||
string ret = m_struct.annotation().canonicalName;
|
if (_structsByName)
|
||||||
if (_addDataLocation && location() == DataLocation::Storage)
|
return canonicalName();
|
||||||
ret += " storage";
|
else
|
||||||
return ret;
|
{
|
||||||
|
TypePointers memberTypes = memoryMemberTypes();
|
||||||
|
auto memberTypeStrings = memberTypes | boost::adaptors::transformed([&](TypePointer _t) -> string
|
||||||
|
{
|
||||||
|
solAssert(_t, "Parameter should have external type.");
|
||||||
|
auto t = _t->interfaceType(_structsByName);
|
||||||
|
solAssert(t, "");
|
||||||
|
return t->signatureInExternalFunction(_structsByName);
|
||||||
|
});
|
||||||
|
return "(" + boost::algorithm::join(memberTypeStrings, ",") + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string StructType::canonicalName() const
|
||||||
|
{
|
||||||
|
return m_struct.annotation().canonicalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionTypePointer StructType::constructorType() const
|
FunctionTypePointer StructType::constructorType() const
|
||||||
@ -1822,6 +1886,15 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypePointers StructType::memoryMemberTypes() const
|
||||||
|
{
|
||||||
|
TypePointers types;
|
||||||
|
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
|
||||||
|
if (variable->annotation().type->canLiveOutsideStorage())
|
||||||
|
types.push_back(variable->annotation().type);
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
set<string> StructType::membersMissingInMemory() const
|
set<string> StructType::membersMissingInMemory() const
|
||||||
{
|
{
|
||||||
set<string> missing;
|
set<string> missing;
|
||||||
@ -1831,6 +1904,33 @@ set<string> StructType::membersMissingInMemory() const
|
|||||||
return missing;
|
return missing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StructType::recursive() const
|
||||||
|
{
|
||||||
|
if (!m_recursive.is_initialized())
|
||||||
|
{
|
||||||
|
set<StructDefinition const*> structsSeen;
|
||||||
|
function<bool(StructType const*)> check = [&](StructType const* t) -> bool
|
||||||
|
{
|
||||||
|
StructDefinition const* str = &t->structDefinition();
|
||||||
|
if (structsSeen.count(str))
|
||||||
|
return true;
|
||||||
|
structsSeen.insert(str);
|
||||||
|
for (ASTPointer<VariableDeclaration> const& variable: str->members())
|
||||||
|
{
|
||||||
|
Type const* memberType = variable->annotation().type.get();
|
||||||
|
while (dynamic_cast<ArrayType const*>(memberType))
|
||||||
|
memberType = dynamic_cast<ArrayType const*>(memberType)->baseType().get();
|
||||||
|
if (StructType const* innerStruct = dynamic_cast<StructType const*>(memberType))
|
||||||
|
if (check(innerStruct))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
m_recursive = check(this);
|
||||||
|
}
|
||||||
|
return *m_recursive;
|
||||||
|
}
|
||||||
|
|
||||||
TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
|
TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
|
||||||
{
|
{
|
||||||
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
|
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
|
||||||
@ -1863,7 +1963,7 @@ string EnumType::toString(bool) const
|
|||||||
return string("enum ") + m_enum.annotation().canonicalName;
|
return string("enum ") + m_enum.annotation().canonicalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
string EnumType::canonicalName(bool) const
|
string EnumType::canonicalName() const
|
||||||
{
|
{
|
||||||
return m_enum.annotation().canonicalName;
|
return m_enum.annotation().canonicalName;
|
||||||
}
|
}
|
||||||
@ -2030,7 +2130,9 @@ FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal
|
|||||||
}
|
}
|
||||||
|
|
||||||
FunctionType::FunctionType(VariableDeclaration const& _varDecl):
|
FunctionType::FunctionType(VariableDeclaration const& _varDecl):
|
||||||
m_kind(Kind::External), m_stateMutability(StateMutability::View), m_declaration(&_varDecl)
|
m_kind(Kind::External),
|
||||||
|
m_stateMutability(StateMutability::View),
|
||||||
|
m_declaration(&_varDecl)
|
||||||
{
|
{
|
||||||
TypePointers paramTypes;
|
TypePointers paramTypes;
|
||||||
vector<string> paramNames;
|
vector<string> paramNames;
|
||||||
@ -2090,7 +2192,9 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
|
|||||||
}
|
}
|
||||||
|
|
||||||
FunctionType::FunctionType(EventDefinition const& _event):
|
FunctionType::FunctionType(EventDefinition const& _event):
|
||||||
m_kind(Kind::Event), m_stateMutability(StateMutability::View), m_declaration(&_event)
|
m_kind(Kind::Event),
|
||||||
|
m_stateMutability(StateMutability::NonPayable),
|
||||||
|
m_declaration(&_event)
|
||||||
{
|
{
|
||||||
TypePointers params;
|
TypePointers params;
|
||||||
vector<string> paramNames;
|
vector<string> paramNames;
|
||||||
@ -2160,7 +2264,6 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c
|
|||||||
strings{""},
|
strings{""},
|
||||||
Kind::Creation,
|
Kind::Creation,
|
||||||
false,
|
false,
|
||||||
nullptr,
|
|
||||||
stateMutability
|
stateMutability
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2287,7 +2390,7 @@ TypePointer FunctionType::binaryOperatorResult(Token::Value _operator, TypePoint
|
|||||||
return TypePointer();
|
return TypePointer();
|
||||||
}
|
}
|
||||||
|
|
||||||
string FunctionType::canonicalName(bool) const
|
string FunctionType::canonicalName() const
|
||||||
{
|
{
|
||||||
solAssert(m_kind == Kind::External, "");
|
solAssert(m_kind == Kind::External, "");
|
||||||
return "function";
|
return "function";
|
||||||
@ -2412,8 +2515,8 @@ FunctionTypePointer FunctionType::interfaceFunctionType() const
|
|||||||
m_returnParameterNames,
|
m_returnParameterNames,
|
||||||
m_kind,
|
m_kind,
|
||||||
m_arbitraryParameters,
|
m_arbitraryParameters,
|
||||||
m_declaration,
|
m_stateMutability,
|
||||||
m_stateMutability
|
m_declaration
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2428,6 +2531,11 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
case Kind::BareDelegateCall:
|
case Kind::BareDelegateCall:
|
||||||
{
|
{
|
||||||
MemberList::MemberMap members;
|
MemberList::MemberMap members;
|
||||||
|
if (m_kind == Kind::External)
|
||||||
|
members.push_back(MemberList::Member(
|
||||||
|
"selector",
|
||||||
|
make_shared<FixedBytesType>(4)
|
||||||
|
));
|
||||||
if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
|
if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
|
||||||
{
|
{
|
||||||
if (isPayable())
|
if (isPayable())
|
||||||
@ -2440,8 +2548,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
strings(),
|
strings(),
|
||||||
Kind::SetValue,
|
Kind::SetValue,
|
||||||
false,
|
false,
|
||||||
nullptr,
|
|
||||||
StateMutability::NonPayable,
|
StateMutability::NonPayable,
|
||||||
|
nullptr,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet
|
m_valueSet
|
||||||
)
|
)
|
||||||
@ -2457,8 +2565,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
|
|||||||
strings(),
|
strings(),
|
||||||
Kind::SetGas,
|
Kind::SetGas,
|
||||||
false,
|
false,
|
||||||
nullptr,
|
|
||||||
StateMutability::NonPayable,
|
StateMutability::NonPayable,
|
||||||
|
nullptr,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet
|
m_valueSet
|
||||||
)
|
)
|
||||||
@ -2542,20 +2650,19 @@ string FunctionType::externalSignature() const
|
|||||||
solAssert(m_declaration != nullptr, "External signature of function needs declaration");
|
solAssert(m_declaration != nullptr, "External signature of function needs declaration");
|
||||||
solAssert(!m_declaration->name().empty(), "Fallback function has no signature.");
|
solAssert(!m_declaration->name().empty(), "Fallback function has no signature.");
|
||||||
|
|
||||||
bool _inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary();
|
bool const inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary();
|
||||||
|
|
||||||
string ret = m_declaration->name() + "(";
|
|
||||||
|
|
||||||
FunctionTypePointer external = interfaceFunctionType();
|
FunctionTypePointer external = interfaceFunctionType();
|
||||||
solAssert(!!external, "External function type requested.");
|
solAssert(!!external, "External function type requested.");
|
||||||
TypePointers externalParameterTypes = external->parameterTypes();
|
auto parameterTypes = external->parameterTypes();
|
||||||
for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it)
|
auto typeStrings = parameterTypes | boost::adaptors::transformed([&](TypePointer _t) -> string
|
||||||
{
|
{
|
||||||
solAssert(!!(*it), "Parameter should have external type");
|
solAssert(_t, "Parameter should have external type.");
|
||||||
ret += (*it)->canonicalName(_inLibrary) + (it + 1 == externalParameterTypes.cend() ? "" : ",");
|
string typeName = _t->signatureInExternalFunction(inLibrary);
|
||||||
}
|
if (inLibrary && _t->dataStoredIn(DataLocation::Storage))
|
||||||
|
typeName += " storage";
|
||||||
return ret + ")";
|
return typeName;
|
||||||
|
});
|
||||||
|
return m_declaration->name() + "(" + boost::algorithm::join(typeStrings, ",") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
u256 FunctionType::externalIdentifier() const
|
u256 FunctionType::externalIdentifier() const
|
||||||
@ -2565,6 +2672,8 @@ u256 FunctionType::externalIdentifier() const
|
|||||||
|
|
||||||
bool FunctionType::isPure() const
|
bool FunctionType::isPure() const
|
||||||
{
|
{
|
||||||
|
// FIXME: replace this with m_stateMutability == StateMutability::Pure once
|
||||||
|
// the callgraph analyzer is in place
|
||||||
return
|
return
|
||||||
m_kind == Kind::SHA3 ||
|
m_kind == Kind::SHA3 ||
|
||||||
m_kind == Kind::ECRecover ||
|
m_kind == Kind::ECRecover ||
|
||||||
@ -2593,8 +2702,8 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
|
|||||||
m_returnParameterNames,
|
m_returnParameterNames,
|
||||||
m_kind,
|
m_kind,
|
||||||
m_arbitraryParameters,
|
m_arbitraryParameters,
|
||||||
m_declaration,
|
|
||||||
m_stateMutability,
|
m_stateMutability,
|
||||||
|
m_declaration,
|
||||||
m_gasSet || _setGas,
|
m_gasSet || _setGas,
|
||||||
m_valueSet || _setValue,
|
m_valueSet || _setValue,
|
||||||
m_bound
|
m_bound
|
||||||
@ -2642,8 +2751,8 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
|
|||||||
m_returnParameterNames,
|
m_returnParameterNames,
|
||||||
kind,
|
kind,
|
||||||
m_arbitraryParameters,
|
m_arbitraryParameters,
|
||||||
m_declaration,
|
|
||||||
m_stateMutability,
|
m_stateMutability,
|
||||||
|
m_declaration,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet,
|
m_valueSet,
|
||||||
_bound
|
_bound
|
||||||
@ -2684,9 +2793,9 @@ string MappingType::toString(bool _short) const
|
|||||||
return "mapping(" + keyType()->toString(_short) + " => " + valueType()->toString(_short) + ")";
|
return "mapping(" + keyType()->toString(_short) + " => " + valueType()->toString(_short) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
string MappingType::canonicalName(bool) const
|
string MappingType::canonicalName() const
|
||||||
{
|
{
|
||||||
return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")";
|
return "mapping(" + keyType()->canonicalName() + " => " + valueType()->canonicalName() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
string TypeType::identifier() const
|
string TypeType::identifier() const
|
||||||
@ -2861,7 +2970,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
|
|||||||
return MemberList::MemberMap({
|
return MemberList::MemberMap({
|
||||||
{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
|
{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
|
||||||
{"timestamp", make_shared<IntegerType>(256)},
|
{"timestamp", make_shared<IntegerType>(256)},
|
||||||
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash)},
|
{"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)},
|
||||||
{"difficulty", make_shared<IntegerType>(256)},
|
{"difficulty", make_shared<IntegerType>(256)},
|
||||||
{"number", make_shared<IntegerType>(256)},
|
{"number", make_shared<IntegerType>(256)},
|
||||||
{"gaslimit", make_shared<IntegerType>(256)}
|
{"gaslimit", make_shared<IntegerType>(256)}
|
||||||
|
@ -32,10 +32,12 @@
|
|||||||
|
|
||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <boost/rational.hpp>
|
#include <boost/rational.hpp>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
{
|
{
|
||||||
@ -244,9 +246,15 @@ public:
|
|||||||
|
|
||||||
virtual std::string toString(bool _short) const = 0;
|
virtual std::string toString(bool _short) const = 0;
|
||||||
std::string toString() const { return toString(false); }
|
std::string toString() const { return toString(false); }
|
||||||
/// @returns the canonical name of this type for use in function signatures.
|
/// @returns the canonical name of this type for use in library function signatures.
|
||||||
/// @param _addDataLocation if true, includes data location for reference types if it is "storage".
|
virtual std::string canonicalName() const { return toString(true); }
|
||||||
virtual std::string canonicalName(bool /*_addDataLocation*/) const { return toString(true); }
|
/// @returns the signature of this type in external functions, i.e. `uint256` for integers
|
||||||
|
/// or `(uint256,bytes8)[2]` for an array of structs. If @a _structsByName,
|
||||||
|
/// structs are given by canonical name like `ContractName.StructName[2]`.
|
||||||
|
virtual std::string signatureInExternalFunction(bool /*_structsByName*/) const
|
||||||
|
{
|
||||||
|
return canonicalName();
|
||||||
|
}
|
||||||
virtual u256 literalValue(Literal const*) const
|
virtual u256 literalValue(Literal const*) const
|
||||||
{
|
{
|
||||||
solAssert(false, "Literal value requested for type without literals.");
|
solAssert(false, "Literal value requested for type without literals.");
|
||||||
@ -618,7 +626,8 @@ public:
|
|||||||
virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
|
virtual bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); }
|
||||||
virtual unsigned sizeOnStack() const override;
|
virtual unsigned sizeOnStack() const override;
|
||||||
virtual std::string toString(bool _short) const override;
|
virtual std::string toString(bool _short) const override;
|
||||||
virtual std::string canonicalName(bool _addDataLocation) const override;
|
virtual std::string canonicalName() const override;
|
||||||
|
virtual std::string signatureInExternalFunction(bool _structsByName) const override;
|
||||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||||
virtual TypePointer encodingType() const override;
|
virtual TypePointer encodingType() const override;
|
||||||
virtual TypePointer decodingType() const override;
|
virtual TypePointer decodingType() const override;
|
||||||
@ -676,7 +685,7 @@ public:
|
|||||||
virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
|
virtual unsigned sizeOnStack() const override { return m_super ? 0 : 1; }
|
||||||
virtual bool isValueType() const override { return true; }
|
virtual bool isValueType() const override { return true; }
|
||||||
virtual std::string toString(bool _short) const override;
|
virtual std::string toString(bool _short) const override;
|
||||||
virtual std::string canonicalName(bool _addDataLocation) const override;
|
virtual std::string canonicalName() const override;
|
||||||
|
|
||||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||||
virtual TypePointer encodingType() const override
|
virtual TypePointer encodingType() const override
|
||||||
@ -737,13 +746,15 @@ public:
|
|||||||
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override;
|
||||||
virtual TypePointer encodingType() const override
|
virtual TypePointer encodingType() const override
|
||||||
{
|
{
|
||||||
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : TypePointer();
|
return location() == DataLocation::Storage ? std::make_shared<IntegerType>(256) : shared_from_this();
|
||||||
}
|
}
|
||||||
virtual TypePointer interfaceType(bool _inLibrary) const override;
|
virtual TypePointer interfaceType(bool _inLibrary) const override;
|
||||||
|
virtual bool canBeUsedExternally(bool _inLibrary) const override;
|
||||||
|
|
||||||
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override;
|
TypePointer copyForLocation(DataLocation _location, bool _isPointer) const override;
|
||||||
|
|
||||||
virtual std::string canonicalName(bool _addDataLocation) const override;
|
virtual std::string canonicalName() const override;
|
||||||
|
virtual std::string signatureInExternalFunction(bool _structsByName) const override;
|
||||||
|
|
||||||
/// @returns a function that peforms the type conversion between a list of struct members
|
/// @returns a function that peforms the type conversion between a list of struct members
|
||||||
/// and a memory struct of this type.
|
/// and a memory struct of this type.
|
||||||
@ -754,11 +765,19 @@ public:
|
|||||||
|
|
||||||
StructDefinition const& structDefinition() const { return m_struct; }
|
StructDefinition const& structDefinition() const { return m_struct; }
|
||||||
|
|
||||||
|
/// @returns the vector of types of members available in memory.
|
||||||
|
TypePointers memoryMemberTypes() const;
|
||||||
/// @returns the set of all members that are removed in the memory version (typically mappings).
|
/// @returns the set of all members that are removed in the memory version (typically mappings).
|
||||||
std::set<std::string> membersMissingInMemory() const;
|
std::set<std::string> membersMissingInMemory() const;
|
||||||
|
|
||||||
|
/// @returns true if the same struct is used recursively in one of its members. Only
|
||||||
|
/// analyses the "memory" representation, i.e. mappings are ignored in all structs.
|
||||||
|
bool recursive() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
StructDefinition const& m_struct;
|
StructDefinition const& m_struct;
|
||||||
|
/// Cache for the recursive() function.
|
||||||
|
mutable boost::optional<bool> m_recursive;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -779,7 +798,7 @@ public:
|
|||||||
virtual unsigned storageBytes() const override;
|
virtual unsigned storageBytes() const override;
|
||||||
virtual bool canLiveOutsideStorage() const override { return true; }
|
virtual bool canLiveOutsideStorage() const override { return true; }
|
||||||
virtual std::string toString(bool _short) const override;
|
virtual std::string toString(bool _short) const override;
|
||||||
virtual std::string canonicalName(bool _addDataLocation) const override;
|
virtual std::string canonicalName() const override;
|
||||||
virtual bool isValueType() const override { return true; }
|
virtual bool isValueType() const override { return true; }
|
||||||
|
|
||||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||||
@ -898,7 +917,6 @@ public:
|
|||||||
strings(),
|
strings(),
|
||||||
_kind,
|
_kind,
|
||||||
_arbitraryParameters,
|
_arbitraryParameters,
|
||||||
nullptr,
|
|
||||||
_stateMutability
|
_stateMutability
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -915,8 +933,8 @@ public:
|
|||||||
strings _returnParameterNames = strings(),
|
strings _returnParameterNames = strings(),
|
||||||
Kind _kind = Kind::Internal,
|
Kind _kind = Kind::Internal,
|
||||||
bool _arbitraryParameters = false,
|
bool _arbitraryParameters = false,
|
||||||
Declaration const* _declaration = nullptr,
|
|
||||||
StateMutability _stateMutability = StateMutability::NonPayable,
|
StateMutability _stateMutability = StateMutability::NonPayable,
|
||||||
|
Declaration const* _declaration = nullptr,
|
||||||
bool _gasSet = false,
|
bool _gasSet = false,
|
||||||
bool _valueSet = false,
|
bool _valueSet = false,
|
||||||
bool _bound = false
|
bool _bound = false
|
||||||
@ -951,7 +969,7 @@ public:
|
|||||||
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
|
||||||
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
|
||||||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
|
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
|
||||||
virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
|
virtual std::string canonicalName() const override;
|
||||||
virtual std::string toString(bool _short) const override;
|
virtual std::string toString(bool _short) const override;
|
||||||
virtual unsigned calldataEncodedSize(bool _padded) const override;
|
virtual unsigned calldataEncodedSize(bool _padded) const override;
|
||||||
virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
virtual bool canBeStored() const override { return m_kind == Kind::Internal || m_kind == Kind::External; }
|
||||||
@ -1053,7 +1071,7 @@ public:
|
|||||||
virtual std::string identifier() const override;
|
virtual std::string identifier() const override;
|
||||||
virtual bool operator==(Type const& _other) const override;
|
virtual bool operator==(Type const& _other) const override;
|
||||||
virtual std::string toString(bool _short) const override;
|
virtual std::string toString(bool _short) const override;
|
||||||
virtual std::string canonicalName(bool _addDataLocation) const override;
|
virtual std::string canonicalName() const override;
|
||||||
virtual bool canLiveOutsideStorage() const override { return false; }
|
virtual bool canLiveOutsideStorage() const override { return false; }
|
||||||
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
|
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
|
||||||
virtual TypePointer encodingType() const override
|
virtual TypePointer encodingType() const override
|
||||||
@ -1064,6 +1082,7 @@ public:
|
|||||||
{
|
{
|
||||||
return _inLibrary ? shared_from_this() : TypePointer();
|
return _inLibrary ? shared_from_this() : TypePointer();
|
||||||
}
|
}
|
||||||
|
virtual bool dataStoredIn(DataLocation _location) const override { return _location == DataLocation::Storage; }
|
||||||
|
|
||||||
TypePointer const& keyType() const { return m_keyType; }
|
TypePointer const& keyType() const { return m_keyType; }
|
||||||
TypePointer const& valueType() const { return m_valueType; }
|
TypePointer const& valueType() const { return m_valueType; }
|
||||||
|
@ -30,32 +30,35 @@ using namespace std;
|
|||||||
using namespace dev;
|
using namespace dev;
|
||||||
using namespace dev::solidity;
|
using namespace dev::solidity;
|
||||||
|
|
||||||
ABIFunctions::~ABIFunctions()
|
|
||||||
{
|
|
||||||
// This throws an exception and thus might cause immediate termination, but hey,
|
|
||||||
// it's a failed assertion anyway :-)
|
|
||||||
solAssert(m_requestedFunctions.empty(), "Forgot to call ``requestedFunctions()``.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string ABIFunctions::tupleEncoder(
|
string ABIFunctions::tupleEncoder(
|
||||||
TypePointers const& _givenTypes,
|
TypePointers const& _givenTypes,
|
||||||
TypePointers const& _targetTypes,
|
TypePointers const& _targetTypes,
|
||||||
bool _encodeAsLibraryTypes
|
bool _encodeAsLibraryTypes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
string functionName = string("abi_encode_tuple_");
|
||||||
|
for (auto const& t: _givenTypes)
|
||||||
|
functionName += t->identifier() + "_";
|
||||||
|
functionName += "_to_";
|
||||||
|
for (auto const& t: _targetTypes)
|
||||||
|
functionName += t->identifier() + "_";
|
||||||
|
if (_encodeAsLibraryTypes)
|
||||||
|
functionName += "_library";
|
||||||
|
|
||||||
|
return createFunction(functionName, [&]() {
|
||||||
solAssert(!_givenTypes.empty(), "");
|
solAssert(!_givenTypes.empty(), "");
|
||||||
size_t const headSize_ = headSize(_targetTypes);
|
|
||||||
|
|
||||||
Whiskers encoder(R"(
|
// Note that the values are in reverse due to the difference in calling semantics.
|
||||||
{
|
Whiskers templ(R"(
|
||||||
let tail := add($headStart, <headSize>)
|
function <functionName>(headStart <valueParams>) -> tail {
|
||||||
|
tail := add(headStart, <headSize>)
|
||||||
<encodeElements>
|
<encodeElements>
|
||||||
<deepestStackElement> := tail
|
|
||||||
}
|
}
|
||||||
)");
|
)");
|
||||||
encoder("headSize", to_string(headSize_));
|
templ("functionName", functionName);
|
||||||
|
size_t const headSize_ = headSize(_targetTypes);
|
||||||
|
templ("headSize", to_string(headSize_));
|
||||||
|
string valueParams;
|
||||||
string encodeElements;
|
string encodeElements;
|
||||||
size_t headPos = 0;
|
size_t headPos = 0;
|
||||||
size_t stackPos = 0;
|
size_t stackPos = 0;
|
||||||
@ -66,16 +69,20 @@ string ABIFunctions::tupleEncoder(
|
|||||||
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
|
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
|
||||||
string valueNames = "";
|
string valueNames = "";
|
||||||
for (size_t j = 0; j < sizeOnStack; j++)
|
for (size_t j = 0; j < sizeOnStack; j++)
|
||||||
valueNames += "$value" + to_string(stackPos++) + ", ";
|
{
|
||||||
|
valueNames += "value" + to_string(stackPos) + ", ";
|
||||||
|
valueParams = ", value" + to_string(stackPos) + valueParams;
|
||||||
|
stackPos++;
|
||||||
|
}
|
||||||
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
|
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
|
||||||
Whiskers elementTempl(
|
Whiskers elementTempl(
|
||||||
dynamic ?
|
dynamic ?
|
||||||
string(R"(
|
string(R"(
|
||||||
mstore(add($headStart, <pos>), sub(tail, $headStart))
|
mstore(add(headStart, <pos>), sub(tail, headStart))
|
||||||
tail := <abiEncode>(<values> tail)
|
tail := <abiEncode>(<values> tail)
|
||||||
)") :
|
)") :
|
||||||
string(R"(
|
string(R"(
|
||||||
<abiEncode>(<values> add($headStart, <pos>))
|
<abiEncode>(<values> add(headStart, <pos>))
|
||||||
)")
|
)")
|
||||||
);
|
);
|
||||||
elementTempl("values", valueNames);
|
elementTempl("values", valueNames);
|
||||||
@ -85,10 +92,11 @@ string ABIFunctions::tupleEncoder(
|
|||||||
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
|
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
|
||||||
}
|
}
|
||||||
solAssert(headPos == headSize_, "");
|
solAssert(headPos == headSize_, "");
|
||||||
encoder("encodeElements", encodeElements);
|
templ("valueParams", valueParams);
|
||||||
encoder("deepestStackElement", stackPos > 0 ? "$value0" : "$headStart");
|
templ("encodeElements", encodeElements);
|
||||||
|
|
||||||
return encoder.render();
|
return templ.render();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
string ABIFunctions::requestedFunctions()
|
string ABIFunctions::requestedFunctions()
|
||||||
@ -396,9 +404,11 @@ string ABIFunctions::abiEncodingFunction(
|
|||||||
else
|
else
|
||||||
solAssert(false, "");
|
solAssert(false, "");
|
||||||
}
|
}
|
||||||
else if (dynamic_cast<StructType const*>(&to))
|
else if (auto const* toStruct = dynamic_cast<StructType const*>(&to))
|
||||||
{
|
{
|
||||||
solUnimplementedAssert(false, "Structs not yet implemented.");
|
StructType const* fromStruct = dynamic_cast<StructType const*>(&_from);
|
||||||
|
solAssert(fromStruct, "");
|
||||||
|
return abiEncodingFunctionStruct(*fromStruct, *toStruct, _encodeAsLibraryTypes);
|
||||||
}
|
}
|
||||||
else if (_from.category() == Type::Category::Function)
|
else if (_from.category() == Type::Category::Function)
|
||||||
return abiEncodingFunctionFunctionType(
|
return abiEncodingFunctionFunctionType(
|
||||||
@ -526,7 +536,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|||||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||||
{
|
{
|
||||||
mstore(pos, sub(tail, headStart))
|
mstore(pos, sub(tail, headStart))
|
||||||
tail := <encodeToMemoryFun>(<arrayElementAccess>(srcPtr), tail)
|
tail := <encodeToMemoryFun>(<arrayElementAccess>, tail)
|
||||||
srcPtr := <nextArrayElement>(srcPtr)
|
srcPtr := <nextArrayElement>(srcPtr)
|
||||||
pos := add(pos, <elementEncodedSize>)
|
pos := add(pos, <elementEncodedSize>)
|
||||||
}
|
}
|
||||||
@ -541,7 +551,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|||||||
let srcPtr := <dataAreaFun>(value)
|
let srcPtr := <dataAreaFun>(value)
|
||||||
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
for { let i := 0 } lt(i, length) { i := add(i, 1) }
|
||||||
{
|
{
|
||||||
<encodeToMemoryFun>(<arrayElementAccess>(srcPtr), pos)
|
<encodeToMemoryFun>(<arrayElementAccess>, pos)
|
||||||
srcPtr := <nextArrayElement>(srcPtr)
|
srcPtr := <nextArrayElement>(srcPtr)
|
||||||
pos := add(pos, <elementEncodedSize>)
|
pos := add(pos, <elementEncodedSize>)
|
||||||
}
|
}
|
||||||
@ -565,7 +575,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
|||||||
_encodeAsLibraryTypes,
|
_encodeAsLibraryTypes,
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
templ("arrayElementAccess", inMemory ? "mload" : "sload");
|
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||||
templ("nextArrayElement", nextArrayElementFunction(_from));
|
templ("nextArrayElement", nextArrayElementFunction(_from));
|
||||||
return templ.render();
|
return templ.render();
|
||||||
});
|
});
|
||||||
@ -718,6 +728,122 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string ABIFunctions::abiEncodingFunctionStruct(
|
||||||
|
StructType const& _from,
|
||||||
|
StructType const& _to,
|
||||||
|
bool _encodeAsLibraryTypes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
string functionName =
|
||||||
|
"abi_encode_" +
|
||||||
|
_from.identifier() +
|
||||||
|
"_to_" +
|
||||||
|
_to.identifier() +
|
||||||
|
(_encodeAsLibraryTypes ? "_library" : "");
|
||||||
|
|
||||||
|
solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "");
|
||||||
|
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
|
||||||
|
|
||||||
|
return createFunction(functionName, [&]() {
|
||||||
|
bool fromStorage = _from.location() == DataLocation::Storage;
|
||||||
|
bool dynamic = _to.isDynamicallyEncoded();
|
||||||
|
Whiskers templ(R"(
|
||||||
|
function <functionName>(value, pos) <return> {
|
||||||
|
let tail := add(pos, <headSize>)
|
||||||
|
<init>
|
||||||
|
<#members>
|
||||||
|
{
|
||||||
|
// <memberName>
|
||||||
|
<encode>
|
||||||
|
}
|
||||||
|
</members>
|
||||||
|
<assignEnd>
|
||||||
|
}
|
||||||
|
)");
|
||||||
|
templ("functionName", functionName);
|
||||||
|
templ("return", dynamic ? " -> end " : "");
|
||||||
|
templ("assignEnd", dynamic ? "end := tail" : "");
|
||||||
|
// to avoid multiple loads from the same slot for subsequent members
|
||||||
|
templ("init", fromStorage ? "let slotValue := 0" : "");
|
||||||
|
u256 previousSlotOffset(-1);
|
||||||
|
u256 encodingOffset = 0;
|
||||||
|
vector<map<string, string>> members;
|
||||||
|
for (auto const& member: _to.members(nullptr))
|
||||||
|
{
|
||||||
|
solAssert(member.type, "");
|
||||||
|
if (!member.type->canLiveOutsideStorage())
|
||||||
|
continue;
|
||||||
|
solUnimplementedAssert(
|
||||||
|
member.type->mobileType() &&
|
||||||
|
member.type->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
|
||||||
|
member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
|
||||||
|
"Encoding type \"" + member.type->toString() + "\" not yet implemented."
|
||||||
|
);
|
||||||
|
auto memberTypeTo = member.type->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
|
||||||
|
auto memberTypeFrom = _from.memberType(member.name);
|
||||||
|
solAssert(memberTypeFrom, "");
|
||||||
|
bool dynamicMember = memberTypeTo->isDynamicallyEncoded();
|
||||||
|
if (dynamicMember)
|
||||||
|
solAssert(dynamic, "");
|
||||||
|
Whiskers memberTempl(R"(
|
||||||
|
<preprocess>
|
||||||
|
let memberValue := <retrieveValue>
|
||||||
|
)" + (
|
||||||
|
dynamicMember ?
|
||||||
|
string(R"(
|
||||||
|
mstore(add(pos, <encodingOffset>), sub(tail, pos))
|
||||||
|
tail := <abiEncode>(memberValue, tail)
|
||||||
|
)") :
|
||||||
|
string(R"(
|
||||||
|
<abiEncode>(memberValue, add(pos, <encodingOffset>))
|
||||||
|
)")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (fromStorage)
|
||||||
|
{
|
||||||
|
solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), "");
|
||||||
|
u256 storageSlotOffset;
|
||||||
|
size_t intraSlotOffset;
|
||||||
|
tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name);
|
||||||
|
if (memberTypeFrom->isValueType())
|
||||||
|
{
|
||||||
|
if (storageSlotOffset != previousSlotOffset)
|
||||||
|
{
|
||||||
|
memberTempl("preprocess", "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))");
|
||||||
|
previousSlotOffset = storageSlotOffset;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
memberTempl("preprocess", "");
|
||||||
|
memberTempl("retrieveValue", shiftRightFunction(intraSlotOffset * 8, false) + "(slotValue)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), "");
|
||||||
|
solAssert(intraSlotOffset == 0, "");
|
||||||
|
memberTempl("preprocess", "");
|
||||||
|
memberTempl("retrieveValue", "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memberTempl("preprocess", "");
|
||||||
|
string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name));
|
||||||
|
memberTempl("retrieveValue", "mload(add(value, " + sourceOffset + "))");
|
||||||
|
}
|
||||||
|
memberTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset));
|
||||||
|
encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize();
|
||||||
|
memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, _encodeAsLibraryTypes, false));
|
||||||
|
|
||||||
|
members.push_back({});
|
||||||
|
members.back()["encode"] = memberTempl.render();
|
||||||
|
members.back()["memberName"] = member.name;
|
||||||
|
}
|
||||||
|
templ("members", members);
|
||||||
|
templ("headSize", toCompactHexWithPrefix(encodingOffset));
|
||||||
|
return templ.render();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
string ABIFunctions::abiEncodingFunctionStringLiteral(
|
string ABIFunctions::abiEncodingFunctionStringLiteral(
|
||||||
Type const& _from,
|
Type const& _from,
|
||||||
Type const& _to,
|
Type const& _to,
|
||||||
|
@ -44,15 +44,18 @@ using TypePointers = std::vector<TypePointer>;
|
|||||||
/// multiple times.
|
/// multiple times.
|
||||||
///
|
///
|
||||||
/// Make sure to include the result of ``requestedFunctions()`` to a block that
|
/// Make sure to include the result of ``requestedFunctions()`` to a block that
|
||||||
/// is visible from the code that was generated here.
|
/// is visible from the code that was generated here, or use named labels.
|
||||||
class ABIFunctions
|
class ABIFunctions
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
~ABIFunctions();
|
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes
|
||||||
|
|
||||||
/// @returns assembly code block to ABI-encode values of @a _givenTypes residing on the stack
|
|
||||||
/// into memory, converting the types to @a _targetTypes on the fly.
|
/// into memory, converting the types to @a _targetTypes on the fly.
|
||||||
/// Assumed variables to be present: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
/// Parameters are: <headStart> <value_n> ... <value_1>, i.e.
|
||||||
|
/// the layout on the stack is <value_1> ... <value_n> <headStart> with
|
||||||
|
/// the top of the stack on the right.
|
||||||
|
/// The values represent stack slots. If a type occupies more or less than one
|
||||||
|
/// stack slot, it takes exactly that number of values.
|
||||||
|
/// Returns a pointer to the end of the area written in memory.
|
||||||
/// Does not allocate memory (does not change the memory head pointer), but writes
|
/// Does not allocate memory (does not change the memory head pointer), but writes
|
||||||
/// to memory starting at $headStart and an unrestricted amount after that.
|
/// to memory starting at $headStart and an unrestricted amount after that.
|
||||||
/// Assigns the end of encoded memory either to $value0 or (if that is not present)
|
/// Assigns the end of encoded memory either to $value0 or (if that is not present)
|
||||||
@ -63,7 +66,7 @@ public:
|
|||||||
bool _encodeAsLibraryTypes = false
|
bool _encodeAsLibraryTypes = false
|
||||||
);
|
);
|
||||||
|
|
||||||
/// @returns auxiliary functions referenced from the block generated in @a tupleEncoder
|
/// @returns concatenation of all generated functions.
|
||||||
std::string requestedFunctions();
|
std::string requestedFunctions();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -120,6 +123,13 @@ private:
|
|||||||
bool _encodeAsLibraryTypes
|
bool _encodeAsLibraryTypes
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Part of @a abiEncodingFunction for struct types.
|
||||||
|
std::string abiEncodingFunctionStruct(
|
||||||
|
StructType const& _givenType,
|
||||||
|
StructType const& _targetType,
|
||||||
|
bool _encodeAsLibraryTypes
|
||||||
|
);
|
||||||
|
|
||||||
// @returns the name of the ABI encoding function with the given type
|
// @returns the name of the ABI encoding function with the given type
|
||||||
// and queues the generation of the function to the requested functions.
|
// and queues the generation of the function to the requested functions.
|
||||||
// Case for _givenType being a string literal
|
// Case for _givenType being a string literal
|
||||||
|
@ -913,10 +913,10 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
|
|||||||
switch (location)
|
switch (location)
|
||||||
{
|
{
|
||||||
case DataLocation::Memory:
|
case DataLocation::Memory:
|
||||||
if (_arrayType.isDynamicallySized())
|
|
||||||
m_context << u256(32) << Instruction::ADD;
|
|
||||||
// fall-through
|
|
||||||
case DataLocation::CallData:
|
case DataLocation::CallData:
|
||||||
|
if (location == DataLocation::Memory && _arrayType.isDynamicallySized())
|
||||||
|
m_context << u256(32) << Instruction::ADD;
|
||||||
|
|
||||||
if (!_arrayType.isByteArray())
|
if (!_arrayType.isByteArray())
|
||||||
{
|
{
|
||||||
m_context << Instruction::SWAP1;
|
m_context << Instruction::SWAP1;
|
||||||
|
@ -40,6 +40,8 @@ public:
|
|||||||
m_context(&m_runtimeContext)
|
m_context(&m_runtimeContext)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
/// Compiles a contract.
|
||||||
|
/// @arg _metadata contains the to be injected metadata CBOR
|
||||||
void compileContract(
|
void compileContract(
|
||||||
ContractDefinition const& _contract,
|
ContractDefinition const& _contract,
|
||||||
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts,
|
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts,
|
||||||
@ -51,14 +53,21 @@ public:
|
|||||||
ContractDefinition const& _contract,
|
ContractDefinition const& _contract,
|
||||||
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
|
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
|
||||||
);
|
);
|
||||||
|
/// @returns Entire assembly.
|
||||||
eth::Assembly const& assembly() const { return m_context.assembly(); }
|
eth::Assembly const& assembly() const { return m_context.assembly(); }
|
||||||
|
/// @returns The entire assembled object (with constructor).
|
||||||
eth::LinkerObject assembledObject() const { return m_context.assembledObject(); }
|
eth::LinkerObject assembledObject() const { return m_context.assembledObject(); }
|
||||||
|
/// @returns Only the runtime object (without constructor).
|
||||||
eth::LinkerObject runtimeObject() const { return m_context.assembledRuntimeObject(m_runtimeSub); }
|
eth::LinkerObject runtimeObject() const { return m_context.assembledRuntimeObject(m_runtimeSub); }
|
||||||
/// @arg _sourceCodes is the map of input files to source code strings
|
/// @arg _sourceCodes is the map of input files to source code strings
|
||||||
/// @arg _inJsonFromat shows whether the out should be in Json format
|
std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const
|
||||||
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const
|
|
||||||
{
|
{
|
||||||
return m_context.streamAssembly(_stream, _sourceCodes, _inJsonFormat);
|
return m_context.assemblyString(_sourceCodes);
|
||||||
|
}
|
||||||
|
/// @arg _sourceCodes is the map of input files to source code strings
|
||||||
|
Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const
|
||||||
|
{
|
||||||
|
return m_context.assemblyJSON(_sourceCodes);
|
||||||
}
|
}
|
||||||
/// @returns Assembly items of the normal compiler context
|
/// @returns Assembly items of the normal compiler context
|
||||||
eth::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); }
|
eth::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); }
|
||||||
|
@ -266,19 +266,9 @@ void CompilerContext::resetVisitedNodes(ASTNode const* _node)
|
|||||||
void CompilerContext::appendInlineAssembly(
|
void CompilerContext::appendInlineAssembly(
|
||||||
string const& _assembly,
|
string const& _assembly,
|
||||||
vector<string> const& _localVariables,
|
vector<string> const& _localVariables,
|
||||||
map<string, string> const& _replacements
|
bool _system
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
string replacedAssembly;
|
|
||||||
string const* assembly = &_assembly;
|
|
||||||
if (!_replacements.empty())
|
|
||||||
{
|
|
||||||
replacedAssembly = _assembly;
|
|
||||||
for (auto const& replacement: _replacements)
|
|
||||||
replacedAssembly = boost::algorithm::replace_all_copy(replacedAssembly, replacement.first, replacement.second);
|
|
||||||
assembly = &replacedAssembly;
|
|
||||||
}
|
|
||||||
|
|
||||||
int startStackHeight = stackHeight();
|
int startStackHeight = stackHeight();
|
||||||
|
|
||||||
julia::ExternalIdentifierAccess identifierAccess;
|
julia::ExternalIdentifierAccess identifierAccess;
|
||||||
@ -320,7 +310,7 @@ void CompilerContext::appendInlineAssembly(
|
|||||||
|
|
||||||
ErrorList errors;
|
ErrorList errors;
|
||||||
ErrorReporter errorReporter(errors);
|
ErrorReporter errorReporter(errors);
|
||||||
auto scanner = make_shared<Scanner>(CharStream(*assembly), "--CODEGEN--");
|
auto scanner = make_shared<Scanner>(CharStream(_assembly), "--CODEGEN--");
|
||||||
auto parserResult = assembly::Parser(errorReporter).parse(scanner);
|
auto parserResult = assembly::Parser(errorReporter).parse(scanner);
|
||||||
solAssert(parserResult, "Failed to parse inline assembly block.");
|
solAssert(parserResult, "Failed to parse inline assembly block.");
|
||||||
solAssert(errorReporter.errors().empty(), "Failed to parse inline assembly block.");
|
solAssert(errorReporter.errors().empty(), "Failed to parse inline assembly block.");
|
||||||
@ -329,7 +319,7 @@ void CompilerContext::appendInlineAssembly(
|
|||||||
assembly::AsmAnalyzer analyzer(analysisInfo, errorReporter, false, identifierAccess.resolve);
|
assembly::AsmAnalyzer analyzer(analysisInfo, errorReporter, false, identifierAccess.resolve);
|
||||||
solAssert(analyzer.analyze(*parserResult), "Failed to analyze inline assembly block.");
|
solAssert(analyzer.analyze(*parserResult), "Failed to analyze inline assembly block.");
|
||||||
solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
|
solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block.");
|
||||||
assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess);
|
assembly::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, identifierAccess, _system);
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
|
FunctionDefinition const& CompilerContext::resolveVirtualFunction(
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <libsolidity/codegen/ABIFunctions.h>
|
||||||
|
|
||||||
#include <libsolidity/ast/ASTForward.h>
|
#include <libsolidity/ast/ASTForward.h>
|
||||||
#include <libsolidity/ast/Types.h>
|
#include <libsolidity/ast/Types.h>
|
||||||
#include <libsolidity/ast/ASTAnnotations.h>
|
#include <libsolidity/ast/ASTAnnotations.h>
|
||||||
@ -56,7 +58,9 @@ public:
|
|||||||
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update currently enabled set of experimental features.
|
||||||
void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; }
|
void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; }
|
||||||
|
/// @returns true if the given feature is enabled.
|
||||||
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
|
bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); }
|
||||||
|
|
||||||
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
|
void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
|
||||||
@ -78,13 +82,15 @@ public:
|
|||||||
/// @returns the entry label of the given function. Might return an AssemblyItem of type
|
/// @returns the entry label of the given function. Might return an AssemblyItem of type
|
||||||
/// UndefinedItem if it does not exist yet.
|
/// UndefinedItem if it does not exist yet.
|
||||||
eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const;
|
eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const;
|
||||||
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
|
|
||||||
/// @returns the entry label of the given function and takes overrides into account.
|
/// @returns the entry label of the given function and takes overrides into account.
|
||||||
FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function);
|
FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function);
|
||||||
/// @returns the function that overrides the given declaration from the most derived class just
|
/// @returns the function that overrides the given declaration from the most derived class just
|
||||||
/// above _base in the current inheritance hierarchy.
|
/// above _base in the current inheritance hierarchy.
|
||||||
FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base);
|
FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base);
|
||||||
|
/// @returns the next constructor in the inheritance hierarchy.
|
||||||
FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const;
|
FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const;
|
||||||
|
/// Sets the current inheritance hierarchy from derived to base.
|
||||||
|
void setInheritanceHierarchy(std::vector<ContractDefinition const*> const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; }
|
||||||
|
|
||||||
/// @returns the next function in the queue of functions that are still to be compiled
|
/// @returns the next function in the queue of functions that are still to be compiled
|
||||||
/// (i.e. that were referenced during compilation but where we did not yet generate code for).
|
/// (i.e. that were referenced during compilation but where we did not yet generate code for).
|
||||||
@ -117,6 +123,7 @@ public:
|
|||||||
);
|
);
|
||||||
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
/// Generates the code for missing low-level functions, i.e. calls the generators passed above.
|
||||||
void appendMissingLowLevelFunctions();
|
void appendMissingLowLevelFunctions();
|
||||||
|
ABIFunctions& abiFunctions() { return m_abiFunctions; }
|
||||||
|
|
||||||
ModifierDefinition const& functionModifier(std::string const& _name) const;
|
ModifierDefinition const& functionModifier(std::string const& _name) const;
|
||||||
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
|
||||||
@ -152,9 +159,12 @@ public:
|
|||||||
eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); }
|
eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); }
|
||||||
/// @returns a new tag without pushing any opcodes or data
|
/// @returns a new tag without pushing any opcodes or data
|
||||||
eth::AssemblyItem newTag() { return m_asm->newTag(); }
|
eth::AssemblyItem newTag() { return m_asm->newTag(); }
|
||||||
|
/// @returns a new tag identified by name.
|
||||||
|
eth::AssemblyItem namedTag(std::string const& _name) { return m_asm->namedTag(_name); }
|
||||||
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
|
||||||
/// on the stack. @returns the pushsub assembly item.
|
/// on the stack. @returns the pushsub assembly item.
|
||||||
eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); }
|
eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); }
|
||||||
|
/// Pushes the size of the subroutine.
|
||||||
void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); }
|
void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); }
|
||||||
/// Pushes the offset of the subroutine.
|
/// Pushes the offset of the subroutine.
|
||||||
void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); }
|
void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); }
|
||||||
@ -180,15 +190,17 @@ public:
|
|||||||
/// Appends inline assembly. @a _replacements are string-matching replacements that are performed
|
/// Appends inline assembly. @a _replacements are string-matching replacements that are performed
|
||||||
/// prior to parsing the inline assembly.
|
/// prior to parsing the inline assembly.
|
||||||
/// @param _localVariables assigns stack positions to variables with the last one being the stack top
|
/// @param _localVariables assigns stack positions to variables with the last one being the stack top
|
||||||
|
/// @param _system if true, this is a "system-level" assembly where all functions use named labels.
|
||||||
void appendInlineAssembly(
|
void appendInlineAssembly(
|
||||||
std::string const& _assembly,
|
std::string const& _assembly,
|
||||||
std::vector<std::string> const& _localVariables = std::vector<std::string>(),
|
std::vector<std::string> const& _localVariables = std::vector<std::string>(),
|
||||||
std::map<std::string, std::string> const& _replacements = std::map<std::string, std::string>{}
|
bool _system = false
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Appends arbitrary data to the end of the bytecode.
|
/// Appends arbitrary data to the end of the bytecode.
|
||||||
void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); }
|
void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); }
|
||||||
|
|
||||||
|
/// Run optimisation step.
|
||||||
void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); }
|
void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); }
|
||||||
|
|
||||||
/// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise.
|
/// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise.
|
||||||
@ -196,16 +208,22 @@ public:
|
|||||||
/// @returns the identifier of the runtime subroutine.
|
/// @returns the identifier of the runtime subroutine.
|
||||||
size_t runtimeSub() const { return m_runtimeSub; }
|
size_t runtimeSub() const { return m_runtimeSub; }
|
||||||
|
|
||||||
|
/// @returns a const reference to the underlying assembly.
|
||||||
eth::Assembly const& assembly() const { return *m_asm; }
|
eth::Assembly const& assembly() const { return *m_asm; }
|
||||||
/// @returns non-const reference to the underlying assembly. Should be avoided in favour of
|
/// @returns non-const reference to the underlying assembly. Should be avoided in favour of
|
||||||
/// wrappers in this class.
|
/// wrappers in this class.
|
||||||
eth::Assembly& nonConstAssembly() { return *m_asm; }
|
eth::Assembly& nonConstAssembly() { return *m_asm; }
|
||||||
|
|
||||||
/// @arg _sourceCodes is the map of input files to source code strings
|
/// @arg _sourceCodes is the map of input files to source code strings
|
||||||
/// @arg _inJsonFormat shows whether the out should be in Json format
|
std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const
|
||||||
Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const
|
|
||||||
{
|
{
|
||||||
return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat);
|
return m_asm->assemblyString(_sourceCodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @arg _sourceCodes is the map of input files to source code strings
|
||||||
|
Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const
|
||||||
|
{
|
||||||
|
return m_asm->assemblyJSON(_sourceCodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
eth::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
|
eth::LinkerObject const& assembledObject() const { return m_asm->assemble(); }
|
||||||
@ -287,6 +305,8 @@ private:
|
|||||||
size_t m_runtimeSub = -1;
|
size_t m_runtimeSub = -1;
|
||||||
/// An index of low-level function labels by name.
|
/// An index of low-level function labels by name.
|
||||||
std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
|
std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
|
||||||
|
/// Container for ABI functions to be generated.
|
||||||
|
ABIFunctions m_abiFunctions;
|
||||||
/// The queue of low-level functions to generate.
|
/// The queue of low-level functions to generate.
|
||||||
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
|
||||||
};
|
};
|
||||||
|
@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
|
|||||||
{
|
{
|
||||||
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
|
||||||
{
|
{
|
||||||
solAssert(ref->location() == DataLocation::Memory, "");
|
solUnimplementedAssert(ref->location() == DataLocation::Memory, "");
|
||||||
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
|
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
|
||||||
}
|
}
|
||||||
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
|
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
|
||||||
@ -310,18 +310,13 @@ void CompilerUtils::abiEncode(
|
|||||||
{
|
{
|
||||||
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
// stack: <$value0> <$value1> ... <$value(n-1)> <$headStart>
|
||||||
|
|
||||||
vector<string> variables;
|
auto ret = m_context.pushNewTag();
|
||||||
size_t numValues = sizeOnStack(_givenTypes);
|
moveIntoStack(sizeOnStack(_givenTypes) + 1);
|
||||||
for (size_t i = 0; i < numValues; ++i)
|
|
||||||
variables.push_back("$value" + to_string(i));
|
|
||||||
variables.push_back("$headStart");
|
|
||||||
|
|
||||||
ABIFunctions funs;
|
string encoderName = m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes);
|
||||||
string routine = funs.tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes);
|
m_context.appendJumpTo(m_context.namedTag(encoderName));
|
||||||
routine += funs.requestedFunctions();
|
m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1);
|
||||||
m_context.appendInlineAssembly("{" + routine + "}", variables);
|
m_context << ret.tag();
|
||||||
// Remove everyhing except for "value0" / the final memory pointer.
|
|
||||||
popStackSlots(numValues);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||||
@ -829,6 +824,7 @@ void CompilerUtils::convertType(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// fall-through
|
||||||
default:
|
default:
|
||||||
// All other types should not be convertible to non-equal types.
|
// All other types should not be convertible to non-equal types.
|
||||||
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
|
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
|
||||||
|
@ -38,14 +38,20 @@ public:
|
|||||||
/// Stores the initial value of the free-memory-pointer at its position;
|
/// Stores the initial value of the free-memory-pointer at its position;
|
||||||
void initialiseFreeMemoryPointer();
|
void initialiseFreeMemoryPointer();
|
||||||
/// Copies the free memory pointer to the stack.
|
/// Copies the free memory pointer to the stack.
|
||||||
|
/// Stack pre:
|
||||||
|
/// Stack post: <mem_start>
|
||||||
void fetchFreeMemoryPointer();
|
void fetchFreeMemoryPointer();
|
||||||
/// Stores the free memory pointer from the stack.
|
/// Stores the free memory pointer from the stack.
|
||||||
|
/// Stack pre: <mem_end>
|
||||||
|
/// Stack post:
|
||||||
void storeFreeMemoryPointer();
|
void storeFreeMemoryPointer();
|
||||||
/// Allocates a number of bytes in memory as given on the stack.
|
/// Allocates a number of bytes in memory as given on the stack.
|
||||||
/// Stack pre: <size>
|
/// Stack pre: <size>
|
||||||
/// Stack post: <mem_start>
|
/// Stack post: <mem_start>
|
||||||
void allocateMemory();
|
void allocateMemory();
|
||||||
/// Appends code that transforms memptr to (memptr - free_memptr) memptr
|
/// Appends code that transforms memptr to (memptr - free_memptr) memptr
|
||||||
|
/// Stack pre: <mem_end>
|
||||||
|
/// Stack post: <size> <mem_start>
|
||||||
void toSizeAfterFreeMemoryPointer();
|
void toSizeAfterFreeMemoryPointer();
|
||||||
|
|
||||||
/// Loads data from memory to the stack.
|
/// Loads data from memory to the stack.
|
||||||
@ -105,6 +111,8 @@ public:
|
|||||||
|
|
||||||
/// Special case of @a encodeToMemory which assumes that everything is padded to words
|
/// Special case of @a encodeToMemory which assumes that everything is padded to words
|
||||||
/// and dynamic data is not copied in place (i.e. a proper ABI encoding).
|
/// and dynamic data is not copied in place (i.e. a proper ABI encoding).
|
||||||
|
/// Stack pre: <value0> <value1> ... <valueN-1> <head_start>
|
||||||
|
/// Stack post: <mem_ptr>
|
||||||
void abiEncode(
|
void abiEncode(
|
||||||
TypePointers const& _givenTypes,
|
TypePointers const& _givenTypes,
|
||||||
TypePointers const& _targetTypes,
|
TypePointers const& _targetTypes,
|
||||||
@ -185,9 +193,13 @@ public:
|
|||||||
static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes);
|
static unsigned sizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes);
|
||||||
|
|
||||||
/// Helper function to shift top value on the stack to the left.
|
/// Helper function to shift top value on the stack to the left.
|
||||||
|
/// Stack pre: <value> <shift_by_bits>
|
||||||
|
/// Stack post: <shifted_value>
|
||||||
void leftShiftNumberOnStack(unsigned _bits);
|
void leftShiftNumberOnStack(unsigned _bits);
|
||||||
|
|
||||||
/// Helper function to shift top value on the stack to the right.
|
/// Helper function to shift top value on the stack to the right.
|
||||||
|
/// Stack pre: <value> <shift_by_bits>
|
||||||
|
/// Stack post: <shifted_value>
|
||||||
void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false);
|
void rightShiftNumberOnStack(unsigned _bits, bool _isSigned = false);
|
||||||
|
|
||||||
/// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type.
|
/// Appends code that computes tha Keccak-256 hash of the topmost stack element of 32 byte type.
|
||||||
|
@ -39,6 +39,9 @@ using namespace std;
|
|||||||
using namespace dev;
|
using namespace dev;
|
||||||
using namespace dev::solidity;
|
using namespace dev::solidity;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple helper class to ensure that the stack height is the same at certain places in the code.
|
* Simple helper class to ensure that the stack height is the same at certain places in the code.
|
||||||
*/
|
*/
|
||||||
@ -53,6 +56,8 @@ private:
|
|||||||
unsigned stackHeight;
|
unsigned stackHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void ContractCompiler::compileContract(
|
void ContractCompiler::compileContract(
|
||||||
ContractDefinition const& _contract,
|
ContractDefinition const& _contract,
|
||||||
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts
|
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts
|
||||||
@ -117,6 +122,7 @@ void ContractCompiler::appendCallValueCheck()
|
|||||||
|
|
||||||
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
|
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
|
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||||
// Determine the arguments that are used for the base constructors.
|
// Determine the arguments that are used for the base constructors.
|
||||||
std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
|
std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
|
||||||
for (ContractDefinition const* contract: bases)
|
for (ContractDefinition const* contract: bases)
|
||||||
@ -169,6 +175,7 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
|
|||||||
appendMissingFunctions();
|
appendMissingFunctions();
|
||||||
m_runtimeCompiler->appendMissingFunctions();
|
m_runtimeCompiler->appendMissingFunctions();
|
||||||
|
|
||||||
|
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||||
m_context << deployRoutine;
|
m_context << deployRoutine;
|
||||||
|
|
||||||
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
|
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
|
||||||
@ -326,7 +333,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter
|
|||||||
{
|
{
|
||||||
// stack: v1 v2 ... v(k-1) base_offset current_offset
|
// stack: v1 v2 ... v(k-1) base_offset current_offset
|
||||||
TypePointer type = parameterType->decodingType();
|
TypePointer type = parameterType->decodingType();
|
||||||
solAssert(type, "No decoding type found.");
|
solUnimplementedAssert(type, "No decoding type found.");
|
||||||
if (type->category() == Type::Category::Array)
|
if (type->category() == Type::Category::Array)
|
||||||
{
|
{
|
||||||
auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
|
auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
|
||||||
@ -887,6 +894,9 @@ void ContractCompiler::appendMissingFunctions()
|
|||||||
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
|
||||||
}
|
}
|
||||||
m_context.appendMissingLowLevelFunctions();
|
m_context.appendMissingLowLevelFunctions();
|
||||||
|
string abiFunctions = m_context.abiFunctions().requestedFunctions();
|
||||||
|
if (!abiFunctions.empty())
|
||||||
|
m_context.appendInlineAssembly("{" + move(abiFunctions) + "}", {}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContractCompiler::appendModifierOrFunctionCode()
|
void ContractCompiler::appendModifierOrFunctionCode()
|
||||||
|
@ -96,8 +96,8 @@ private:
|
|||||||
virtual bool visit(IfStatement const& _ifStatement) override;
|
virtual bool visit(IfStatement const& _ifStatement) override;
|
||||||
virtual bool visit(WhileStatement const& _whileStatement) override;
|
virtual bool visit(WhileStatement const& _whileStatement) override;
|
||||||
virtual bool visit(ForStatement const& _forStatement) override;
|
virtual bool visit(ForStatement const& _forStatement) override;
|
||||||
virtual bool visit(Continue const& _continue) override;
|
virtual bool visit(Continue const& _continueStatement) override;
|
||||||
virtual bool visit(Break const& _break) override;
|
virtual bool visit(Break const& _breakStatement) override;
|
||||||
virtual bool visit(Return const& _return) override;
|
virtual bool visit(Return const& _return) override;
|
||||||
virtual bool visit(Throw const& _throw) override;
|
virtual bool visit(Throw const& _throw) override;
|
||||||
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
virtual bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
|
||||||
|
@ -644,8 +644,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
strings(),
|
strings(),
|
||||||
FunctionType::Kind::BareCall,
|
FunctionType::Kind::BareCall,
|
||||||
false,
|
false,
|
||||||
nullptr,
|
|
||||||
StateMutability::NonPayable,
|
StateMutability::NonPayable,
|
||||||
|
nullptr,
|
||||||
true,
|
true,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
@ -1047,6 +1047,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
|||||||
if (!alsoSearchInteger)
|
if (!alsoSearchInteger)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// fall-through
|
||||||
case Type::Category::Integer:
|
case Type::Category::Integer:
|
||||||
if (member == "balance")
|
if (member == "balance")
|
||||||
{
|
{
|
||||||
@ -1067,6 +1068,13 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
|
|||||||
solAssert(false, "Invalid member access to integer");
|
solAssert(false, "Invalid member access to integer");
|
||||||
break;
|
break;
|
||||||
case Type::Category::Function:
|
case Type::Category::Function:
|
||||||
|
if (member == "selector")
|
||||||
|
{
|
||||||
|
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||||
|
/// need to store store it as bytes4
|
||||||
|
utils().leftShiftNumberOnStack(224);
|
||||||
|
}
|
||||||
|
else
|
||||||
solAssert(!!_memberAccess.expression().annotation().type->memberType(member),
|
solAssert(!!_memberAccess.expression().annotation().type->memberType(member),
|
||||||
"Invalid member access to function.");
|
"Invalid member access to function.");
|
||||||
break;
|
break;
|
||||||
@ -1811,7 +1819,7 @@ void ExpressionCompiler::setLValueToStorageItem(Expression const& _expression)
|
|||||||
setLValue<StorageItem>(_expression, *_expression.annotation().type);
|
setLValue<StorageItem>(_expression, *_expression.annotation().type);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op) const
|
bool ExpressionCompiler::cleanupNeededForOp(Type::Category _type, Token::Value _op)
|
||||||
{
|
{
|
||||||
if (Token::isCompareOp(_op) || Token::isShiftOp(_op))
|
if (Token::isCompareOp(_op) || Token::isShiftOp(_op))
|
||||||
return true;
|
return true;
|
||||||
|
@ -119,7 +119,7 @@ private:
|
|||||||
|
|
||||||
/// @returns true if the operator applied to the given type requires a cleanup prior to the
|
/// @returns true if the operator applied to the given type requires a cleanup prior to the
|
||||||
/// operation.
|
/// operation.
|
||||||
bool cleanupNeededForOp(Type::Category _type, Token::Value _op) const;
|
static bool cleanupNeededForOp(Type::Category _type, Token::Value _op);
|
||||||
|
|
||||||
/// @returns the CompilerUtils object containing the current context.
|
/// @returns the CompilerUtils object containing the current context.
|
||||||
CompilerUtils utils();
|
CompilerUtils utils();
|
||||||
|
@ -41,7 +41,7 @@ namespace smt
|
|||||||
class SMTLib2Interface: public SolverInterface, public boost::noncopyable
|
class SMTLib2Interface: public SolverInterface, public boost::noncopyable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SMTLib2Interface(ReadCallback::Callback const& _queryCallback);
|
explicit SMTLib2Interface(ReadCallback::Callback const& _queryCallback);
|
||||||
|
|
||||||
void reset() override;
|
void reset() override;
|
||||||
|
|
||||||
|
@ -56,10 +56,10 @@ public:
|
|||||||
Expression(u256 const& _number): name(_number.str()) {}
|
Expression(u256 const& _number): name(_number.str()) {}
|
||||||
Expression(bigint const& _number): name(_number.str()) {}
|
Expression(bigint const& _number): name(_number.str()) {}
|
||||||
|
|
||||||
Expression(Expression const& _other) = default;
|
Expression(Expression const&) = default;
|
||||||
Expression(Expression&& _other) = default;
|
Expression(Expression&&) = default;
|
||||||
Expression& operator=(Expression const& _other) = default;
|
Expression& operator=(Expression const&) = default;
|
||||||
Expression& operator=(Expression&& _other) = default;
|
Expression& operator=(Expression&&) = default;
|
||||||
|
|
||||||
static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue)
|
static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue)
|
||||||
{
|
{
|
||||||
|
@ -163,10 +163,24 @@ bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
|
|||||||
|
|
||||||
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
|
bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
|
int const expectedItems = _assignment.variableNames.size();
|
||||||
|
solAssert(expectedItems >= 1, "");
|
||||||
int const stackHeight = m_stackHeight;
|
int const stackHeight = m_stackHeight;
|
||||||
bool success = boost::apply_visitor(*this, *_assignment.value);
|
bool success = boost::apply_visitor(*this, *_assignment.value);
|
||||||
solAssert(m_stackHeight >= stackHeight, "Negative value size.");
|
if ((m_stackHeight - stackHeight) != expectedItems)
|
||||||
if (!checkAssignment(_assignment.variableName, m_stackHeight - stackHeight))
|
{
|
||||||
|
m_errorReporter.declarationError(
|
||||||
|
_assignment.location,
|
||||||
|
"Variable count does not match number of values (" +
|
||||||
|
to_string(expectedItems) +
|
||||||
|
" vs. " +
|
||||||
|
to_string(m_stackHeight - stackHeight) +
|
||||||
|
")"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto const& variableName: _assignment.variableNames)
|
||||||
|
if (!checkAssignment(variableName, 1))
|
||||||
success = false;
|
success = false;
|
||||||
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
|
m_info.stackHeightInfo[&_assignment] = m_stackHeight;
|
||||||
return success;
|
return success;
|
||||||
|
@ -83,6 +83,10 @@ public:
|
|||||||
{
|
{
|
||||||
return assemblyTagToIdentifier(m_assembly.newTag());
|
return assemblyTagToIdentifier(m_assembly.newTag());
|
||||||
}
|
}
|
||||||
|
virtual size_t namedLabel(std::string const& _name) override
|
||||||
|
{
|
||||||
|
return assemblyTagToIdentifier(m_assembly.namedTag(_name));
|
||||||
|
}
|
||||||
virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override
|
virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override
|
||||||
{
|
{
|
||||||
m_assembly.appendLibraryAddress(_linkerSymbol);
|
m_assembly.appendLibraryAddress(_linkerSymbol);
|
||||||
@ -141,9 +145,17 @@ void assembly::CodeGenerator::assemble(
|
|||||||
Block const& _parsedData,
|
Block const& _parsedData,
|
||||||
AsmAnalysisInfo& _analysisInfo,
|
AsmAnalysisInfo& _analysisInfo,
|
||||||
eth::Assembly& _assembly,
|
eth::Assembly& _assembly,
|
||||||
julia::ExternalIdentifierAccess const& _identifierAccess
|
julia::ExternalIdentifierAccess const& _identifierAccess,
|
||||||
|
bool _useNamedLabelsForFunctions
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
EthAssemblyAdapter assemblyAdapter(_assembly);
|
EthAssemblyAdapter assemblyAdapter(_assembly);
|
||||||
julia::CodeTransform(assemblyAdapter, _analysisInfo, false, false, _identifierAccess)(_parsedData);
|
julia::CodeTransform(
|
||||||
|
assemblyAdapter,
|
||||||
|
_analysisInfo,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
_identifierAccess,
|
||||||
|
_useNamedLabelsForFunctions
|
||||||
|
)(_parsedData);
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,8 @@ public:
|
|||||||
Block const& _parsedData,
|
Block const& _parsedData,
|
||||||
AsmAnalysisInfo& _analysisInfo,
|
AsmAnalysisInfo& _analysisInfo,
|
||||||
eth::Assembly& _assembly,
|
eth::Assembly& _assembly,
|
||||||
julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess()
|
julia::ExternalIdentifierAccess const& _identifierAccess = julia::ExternalIdentifierAccess(),
|
||||||
|
bool _useNamedLabelsForFunctions = false
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -54,7 +54,11 @@ struct Label { SourceLocation location; std::string name; };
|
|||||||
struct StackAssignment { SourceLocation location; Identifier variableName; };
|
struct StackAssignment { SourceLocation location; Identifier variableName; };
|
||||||
/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand
|
/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand
|
||||||
/// side and requires x to occupy exactly one stack slot.
|
/// side and requires x to occupy exactly one stack slot.
|
||||||
struct Assignment { SourceLocation location; Identifier variableName; std::shared_ptr<Statement> value; };
|
///
|
||||||
|
/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy
|
||||||
|
/// a single stack slot and expects a single expression on the right hand returning
|
||||||
|
/// the same amount of items as the number of variables.
|
||||||
|
struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; };
|
||||||
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
|
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
|
||||||
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
|
struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
|
||||||
struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
|
struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
|
||||||
|
@ -122,6 +122,34 @@ assembly::Statement Parser::parseStatement()
|
|||||||
{
|
{
|
||||||
case Token::LParen:
|
case Token::LParen:
|
||||||
return parseCall(std::move(statement));
|
return parseCall(std::move(statement));
|
||||||
|
case Token::Comma:
|
||||||
|
{
|
||||||
|
// if a comma follows, a multiple assignment is assumed
|
||||||
|
|
||||||
|
if (statement.type() != typeid(assembly::Identifier))
|
||||||
|
fatalParserError("Label name / variable name must precede \",\" (multiple assignment).");
|
||||||
|
assembly::Identifier const& identifier = boost::get<assembly::Identifier>(statement);
|
||||||
|
|
||||||
|
Assignment assignment = createWithLocation<Assignment>(identifier.location);
|
||||||
|
assignment.variableNames.emplace_back(identifier);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
expectToken(Token::Comma);
|
||||||
|
statement = parseElementaryOperation(false);
|
||||||
|
if (statement.type() != typeid(assembly::Identifier))
|
||||||
|
fatalParserError("Variable name expected in multiple assignemnt.");
|
||||||
|
assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(statement));
|
||||||
|
}
|
||||||
|
while (currentToken() == Token::Comma);
|
||||||
|
|
||||||
|
expectToken(Token::Colon);
|
||||||
|
expectToken(Token::Assign);
|
||||||
|
|
||||||
|
assignment.value.reset(new Statement(parseExpression()));
|
||||||
|
assignment.location.end = locationOf(*assignment.value).end;
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
case Token::Colon:
|
case Token::Colon:
|
||||||
{
|
{
|
||||||
if (statement.type() != typeid(assembly::Identifier))
|
if (statement.type() != typeid(assembly::Identifier))
|
||||||
@ -136,7 +164,7 @@ assembly::Statement Parser::parseStatement()
|
|||||||
if (!m_julia && instructions().count(identifier.name))
|
if (!m_julia && instructions().count(identifier.name))
|
||||||
fatalParserError("Cannot use instruction names for identifier names.");
|
fatalParserError("Cannot use instruction names for identifier names.");
|
||||||
advance();
|
advance();
|
||||||
assignment.variableName = identifier;
|
assignment.variableNames.emplace_back(identifier);
|
||||||
assignment.value.reset(new Statement(parseExpression()));
|
assignment.value.reset(new Statement(parseExpression()));
|
||||||
assignment.location.end = locationOf(*assignment.value).end;
|
assignment.location.end = locationOf(*assignment.value).end;
|
||||||
return assignment;
|
return assignment;
|
||||||
|
@ -116,7 +116,11 @@ string AsmPrinter::operator()(assembly::StackAssignment const& _assignment)
|
|||||||
|
|
||||||
string AsmPrinter::operator()(assembly::Assignment const& _assignment)
|
string AsmPrinter::operator()(assembly::Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
return (*this)(_assignment.variableName) + " := " + boost::apply_visitor(*this, *_assignment.value);
|
solAssert(_assignment.variableNames.size() >= 1, "");
|
||||||
|
string variables = (*this)(_assignment.variableNames.front());
|
||||||
|
for (size_t i = 1; i < _assignment.variableNames.size(); ++i)
|
||||||
|
variables += ", " + (*this)(_assignment.variableNames[i]);
|
||||||
|
return variables + " := " + boost::apply_visitor(*this, *_assignment.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
|
string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
|
||||||
|
@ -32,13 +32,14 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
|||||||
for (auto it: _contractDef.interfaceFunctions())
|
for (auto it: _contractDef.interfaceFunctions())
|
||||||
{
|
{
|
||||||
auto externalFunctionType = it.second->interfaceFunctionType();
|
auto externalFunctionType = it.second->interfaceFunctionType();
|
||||||
|
solAssert(!!externalFunctionType, "");
|
||||||
Json::Value method;
|
Json::Value method;
|
||||||
method["type"] = "function";
|
method["type"] = "function";
|
||||||
method["name"] = it.second->declaration().name();
|
method["name"] = it.second->declaration().name();
|
||||||
// TODO: deprecate constant in a future release
|
// TODO: deprecate constant in a future release
|
||||||
method["constant"] = it.second->stateMutability() == StateMutability::Pure || it.second->stateMutability() == StateMutability::View;
|
method["constant"] = externalFunctionType->stateMutability() == StateMutability::Pure || it.second->stateMutability() == StateMutability::View;
|
||||||
method["payable"] = it.second->isPayable();
|
method["payable"] = externalFunctionType->isPayable();
|
||||||
method["stateMutability"] = stateMutabilityToString(it.second->stateMutability());
|
method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability());
|
||||||
method["inputs"] = formatTypeList(
|
method["inputs"] = formatTypeList(
|
||||||
externalFunctionType->parameterNames(),
|
externalFunctionType->parameterNames(),
|
||||||
externalFunctionType->parameterTypes(),
|
externalFunctionType->parameterTypes(),
|
||||||
@ -53,15 +54,15 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
|||||||
}
|
}
|
||||||
if (_contractDef.constructor())
|
if (_contractDef.constructor())
|
||||||
{
|
{
|
||||||
|
auto externalFunctionType = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
|
||||||
|
solAssert(!!externalFunctionType, "");
|
||||||
Json::Value method;
|
Json::Value method;
|
||||||
method["type"] = "constructor";
|
method["type"] = "constructor";
|
||||||
auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
|
method["payable"] = externalFunctionType->isPayable();
|
||||||
solAssert(!!externalFunction, "");
|
method["stateMutability"] = stateMutabilityToString(externalFunctionType->stateMutability());
|
||||||
method["payable"] = externalFunction->isPayable();
|
|
||||||
method["stateMutability"] = stateMutabilityToString(externalFunction->stateMutability());
|
|
||||||
method["inputs"] = formatTypeList(
|
method["inputs"] = formatTypeList(
|
||||||
externalFunction->parameterNames(),
|
externalFunctionType->parameterNames(),
|
||||||
externalFunction->parameterTypes(),
|
externalFunctionType->parameterTypes(),
|
||||||
_contractDef.isLibrary()
|
_contractDef.isLibrary()
|
||||||
);
|
);
|
||||||
abi.append(method);
|
abi.append(method);
|
||||||
@ -85,12 +86,12 @@ Json::Value ABI::generate(ContractDefinition const& _contractDef)
|
|||||||
Json::Value params(Json::arrayValue);
|
Json::Value params(Json::arrayValue);
|
||||||
for (auto const& p: it->parameters())
|
for (auto const& p: it->parameters())
|
||||||
{
|
{
|
||||||
solAssert(!!p->annotation().type->interfaceType(false), "");
|
auto type = p->annotation().type->interfaceType(false);
|
||||||
|
solAssert(type, "");
|
||||||
Json::Value input;
|
Json::Value input;
|
||||||
input["name"] = p->name();
|
auto param = formatType(p->name(), *type, false);
|
||||||
input["type"] = p->annotation().type->interfaceType(false)->canonicalName(false);
|
param["indexed"] = p->isIndexed();
|
||||||
input["indexed"] = p->isIndexed();
|
params.append(param);
|
||||||
params.append(input);
|
|
||||||
}
|
}
|
||||||
event["inputs"] = params;
|
event["inputs"] = params;
|
||||||
abi.append(event);
|
abi.append(event);
|
||||||
@ -110,10 +111,53 @@ Json::Value ABI::formatTypeList(
|
|||||||
for (unsigned i = 0; i < _names.size(); ++i)
|
for (unsigned i = 0; i < _names.size(); ++i)
|
||||||
{
|
{
|
||||||
solAssert(_types[i], "");
|
solAssert(_types[i], "");
|
||||||
Json::Value param;
|
params.append(formatType(_names[i], *_types[i], _forLibrary));
|
||||||
param["name"] = _names[i];
|
|
||||||
param["type"] = _types[i]->canonicalName(_forLibrary);
|
|
||||||
params.append(param);
|
|
||||||
}
|
}
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Json::Value ABI::formatType(string const& _name, Type const& _type, bool _forLibrary)
|
||||||
|
{
|
||||||
|
Json::Value ret;
|
||||||
|
ret["name"] = _name;
|
||||||
|
string suffix = (_forLibrary && _type.dataStoredIn(DataLocation::Storage)) ? " storage" : "";
|
||||||
|
if (_type.isValueType() || (_forLibrary && _type.dataStoredIn(DataLocation::Storage)))
|
||||||
|
ret["type"] = _type.canonicalName() + suffix;
|
||||||
|
else if (ArrayType const* arrayType = dynamic_cast<ArrayType const*>(&_type))
|
||||||
|
{
|
||||||
|
if (arrayType->isByteArray())
|
||||||
|
ret["type"] = _type.canonicalName() + suffix;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string suffix;
|
||||||
|
if (arrayType->isDynamicallySized())
|
||||||
|
suffix = "[]";
|
||||||
|
else
|
||||||
|
suffix = string("[") + arrayType->length().str() + "]";
|
||||||
|
solAssert(arrayType->baseType(), "");
|
||||||
|
Json::Value subtype = formatType("", *arrayType->baseType(), _forLibrary);
|
||||||
|
if (subtype.isMember("components"))
|
||||||
|
{
|
||||||
|
ret["type"] = subtype["type"].asString() + suffix;
|
||||||
|
ret["components"] = subtype["components"];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ret["type"] = subtype["type"].asString() + suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (StructType const* structType = dynamic_cast<StructType const*>(&_type))
|
||||||
|
{
|
||||||
|
ret["type"] = "tuple";
|
||||||
|
ret["components"] = Json::arrayValue;
|
||||||
|
for (auto const& member: structType->members(nullptr))
|
||||||
|
{
|
||||||
|
solAssert(member.type, "");
|
||||||
|
auto t = member.type->interfaceType(_forLibrary);
|
||||||
|
solAssert(t, "");
|
||||||
|
ret["components"].append(formatType(member.name, *t, _forLibrary));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
solAssert(false, "Invalid type.");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
@ -50,6 +50,10 @@ private:
|
|||||||
std::vector<TypePointer> const& _types,
|
std::vector<TypePointer> const& _types,
|
||||||
bool _forLibrary
|
bool _forLibrary
|
||||||
);
|
);
|
||||||
|
/// @returns a Json object with "name", "type" and potentially "components" keys, according
|
||||||
|
/// to the ABI specification.
|
||||||
|
/// If it is possible to express the type as a single string, it is allowed to return a single string.
|
||||||
|
static Json::Value formatType(std::string const& _name, Type const& _type, bool _forLibrary);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -91,9 +91,7 @@ MachineAssemblyObject AssemblyStack::assemble(Machine _machine) const
|
|||||||
eth::Assembly assembly;
|
eth::Assembly assembly;
|
||||||
assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo, assembly);
|
assembly::CodeGenerator::assemble(*m_parserResult, *m_analysisInfo, assembly);
|
||||||
object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble());
|
object.bytecode = make_shared<eth::LinkerObject>(assembly.assemble());
|
||||||
ostringstream tmp;
|
object.assembly = assembly.assemblyString();
|
||||||
assembly.stream(tmp);
|
|
||||||
object.assembly = tmp.str();
|
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
case Machine::EVM15:
|
case Machine::EVM15:
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user