Merge pull request #2947 from ethereum/develop

Merge develop into release for 0.4.17.
This commit is contained in:
chriseth 2017-09-21 16:56:16 +02:00 committed by GitHub
commit bdeb9e52a2
146 changed files with 4720 additions and 2206 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "deps"]
path = deps
url = https://github.com/ethereum/cpp-dependencies

View File

@ -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.
# #

View File

@ -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)

View File

@ -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:

View File

@ -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.

View File

@ -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
View 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()

View File

@ -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
View 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 +0,0 @@
Subproject commit e5c8316db8d3daa0abc3b5af8545ce330057608c

View File

@ -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": []
}
]

View File

@ -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.

View File

@ -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",

View File

@ -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

View File

@ -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
======== ========

View File

@ -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".

View File

@ -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``?
====================================================================== ======================================================================

View File

@ -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

View File

@ -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

View File

@ -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
View 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.

View File

@ -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
================ ================

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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 u256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, 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 s256 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<256, 256, boost::multiprecision::signed_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 u160 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<160, 160, 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 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
};
} }

View File

@ -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')

View File

@ -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
{ {

View File

@ -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);

View 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 {};
}

View File

@ -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]);

View File

@ -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); }
} }

View File

@ -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());

View File

@ -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);
} }

View File

@ -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); }
} }

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
{ {

View File

@ -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));

View File

@ -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

View 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;
}

View 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;
};
}
}

View File

@ -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);

View File

@ -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)};

View File

@ -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;
}

View File

@ -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);
}; };
} }

View File

@ -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;

View File

@ -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.");

View File

@ -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;

View File

@ -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, "");

View File

@ -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

View File

@ -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)
{ {

View File

@ -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;

View File

@ -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))})
{ {
} }

View File

@ -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;

View File

@ -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;

View File

@ -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();
} }

View File

@ -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
{ {

View File

@ -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())

View File

@ -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;

View File

@ -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,28 +169,52 @@ 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])))
{ {
m_errorReporter.declarationError( ssl.append("Other declaration is here:", overloads[j]->location());
overloads[j]->location(), reported.insert(j);
SecondarySourceLocation().append(
"Other declaration is here:",
overloads[i]->location()
),
"Function with same name and arguments defined twice."
);
} }
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(
overloads[i]->location(),
ssl,
msg
);
}
}
} }
} }
@ -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.");
@ -458,7 +490,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
" to " + " to " +
parameterTypes[i]->toString() + parameterTypes[i]->toString() +
" requested." " requested."
); );
} }
void TypeChecker::endVisit(UsingForDirective const& _usingFor) void TypeChecker::endVisit(UsingForDirective const& _usingFor)
@ -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 (_eventDef.isAnonymous() && numIndexed > 4)
m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
else if (!_eventDef.isAnonymous() && numIndexed > 3)
m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
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 (!type(*var)->interfaceType(false)) if (!type(*var)->interfaceType(false))
m_errorReporter.typeError(var->location(), "Internal type is not allowed as event parameter type."); m_errorReporter.typeError(var->location(), "Internal or recursive type is not allowed as event parameter type.");
} }
if (_eventDef.isAnonymous() && numIndexed > 4)
m_errorReporter.typeError(_eventDef.location(), "More than 4 indexed arguments for anonymous event.");
else if (!_eventDef.isAnonymous() && numIndexed > 3)
m_errorReporter.typeError(_eventDef.location(), "More than 3 indexed arguments for event.");
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;
} }

View File

@ -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);

View 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), "");
}

View 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;
};
}
}

View File

@ -176,11 +176,19 @@ 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) {
/// 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(e->name()); 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)

View File

@ -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;

View File

@ -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

View File

@ -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()),

View File

@ -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

View File

@ -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;
} }
} }

View File

@ -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 },
}; };

View File

@ -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)}

View File

@ -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; }

View File

@ -30,65 +30,73 @@ 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";
solAssert(!_givenTypes.empty(), ""); return createFunction(functionName, [&]() {
size_t const headSize_ = headSize(_targetTypes); solAssert(!_givenTypes.empty(), "");
Whiskers encoder(R"( // Note that the values are in reverse due to the difference in calling semantics.
Whiskers templ(R"(
function <functionName>(headStart <valueParams>) -> tail {
tail := add(headStart, <headSize>)
<encodeElements>
}
)");
templ("functionName", functionName);
size_t const headSize_ = headSize(_targetTypes);
templ("headSize", to_string(headSize_));
string valueParams;
string encodeElements;
size_t headPos = 0;
size_t stackPos = 0;
for (size_t i = 0; i < _givenTypes.size(); ++i)
{ {
let tail := add($headStart, <headSize>) solAssert(_givenTypes[i], "");
<encodeElements> solAssert(_targetTypes[i], "");
<deepestStackElement> := tail size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
string valueNames = "";
for (size_t j = 0; j < sizeOnStack; j++)
{
valueNames += "value" + to_string(stackPos) + ", ";
valueParams = ", value" + to_string(stackPos) + valueParams;
stackPos++;
}
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
Whiskers elementTempl(
dynamic ?
string(R"(
mstore(add(headStart, <pos>), sub(tail, headStart))
tail := <abiEncode>(<values> tail)
)") :
string(R"(
<abiEncode>(<values> add(headStart, <pos>))
)")
);
elementTempl("values", valueNames);
elementTempl("pos", to_string(headPos));
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, false));
encodeElements += elementTempl.render();
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
} }
)"); solAssert(headPos == headSize_, "");
encoder("headSize", to_string(headSize_)); templ("valueParams", valueParams);
string encodeElements; templ("encodeElements", encodeElements);
size_t headPos = 0;
size_t stackPos = 0;
for (size_t i = 0; i < _givenTypes.size(); ++i)
{
solAssert(_givenTypes[i], "");
solAssert(_targetTypes[i], "");
size_t sizeOnStack = _givenTypes[i]->sizeOnStack();
string valueNames = "";
for (size_t j = 0; j < sizeOnStack; j++)
valueNames += "$value" + to_string(stackPos++) + ", ";
bool dynamic = _targetTypes[i]->isDynamicallyEncoded();
Whiskers elementTempl(
dynamic ?
string(R"(
mstore(add($headStart, <pos>), sub(tail, $headStart))
tail := <abiEncode>(<values> tail)
)") :
string(R"(
<abiEncode>(<values> add($headStart, <pos>))
)")
);
elementTempl("values", valueNames);
elementTempl("pos", to_string(headPos));
elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, false));
encodeElements += elementTempl.render();
headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize();
}
solAssert(headPos == headSize_, "");
encoder("encodeElements", encodeElements);
encoder("deepestStackElement", stackPos > 0 ? "$value0" : "$headStart");
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,

View File

@ -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

View File

@ -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;

View File

@ -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(); }

View File

@ -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(

View File

@ -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;
}; };

View File

@ -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.");

View File

@ -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.

View File

@ -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()

View File

@ -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;

View File

@ -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,7 +1068,14 @@ 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:
solAssert(!!_memberAccess.expression().annotation().type->memberType(member), 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),
"Invalid member access to function."); "Invalid member access to function.");
break; break;
case Type::Category::Magic: case Type::Category::Magic:
@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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)
{ {

View File

@ -163,11 +163,25 @@ 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)) {
success = false; 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;
m_info.stackHeightInfo[&_assignment] = m_stackHeight; m_info.stackHeightInfo[&_assignment] = m_stackHeight;
return success; return success;
} }

View File

@ -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);
} }

View File

@ -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
); );
}; };

View File

@ -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; };

View File

@ -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;

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}; };
} }

View File

@ -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