diff options
author | chriseth <chris@ethereum.org> | 2019-01-22 20:49:41 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-22 20:49:41 +0800 |
commit | 10d17f245839f208ec5085309022a32cd2502f55 (patch) | |
tree | b2c9f68980d0d418cd6f511e9f3f3f71369abe25 | |
parent | 1df8f40cd2fd7b47698d847907b8ca7b47eb488d (diff) | |
parent | 0ecafe032a84cb6960545dd7f18733430c1f782d (diff) | |
download | dexon-solidity-10d17f245839f208ec5085309022a32cd2502f55.tar.gz dexon-solidity-10d17f245839f208ec5085309022a32cd2502f55.tar.zst dexon-solidity-10d17f245839f208ec5085309022a32cd2502f55.zip |
Merge pull request #5836 from ethereum/develop
Merge develop into release for 0.5.3.
388 files changed, 10085 insertions, 5733 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index cec69e31..40aa6268 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,14 +34,14 @@ version: 2 jobs: build_emscripten: docker: - - image: trzeci/emscripten:sdk-tag-1.37.21-64bit + - image: trzeci/emscripten:sdk-tag-1.38.22-64bit environment: TERM: xterm steps: - checkout - 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" }} + key: &boost-cache-key emscripten-boost-{{ checksum "scripts/travis-emscripten/install_deps.sh" }}{{ checksum "scripts/build_emscripten.sh" }}{{ checksum "scripts/travis-emscripten/build_emscripten.sh" }} - run: name: Bootstrap Boost command: | @@ -54,7 +54,7 @@ jobs: name: Save Boost build key: *boost-cache-key paths: - - boost_1_67_0 + - boost_1_68_0 - store_artifacts: path: build/libsolc/soljson.js destination: soljson.js @@ -104,7 +104,7 @@ jobs: test/externalTests.sh /tmp/workspace/soljson.js || test/externalTests.sh /tmp/workspace/soljson.js build_x86_linux: docker: - - image: buildpack-deps:artful + - image: buildpack-deps:bionic environment: TERM: xterm COVERAGE: "ON" @@ -212,7 +212,7 @@ jobs: test_check_style: docker: - - image: buildpack-deps:artful + - image: buildpack-deps:bionic steps: - checkout - run: @@ -238,7 +238,7 @@ jobs: test_x86_linux: docker: - - image: buildpack-deps:artful + - image: buildpack-deps:bionic environment: TERM: xterm steps: @@ -315,7 +315,9 @@ jobs: docs: docker: - - image: buildpack-deps:artful + - image: buildpack-deps:bionic + environment: + DEBIAN_FRONTEND: noninteractive steps: - checkout - run: diff --git a/.travis.yml b/.travis.yml index 8da17c45..6d3d70e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -114,7 +114,7 @@ matrix: before_install: - nvm install 8 - nvm use 8 - - docker pull trzeci/emscripten:sdk-tag-1.37.21-64bit + - docker pull trzeci/emscripten:sdk-tag-1.38.22-64bit env: - SOLC_EMSCRIPTEN=On - SOLC_INSTALL_DEPS_TRAVIS=Off @@ -122,6 +122,16 @@ matrix: - SOLC_TESTS=Off - ZIP_SUFFIX=emscripten - SOLC_STOREBYTECODE=On + # Travis doesn't seem to support "dynamic" cache keys where we could include + # the hashes of certain files. Our CircleCI configuration contains the hash of + # relevant emscripten files. + # + # It is important to invalidate the cache with each emscripten update, because + # dependencies, such as boost, might be broken otherwise. + # + # This key here has no significant on anything, apart from caching. Please keep + # it in sync with the version above. + - EMSCRIPTEN_VERSION_KEY="1.38.22" # OS X Mavericks (10.9) # https://en.wikipedia.org/wiki/OS_X_Mavericks @@ -177,7 +187,7 @@ git: cache: ccache: true directories: - - boost_1_67_0 + - boost_1_68_0 - $HOME/.local install: diff --git a/CMakeLists.txt b/CMakeLists.txt index 399dd9df..1db1d052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,13 +3,15 @@ cmake_minimum_required(VERSION 3.0.0) set(ETH_CMAKE_DIR "${CMAKE_CURRENT_LIST_DIR}/cmake" CACHE PATH "The the path to the cmake directory") list(APPEND CMAKE_MODULE_PATH ${ETH_CMAKE_DIR}) +include(EthToolchains) + # Set cmake_policies include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.5.2") -project(solidity VERSION ${PROJECT_VERSION}) +set(PROJECT_VERSION "0.5.3") +project(solidity VERSION ${PROJECT_VERSION} LANGUAGES CXX) option(LLL "Build LLL" OFF) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) diff --git a/Changelog.md b/Changelog.md index 57c611a3..1238d4b5 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,28 @@ +### 0.5.3 (2019-01-22) + +Language Features: + * Provide access to creation and runtime code of contracts via ``type(C).creationCode`` / ``type(C).runtimeCode``. + + +Compiler Features: + * Control Flow Graph: Warn about unreachable code. + * SMTChecker: Support basic typecasts without truncation. + * SMTChecker: Support external function calls and erase all knowledge regarding storage variables and references. + + +Bugfixes: + * Emscripten: Split simplification rule initialization up further to work around issues with soljson.js in some browsers. + * Type Checker: Disallow calldata structs until implemented. + * Type Checker: Return type error if fixed point encoding is attempted instead of throwing ``UnimplementedFeatureError``. + * Yul: Check that arguments to ``dataoffset`` and ``datasize`` are literals at parse time and properly take this into account in the optimizer. + * Yul: Parse number literals for detecting duplicate switch cases. + * Yul: Require switch cases to have the same type. + + +Build System: + * Emscripten: Upgrade to emscripten 1.38.8 on travis and circleci. + + ### 0.5.2 (2018-12-19) Language Features: @@ -19,6 +19,8 @@ that run on the Ethereum Virtual Machine. Smart contracts are programs that are network where nobody has special authority over the execution and thus they allow to implement tokens of value, ownership, voting and other kinds of logics. +When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number [to indicate this fast pace of change](https://semver.org/#spec-item-4). + ## Build and Install Instructions about how to build and install the Solidity compiler can be found in the [Solidity documentation](https://solidity.readthedocs.io/en/latest/installing-solidity.html#building-from-source) @@ -29,7 +31,7 @@ Instructions about how to build and install the Solidity compiler can be found i A "Hello World" program in Solidity is of even less use than in other languages, but still: ``` -pragma solidity ^0.4.16; +pragma solidity ^0.5.0; contract HelloWorld { function helloWorld() external pure returns (string memory) { diff --git a/ReleaseChecklist.md b/ReleaseChecklist.md index 04734544..2610daa2 100644 --- a/ReleaseChecklist.md +++ b/ReleaseChecklist.md @@ -4,13 +4,15 @@ Checklist for making a release: - [ ] Check that all issues and pull requests from the Github project to be released are merged to ``develop``. - [ ] Create a commit in ``develop`` that updates the ``Changelog`` to include a release date (run ``./scripts/tests.sh`` to update the bug list). Sort the changelog entries alphabetically and correct any errors you notice. - [ ] Create a pull request and wait for the tests, merge it. + - [ ] Thank voluntary contributors in the Github release page (use ``git shortlog -s -n -e origin/release..origin/develop``). - [ ] Create a pull request from ``develop`` to ``release``, wait for the tests, then merge it. - - [ ] Make a final check that there are no platform-dependency issues in the ``solc-test-bytecode`` repository. + - [ ] Make a final check that there are no platform-dependency issues in the ``solidity-test-bytecode`` repository. - [ ] Wait for the tests for the commit on ``release``, create a release in Github, creating the tag. - - [ ] Thank voluntary contributors in the Github release page (use ``git shortlog -s -n -e origin/release..origin/develop``). - [ ] Wait for the CI runs on the tag itself (they should push artifacts onto the Github release page). + - [ ] Run ``scripts/create_source_tarball.sh`` while being on the tag to create the source tarball. + - [ ] Upload the source tarball (in the upload directory) to the release page. - [ ] Run ``scripts/release_ppa.sh release`` to create the PPA release (you need the relevant openssl key). - - [ ] Once the ``~ethereum/ubuntu/ethereum-static`` PPA build is finished and published for all platforms (make sure not to do this earlier), copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty`` while selecting ``Copy existing binaries``. + - [ ] Once the ``~ethereum/ubuntu/ethereum-static`` PPA build is finished and published for all platforms (make sure not to do this earlier), copy the static package to the ``~ethereum/ubuntu/ethereum`` PPA for the destination series ``Trusty`` and ``Xenial`` while selecting ``Copy existing binaries``. - [ ] Check that the Docker release was pushed to Docker Hub (this still seems to have problems, run ``./scripts/docker_deploy_manual.sh release``). - [ ] Update the homebrew realease in https://github.com/ethereum/homebrew-ethereum/blob/master/solidity.rb (version and hash) - [ ] Update the default version on readthedocs. diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 70505fdb..d05ccaff 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -24,10 +24,6 @@ endif() eth_add_cxx_compiler_flag_if_supported(-Wimplicit-fallthrough) if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) - - # Use ISO C++14 standard language. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") - # Enables all the warnings about constructions that some users consider questionable, # and that are easy to avoid. Also enable some extra warning flags that are not # enabled by -Wall. Finally, treat at warnings-as-errors, which forces developers @@ -78,10 +74,8 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # into errors, which makes sense. # http://stackoverflow.com/questions/21617158/how-to-silence-unused-command-line-argument-error-with-clang-without-disabling-i add_compile_options(-Qunused-arguments) - endif() - if (EMSCRIPTEN) - # Do not emit a separate memory initialiser file + elseif(EMSCRIPTEN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --memory-init-file 0") # Leave only exported symbols as public and aggressively remove others set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -Wl,--gc-sections -fvisibility=hidden") @@ -104,7 +98,13 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA # Abort if linking results in any undefined symbols # Note: this is on by default in the CMake Emscripten module which we aren't using set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s ERROR_ON_UNDEFINED_SYMBOLS=1") - add_definitions(-DETH_EMSCRIPTEN=1) + # Disallow deprecated emscripten build options. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s STRICT=1") + # Export the Emscripten-generated auxiliary methods which are needed by solc-js. + # Which methods of libsolc itself are exported is specified in libsolc/CMakeLists.txt. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap','addFunction','removeFunction','Pointer_stringify','lengthBytesUTF8','_malloc','stringToUTF8','setValue']") + # Do not build as a WebAssembly target - we need an asm.js output. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s WASM=0") endif() endif() @@ -166,9 +166,8 @@ option(USE_CVC4 "Allow compiling with CVC4 SMT solver integration" ON) if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) option(USE_LD_GOLD "Use GNU gold linker" ON) if (USE_LD_GOLD) - execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) if ("${LD_VERSION}" MATCHES "GNU gold") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=gold") endif () endif () diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index cc2f8711..477a604d 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -5,6 +5,9 @@ function(eth_show_dependency DEP NAME) get_property(DISPLAYED GLOBAL PROPERTY ETH_${DEP}_DISPLAYED) if (NOT DISPLAYED) set_property(GLOBAL PROPERTY ETH_${DEP}_DISPLAYED TRUE) + if (NOT("${${DEP}_VERSION}" STREQUAL "")) + message(STATUS "${NAME} version: ${${DEP}_VERSION}") + endif() message(STATUS "${NAME} headers: ${${DEP}_INCLUDE_DIRS}") message(STATUS "${NAME} lib : ${${DEP}_LIBRARIES}") if (NOT("${${DEP}_DLLS}" STREQUAL "")) @@ -38,6 +41,6 @@ set(ETH_SCRIPTS_DIR ${ETH_CMAKE_DIR}/scripts) set(Boost_USE_MULTITHREADED ON) option(Boost_USE_STATIC_LIBS "Link Boost statically" ON) -find_package(Boost 1.54.0 QUIET REQUIRED COMPONENTS regex filesystem unit_test_framework program_options system) +find_package(Boost 1.65.0 QUIET REQUIRED COMPONENTS regex filesystem unit_test_framework program_options system) eth_show_dependency(Boost boost) diff --git a/cmake/EthToolchains.cmake b/cmake/EthToolchains.cmake new file mode 100644 index 00000000..a4263b7d --- /dev/null +++ b/cmake/EthToolchains.cmake @@ -0,0 +1,8 @@ +if(NOT CMAKE_TOOLCHAIN_FILE) + # Use default toolchain file if none is provided. + set( + CMAKE_TOOLCHAIN_FILE + "${CMAKE_CURRENT_LIST_DIR}/toolchains/default.cmake" + CACHE FILEPATH "The CMake toolchain file" + ) +endif() diff --git a/cmake/jsoncpp.cmake b/cmake/jsoncpp.cmake index 4db7b9c3..4ca8581d 100644 --- a/cmake/jsoncpp.cmake +++ b/cmake/jsoncpp.cmake @@ -35,7 +35,6 @@ ExternalProject_Add(jsoncpp-project URL_HASH SHA256=c49deac9e0933bcb7044f08516861a2d560988540b23de2ac1ad443b219afdb6 CMAKE_COMMAND ${JSONCPP_CMAKE_COMMAND} CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR> - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_INSTALL_LIBDIR=lib # Build static lib but suitable to be included in a shared lib. diff --git a/cmake/toolchains/default.cmake b/cmake/toolchains/default.cmake new file mode 100644 index 00000000..baf859b7 --- /dev/null +++ b/cmake/toolchains/default.cmake @@ -0,0 +1,4 @@ +# Require C++14. +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) +set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/cmake/toolchains/emscripten.cmake b/cmake/toolchains/emscripten.cmake new file mode 100644 index 00000000..6c29074f --- /dev/null +++ b/cmake/toolchains/emscripten.cmake @@ -0,0 +1,2 @@ +include("${CMAKE_CURRENT_LIST_DIR}/default.cmake") +include("$ENV{EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake") diff --git a/docs/050-breaking-changes.rst b/docs/050-breaking-changes.rst index 48112cd9..01d21c8c 100644 --- a/docs/050-breaking-changes.rst +++ b/docs/050-breaking-changes.rst @@ -171,7 +171,7 @@ Command Line and JSON Interfaces the first 36 hex characters of the keccak256 hash of the fully qualified library name, surrounded by ``$...$``. Previously, just the fully qualified library name was used. - This recudes the chances of collisions, especially when long paths are used. + This reduces the chances of collisions, especially when long paths are used. Binary files now also contain a list of mappings from these placeholders to the fully qualified names. @@ -308,7 +308,7 @@ This will no longer compile with Solidity v0.5.0. However, you can define a comp :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; interface OldContract { function someOldFunction(uint8 a) external; function anotherOldFunction() external returns (bool); @@ -325,7 +325,7 @@ Given the interface defined above, you can now easily use the already deployed p :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; interface OldContract { function someOldFunction(uint8 a) external; @@ -345,7 +345,7 @@ commandline compiler for linking): :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; library OldLibrary { function someFunction(uint8 a) public returns(bool); @@ -430,7 +430,7 @@ New version: :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract OtherContract { uint x; diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 0f4a16b6..26293a1f 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -471,7 +471,7 @@ For example, :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract Test { constructor() public { b = hex"12345678901234567890123456789012"; } @@ -597,7 +597,7 @@ Strict encoding mode is the mode that leads to exactly the same encoding as defi This means offsets have to be as small as possible while still not creating overlaps in the data areas and thus no gaps are allowed. -Usually, ABI decoders are written in a straigthforward way just following offset pointers, but some decoders +Usually, ABI decoders are written in a straightforward way just following offset pointers, but some decoders might enforce strict mode. The Solidity ABI decoder currently does not enforce strict mode, but the encoder always creates data in strict mode. @@ -609,22 +609,30 @@ Through ``abi.encodePacked()``, Solidity supports a non-standard packed mode whe - types shorter than 32 bytes are neither zero padded nor sign extended and - dynamic types are encoded in-place and without the length. -As an example encoding ``int8, bytes1, uint16, string`` with values ``-1, 0x42, 0x2424, "Hello, world!"`` results in: +This packed mode is mainly used for indexed event parameters. + +As an example, the encoding of ``int16(-1), bytes1(0x42), uint16(0x03), string("Hello, world!")`` results in: .. code-block:: none - 0xff42242448656c6c6f2c20776f726c6421 - ^^ int8(-1) - ^^ bytes1(0x42) - ^^^^ uint16(0x2424) - ^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field + 0xffff42000348656c6c6f2c20776f726c6421 + ^^^^ int16(-1) + ^^ bytes1(0x42) + ^^^^ uint16(0x03) + ^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a length field + +More specifically: + - Each value type takes as many bytes as its range has. + - The encoding of a struct or fixed-size array is the concatenation of the + encoding of its members/elements without any separator or padding. + - Mapping members of structs are ignored as usual. + - Dynamically-sized types like ``string``, ``bytes`` or ``uint[]`` are encoded without + their length field. -More specifically, each statically-sized type takes as many bytes as its range has -and dynamically-sized types like ``string``, ``bytes`` or ``uint[]`` are encoded without -their length field. This means that the encoding is ambiguous as soon as there are two -dynamically-sized elements. +In general, the encoding is ambiguous as soon as there are two dynamically-sized elements, +because of the missing length field. If padding is needed, explicit type conversions can be used: ``abi.encodePacked(uint16(0x12)) == hex"0012"``. Since packed encoding is not used when calling functions, there is no special support -for prepending a function selector. +for prepending a function selector. Since the encoding is ambiguous, there is no decoding function. diff --git a/docs/bugs.json b/docs/bugs.json index 28c0fe62..41ebce7b 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -43,7 +43,7 @@ { "name": "DelegateCallReturnValue", "summary": "The low-level .delegatecall() does not return the execution outcome, but converts the value returned by the functioned called to a boolean instead.", - "description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successuful.", + "description": "The return value of the low-level .delegatecall() function is taken from a position in memory, where the call data or the return data resides. This value is interpreted as a boolean and put onto the stack. This means if the called function returns at least 32 zero bytes, .delegatecall() returns false even if the call was successful.", "introduced": "0.3.0", "fixed": "0.4.15", "severity": "low" diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json index 438abbdd..e1581293 100644 --- a/docs/bugs_by_version.json +++ b/docs/bugs_by_version.json @@ -620,5 +620,9 @@ "0.5.2": { "bugs": [], "released": "2018-12-19" + }, + "0.5.3": { + "bugs": [], + "released": "2019-01-22" } }
\ No newline at end of file diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index 84c18936..65bf7f44 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -28,7 +28,7 @@ become the new richest. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract WithdrawalContract { address public richest; @@ -65,7 +65,7 @@ This is as opposed to the more intuitive sending pattern: :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract SendContract { address payable public richest; @@ -117,7 +117,7 @@ to read the data, so will everyone else. You can restrict read access to your contract's state by **other contracts**. That is actually the default -unless you declare make your state variables ``public``. +unless you declare your state variables ``public``. Furthermore, you can restrict who can make modifications to your contract's state or call your contract's diff --git a/docs/conf.py b/docs/conf.py index 233ff7b6..87a6ec03 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2016-2018, Ethereum' +copyright = '2016-2019, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -81,7 +81,7 @@ else: # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ['_build', 'contracts', 'types', 'examples'] # The reST default role (used for this markup: `text`) to use for all # documents. diff --git a/docs/contracts.rst b/docs/contracts.rst index 682cb378..5bab6e78 100644 --- a/docs/contracts.rst +++ b/docs/contracts.rst @@ -10,1764 +10,25 @@ Contracts in Solidity are similar to classes in object-oriented languages. They contain persistent data in state variables and functions that can modify these variables. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables are -inaccessible. +inaccessible. A contract and its functions need to be called for anything to happen. +There is no "cron" concept in Ethereum to call a function at a particular event automatically. -.. index:: ! contract;creation, constructor +.. include:: contracts/creating-contracts.rst -****************** -Creating Contracts -****************** +.. include:: contracts/visibility-and-getters.rst -Contracts can be created "from outside" via Ethereum transactions or from within Solidity contracts. +.. include:: contracts/function-modifiers.rst -IDEs, such as `Remix <https://remix.ethereum.org/>`_, make the creation process seamless using UI elements. +.. include:: contracts/constant-state-variables.rst +.. include:: contracts/functions.rst -Creating contracts programmatically on Ethereum is best done via using the JavaScript API `web3.js <https://github.com/ethereum/web3.js>`_. -It has a function called `web3.eth.Contract <https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#new-contract>`_ -to facilitate contract creation. +.. include:: contracts/events.rst -When a contract is created, its constructor_ (a function declared with the ``constructor`` keyword) is executed once. +.. include:: contracts/inheritance.rst -A constructor is optional. Only one constructor is allowed, which means -overloading is not supported. +.. include:: contracts/abstract-contracts.rst +.. include:: contracts/interfaces.rst -After the constructor has executed, the final code of the contract is deployed to the -blockchain. This code includes all public and external functions and all functions -that are reachable from there through function calls. The deployed code does not -include the constructor code or internal functions only called from the constructor. +.. include:: contracts/libraries.rst -.. index:: constructor;arguments - -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 if you use ``web3.js``. - -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. -This means that cyclic creation dependencies are impossible. - -:: - - pragma solidity >=0.4.22 <0.6.0; - - contract OwnedToken { - // `TokenCreator` is a contract type that is defined below. - // It is fine to reference it as long as it is not used - // to create a new contract. - TokenCreator creator; - address owner; - bytes32 name; - - // This is the constructor which registers the - // creator and the assigned name. - constructor(bytes32 _name) public { - // State variables are accessed via their name - // and not via e.g. `this.owner`. Functions can - // be accessed directly or through `this.f`, - // but the latter provides an external view - // to the function. Especially in the constructor, - // you should not access functions externally, - // because the function does not exist yet. - // See the next section for details. - owner = msg.sender; - - // We do an explicit type conversion from `address` - // to `TokenCreator` and assume that the type of - // the calling contract is `TokenCreator`, there is - // no real way to check that. - creator = TokenCreator(msg.sender); - name = _name; - } - - function changeName(bytes32 newName) public { - // Only the creator can alter the name -- - // the comparison is possible since contracts - // are explicitly convertible to addresses. - if (msg.sender == address(creator)) - name = newName; - } - - function transfer(address newOwner) public { - // Only the current owner can transfer the token. - if (msg.sender != owner) return; - - // We ask the creator contract if the transfer - // should proceed by using a function of the - // `TokenCreator` contract defined below. If - // the call fails (e.g. due to out-of-gas), - // the execution also fails here. - if (creator.isTokenTransferOK(owner, newOwner)) - owner = newOwner; - } - } - - contract TokenCreator { - function createToken(bytes32 name) - public - returns (OwnedToken tokenAddress) - { - // Create a new `Token` contract and return its address. - // From the JavaScript side, the return type is - // `address`, as this is the closest type available in - // the ABI. - return new OwnedToken(name); - } - - function changeName(OwnedToken tokenAddress, bytes32 name) public { - // Again, the external type of `tokenAddress` is - // simply `address`. - tokenAddress.changeName(name); - } - - // Perform checks to determine if transferring a token to the - // `OwnedToken` contract should proceed - function isTokenTransferOK(address currentOwner, address newOwner) - public - pure - returns (bool ok) - { - // Check an arbitrary condition to see if transfer should proceed - return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f; - } - } - -.. index:: ! visibility, external, public, private, internal - -.. _visibility-and-getters: - -********************** -Visibility and Getters -********************** - -Since Solidity knows two kinds of function calls (internal -ones that do not create an actual EVM call (also called -a "message call") and external -ones that do), there are four types of visibilities for -functions and state variables. - -Functions have to be specified as being ``external``, -``public``, ``internal`` or ``private``. -For state variables, ``external`` is not possible. - -``external``: - External functions are part of the contract interface, - which means they can be called from other contracts and - via transactions. An external function ``f`` cannot be called - internally (i.e. ``f()`` does not work, but ``this.f()`` works). - External functions are sometimes more efficient when - they receive large arrays of data. - -``public``: - Public functions are part of the contract interface - and can be either called internally or via - messages. For public state variables, an automatic getter - function (see below) is generated. - -``internal``: - Those functions and state variables can only be - accessed internally (i.e. from within the current contract - or contracts deriving from it), without using ``this``. - -``private``: - Private functions and state variables are only - visible for the contract they are defined in and not in - derived contracts. - -.. note:: - Everything that is inside a contract is visible to - all observers external to the blockchain. Making something ``private`` - only prevents other contracts from accessing and modifying - the information, but it will still be visible to the - whole world outside of the blockchain. - -The visibility specifier is given after the type for -state variables and between parameter list and -return parameter list for functions. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract C { - function f(uint a) private pure returns (uint b) { return a + 1; } - function setData(uint a) internal { data = a; } - uint public data; - } - -In the following example, ``D``, can call ``c.getData()`` to retrieve the value of -``data`` in state storage, but is not able to call ``f``. Contract ``E`` is derived from -``C`` and, thus, can call ``compute``. - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract C { - uint private data; - - function f(uint a) private pure returns(uint b) { return a + 1; } - function setData(uint a) public { data = a; } - function getData() public view returns(uint) { return data; } - function compute(uint a, uint b) internal pure returns (uint) { return a + b; } - } - - // This will not compile - contract D { - function readData() public { - C c = new C(); - uint local = c.f(7); // error: member `f` is not visible - c.setData(3); - local = c.getData(); - local = c.compute(3, 5); // error: member `compute` is not visible - } - } - - contract E is C { - function g() public { - C c = new C(); - uint val = compute(3, 5); // access to internal member (from derived to parent contract) - } - } - -.. index:: ! getter;function, ! function;getter -.. _getter-functions: - -Getter Functions -================ - -The compiler automatically creates getter functions for -all **public** state variables. For the contract given below, the compiler will -generate a function called ``data`` that does not take any -arguments and returns a ``uint``, the value of the state -variable ``data``. State variables can be initialized -when they are declared. - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract C { - uint public data = 42; - } - - contract Caller { - C c = new C(); - function f() public view returns (uint) { - return c.data(); - } - } - -The getter functions have external visibility. If the -symbol is accessed internally (i.e. without ``this.``), -it evaluates to a state variable. If it is accessed externally -(i.e. with ``this.``), it evaluates to a function. - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract C { - uint public data; - function x() public returns (uint) { - data = 3; // internal access - return this.data(); // external access - } - } - -If you have a ``public`` state variable of array type, then you can only retrieve -single elements of the array via the generated getter function. This mechanism -exists to avoid high gas costs when returning an entire array. You can use -arguments to specify which individual element to return, for example -``data(0)``. If you want to return an entire array in one call, then you need -to write a function, for example: - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract arrayExample { - // public state variable - uint[] public myArray; - - // Getter function generated by the compiler - /* - function myArray(uint i) returns (uint) { - return myArray[i]; - } - */ - - // function that returns entire array - function getArray() returns (uint[] memory) { - return myArray; - } - } - -Now you can use ``getArray()`` to retrieve the entire array, instead of -``myArray(i)``, which returns a single element per call. - -The next example is more complex: - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract Complex { - struct Data { - uint a; - bytes3 b; - mapping (uint => uint) map; - } - mapping (uint => mapping(bool => Data[])) public data; - } - -It generates a function of the following form. The mapping in the struct is omitted -because there is no good way to provide the key for the mapping: - -:: - - function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) { - a = data[arg1][arg2][arg3].a; - b = data[arg1][arg2][arg3].b; - } - -.. index:: ! function;modifier - -.. _modifiers: - -****************** -Function Modifiers -****************** - -Modifiers can be used to easily change the behaviour of functions. For example, -they can automatically check a condition prior to executing the function. Modifiers are -inheritable properties of contracts and may be overridden by derived contracts. - -:: - - pragma solidity >0.4.99 <0.6.0; - - contract owned { - constructor() public { owner = msg.sender; } - address payable owner; - - // This contract only defines a modifier but does not use - // it: it will be used in derived contracts. - // The function body is inserted where the special symbol - // `_;` in the definition of a modifier appears. - // This means that if the owner calls this function, the - // function is executed and otherwise, an exception is - // thrown. - modifier onlyOwner { - require( - msg.sender == owner, - "Only owner can call this function." - ); - _; - } - } - - contract mortal is owned { - // This contract inherits the `onlyOwner` modifier from - // `owned` and applies it to the `close` function, which - // causes that calls to `close` only have an effect if - // they are made by the stored owner. - function close() public onlyOwner { - selfdestruct(owner); - } - } - - contract priced { - // Modifiers can receive arguments: - modifier costs(uint price) { - if (msg.value >= price) { - _; - } - } - } - - contract Register is priced, owned { - mapping (address => bool) registeredAddresses; - uint price; - - constructor(uint initialPrice) public { price = initialPrice; } - - // It is important to also provide the - // `payable` keyword here, otherwise the function will - // automatically reject all Ether sent to it. - function register() public payable costs(price) { - registeredAddresses[msg.sender] = true; - } - - function changePrice(uint _price) public onlyOwner { - price = _price; - } - } - - contract Mutex { - bool locked; - modifier noReentrancy() { - require( - !locked, - "Reentrant call." - ); - locked = true; - _; - locked = false; - } - - /// This function is protected by a mutex, which means that - /// reentrant calls from within `msg.sender.call` cannot call `f` again. - /// The `return 7` statement assigns 7 to the return value but still - /// executes the statement `locked = false` in the modifier. - function f() public noReentrancy returns (uint) { - (bool success,) = msg.sender.call(""); - require(success); - return 7; - } - } - -Multiple modifiers are applied to a function by specifying them in a -whitespace-separated list and are evaluated in the order presented. - -.. warning:: - In an earlier version of Solidity, ``return`` statements in functions - having modifiers behaved differently. - -Explicit returns from a modifier or function body only leave the current -modifier or function body. Return variables are assigned and -control flow continues after the "_" in the preceding modifier. - -Arbitrary expressions are allowed for modifier arguments and in this context, -all symbols visible from the function are visible in the modifier. Symbols -introduced in the modifier are not visible in the function (as they might -change by overriding). - -.. index:: ! constant - -************************ -Constant State Variables -************************ - -State variables can be declared as ``constant``. In this case, they have to be -assigned from an expression which is a constant at compile time. Any expression -that accesses storage, blockchain data (e.g. ``now``, ``address(this).balance`` or -``block.number``) or -execution data (``msg.value`` or ``gasleft()``) or makes calls to external contracts is disallowed. Expressions -that might have a side-effect on memory allocation are allowed, but those that -might have a side-effect on other memory objects are not. The built-in functions -``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod`` -are allowed (even though, with the exception of ``keccak256``, they do call external contracts). - -The reason behind allowing side-effects on the memory allocator is that it -should be possible to construct complex objects like e.g. lookup-tables. -This feature is not yet fully usable. - -The compiler does not reserve a storage slot for these variables, and every occurrence is -replaced by the respective constant expression (which might be computed to a single value by the optimizer). - -Not all types for constants are implemented at this time. The only supported types are -value types and strings. - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract C { - uint constant x = 32**22 + 8; - string constant text = "abc"; - bytes32 constant myHash = keccak256("abc"); - } - -.. index:: ! functions - -.. _functions: - -********* -Functions -********* - -.. _function-parameters-return-variables: - -Function Parameters and Return Variables -======================================== - -As in JavaScript, functions may take parameters as input. Unlike in JavaScript -and C, functions may also return an arbitrary number of values as output. - -Function Parameters -------------------- - -Function parameters are declared the same way as variables, and the name of -unused parameters can be omitted. - -For example, if you want your contract to accept one kind of external call -with two integers, you would use something like:: - - pragma solidity >=0.4.16 <0.6.0; - - contract Simple { - uint sum; - function taker(uint _a, uint _b) public { - sum = _a + _b; - } - } - -Function parameters can be used as any other local variable and they can also be assigned to. - -.. note:: - - An :ref:`external function<external-function-calls>` cannot accept a - multi-dimensional array as an input - parameter. This functionality is possible if you enable the new - experimental ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file. - - An :ref:`internal function<external-function-calls>` can accept a - multi-dimensional array without enabling the feature. - -.. index:: return array, return string, array, string, array of strings, dynamic array, variably sized array, return struct, struct - -Return Variables ----------------- - -Function return variables are declared with the same syntax after the -``returns`` keyword. - -For example, suppose you want to return two results: the sum and the product of -two integers passed as function parameters, then you use something like:: - - pragma solidity >=0.4.16 <0.6.0; - - contract Simple { - function arithmetic(uint _a, uint _b) - public - pure - returns (uint o_sum, uint o_product) - { - o_sum = _a + _b; - o_product = _a * _b; - } - } - -The names of return variables can be omitted. -Return variables can be used as any other local variable and they -are initialized with their :ref:`default value <default-value>` and have that value unless explicitly set. - -You can either explicitly assign to return variables and -then leave the function using ``return;``, -or you can provide return values -(either a single or :ref:`multiple ones<multi-return>`) directly with the ``return`` -statement:: - - pragma solidity >=0.4.16 <0.6.0; - - contract Simple { - function arithmetic(uint _a, uint _b) - public - pure - returns (uint o_sum, uint o_product) - { - return (_a + _b, _a * _b); - } - } - -This form is equivalent to first assigning values to the -return variables and then using ``return;`` to leave the function. - -.. note:: - You cannot return some types from non-internal functions, notably - multi-dimensional dynamic arrays and structs. If you enable the - new experimental ``ABIEncoderV2`` feature by adding ``pragma experimental - ABIEncoderV2;`` to your source file then more types are available, but - ``mapping`` types are still limited to inside a single contract and you - cannot transfer them. - -.. _multi-return: - -Returning Multiple Values -------------------------- - -When a function has multiple return types, the statement ``return (v0, v1, ..., vn) can be used to return multiple values. -vn)`` can return multiple values. The number of components must be -the same as the number of return types. - -.. index:: ! view function, function;view - -.. _view-functions: - -View Functions -============== - -Functions can be declared ``view`` in which case they promise not to modify the state. - -.. note:: - If the compiler's EVM target is Byzantium or newer (default) the opcode - ``STATICCALL`` is used for ``view`` functions which enforces the state - to stay unmodified as part of the EVM execution. For library ``view`` functions - ``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``. - This means library ``view`` functions do not have run-time checks that prevent state - modifications. This should not impact security negatively because library code is - usually known at compile-time and the static checker performs compile-time checks. - -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.99 <0.6.0; - - contract C { - function f(uint a, uint b) public view returns (uint) { - return a * (b + 42) + now; - } - } - -.. note:: - ``constant`` on functions used to be an alias to ``view``, but this was dropped in version 0.5.0. - -.. note:: - Getter methods are automatically marked ``view``. - -.. note:: - Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode - for ``view`` functions. - This enabled state modifications in ``view`` functions through the use of - invalid explicit type conversions. - By using ``STATICCALL`` for ``view`` functions, modifications to the - state are prevented on the level of the EVM. - -.. index:: ! pure function, function;pure - -.. _pure-functions: - -Pure Functions -============== - -Functions can be declared ``pure`` in which case they promise not to read from or modify the state. - -.. note:: - If the compiler's EVM target is Byzantium or newer (default) the opcode ``STATICCALL`` is used, - which does not guarantee that the state is not read, but at least that it is not modified. - -In addition to the list of state modifying statements explained above, the following are considered reading from the state: - -#. Reading from state variables. -#. Accessing ``address(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.99 <0.6.0; - - contract C { - function f(uint a, uint b) public pure returns (uint) { - return a * (b + 42); - } - } - -Pure functions are able to use the `revert()` and `require()` functions to revert -potential state changes when an :ref:`error occurs <assert-and-require>`. - -Reverting a state change is not considered a "state modification", as only changes to the -state made previously in code that did not have the ``view`` or ``pure`` restriction -are reverted and that code has the option to catch the ``revert`` and not pass it on. - -This behaviour is also in line with the ``STATICCALL`` opcode. - -.. warning:: - It is not possible to prevent functions from reading the state at the level - of the EVM, it is only possible to prevent them from writing to the state - (i.e. only ``view`` can be enforced at the EVM level, ``pure`` can not). - -.. note:: - Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode - for ``pure`` functions. - This enabled state modifications in ``pure`` functions through the use of - invalid explicit type conversions. - By using ``STATICCALL`` for ``pure`` functions, modifications to the - state are prevented on the level of the EVM. - -.. note:: - Prior to version 0.4.17 the compiler did not enforce that ``pure`` is not reading the state. - It is a compile-time type check, which can be circumvented doing invalid explicit conversions - between contract types, because the compiler can verify that the type of the contract does - not do state-changing operations, but it cannot check that the contract that will be called - at runtime is actually of that type. - -.. index:: ! fallback function, function;fallback - -.. _fallback-function: - -Fallback Function -================= - -A contract can have exactly one unnamed function. This function cannot have -arguments, cannot return anything and has to have ``external`` visibility. -It is executed on a call to the contract if none of the other -functions match the given function identifier (or if no data was supplied at -all). - -Furthermore, this function is executed whenever the contract receives plain -Ether (without data). Additionally, in order to receive Ether, the fallback function -must be marked ``payable``. If no such function exists, the contract cannot receive -Ether through regular transactions. - -In the worst case, the fallback function can only rely on 2300 gas being -available (for example when `send` or `transfer` is used), leaving little -room to perform other operations except basic logging. The following operations -will consume more gas than the 2300 gas stipend: - -- Writing to storage -- Creating a contract -- Calling an external function which consumes a large amount of gas -- Sending Ether - -Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. - -.. note:: - Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve - any payload supplied with the call. - -.. warning:: - The fallback function is also executed if the caller meant to call - a function that is not available. If you want to implement the fallback - function only to receive ether, you should add a check - like ``require(msg.data.length == 0)`` to prevent invalid calls. - -.. warning:: - Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) - but do not define a fallback function - throw an exception, sending back the Ether (this was different - before Solidity v0.4.0). So if you want your contract to receive Ether, - you have to implement a payable 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 ``address(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.99 <0.6.0; - - contract Test { - // This function is called for all messages sent to - // this contract (there is no other function). - // Sending Ether to this contract will cause an exception, - // because the fallback function does not have the `payable` - // modifier. - function() external { x = 1; } - uint x; - } - - - // This contract keeps all Ether sent to it with no way - // to get it back. - contract Sink { - function() external payable { } - } - - contract Caller { - function callTest(Test test) public returns (bool) { - (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); - require(success); - // results in test.x becoming == 1. - - // address(test) will not allow to call ``send`` directly, since ``test`` has no payable - // fallback function. It has to be converted to the ``address payable`` type via an - // intermediate conversion to ``uint160`` to even allow calling ``send`` on it. - address payable testPayable = address(uint160(address(test))); - - // If someone sends ether to that contract, - // the transfer will fail, i.e. this returns false here. - return testPayable.send(2 ether); - } - } - -.. index:: ! overload - -.. _overload-function: - -Function Overloading -==================== - -A contract can have multiple functions of the same name but with different parameter -types. -This process is called "overloading" and also applies to inherited functions. -The following example shows overloading of the function -``f`` in the scope of contract ``A``. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract A { - function f(uint _in) public pure returns (uint out) { - out = _in; - } - - function f(uint _in, bool _really) public pure returns (uint out) { - if (_really) - out = _in; - } - } - -Overloaded functions are also present in the external interface. It is an error if two -externally visible functions differ by their Solidity types but not by their external types. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - // This will not compile - contract A { - function f(B _in) public pure returns (B out) { - out = _in; - } - - function f(address _in) public pure returns (address out) { - out = _in; - } - } - - contract B { - } - - -Both ``f`` function overloads above end up accepting the address type for the ABI although -they are considered different inside Solidity. - -Overload resolution and Argument matching ------------------------------------------ - -Overloaded functions are selected by matching the function declarations in the current scope -to the arguments supplied in the function call. Functions are selected as overload candidates -if all arguments can be implicitly converted to the expected types. If there is not exactly one -candidate, resolution fails. - -.. note:: - Return parameters are not taken into account for overload resolution. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract A { - function f(uint8 _in) public pure returns (uint8 out) { - out = _in; - } - - function f(uint256 _in) public pure returns (uint256 out) { - out = _in; - } - } - -Calling ``f(50)`` would create a type error since ``50`` can be implicitly converted both to ``uint8`` -and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly -converted to ``uint8``. - -.. index:: ! event - -.. _events: - -****** -Events -****** - -Solidity events give an abstraction on top of the EVM's logging functionality. -Applications can subscribe and listen to these events through the RPC interface of an Ethereum client. - -Events are inheritable members of contracts. When you call them, they cause the -arguments to be stored in the transaction's log - a special data structure -in the blockchain. These logs are associated with the address of the contract, -are incorporated into the blockchain, and stay there as long as a block is -accessible (forever as of the Frontier and Homestead releases, but this might -change with Serenity). The Log and its event data is not accessible from within -contracts (not even from the contract that created them). - -It is possible to request a simple payment verification (SPV) for logs, so if -an external entity supplies a contract with such a verification, it can check -that the log actually exists inside the blockchain. You have to supply block headers -because the contract can only see the last 256 block hashes. - -You can add the attribute ``indexed`` to up to three parameters which adds them -to a special data structure known as :ref:`"topics" <abi_events>` instead of -the data part of the log. If you use arrays (including ``string`` and ``bytes``) -as indexed arguments, its Keccak-256 hash is stored as a topic instead, this is -because a topic can only hold a single word (32 bytes). - -All parameters without the ``indexed`` attribute are :ref:`ABI-encoded <ABI>` -into the data part of the log. - -Topics allow you to search for events, for example when filtering a sequence of -blocks for certain events. You can also filter events by the address of the -contract that emitted the event. - -For example, the code below uses the web3.js ``subscribe("logs")`` -`method <https://web3js.readthedocs.io/en/1.0/web3-eth-subscribe.html#subscribe-logs>`_ to filter -logs that match a topic with a certain address value: - -.. code-block:: javascript - - var options = { - fromBlock: 0, - address: web3.eth.defaultAccount, - topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null] - }; - web3.eth.subscribe('logs', options, function (error, result) { - if (!error) - console.log(result); - }) - .on("data", function (log) { - console.log(log); - }) - .on("changed", function (log) { - }); - - -The hash of the signature of the event is one of the topics, except if you -declared the event with the ``anonymous`` specifier. This means that it is -not possible to filter for specific anonymous events by name. - -:: - - pragma solidity >=0.4.21 <0.6.0; - - contract ClientReceipt { - event Deposit( - address indexed _from, - bytes32 indexed _id, - uint _value - ); - - function deposit(bytes32 _id) public payable { - // Events are emitted using `emit`, followed by - // the name of the event and the arguments - // (if any) in parentheses. Any such invocation - // (even deeply nested) can be detected from - // the JavaScript API by filtering for `Deposit`. - emit Deposit(msg.sender, _id, msg.value); - } - } - -The use in the JavaScript API is as follows: - -:: - - var abi = /* abi as generated by the compiler */; - var ClientReceipt = web3.eth.contract(abi); - var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */); - - var event = clientReceipt.Deposit(); - - // watch for changes - event.watch(function(error, result){ - // result contains non-indexed arguments and topics - // given to the `Deposit` call. - if (!error) - console.log(result); - }); - - - // Or pass a callback to start watching immediately - var event = clientReceipt.Deposit(function(error, result) { - if (!error) - console.log(result); - }); - -The output of the above looks like the following (trimmed): - -.. code-block:: json - - { - "returnValues": { - "_from": "0x1111…FFFFCCCC", - "_id": "0x50…sd5adb20", - "_value": "0x420042" - }, - "raw": { - "data": "0x7f…91385", - "topics": ["0xfd4…b4ead7", "0x7f…1a91385"] - } - } - -.. index:: ! log - -Low-Level Interface to Logs -=========================== - -It is also possible to access the low-level interface to the logging -mechanism via the functions ``log0``, ``log1``, ``log2``, ``log3`` and ``log4``. -``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first -argument will be used for the data part of the log and the others -as topics. The event call above can be performed in the same way as - -:: - - pragma solidity >=0.4.10 <0.6.0; - - contract C { - function f() public payable { - uint256 _id = 0x420042; - log3( - bytes32(msg.value), - bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), - bytes32(uint256(msg.sender)), - bytes32(_id) - ); - } - } - -where the long hexadecimal number is equal to -``keccak256("Deposit(address,bytes32,uint256)")``, the signature of the event. - -Additional Resources for Understanding Events -============================================== - -- `Javascript documentation <https://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events>`_ -- `Example usage of events <https://github.com/debris/smart-exchange/blob/master/lib/contracts/SmartExchange.sol>`_ -- `How to access them in js <https://github.com/debris/smart-exchange/blob/master/lib/exchange_transactions.js>`_ - -.. index:: ! inheritance, ! base class, ! contract;base, ! deriving - -*********** -Inheritance -*********** - -Solidity supports multiple inheritance including polymorphism. - -All function calls are virtual, which means that the most derived function -is called, except when the contract name is explicitly given or the -``super`` keyword is used. - -When a contract inherits from other contracts, only a single -contract is created on the blockchain, and the code from all the base contracts -is compiled into the created contract. - -The general inheritance system is very similar to -`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_, -especially concerning multiple inheritance, but there are also -some :ref:`differences <multi-inheritance>`. - -Details are given in the following example. - -:: - - pragma solidity >0.4.99 <0.6.0; - - contract owned { - constructor() public { owner = msg.sender; } - address payable owner; - } - - // Use `is` to derive from another contract. Derived - // contracts can access all non-private members including - // internal functions and state variables. These cannot be - // accessed externally via `this`, though. - contract mortal is owned { - function kill() public { - if (msg.sender == owner) selfdestruct(owner); - } - } - - // These abstract contracts are only provided to make the - // interface known to the compiler. Note the function - // without body. If a contract does not implement all - // functions it can only be used as an interface. - contract Config { - function lookup(uint id) public returns (address adr); - } - - contract NameReg { - function register(bytes32 name) public; - function unregister() public; - } - - // Multiple inheritance is possible. Note that `owned` is - // also a base class of `mortal`, yet there is only a single - // instance of `owned` (as for virtual inheritance in C++). - contract named is owned, mortal { - constructor(bytes32 name) public { - Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); - NameReg(config.lookup(1)).register(name); - } - - // Functions can be overridden by another function with the same name and - // the same number/types of inputs. If the overriding function has different - // types of output parameters, that causes an error. - // Both local and message-based function calls take these overrides - // into account. - function kill() public { - if (msg.sender == owner) { - Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); - NameReg(config.lookup(1)).unregister(); - // It is still possible to call a specific - // overridden function. - mortal.kill(); - } - } - } - - // If a constructor takes an argument, it needs to be - // provided in the header (or modifier-invocation-style at - // the constructor of the derived contract (see below)). - contract PriceFeed is owned, mortal, named("GoldFeed") { - function updateInfo(uint newInfo) public { - if (msg.sender == owner) info = newInfo; - } - - function get() public view returns(uint r) { return info; } - - uint info; - } - -Note that above, we call ``mortal.kill()`` to "forward" the -destruction request. The way this is done is problematic, as -seen in the following example:: - - pragma solidity >=0.4.22 <0.6.0; - - contract owned { - constructor() public { owner = msg.sender; } - address payable owner; - } - - contract mortal is owned { - function kill() public { - if (msg.sender == owner) selfdestruct(owner); - } - } - - contract Base1 is mortal { - function kill() public { /* do cleanup 1 */ mortal.kill(); } - } - - contract Base2 is mortal { - function kill() public { /* do cleanup 2 */ mortal.kill(); } - } - - contract Final is Base1, Base2 { - } - -A call to ``Final.kill()`` will call ``Base2.kill`` as the most -derived override, but this function will bypass -``Base1.kill``, basically because it does not even know about -``Base1``. The way around this is to use ``super``:: - - pragma solidity >=0.4.22 <0.6.0; - - contract owned { - constructor() public { owner = msg.sender; } - address payable owner; - } - - contract mortal is owned { - function kill() public { - if (msg.sender == owner) selfdestruct(owner); - } - } - - contract Base1 is mortal { - function kill() public { /* do cleanup 1 */ super.kill(); } - } - - - contract Base2 is mortal { - function kill() public { /* do cleanup 2 */ super.kill(); } - } - - contract Final is Base1, Base2 { - } - -If ``Base2`` calls a function of ``super``, it does not simply -call this function on one of its base contracts. Rather, it -calls this function on the next base contract in the final -inheritance graph, so it will call ``Base1.kill()`` (note that -the final inheritance sequence is -- starting with the most -derived contract: Final, Base2, Base1, mortal, owned). -The actual function that is called when using super is -not known in the context of the class where it is used, -although its type is known. This is similar for ordinary -virtual method lookup. - -.. index:: ! constructor - -.. _constructor: - -Constructors -============ - -A constructor is an optional function declared with the ``constructor`` keyword -which is executed upon contract creation, and where you can run contract -initialisation code. - -Before the constructor code is executed, state variables are initialised to -their specified value if you initialise them inline, or zero if you do not. - -After the constructor has run, the final code of the contract is deployed -to the blockchain. The deployment of -the code costs additional gas linear to the length of the code. -This code includes all functions that are part of the public interface -and all functions that are reachable from there through function calls. -It does not include the constructor code or internal functions that are -only called from the constructor. - -Constructor functions can be either ``public`` or ``internal``. If there is no -constructor, the contract will assume the default constructor, which is -equivalent to ``constructor() public {}``. For example: - -:: - - pragma solidity >0.4.99 <0.6.0; - - contract A { - uint public a; - - constructor(uint _a) internal { - a = _a; - } - } - - contract B is A(1) { - constructor() public {} - } - -A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`. - -.. warning :: - Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. - This syntax was deprecated and is not allowed anymore in version 0.5.0. - - -.. index:: ! base;constructor - -Arguments for Base Constructors -=============================== - -The constructors of all the base contracts will be called following the -linearization rules explained below. If the base constructors have arguments, -derived contracts need to specify all of them. This can be done in two ways:: - - pragma solidity >=0.4.22 <0.6.0; - - contract Base { - uint x; - constructor(uint _x) public { x = _x; } - } - - // Either directly specify in the inheritance list... - contract Derived1 is Base(7) { - constructor() public {} - } - - // or through a "modifier" of the derived constructor. - contract Derived2 is Base { - constructor(uint _y) Base(_y * _y) public {} - } - -One way is directly in the inheritance list (``is Base(7)``). The other is in -the way a modifier is invoked as part of -the derived constructor (``Base(_y * _y)``). The first way to -do it is more convenient if the constructor argument is a -constant and defines the behaviour of the contract or -describes it. The second way has to be used if the -constructor arguments of the base depend on those of the -derived contract. Arguments have to be given either in the -inheritance list or in modifier-style in the derived constructor. -Specifying arguments in both places is an error. - -If a derived contract does not specify the arguments to all of its base -contracts' constructors, it will be abstract. - -.. index:: ! inheritance;multiple, ! linearization, ! C3 linearization - -.. _multi-inheritance: - -Multiple Inheritance and Linearization -====================================== - -Languages that allow multiple inheritance have to deal with -several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_. -Solidity is similar to Python in that it uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_" -to force a specific order in the directed acyclic graph (DAG) of base classes. This -results in the desirable property of monotonicity but -disallows some inheritance graphs. Especially, the order in -which the base classes are given in the ``is`` directive is -important: You have to list the direct base contracts -in the order from "most base-like" to "most derived". -Note that this order is the reverse of the one used in Python. - -Another simplifying way to explain this is that when a function is called that -is defined multiple times in different contracts, the given bases -are searched from right to left (left to right in Python) in a depth-first manner, -stopping at the first match. If a base contract has already been searched, it is skipped. - -In the following code, Solidity will give the -error "Linearization of inheritance graph impossible". - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract X {} - contract A is X {} - // This will not compile - contract C is A, X {} - -The reason for this is that ``C`` requests ``X`` to override ``A`` -(by specifying ``A, X`` in this order), but ``A`` itself -requests to override ``X``, which is a contradiction that -cannot be resolved. - - - -Inheriting Different Kinds of Members of the Same Name -====================================================== - -When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. -This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. -As an exception, a state variable getter can override a public function. - -.. index:: ! contract;abstract, ! abstract contract - -.. _abstract-contract: - -****************** -Abstract Contracts -****************** - -Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``):: - - pragma solidity >=0.4.0 <0.6.0; - - contract Feline { - function utterance() public returns (bytes32); - } - -Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:: - - pragma solidity >=0.4.0 <0.6.0; - - contract Feline { - function utterance() public returns (bytes32); - } - - contract Cat is Feline { - function utterance() public returns (bytes32) { return "miaow"; } - } - -If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. - -Note that a function without implementation is different from a :ref:`Function Type <function_types>` even though their syntax looks very similar. - -Example of function without implementation (a function declaration):: - - function foo(address) external returns (address); - -Example of a Function Type (a variable declaration, where the variable is of type ``function``):: - - function(address) external returns (address) foo; - -Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and -facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication. -Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method". - - -.. index:: ! contract;interface, ! interface contract - -.. _interfaces: - -********** -Interfaces -********** - -Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions: - -- They cannot inherit other contracts or interfaces. -- All declared functions must be external. -- They cannot declare a constructor. -- They cannot declare state variables. - -Some of these restrictions might be lifted in the future. - -Interfaces are basically limited to what the Contract ABI can represent, and the conversion between the ABI and -an interface should be possible without any information loss. - -Interfaces are denoted by their own keyword: - -:: - - pragma solidity >=0.5.0 <0.6.0; - - interface Token { - enum TokenType { Fungible, NonFungible } - struct Coin { string obverse; string reverse; } - function transfer(address recipient, uint amount) external; - } - -Contracts can inherit interfaces as they would inherit other contracts. - -Types defined inside interfaces and other contract-like structures -can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. - -.. index:: ! library, callcode, delegatecall - -.. _libraries: - -********* -Libraries -********* - -Libraries are similar to contracts, but their purpose is that they are deployed -only once at a specific address and their code is reused using the ``DELEGATECALL`` -(``CALLCODE`` until Homestead) -feature of the EVM. This means that if library functions are called, their code -is executed in the context of the calling contract, i.e. ``this`` points to the -calling contract, and especially the storage from the calling contract can be -accessed. As a library is an isolated piece of source code, it can only access -state variables of the calling contract if they are explicitly supplied (it -would have no way to name them, otherwise). Library functions can only be -called directly (i.e. without the use of ``DELEGATECALL``) if they do not modify -the state (i.e. if they are ``view`` or ``pure`` functions), -because libraries are assumed to be stateless. In particular, it is -not possible to destroy a library. - -.. note:: - Until version 0.4.20, it was possible to destroy libraries by - circumventing Solidity's type system. Starting from that version, - libraries contain a :ref:`mechanism<call-protection>` that - disallows state-modifying functions - to be called directly (i.e. without ``DELEGATECALL``). - -Libraries can be seen as implicit base contracts of the contracts that use them. -They will not be explicitly visible in the inheritance hierarchy, but calls -to library functions look just like calls to functions of explicit base -contracts (``L.f()`` if ``L`` is the name of the library). Furthermore, -``internal`` functions of libraries are visible in all contracts, just as -if the library were a base contract. Of course, calls to internal functions -use the internal calling convention, which means that all internal types -can be passed and types :ref:`stored in memory <data-location>` will be passed by reference and not copied. -To realize this in the EVM, code of internal library functions -and all functions called from therein will at compile time be pulled into the calling -contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``. - -.. index:: using for, set - -The following example illustrates how to use libraries (but manual method -be sure to check out :ref:`using for <using-for>` for a -more advanced example to implement a set). - -:: - - pragma solidity >=0.4.22 <0.6.0; - - library Set { - // We define a new struct datatype that will be used to - // hold its data in the calling contract. - struct Data { mapping(uint => bool) flags; } - - // Note that the first parameter is of type "storage - // reference" and thus only its storage address and not - // its contents is passed as part of the call. This is a - // special feature of library functions. It is idiomatic - // to call the first parameter `self`, if the function can - // be seen as a method of that object. - function insert(Data storage self, uint value) - public - returns (bool) - { - if (self.flags[value]) - return false; // already there - self.flags[value] = true; - return true; - } - - function remove(Data storage self, uint value) - public - returns (bool) - { - if (!self.flags[value]) - return false; // not there - self.flags[value] = false; - return true; - } - - function contains(Data storage self, uint value) - public - view - returns (bool) - { - return self.flags[value]; - } - } - - contract C { - Set.Data knownValues; - - function register(uint value) public { - // The library functions can be called without a - // specific instance of the library, since the - // "instance" will be the current contract. - require(Set.insert(knownValues, value)); - } - // In this contract, we can also directly access knownValues.flags, if we want. - } - -Of course, you do not have to follow this way to use -libraries: they can also be used without defining struct -data types. Functions also work without any storage -reference parameters, and they can have multiple storage reference -parameters and in any position. - -The calls to ``Set.contains``, ``Set.insert`` and ``Set.remove`` -are all compiled as calls (``DELEGATECALL``) to an external -contract/library. If you use libraries, be aware that an -actual external function call is performed. -``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 -``msg.value`` changed, though). - -The following example shows how to use :ref:`types stored in memory <data-location>` and -internal functions in libraries in order to implement -custom types without the overhead of external function calls: - -:: - - pragma solidity >=0.4.16 <0.6.0; - - library BigInt { - struct bigint { - uint[] limbs; - } - - function fromUint(uint x) internal pure returns (bigint memory r) { - r.limbs = new uint[](1); - r.limbs[0] = x; - } - - function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) { - r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); - uint carry = 0; - for (uint i = 0; i < r.limbs.length; ++i) { - uint a = limb(_a, i); - uint b = limb(_b, i); - r.limbs[i] = a + b + carry; - if (a + b < a || (a + b == uint(-1) && carry > 0)) - carry = 1; - else - carry = 0; - } - if (carry > 0) { - // too bad, we have to add a limb - uint[] memory newLimbs = new uint[](r.limbs.length + 1); - uint i; - for (i = 0; i < r.limbs.length; ++i) - newLimbs[i] = r.limbs[i]; - newLimbs[i] = carry; - r.limbs = newLimbs; - } - } - - function limb(bigint memory _a, uint _limb) internal pure returns (uint) { - return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; - } - - function max(uint a, uint b) private pure returns (uint) { - return a > b ? a : b; - } - } - - contract C { - using BigInt for BigInt.bigint; - - function f() public pure { - BigInt.bigint memory x = BigInt.fromUint(7); - BigInt.bigint memory y = BigInt.fromUint(uint(-1)); - BigInt.bigint memory z = x.add(y); - assert(z.limb(1) > 0); - } - } - -As the compiler cannot know where the library will be -deployed at, these addresses have to be filled into the -final bytecode by a linker -(see :ref:`commandline-compiler` for how to use the -commandline compiler for linking). If the addresses are not -given as arguments to the compiler, the compiled hex code -will contain placeholders of the form ``__Set______`` (where -``Set`` is the name of the library). The address can be filled -manually by replacing all those 40 symbols by the hex -encoding of the address of the library contract. - -.. note:: - Manually linking libraries on the generated bytecode is discouraged, because - it is restricted to 36 characters. - You should ask the compiler to link the libraries at the time - a contract is compiled by either using - the ``--libraries`` option of ``solc`` or the ``libraries`` key if you use - the standard-JSON interface to the compiler. - -Restrictions for libraries in comparison to contracts: - -- No state variables -- Cannot inherit nor be inherited -- Cannot receive Ether - -(These might be lifted at a later point.) - -.. _call-protection: - -Call Protection For Libraries -============================= - -As mentioned in the introduction, if a library's code is executed -using a ``CALL`` instead of a ``DELEGATECALL`` or ``CALLCODE``, -it will revert unless a ``view`` or ``pure`` function is called. - -The EVM does not provide a direct way for a contract to detect -whether it was called using ``CALL`` or not, but a contract -can use the ``ADDRESS`` opcode to find out "where" it is -currently running. The generated code compares this address -to the address used at construction time to determine the mode -of calling. - -More specifically, the runtime code of a library always starts -with a push instruction, which is a zero of 20 bytes at -compilation time. When the deploy code runs, this constant -is replaced in memory by the current address and this -modified code is stored in the contract. At runtime, -this causes the deploy time address to be the first -constant to be pushed onto the stack and the dispatcher -code compares the current address against this constant -for any non-view and non-pure function. - -.. index:: ! using for, library - -.. _using-for: - -********* -Using For -********* - -The directive ``using A for B;`` can be used to attach library -functions (from the library ``A``) to any type (``B``). -These functions will receive the object they are called on -as their first parameter (like the ``self`` variable in Python). - -The effect of ``using A for *;`` is that the functions from -the library ``A`` are attached to *any* type. - -In both situations, *all* functions in the library are attached, -even those where the type of the first parameter does not -match the type of the object. The type is checked at the -point the function is called and function overload -resolution is performed. - -The ``using A for B;`` directive is active only within the current -contract, including within all of its functions, and has no effect -outside of the contract in which it is used. The directive -may only be used inside a contract, not inside any of its functions. - -By including a library, its data types including library functions are -available without having to add further code. - -Let us rewrite the set example from the -:ref:`libraries` in this way:: - - pragma solidity >=0.4.16 <0.6.0; - - // This is the same code as before, just without comments - library Set { - struct Data { mapping(uint => bool) flags; } - - function insert(Data storage self, uint value) - public - returns (bool) - { - if (self.flags[value]) - return false; // already there - self.flags[value] = true; - return true; - } - - function remove(Data storage self, uint value) - public - returns (bool) - { - if (!self.flags[value]) - return false; // not there - self.flags[value] = false; - return true; - } - - function contains(Data storage self, uint value) - public - view - returns (bool) - { - return self.flags[value]; - } - } - - contract C { - using Set for Set.Data; // this is the crucial change - Set.Data knownValues; - - function register(uint value) public { - // Here, all variables of type Set.Data have - // corresponding member functions. - // The following function call is identical to - // `Set.insert(knownValues, value)` - require(knownValues.insert(value)); - } - } - -It is also possible to extend elementary types in that way:: - - pragma solidity >=0.4.16 <0.6.0; - - library Search { - function indexOf(uint[] storage self, uint value) - public - view - returns (uint) - { - for (uint i = 0; i < self.length; i++) - if (self[i] == value) return i; - return uint(-1); - } - } - - contract C { - using Search for uint[]; - uint[] data; - - function append(uint value) public { - data.push(value); - } - - function replace(uint _old, uint _new) public { - // This performs the library function call - uint index = data.indexOf(_old); - if (index == uint(-1)) - data.push(_new); - else - data[index] = _new; - } - } - -Note that all library calls are actual EVM function calls. This means that -if you pass memory or value types, a copy will be performed, even of the -``self`` variable. The only situation where no copy will be performed -is when storage reference variables are used. +.. include:: contracts/using-for.rst
\ No newline at end of file diff --git a/docs/contracts/abstract-contracts.rst b/docs/contracts/abstract-contracts.rst new file mode 100644 index 00000000..87340733 --- /dev/null +++ b/docs/contracts/abstract-contracts.rst @@ -0,0 +1,43 @@ +.. index:: ! contract;abstract, ! abstract contract + +.. _abstract-contract: + +****************** +Abstract Contracts +****************** + +Contracts are marked as abstract when at least one of their functions lacks an implementation as in the following example (note that the function declaration header is terminated by ``;``):: + + pragma solidity >=0.4.0 <0.6.0; + + contract Feline { + function utterance() public returns (bytes32); + } + +Such contracts cannot be compiled (even if they contain implemented functions alongside non-implemented functions), but they can be used as base contracts:: + + pragma solidity >=0.4.0 <0.6.0; + + contract Feline { + function utterance() public returns (bytes32); + } + + contract Cat is Feline { + function utterance() public returns (bytes32) { return "miaow"; } + } + +If a contract inherits from an abstract contract and does not implement all non-implemented functions by overriding, it will itself be abstract. + +Note that a function without implementation is different from a :ref:`Function Type <function_types>` even though their syntax looks very similar. + +Example of function without implementation (a function declaration):: + + function foo(address) external returns (address); + +Example of a Function Type (a variable declaration, where the variable is of type ``function``):: + + function(address) external returns (address) foo; + +Abstract contracts decouple the definition of a contract from its implementation providing better extensibility and self-documentation and +facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/Template_method_pattern>`_ and removing code duplication. +Abstract contracts are useful in the same way that defining methods in an interface is useful. It is a way for the designer of the abstract contract to say "any child of mine must implement this method". diff --git a/docs/contracts/constant-state-variables.rst b/docs/contracts/constant-state-variables.rst new file mode 100644 index 00000000..3e615ed0 --- /dev/null +++ b/docs/contracts/constant-state-variables.rst @@ -0,0 +1,35 @@ +.. index:: ! constant + +************************ +Constant State Variables +************************ + +State variables can be declared as ``constant``. In this case, they have to be +assigned from an expression which is a constant at compile time. Any expression +that accesses storage, blockchain data (e.g. ``now``, ``address(this).balance`` or +``block.number``) or +execution data (``msg.value`` or ``gasleft()``) or makes calls to external contracts is disallowed. Expressions +that might have a side-effect on memory allocation are allowed, but those that +might have a side-effect on other memory objects are not. The built-in functions +``keccak256``, ``sha256``, ``ripemd160``, ``ecrecover``, ``addmod`` and ``mulmod`` +are allowed (even though, with the exception of ``keccak256``, they do call external contracts). + +The reason behind allowing side-effects on the memory allocator is that it +should be possible to construct complex objects like e.g. lookup-tables. +This feature is not yet fully usable. + +The compiler does not reserve a storage slot for these variables, and every occurrence is +replaced by the respective constant expression (which might be computed to a single value by the optimizer). + +Not all types for constants are implemented at this time. The only supported types are +value types and strings. + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract C { + uint constant x = 32**22 + 8; + string constant text = "abc"; + bytes32 constant myHash = keccak256("abc"); + } diff --git a/docs/contracts/creating-contracts.rst b/docs/contracts/creating-contracts.rst new file mode 100644 index 00000000..981243b1 --- /dev/null +++ b/docs/contracts/creating-contracts.rst @@ -0,0 +1,117 @@ +.. index:: ! contract;creation, constructor + +****************** +Creating 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 programmatically on Ethereum is best done via using the JavaScript API `web3.js <https://github.com/ethereum/web3.js>`_. +It has a function 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 :ref:`constructor <constructor>` (a function declared with the ``constructor`` keyword) is executed once. + +A constructor is optional. Only one constructor is allowed, which means +overloading is not supported. + +After the constructor has executed, the final code of the contract is deployed to the +blockchain. This code includes all public and external functions and all functions +that are reachable from there through function calls. The deployed code does not +include the constructor code or internal functions only called from the constructor. + +.. index:: constructor;arguments + +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 if you use ``web3.js``. + +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. +This means that cyclic creation dependencies are impossible. + +:: + + pragma solidity >=0.4.22 <0.6.0; + + contract OwnedToken { + // `TokenCreator` is a contract type that is defined below. + // It is fine to reference it as long as it is not used + // to create a new contract. + TokenCreator creator; + address owner; + bytes32 name; + + // This is the constructor which registers the + // creator and the assigned name. + constructor(bytes32 _name) public { + // State variables are accessed via their name + // and not via e.g. `this.owner`. Functions can + // be accessed directly or through `this.f`, + // but the latter provides an external view + // to the function. Especially in the constructor, + // you should not access functions externally, + // because the function does not exist yet. + // See the next section for details. + owner = msg.sender; + + // We do an explicit type conversion from `address` + // to `TokenCreator` and assume that the type of + // the calling contract is `TokenCreator`, there is + // no real way to check that. + creator = TokenCreator(msg.sender); + name = _name; + } + + function changeName(bytes32 newName) public { + // Only the creator can alter the name -- + // the comparison is possible since contracts + // are explicitly convertible to addresses. + if (msg.sender == address(creator)) + name = newName; + } + + function transfer(address newOwner) public { + // Only the current owner can transfer the token. + if (msg.sender != owner) return; + + // We ask the creator contract if the transfer + // should proceed by using a function of the + // `TokenCreator` contract defined below. If + // the call fails (e.g. due to out-of-gas), + // the execution also fails here. + if (creator.isTokenTransferOK(owner, newOwner)) + owner = newOwner; + } + } + + contract TokenCreator { + function createToken(bytes32 name) + public + returns (OwnedToken tokenAddress) + { + // Create a new `Token` contract and return its address. + // From the JavaScript side, the return type is + // `address`, as this is the closest type available in + // the ABI. + return new OwnedToken(name); + } + + function changeName(OwnedToken tokenAddress, bytes32 name) public { + // Again, the external type of `tokenAddress` is + // simply `address`. + tokenAddress.changeName(name); + } + + // Perform checks to determine if transferring a token to the + // `OwnedToken` contract should proceed + function isTokenTransferOK(address currentOwner, address newOwner) + public + pure + returns (bool ok) + { + // Check an arbitrary condition to see if transfer should proceed + return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f; + } + } diff --git a/docs/contracts/events.rst b/docs/contracts/events.rst new file mode 100644 index 00000000..ecb0a87f --- /dev/null +++ b/docs/contracts/events.rst @@ -0,0 +1,161 @@ +.. index:: ! event + +.. _events: + +****** +Events +****** + +Solidity events give an abstraction on top of the EVM's logging functionality. +Applications can subscribe and listen to these events through the RPC interface of an Ethereum client. + +Events are inheritable members of contracts. When you call them, they cause the +arguments to be stored in the transaction's log - a special data structure +in the blockchain. These logs are associated with the address of the contract, +are incorporated into the blockchain, and stay there as long as a block is +accessible (forever as of the Frontier and Homestead releases, but this might +change with Serenity). The Log and its event data is not accessible from within +contracts (not even from the contract that created them). + +It is possible to request a simple payment verification (SPV) for logs, so if +an external entity supplies a contract with such a verification, it can check +that the log actually exists inside the blockchain. You have to supply block headers +because the contract can only see the last 256 block hashes. + +You can add the attribute ``indexed`` to up to three parameters which adds them +to a special data structure known as :ref:`"topics" <abi_events>` instead of +the data part of the log. If you use arrays (including ``string`` and ``bytes``) +as indexed arguments, its Keccak-256 hash is stored as a topic instead, this is +because a topic can only hold a single word (32 bytes). + +All parameters without the ``indexed`` attribute are :ref:`ABI-encoded <ABI>` +into the data part of the log. + +Topics allow you to search for events, for example when filtering a sequence of +blocks for certain events. You can also filter events by the address of the +contract that emitted the event. + +For example, the code below uses the web3.js ``subscribe("logs")`` +`method <https://web3js.readthedocs.io/en/1.0/web3-eth-subscribe.html#subscribe-logs>`_ to filter +logs that match a topic with a certain address value: + +.. code-block:: javascript + + var options = { + fromBlock: 0, + address: web3.eth.defaultAccount, + topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null] + }; + web3.eth.subscribe('logs', options, function (error, result) { + if (!error) + console.log(result); + }) + .on("data", function (log) { + console.log(log); + }) + .on("changed", function (log) { + }); + + +The hash of the signature of the event is one of the topics, except if you +declared the event with the ``anonymous`` specifier. This means that it is +not possible to filter for specific anonymous events by name. + +:: + + pragma solidity >=0.4.21 <0.6.0; + + contract ClientReceipt { + event Deposit( + address indexed _from, + bytes32 indexed _id, + uint _value + ); + + function deposit(bytes32 _id) public payable { + // Events are emitted using `emit`, followed by + // the name of the event and the arguments + // (if any) in parentheses. Any such invocation + // (even deeply nested) can be detected from + // the JavaScript API by filtering for `Deposit`. + emit Deposit(msg.sender, _id, msg.value); + } + } + +The use in the JavaScript API is as follows: + +:: + + var abi = /* abi as generated by the compiler */; + var ClientReceipt = web3.eth.contract(abi); + var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */); + + var event = clientReceipt.Deposit(); + + // watch for changes + event.watch(function(error, result){ + // result contains non-indexed arguments and topics + // given to the `Deposit` call. + if (!error) + console.log(result); + }); + + + // Or pass a callback to start watching immediately + var event = clientReceipt.Deposit(function(error, result) { + if (!error) + console.log(result); + }); + +The output of the above looks like the following (trimmed): + +.. code-block:: json + + { + "returnValues": { + "_from": "0x1111…FFFFCCCC", + "_id": "0x50…sd5adb20", + "_value": "0x420042" + }, + "raw": { + "data": "0x7f…91385", + "topics": ["0xfd4…b4ead7", "0x7f…1a91385"] + } + } + +.. index:: ! log + +Low-Level Interface to Logs +=========================== + +It is also possible to access the low-level interface to the logging +mechanism via the functions ``log0``, ``log1``, ``log2``, ``log3`` and ``log4``. +``logi`` takes ``i + 1`` parameter of type ``bytes32``, where the first +argument will be used for the data part of the log and the others +as topics. The event call above can be performed in the same way as + +:: + + pragma solidity >=0.4.10 <0.6.0; + + contract C { + function f() public payable { + uint256 _id = 0x420042; + log3( + bytes32(msg.value), + bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20), + bytes32(uint256(msg.sender)), + bytes32(_id) + ); + } + } + +where the long hexadecimal number is equal to +``keccak256("Deposit(address,bytes32,uint256)")``, the signature of the event. + +Additional Resources for Understanding Events +============================================== + +- `Javascript documentation <https://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events>`_ +- `Example usage of events <https://github.com/debris/smart-exchange/blob/master/lib/contracts/SmartExchange.sol>`_ +- `How to access them in js <https://github.com/debris/smart-exchange/blob/master/lib/exchange_transactions.js>`_ diff --git a/docs/contracts/function-modifiers.rst b/docs/contracts/function-modifiers.rst new file mode 100644 index 00000000..376cd9fa --- /dev/null +++ b/docs/contracts/function-modifiers.rst @@ -0,0 +1,111 @@ +.. index:: ! function;modifier + +.. _modifiers: + +****************** +Function Modifiers +****************** + +Modifiers can be used to easily change the behaviour of functions. For example, +they can automatically check a condition prior to executing the function. Modifiers are +inheritable properties of contracts and may be overridden by derived contracts. + +:: + + pragma solidity ^0.5.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + + // This contract only defines a modifier but does not use + // it: it will be used in derived contracts. + // The function body is inserted where the special symbol + // `_;` in the definition of a modifier appears. + // This means that if the owner calls this function, the + // function is executed and otherwise, an exception is + // thrown. + modifier onlyOwner { + require( + msg.sender == owner, + "Only owner can call this function." + ); + _; + } + } + + contract mortal is owned { + // This contract inherits the `onlyOwner` modifier from + // `owned` and applies it to the `close` function, which + // causes that calls to `close` only have an effect if + // they are made by the stored owner. + function close() public onlyOwner { + selfdestruct(owner); + } + } + + contract priced { + // Modifiers can receive arguments: + modifier costs(uint price) { + if (msg.value >= price) { + _; + } + } + } + + contract Register is priced, owned { + mapping (address => bool) registeredAddresses; + uint price; + + constructor(uint initialPrice) public { price = initialPrice; } + + // It is important to also provide the + // `payable` keyword here, otherwise the function will + // automatically reject all Ether sent to it. + function register() public payable costs(price) { + registeredAddresses[msg.sender] = true; + } + + function changePrice(uint _price) public onlyOwner { + price = _price; + } + } + + contract Mutex { + bool locked; + modifier noReentrancy() { + require( + !locked, + "Reentrant call." + ); + locked = true; + _; + locked = false; + } + + /// This function is protected by a mutex, which means that + /// reentrant calls from within `msg.sender.call` cannot call `f` again. + /// The `return 7` statement assigns 7 to the return value but still + /// executes the statement `locked = false` in the modifier. + function f() public noReentrancy returns (uint) { + (bool success,) = msg.sender.call(""); + require(success); + return 7; + } + } + +Multiple modifiers are applied to a function by specifying them in a +whitespace-separated list and are evaluated in the order presented. + +.. warning:: + In an earlier version of Solidity, ``return`` statements in functions + having modifiers behaved differently. + +Explicit returns from a modifier or function body only leave the current +modifier or function body. Return variables are assigned and +control flow continues after the "_" in the preceding modifier. + +Arbitrary expressions are allowed for modifier arguments and in this context, +all symbols visible from the function are visible in the modifier. Symbols +introduced in the modifier are not visible in the function (as they might +change by overriding). diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst new file mode 100644 index 00000000..522ce5c4 --- /dev/null +++ b/docs/contracts/functions.rst @@ -0,0 +1,398 @@ +.. index:: ! functions + +.. _functions: + +********* +Functions +********* + +.. _function-parameters-return-variables: + +Function Parameters and Return Variables +======================================== + +As in JavaScript, functions may take parameters as input. Unlike in JavaScript +and C, functions may also return an arbitrary number of values as output. + +Function Parameters +------------------- + +Function parameters are declared the same way as variables, and the name of +unused parameters can be omitted. + +For example, if you want your contract to accept one kind of external call +with two integers, you would use something like:: + + pragma solidity >=0.4.16 <0.6.0; + + contract Simple { + uint sum; + function taker(uint _a, uint _b) public { + sum = _a + _b; + } + } + +Function parameters can be used as any other local variable and they can also be assigned to. + +.. note:: + + An :ref:`external function<external-function-calls>` cannot accept a + multi-dimensional array as an input + parameter. This functionality is possible if you enable the new + experimental ``ABIEncoderV2`` feature by adding ``pragma experimental ABIEncoderV2;`` to your source file. + + An :ref:`internal function<external-function-calls>` can accept a + multi-dimensional array without enabling the feature. + +.. index:: return array, return string, array, string, array of strings, dynamic array, variably sized array, return struct, struct + +Return Variables +---------------- + +Function return variables are declared with the same syntax after the +``returns`` keyword. + +For example, suppose you want to return two results: the sum and the product of +two integers passed as function parameters, then you use something like:: + + pragma solidity >=0.4.16 <0.6.0; + + contract Simple { + function arithmetic(uint _a, uint _b) + public + pure + returns (uint o_sum, uint o_product) + { + o_sum = _a + _b; + o_product = _a * _b; + } + } + +The names of return variables can be omitted. +Return variables can be used as any other local variable and they +are initialized with their :ref:`default value <default-value>` and have that value unless explicitly set. + +You can either explicitly assign to return variables and +then leave the function using ``return;``, +or you can provide return values +(either a single or :ref:`multiple ones<multi-return>`) directly with the ``return`` +statement:: + + pragma solidity >=0.4.16 <0.6.0; + + contract Simple { + function arithmetic(uint _a, uint _b) + public + pure + returns (uint o_sum, uint o_product) + { + return (_a + _b, _a * _b); + } + } + +This form is equivalent to first assigning values to the +return variables and then using ``return;`` to leave the function. + +.. note:: + You cannot return some types from non-internal functions, notably + multi-dimensional dynamic arrays and structs. If you enable the + new experimental ``ABIEncoderV2`` feature by adding ``pragma experimental + ABIEncoderV2;`` to your source file then more types are available, but + ``mapping`` types are still limited to inside a single contract and you + cannot transfer them. + +.. _multi-return: + +Returning Multiple Values +------------------------- + +When a function has multiple return types, the statement ``return (v0, v1, ..., vn)`` can be used to return multiple values. +The number of components must be the same as the number of return types. + +.. index:: ! view function, function;view + +.. _view-functions: + +View Functions +============== + +Functions can be declared ``view`` in which case they promise not to modify the state. + +.. note:: + If the compiler's EVM target is Byzantium or newer (default) the opcode + ``STATICCALL`` is used for ``view`` functions which enforces the state + to stay unmodified as part of the EVM execution. For library ``view`` functions + ``DELEGATECALL`` is used, because there is no combined ``DELEGATECALL`` and ``STATICCALL``. + This means library ``view`` functions do not have run-time checks that prevent state + modifications. This should not impact security negatively because library code is + usually known at compile-time and the static checker performs compile-time checks. + +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.5.0; + + contract C { + function f(uint a, uint b) public view returns (uint) { + return a * (b + 42) + now; + } + } + +.. note:: + ``constant`` on functions used to be an alias to ``view``, but this was dropped in version 0.5.0. + +.. note:: + Getter methods are automatically marked ``view``. + +.. note:: + Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode + for ``view`` functions. + This enabled state modifications in ``view`` functions through the use of + invalid explicit type conversions. + By using ``STATICCALL`` for ``view`` functions, modifications to the + state are prevented on the level of the EVM. + +.. index:: ! pure function, function;pure + +.. _pure-functions: + +Pure Functions +============== + +Functions can be declared ``pure`` in which case they promise not to read from or modify the state. + +.. note:: + If the compiler's EVM target is Byzantium or newer (default) the opcode ``STATICCALL`` is used, + which does not guarantee that the state is not read, but at least that it is not modified. + +In addition to the list of state modifying statements explained above, the following are considered reading from the state: + +#. Reading from state variables. +#. Accessing ``address(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.5.0; + + contract C { + function f(uint a, uint b) public pure returns (uint) { + return a * (b + 42); + } + } + +Pure functions are able to use the `revert()` and `require()` functions to revert +potential state changes when an :ref:`error occurs <assert-and-require>`. + +Reverting a state change is not considered a "state modification", as only changes to the +state made previously in code that did not have the ``view`` or ``pure`` restriction +are reverted and that code has the option to catch the ``revert`` and not pass it on. + +This behaviour is also in line with the ``STATICCALL`` opcode. + +.. warning:: + It is not possible to prevent functions from reading the state at the level + of the EVM, it is only possible to prevent them from writing to the state + (i.e. only ``view`` can be enforced at the EVM level, ``pure`` can not). + +.. note:: + Prior to version 0.5.0, the compiler did not use the ``STATICCALL`` opcode + for ``pure`` functions. + This enabled state modifications in ``pure`` functions through the use of + invalid explicit type conversions. + By using ``STATICCALL`` for ``pure`` functions, modifications to the + state are prevented on the level of the EVM. + +.. note:: + Prior to version 0.4.17 the compiler did not enforce that ``pure`` is not reading the state. + It is a compile-time type check, which can be circumvented doing invalid explicit conversions + between contract types, because the compiler can verify that the type of the contract does + not do state-changing operations, but it cannot check that the contract that will be called + at runtime is actually of that type. + +.. index:: ! fallback function, function;fallback + +.. _fallback-function: + +Fallback Function +================= + +A contract can have exactly one unnamed function. This function cannot have +arguments, cannot return anything and has to have ``external`` visibility. +It is executed on a call to the contract if none of the other +functions match the given function identifier (or if no data was supplied at +all). + +Furthermore, this function is executed whenever the contract receives plain +Ether (without data). To receive Ether and add it to the total balance of the contract, the fallback function +must be marked ``payable``. If no such function exists, the contract cannot receive +Ether through regular transactions and throws an exception. + +In the worst case, the fallback function can only rely on 2300 gas being +available (for example when `send` or `transfer` is used), leaving little +room to perform other operations except basic logging. The following operations +will consume more gas than the 2300 gas stipend: + +- Writing to storage +- Creating a contract +- Calling an external function which consumes a large amount of gas +- Sending Ether + +Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it. + +.. note:: + Even though the fallback function cannot have arguments, one can still use ``msg.data`` to retrieve + any payload supplied with the call. + +.. warning:: + The fallback function is also executed if the caller meant to call + a function that is not available. If you want to implement the fallback + function only to receive ether, you should add a check + like ``require(msg.data.length == 0)`` to prevent invalid calls. + +.. warning:: + Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) + but do not define a fallback function + throw an exception, sending back the Ether (this was different + before Solidity v0.4.0). So if you want your contract to receive Ether, + you have to implement a payable 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 ``address(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.5.0; + + contract Test { + // This function is called for all messages sent to + // this contract (there is no other function). + // Sending Ether to this contract will cause an exception, + // because the fallback function does not have the `payable` + // modifier. + function() external { x = 1; } + uint x; + } + + + // This contract keeps all Ether sent to it with no way + // to get it back. + contract Sink { + function() external payable { } + } + + contract Caller { + function callTest(Test test) public returns (bool) { + (bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()")); + require(success); + // results in test.x becoming == 1. + + // address(test) will not allow to call ``send`` directly, since ``test`` has no payable + // fallback function. It has to be converted to the ``address payable`` type via an + // intermediate conversion to ``uint160`` to even allow calling ``send`` on it. + address payable testPayable = address(uint160(address(test))); + + // If someone sends ether to that contract, + // the transfer will fail, i.e. this returns false here. + return testPayable.send(2 ether); + } + } + +.. index:: ! overload + +.. _overload-function: + +Function Overloading +==================== + +A contract can have multiple functions of the same name but with different parameter +types. +This process is called "overloading" and also applies to inherited functions. +The following example shows overloading of the function +``f`` in the scope of contract ``A``. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract A { + function f(uint _in) public pure returns (uint out) { + out = _in; + } + + function f(uint _in, bool _really) public pure returns (uint out) { + if (_really) + out = _in; + } + } + +Overloaded functions are also present in the external interface. It is an error if two +externally visible functions differ by their Solidity types but not by their external types. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + // This will not compile + contract A { + function f(B _in) public pure returns (B out) { + out = _in; + } + + function f(address _in) public pure returns (address out) { + out = _in; + } + } + + contract B { + } + + +Both ``f`` function overloads above end up accepting the address type for the ABI although +they are considered different inside Solidity. + +Overload resolution and Argument matching +----------------------------------------- + +Overloaded functions are selected by matching the function declarations in the current scope +to the arguments supplied in the function call. Functions are selected as overload candidates +if all arguments can be implicitly converted to the expected types. If there is not exactly one +candidate, resolution fails. + +.. note:: + Return parameters are not taken into account for overload resolution. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract A { + function f(uint8 _in) public pure returns (uint8 out) { + out = _in; + } + + function f(uint256 _in) public pure returns (uint256 out) { + out = _in; + } + } + +Calling ``f(50)`` would create a type error since ``50`` can be implicitly converted both to ``uint8`` +and ``uint256`` types. On another hand ``f(256)`` would resolve to ``f(uint256)`` overload as ``256`` cannot be implicitly +converted to ``uint8``. diff --git a/docs/contracts/inheritance.rst b/docs/contracts/inheritance.rst new file mode 100644 index 00000000..2e94c2f9 --- /dev/null +++ b/docs/contracts/inheritance.rst @@ -0,0 +1,299 @@ +.. index:: ! inheritance, ! base class, ! contract;base, ! deriving + +*********** +Inheritance +*********** + +Solidity supports multiple inheritance including polymorphism. + +All function calls are virtual, which means that the most derived function +is called, except when the contract name is explicitly given or the +``super`` keyword is used. + +When a contract inherits from other contracts, only a single +contract is created on the blockchain, and the code from all the base contracts +is compiled into the created contract. + +The general inheritance system is very similar to +`Python's <https://docs.python.org/3/tutorial/classes.html#inheritance>`_, +especially concerning multiple inheritance, but there are also +some :ref:`differences <multi-inheritance>`. + +Details are given in the following example. + +:: + + pragma solidity ^0.5.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + } + + // Use `is` to derive from another contract. Derived + // contracts can access all non-private members including + // internal functions and state variables. These cannot be + // accessed externally via `this`, though. + contract mortal is owned { + function kill() public { + if (msg.sender == owner) selfdestruct(owner); + } + } + + // These abstract contracts are only provided to make the + // interface known to the compiler. Note the function + // without body. If a contract does not implement all + // functions it can only be used as an interface. + contract Config { + function lookup(uint id) public returns (address adr); + } + + contract NameReg { + function register(bytes32 name) public; + function unregister() public; + } + + // Multiple inheritance is possible. Note that `owned` is + // also a base class of `mortal`, yet there is only a single + // instance of `owned` (as for virtual inheritance in C++). + contract named is owned, mortal { + constructor(bytes32 name) public { + Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); + NameReg(config.lookup(1)).register(name); + } + + // Functions can be overridden by another function with the same name and + // the same number/types of inputs. If the overriding function has different + // types of output parameters, that causes an error. + // Both local and message-based function calls take these overrides + // into account. + function kill() public { + if (msg.sender == owner) { + Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970); + NameReg(config.lookup(1)).unregister(); + // It is still possible to call a specific + // overridden function. + mortal.kill(); + } + } + } + + // If a constructor takes an argument, it needs to be + // provided in the header (or modifier-invocation-style at + // the constructor of the derived contract (see below)). + contract PriceFeed is owned, mortal, named("GoldFeed") { + function updateInfo(uint newInfo) public { + if (msg.sender == owner) info = newInfo; + } + + function get() public view returns(uint r) { return info; } + + uint info; + } + +Note that above, we call ``mortal.kill()`` to "forward" the +destruction request. The way this is done is problematic, as +seen in the following example:: + + pragma solidity >=0.4.22 <0.6.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + } + + contract mortal is owned { + function kill() public { + if (msg.sender == owner) selfdestruct(owner); + } + } + + contract Base1 is mortal { + function kill() public { /* do cleanup 1 */ mortal.kill(); } + } + + contract Base2 is mortal { + function kill() public { /* do cleanup 2 */ mortal.kill(); } + } + + contract Final is Base1, Base2 { + } + +A call to ``Final.kill()`` will call ``Base2.kill`` as the most +derived override, but this function will bypass +``Base1.kill``, basically because it does not even know about +``Base1``. The way around this is to use ``super``:: + + pragma solidity >=0.4.22 <0.6.0; + + contract owned { + constructor() public { owner = msg.sender; } + address payable owner; + } + + contract mortal is owned { + function kill() public { + if (msg.sender == owner) selfdestruct(owner); + } + } + + contract Base1 is mortal { + function kill() public { /* do cleanup 1 */ super.kill(); } + } + + + contract Base2 is mortal { + function kill() public { /* do cleanup 2 */ super.kill(); } + } + + contract Final is Base1, Base2 { + } + +If ``Base2`` calls a function of ``super``, it does not simply +call this function on one of its base contracts. Rather, it +calls this function on the next base contract in the final +inheritance graph, so it will call ``Base1.kill()`` (note that +the final inheritance sequence is -- starting with the most +derived contract: Final, Base2, Base1, mortal, owned). +The actual function that is called when using super is +not known in the context of the class where it is used, +although its type is known. This is similar for ordinary +virtual method lookup. + +.. index:: ! constructor + +.. _constructor: + +Constructors +============ + +A constructor is an optional function declared with the ``constructor`` keyword +which is executed upon contract creation, and where you can run contract +initialisation code. + +Before the constructor code is executed, state variables are initialised to +their specified value if you initialise them inline, or zero if you do not. + +After the constructor has run, the final code of the contract is deployed +to the blockchain. The deployment of +the code costs additional gas linear to the length of the code. +This code includes all functions that are part of the public interface +and all functions that are reachable from there through function calls. +It does not include the constructor code or internal functions that are +only called from the constructor. + +Constructor functions can be either ``public`` or ``internal``. If there is no +constructor, the contract will assume the default constructor, which is +equivalent to ``constructor() public {}``. For example: + +:: + + pragma solidity ^0.5.0; + + contract A { + uint public a; + + constructor(uint _a) internal { + a = _a; + } + } + + contract B is A(1) { + constructor() public {} + } + +A constructor set as ``internal`` causes the contract to be marked as :ref:`abstract <abstract-contract>`. + +.. warning :: + Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. + This syntax was deprecated and is not allowed anymore in version 0.5.0. + + +.. index:: ! base;constructor + +Arguments for Base Constructors +=============================== + +The constructors of all the base contracts will be called following the +linearization rules explained below. If the base constructors have arguments, +derived contracts need to specify all of them. This can be done in two ways:: + + pragma solidity >=0.4.22 <0.6.0; + + contract Base { + uint x; + constructor(uint _x) public { x = _x; } + } + + // Either directly specify in the inheritance list... + contract Derived1 is Base(7) { + constructor() public {} + } + + // or through a "modifier" of the derived constructor. + contract Derived2 is Base { + constructor(uint _y) Base(_y * _y) public {} + } + +One way is directly in the inheritance list (``is Base(7)``). The other is in +the way a modifier is invoked as part of +the derived constructor (``Base(_y * _y)``). The first way to +do it is more convenient if the constructor argument is a +constant and defines the behaviour of the contract or +describes it. The second way has to be used if the +constructor arguments of the base depend on those of the +derived contract. Arguments have to be given either in the +inheritance list or in modifier-style in the derived constructor. +Specifying arguments in both places is an error. + +If a derived contract does not specify the arguments to all of its base +contracts' constructors, it will be abstract. + +.. index:: ! inheritance;multiple, ! linearization, ! C3 linearization + +.. _multi-inheritance: + +Multiple Inheritance and Linearization +====================================== + +Languages that allow multiple inheritance have to deal with +several problems. One is the `Diamond Problem <https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem>`_. +Solidity is similar to Python in that it uses "`C3 Linearization <https://en.wikipedia.org/wiki/C3_linearization>`_" +to force a specific order in the directed acyclic graph (DAG) of base classes. This +results in the desirable property of monotonicity but +disallows some inheritance graphs. Especially, the order in +which the base classes are given in the ``is`` directive is +important: You have to list the direct base contracts +in the order from "most base-like" to "most derived". +Note that this order is the reverse of the one used in Python. + +Another simplifying way to explain this is that when a function is called that +is defined multiple times in different contracts, the given bases +are searched from right to left (left to right in Python) in a depth-first manner, +stopping at the first match. If a base contract has already been searched, it is skipped. + +In the following code, Solidity will give the +error "Linearization of inheritance graph impossible". + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract X {} + contract A is X {} + // This will not compile + contract C is A, X {} + +The reason for this is that ``C`` requests ``X`` to override ``A`` +(by specifying ``A, X`` in this order), but ``A`` itself +requests to override ``X``, which is a contradiction that +cannot be resolved. + + + +Inheriting Different Kinds of Members of the Same Name +====================================================== + +When the inheritance results in a contract with a function and a modifier of the same name, it is considered as an error. +This error is produced also by an event and a modifier of the same name, and a function and an event of the same name. +As an exception, a state variable getter can override a public function. diff --git a/docs/contracts/interfaces.rst b/docs/contracts/interfaces.rst new file mode 100644 index 00000000..b551b518 --- /dev/null +++ b/docs/contracts/interfaces.rst @@ -0,0 +1,36 @@ +.. index:: ! contract;interface, ! interface contract + +.. _interfaces: + +********** +Interfaces +********** + +Interfaces are similar to abstract contracts, but they cannot have any functions implemented. There are further restrictions: + +- They cannot inherit other contracts or interfaces. +- All declared functions must be external. +- They cannot declare a constructor. +- They cannot declare state variables. + +Some of these restrictions might be lifted in the future. + +Interfaces are basically limited to what the Contract ABI can represent, and the conversion between the ABI and +an interface should be possible without any information loss. + +Interfaces are denoted by their own keyword: + +:: + + pragma solidity ^0.5.0; + + interface Token { + enum TokenType { Fungible, NonFungible } + struct Coin { string obverse; string reverse; } + function transfer(address recipient, uint amount) external; + } + +Contracts can inherit interfaces as they would inherit other contracts. + +Types defined inside interfaces and other contract-like structures +can be accessed from other contracts: ``Token.TokenType`` or ``Token.Coin``. diff --git a/docs/contracts/libraries.rst b/docs/contracts/libraries.rst new file mode 100644 index 00000000..0cabe18a --- /dev/null +++ b/docs/contracts/libraries.rst @@ -0,0 +1,230 @@ +.. index:: ! library, callcode, delegatecall + +.. _libraries: + +********* +Libraries +********* + +Libraries are similar to contracts, but their purpose is that they are deployed +only once at a specific address and their code is reused using the ``DELEGATECALL`` +(``CALLCODE`` until Homestead) +feature of the EVM. This means that if library functions are called, their code +is executed in the context of the calling contract, i.e. ``this`` points to the +calling contract, and especially the storage from the calling contract can be +accessed. As a library is an isolated piece of source code, it can only access +state variables of the calling contract if they are explicitly supplied (it +would have no way to name them, otherwise). Library functions can only be +called directly (i.e. without the use of ``DELEGATECALL``) if they do not modify +the state (i.e. if they are ``view`` or ``pure`` functions), +because libraries are assumed to be stateless. In particular, it is +not possible to destroy a library. + +.. note:: + Until version 0.4.20, it was possible to destroy libraries by + circumventing Solidity's type system. Starting from that version, + libraries contain a :ref:`mechanism<call-protection>` that + disallows state-modifying functions + to be called directly (i.e. without ``DELEGATECALL``). + +Libraries can be seen as implicit base contracts of the contracts that use them. +They will not be explicitly visible in the inheritance hierarchy, but calls +to library functions look just like calls to functions of explicit base +contracts (``L.f()`` if ``L`` is the name of the library). Furthermore, +``internal`` functions of libraries are visible in all contracts, just as +if the library were a base contract. Of course, calls to internal functions +use the internal calling convention, which means that all internal types +can be passed and types :ref:`stored in memory <data-location>` will be passed by reference and not copied. +To realize this in the EVM, code of internal library functions +and all functions called from therein will at compile time be pulled into the calling +contract, and a regular ``JUMP`` call will be used instead of a ``DELEGATECALL``. + +.. index:: using for, set + +The following example illustrates how to use libraries (but manual method +be sure to check out :ref:`using for <using-for>` for a +more advanced example to implement a set). + +:: + + pragma solidity >=0.4.22 <0.6.0; + + library Set { + // We define a new struct datatype that will be used to + // hold its data in the calling contract. + struct Data { mapping(uint => bool) flags; } + + // Note that the first parameter is of type "storage + // reference" and thus only its storage address and not + // its contents is passed as part of the call. This is a + // special feature of library functions. It is idiomatic + // to call the first parameter `self`, if the function can + // be seen as a method of that object. + function insert(Data storage self, uint value) + public + returns (bool) + { + if (self.flags[value]) + return false; // already there + self.flags[value] = true; + return true; + } + + function remove(Data storage self, uint value) + public + returns (bool) + { + if (!self.flags[value]) + return false; // not there + self.flags[value] = false; + return true; + } + + function contains(Data storage self, uint value) + public + view + returns (bool) + { + return self.flags[value]; + } + } + + contract C { + Set.Data knownValues; + + function register(uint value) public { + // The library functions can be called without a + // specific instance of the library, since the + // "instance" will be the current contract. + require(Set.insert(knownValues, value)); + } + // In this contract, we can also directly access knownValues.flags, if we want. + } + +Of course, you do not have to follow this way to use +libraries: they can also be used without defining struct +data types. Functions also work without any storage +reference parameters, and they can have multiple storage reference +parameters and in any position. + +The calls to ``Set.contains``, ``Set.insert`` and ``Set.remove`` +are all compiled as calls (``DELEGATECALL``) to an external +contract/library. If you use libraries, be aware that an +actual external function call is performed. +``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 +``msg.value`` changed, though). + +The following example shows how to use :ref:`types stored in memory <data-location>` and +internal functions in libraries in order to implement +custom types without the overhead of external function calls: + +:: + + pragma solidity >=0.4.16 <0.6.0; + + library BigInt { + struct bigint { + uint[] limbs; + } + + function fromUint(uint x) internal pure returns (bigint memory r) { + r.limbs = new uint[](1); + r.limbs[0] = x; + } + + function add(bigint memory _a, bigint memory _b) internal pure returns (bigint memory r) { + r.limbs = new uint[](max(_a.limbs.length, _b.limbs.length)); + uint carry = 0; + for (uint i = 0; i < r.limbs.length; ++i) { + uint a = limb(_a, i); + uint b = limb(_b, i); + r.limbs[i] = a + b + carry; + if (a + b < a || (a + b == uint(-1) && carry > 0)) + carry = 1; + else + carry = 0; + } + if (carry > 0) { + // too bad, we have to add a limb + uint[] memory newLimbs = new uint[](r.limbs.length + 1); + uint i; + for (i = 0; i < r.limbs.length; ++i) + newLimbs[i] = r.limbs[i]; + newLimbs[i] = carry; + r.limbs = newLimbs; + } + } + + function limb(bigint memory _a, uint _limb) internal pure returns (uint) { + return _limb < _a.limbs.length ? _a.limbs[_limb] : 0; + } + + function max(uint a, uint b) private pure returns (uint) { + return a > b ? a : b; + } + } + + contract C { + using BigInt for BigInt.bigint; + + function f() public pure { + BigInt.bigint memory x = BigInt.fromUint(7); + BigInt.bigint memory y = BigInt.fromUint(uint(-1)); + BigInt.bigint memory z = x.add(y); + assert(z.limb(1) > 0); + } + } + +As the compiler cannot know where the library will be +deployed at, these addresses have to be filled into the +final bytecode by a linker +(see :ref:`commandline-compiler` for how to use the +commandline compiler for linking). If the addresses are not +given as arguments to the compiler, the compiled hex code +will contain placeholders of the form ``__Set______`` (where +``Set`` is the name of the library). The address can be filled +manually by replacing all those 40 symbols by the hex +encoding of the address of the library contract. + +.. note:: + Manually linking libraries on the generated bytecode is discouraged, because + it is restricted to 36 characters. + You should ask the compiler to link the libraries at the time + a contract is compiled by either using + the ``--libraries`` option of ``solc`` or the ``libraries`` key if you use + the standard-JSON interface to the compiler. + +Restrictions for libraries in comparison to contracts: + +- No state variables +- Cannot inherit nor be inherited +- Cannot receive Ether + +(These might be lifted at a later point.) + +.. _call-protection: + +Call Protection For Libraries +============================= + +As mentioned in the introduction, if a library's code is executed +using a ``CALL`` instead of a ``DELEGATECALL`` or ``CALLCODE``, +it will revert unless a ``view`` or ``pure`` function is called. + +The EVM does not provide a direct way for a contract to detect +whether it was called using ``CALL`` or not, but a contract +can use the ``ADDRESS`` opcode to find out "where" it is +currently running. The generated code compares this address +to the address used at construction time to determine the mode +of calling. + +More specifically, the runtime code of a library always starts +with a push instruction, which is a zero of 20 bytes at +compilation time. When the deploy code runs, this constant +is replaced in memory by the current address and this +modified code is stored in the contract. At runtime, +this causes the deploy time address to be the first +constant to be pushed onto the stack and the dispatcher +code compares the current address against this constant +for any non-view and non-pure function. diff --git a/docs/contracts/using-for.rst b/docs/contracts/using-for.rst new file mode 100644 index 00000000..ef456ff4 --- /dev/null +++ b/docs/contracts/using-for.rst @@ -0,0 +1,119 @@ +.. index:: ! using for, library + +.. _using-for: + +********* +Using For +********* + +The directive ``using A for B;`` can be used to attach library +functions (from the library ``A``) to any type (``B``). +These functions will receive the object they are called on +as their first parameter (like the ``self`` variable in Python). + +The effect of ``using A for *;`` is that the functions from +the library ``A`` are attached to *any* type. + +In both situations, *all* functions in the library are attached, +even those where the type of the first parameter does not +match the type of the object. The type is checked at the +point the function is called and function overload +resolution is performed. + +The ``using A for B;`` directive is active only within the current +contract, including within all of its functions, and has no effect +outside of the contract in which it is used. The directive +may only be used inside a contract, not inside any of its functions. + +By including a library, its data types including library functions are +available without having to add further code. + +Let us rewrite the set example from the +:ref:`libraries` in this way:: + + pragma solidity >=0.4.16 <0.6.0; + + // This is the same code as before, just without comments + library Set { + struct Data { mapping(uint => bool) flags; } + + function insert(Data storage self, uint value) + public + returns (bool) + { + if (self.flags[value]) + return false; // already there + self.flags[value] = true; + return true; + } + + function remove(Data storage self, uint value) + public + returns (bool) + { + if (!self.flags[value]) + return false; // not there + self.flags[value] = false; + return true; + } + + function contains(Data storage self, uint value) + public + view + returns (bool) + { + return self.flags[value]; + } + } + + contract C { + using Set for Set.Data; // this is the crucial change + Set.Data knownValues; + + function register(uint value) public { + // Here, all variables of type Set.Data have + // corresponding member functions. + // The following function call is identical to + // `Set.insert(knownValues, value)` + require(knownValues.insert(value)); + } + } + +It is also possible to extend elementary types in that way:: + + pragma solidity >=0.4.16 <0.6.0; + + library Search { + function indexOf(uint[] storage self, uint value) + public + view + returns (uint) + { + for (uint i = 0; i < self.length; i++) + if (self[i] == value) return i; + return uint(-1); + } + } + + contract C { + using Search for uint[]; + uint[] data; + + function append(uint value) public { + data.push(value); + } + + function replace(uint _old, uint _new) public { + // This performs the library function call + uint index = data.indexOf(_old); + if (index == uint(-1)) + data.push(_new); + else + data[index] = _new; + } + } + +Note that all library calls are actual EVM function calls. This means that +if you pass memory or value types, a copy will be performed, even of the +``self`` variable. The only situation where no copy will be performed +is when storage reference variables are used. diff --git a/docs/contracts/visibility-and-getters.rst b/docs/contracts/visibility-and-getters.rst new file mode 100644 index 00000000..e78c9674 --- /dev/null +++ b/docs/contracts/visibility-and-getters.rst @@ -0,0 +1,198 @@ +.. index:: ! visibility, external, public, private, internal + +.. _visibility-and-getters: + +********************** +Visibility and Getters +********************** + +Since Solidity knows two kinds of function calls (internal +ones that do not create an actual EVM call (also called +a "message call") and external +ones that do), there are four types of visibilities for +functions and state variables. + +Functions have to be specified as being ``external``, +``public``, ``internal`` or ``private``. +For state variables, ``external`` is not possible. + +``external``: + External functions are part of the contract interface, + which means they can be called from other contracts and + via transactions. An external function ``f`` cannot be called + internally (i.e. ``f()`` does not work, but ``this.f()`` works). + External functions are sometimes more efficient when + they receive large arrays of data. + +``public``: + Public functions are part of the contract interface + and can be either called internally or via + messages. For public state variables, an automatic getter + function (see below) is generated. + +``internal``: + Those functions and state variables can only be + accessed internally (i.e. from within the current contract + or contracts deriving from it), without using ``this``. + +``private``: + Private functions and state variables are only + visible for the contract they are defined in and not in + derived contracts. + +.. note:: + Everything that is inside a contract is visible to + all observers external to the blockchain. Making something ``private`` + only prevents other contracts from accessing and modifying + the information, but it will still be visible to the + whole world outside of the blockchain. + +The visibility specifier is given after the type for +state variables and between parameter list and +return parameter list for functions. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract C { + function f(uint a) private pure returns (uint b) { return a + 1; } + function setData(uint a) internal { data = a; } + uint public data; + } + +In the following example, ``D``, can call ``c.getData()`` to retrieve the value of +``data`` in state storage, but is not able to call ``f``. Contract ``E`` is derived from +``C`` and, thus, can call ``compute``. + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract C { + uint private data; + + function f(uint a) private pure returns(uint b) { return a + 1; } + function setData(uint a) public { data = a; } + function getData() public view returns(uint) { return data; } + function compute(uint a, uint b) internal pure returns (uint) { return a + b; } + } + + // This will not compile + contract D { + function readData() public { + C c = new C(); + uint local = c.f(7); // error: member `f` is not visible + c.setData(3); + local = c.getData(); + local = c.compute(3, 5); // error: member `compute` is not visible + } + } + + contract E is C { + function g() public { + C c = new C(); + uint val = compute(3, 5); // access to internal member (from derived to parent contract) + } + } + +.. index:: ! getter;function, ! function;getter +.. _getter-functions: + +Getter Functions +================ + +The compiler automatically creates getter functions for +all **public** state variables. For the contract given below, the compiler will +generate a function called ``data`` that does not take any +arguments and returns a ``uint``, the value of the state +variable ``data``. State variables can be initialized +when they are declared. + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract C { + uint public data = 42; + } + + contract Caller { + C c = new C(); + function f() public view returns (uint) { + return c.data(); + } + } + +The getter functions have external visibility. If the +symbol is accessed internally (i.e. without ``this.``), +it evaluates to a state variable. If it is accessed externally +(i.e. with ``this.``), it evaluates to a function. + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract C { + uint public data; + function x() public returns (uint) { + data = 3; // internal access + return this.data(); // external access + } + } + +If you have a ``public`` state variable of array type, then you can only retrieve +single elements of the array via the generated getter function. This mechanism +exists to avoid high gas costs when returning an entire array. You can use +arguments to specify which individual element to return, for example +``data(0)``. If you want to return an entire array in one call, then you need +to write a function, for example: + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract arrayExample { + // public state variable + uint[] public myArray; + + // Getter function generated by the compiler + /* + function myArray(uint i) returns (uint) { + return myArray[i]; + } + */ + + // function that returns entire array + function getArray() returns (uint[] memory) { + return myArray; + } + } + +Now you can use ``getArray()`` to retrieve the entire array, instead of +``myArray(i)``, which returns a single element per call. + +The next example is more complex: + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract Complex { + struct Data { + uint a; + bytes3 b; + mapping (uint => uint) map; + } + mapping (uint => mapping(bool => Data[])) public data; + } + +It generates a function of the following form. The mapping in the struct is omitted +because there is no good way to provide the key for the mapping: + +:: + + function data(uint arg1, bool arg2, uint arg3) public returns (uint a, bytes3 b) { + a = data[arg1][arg2][arg3].a; + b = data[arg1][arg2][arg3].b; + } diff --git a/docs/contributing.rst b/docs/contributing.rst index 85816766..5768d944 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -112,7 +112,7 @@ For example, you could run the following command in your ``build`` folder: cmake -DCMAKE_BUILD_TYPE=Debug .. make -This will create symbols such that when you debug a test using the ``--debug`` flag, you will have acecess to functions and varialbes in which you can break or print with. +This will create symbols such that when you debug a test using the ``--debug`` flag, you will have access to functions and variables in which you can break or print with. The script ``./scripts/tests.sh`` also runs commandline tests and compilation tests diff --git a/docs/control-structures.rst b/docs/control-structures.rst index f8016806..46b3a7f1 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -168,7 +168,7 @@ is compiled so recursive creation-dependencies are not possible. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract D { uint public x; @@ -264,6 +264,31 @@ Complications for Arrays and Structs The semantics of assignments are a bit more complicated for non-value types like arrays and structs. Assigning *to* a state variable always creates an independent copy. On the other hand, assigning to a local variable creates an independent copy only for elementary types, i.e. static types that fit into 32 bytes. If structs or arrays (including ``bytes`` and ``string``) are assigned from a state variable to a local variable, the local variable holds a reference to the original state variable. A second assignment to the local variable does not modify the state but only changes the reference. Assignments to members (or elements) of the local variable *do* change the state. +In the example below the call to ``g(x)`` has no effect on ``x`` because it needs +to create an independent copy of the storage value in memory. However ``h(x)`` modifies ``x`` because a reference and +not a copy is passed. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract C { + uint[20] x; + + function f() public { + g(x); + h(x); + } + + function g(uint[20] memory y) internal pure { + y[2] = 3; + } + + function h(uint[20] storage y) internal { + y[3] = 4; + } + } + .. index:: ! scoping, declarations, default value .. _default-value: @@ -291,7 +316,7 @@ the two variables have the same name but disjoint scopes. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract C { function minimalScoping() pure public { { @@ -312,7 +337,7 @@ In any case, you will get a warning about the outer variable being shadowed. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; // This will report a warning contract C { function f() pure public returns (uint) { @@ -332,7 +357,7 @@ In any case, you will get a warning about the outer variable being shadowed. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; // This will not compile contract C { function f() pure public returns (uint) { @@ -379,7 +404,7 @@ a message string for ``require``, but not for ``assert``. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract Sharer { function sendHalf(address payable addr) public payable returns (uint balance) { @@ -425,7 +450,7 @@ The following example shows how an error string can be used together with revert :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract VendingMachine { function buy(uint amount) public payable { diff --git a/docs/examples/blind-auction.rst b/docs/examples/blind-auction.rst new file mode 100644 index 00000000..70d95ad6 --- /dev/null +++ b/docs/examples/blind-auction.rst @@ -0,0 +1,339 @@ +.. index:: auction;blind, auction;open, blind auction, open auction + +************* +Blind Auction +************* + +In this section, we will show how easy it is to create a +completely blind auction contract on Ethereum. +We will start with an open auction where everyone +can see the bids that are made and then extend this +contract into a blind auction where it is not +possible to see the actual bid until the bidding +period ends. + +.. _simple_auction: + +Simple Open Auction +=================== + +The general idea of the following simple auction contract +is that everyone can send their bids during +a bidding period. The bids already include sending +money / ether in order to bind the bidders to their +bid. If the highest bid is raised, the previously +highest bidder gets her money back. +After the end of the bidding period, the +contract has to be called manually for the +beneficiary to receive their money - contracts cannot +activate themselves. + +:: + + pragma solidity >=0.4.22 <0.6.0; + + contract SimpleAuction { + // Parameters of the auction. Times are either + // absolute unix timestamps (seconds since 1970-01-01) + // or time periods in seconds. + address payable public beneficiary; + uint public auctionEndTime; + + // Current state of the auction. + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + // Set to true at the end, disallows any change. + // By default initialized to `false`. + bool ended; + + // Events that will be emitted on changes. + event HighestBidIncreased(address bidder, uint amount); + event AuctionEnded(address winner, uint amount); + + // The following is a so-called natspec comment, + // recognizable by the three slashes. + // It will be shown when the user is asked to + // confirm a transaction. + + /// Create a simple auction with `_biddingTime` + /// seconds bidding time on behalf of the + /// beneficiary address `_beneficiary`. + constructor( + uint _biddingTime, + address payable _beneficiary + ) public { + beneficiary = _beneficiary; + auctionEndTime = now + _biddingTime; + } + + /// Bid on the auction with the value sent + /// together with this transaction. + /// The value will only be refunded if the + /// auction is not won. + function bid() public payable { + // No arguments are necessary, all + // information is already part of + // the transaction. The keyword payable + // is required for the function to + // be able to receive Ether. + + // Revert the call if the bidding + // period is over. + require( + now <= auctionEndTime, + "Auction already ended." + ); + + // If the bid is not higher, send the + // money back. + require( + msg.value > highestBid, + "There already is a higher bid." + ); + + if (highestBid != 0) { + // Sending back the money by simply using + // highestBidder.send(highestBid) is a security risk + // because it could execute an untrusted contract. + // It is always safer to let the recipients + // withdraw their money themselves. + pendingReturns[highestBidder] += highestBid; + } + highestBidder = msg.sender; + highestBid = msg.value; + emit HighestBidIncreased(msg.sender, msg.value); + } + + /// Withdraw a bid that was overbid. + function withdraw() public returns (bool) { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `send` returns. + pendingReturns[msg.sender] = 0; + + if (!msg.sender.send(amount)) { + // No need to call throw here, just reset the amount owing + pendingReturns[msg.sender] = amount; + return false; + } + } + return true; + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() public { + // It is a good guideline to structure functions that interact + // with other contracts (i.e. they call functions or send Ether) + // into three phases: + // 1. checking conditions + // 2. performing actions (potentially changing conditions) + // 3. interacting with other contracts + // If these phases are mixed up, the other contract could call + // back into the current contract and modify the state or cause + // effects (ether payout) to be performed multiple times. + // If functions called internally include interaction with external + // contracts, they also have to be considered interaction with + // external contracts. + + // 1. Conditions + require(now >= auctionEndTime, "Auction not yet ended."); + require(!ended, "auctionEnd has already been called."); + + // 2. Effects + ended = true; + emit AuctionEnded(highestBidder, highestBid); + + // 3. Interaction + beneficiary.transfer(highestBid); + } + } + +Blind Auction +============= + +The previous open auction is extended to a blind auction +in the following. The advantage of a blind auction is +that there is no time pressure towards the end of +the bidding period. Creating a blind auction on a +transparent computing platform might sound like a +contradiction, but cryptography comes to the rescue. + +During the **bidding period**, a bidder does not +actually send her bid, but only a hashed version of it. +Since it is currently considered practically impossible +to find two (sufficiently long) values whose hash +values are equal, the bidder commits to the bid by that. +After the end of the bidding period, the bidders have +to reveal their bids: They send their values +unencrypted and the contract checks that the hash value +is the same as the one provided during the bidding period. + +Another challenge is how to make the auction +**binding and blind** at the same time: The only way to +prevent the bidder from just not sending the money +after they won the auction is to make her send it +together with the bid. Since value transfers cannot +be blinded in Ethereum, anyone can see the value. + +The following contract solves this problem by +accepting any value that is larger than the highest +bid. Since this can of course only be checked during +the reveal phase, some bids might be **invalid**, and +this is on purpose (it even provides an explicit +flag to place invalid bids with high value transfers): +Bidders can confuse competition by placing several +high or low invalid bids. + + +:: + + pragma solidity >0.4.23 <0.6.0; + + contract BlindAuction { + struct Bid { + bytes32 blindedBid; + uint deposit; + } + + address payable public beneficiary; + uint public biddingEnd; + uint public revealEnd; + bool public ended; + + mapping(address => Bid[]) public bids; + + address public highestBidder; + uint public highestBid; + + // Allowed withdrawals of previous bids + mapping(address => uint) pendingReturns; + + event AuctionEnded(address winner, uint highestBid); + + /// Modifiers are a convenient way to validate inputs to + /// functions. `onlyBefore` is applied to `bid` below: + /// The new function body is the modifier's body where + /// `_` is replaced by the old function body. + modifier onlyBefore(uint _time) { require(now < _time); _; } + modifier onlyAfter(uint _time) { require(now > _time); _; } + + constructor( + uint _biddingTime, + uint _revealTime, + address payable _beneficiary + ) public { + beneficiary = _beneficiary; + biddingEnd = now + _biddingTime; + revealEnd = biddingEnd + _revealTime; + } + + /// Place a blinded bid with `_blindedBid` = + /// keccak256(abi.encodePacked(value, fake, secret)). + /// The sent ether is only refunded if the bid is correctly + /// revealed in the revealing phase. The bid is valid if the + /// ether sent together with the bid is at least "value" and + /// "fake" is not true. Setting "fake" to true and sending + /// not the exact amount are ways to hide the real bid but + /// still make the required deposit. The same address can + /// place multiple bids. + function bid(bytes32 _blindedBid) + public + payable + onlyBefore(biddingEnd) + { + bids[msg.sender].push(Bid({ + blindedBid: _blindedBid, + deposit: msg.value + })); + } + + /// Reveal your blinded bids. You will get a refund for all + /// correctly blinded invalid bids and for all bids except for + /// the totally highest. + function reveal( + uint[] memory _values, + bool[] memory _fake, + bytes32[] memory _secret + ) + public + onlyAfter(biddingEnd) + onlyBefore(revealEnd) + { + uint length = bids[msg.sender].length; + require(_values.length == length); + require(_fake.length == length); + require(_secret.length == length); + + uint refund; + for (uint i = 0; i < length; i++) { + Bid storage bidToCheck = bids[msg.sender][i]; + (uint value, bool fake, bytes32 secret) = + (_values[i], _fake[i], _secret[i]); + if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) { + // Bid was not actually revealed. + // Do not refund deposit. + continue; + } + refund += bidToCheck.deposit; + if (!fake && bidToCheck.deposit >= value) { + if (placeBid(msg.sender, value)) + refund -= value; + } + // Make it impossible for the sender to re-claim + // the same deposit. + bidToCheck.blindedBid = bytes32(0); + } + msg.sender.transfer(refund); + } + + // This is an "internal" function which means that it + // can only be called from the contract itself (or from + // derived contracts). + function placeBid(address bidder, uint value) internal + returns (bool success) + { + if (value <= highestBid) { + return false; + } + if (highestBidder != address(0)) { + // Refund the previously highest bidder. + pendingReturns[highestBidder] += highestBid; + } + highestBid = value; + highestBidder = bidder; + return true; + } + + /// Withdraw a bid that was overbid. + function withdraw() public { + uint amount = pendingReturns[msg.sender]; + if (amount > 0) { + // It is important to set this to zero because the recipient + // can call this function again as part of the receiving call + // before `transfer` returns (see the remark above about + // conditions -> effects -> interaction). + pendingReturns[msg.sender] = 0; + + msg.sender.transfer(amount); + } + } + + /// End the auction and send the highest bid + /// to the beneficiary. + function auctionEnd() + public + onlyAfter(revealEnd) + { + require(!ended); + emit AuctionEnded(highestBidder, highestBid); + ended = true; + beneficiary.transfer(highestBid); + } + }
\ No newline at end of file diff --git a/docs/examples/micropayment.rst b/docs/examples/micropayment.rst new file mode 100644 index 00000000..e06ea4a4 --- /dev/null +++ b/docs/examples/micropayment.rst @@ -0,0 +1,429 @@ +******************** +Micropayment Channel +******************** + +In this section we will learn how to build an example implementation +of a payment channel. It uses cryptographic signatures to make +repeated transfers of Ether between the same parties secure, instantaneous, and +without transaction fees. For the example, we need to understand how to +sign and verify signatures, and setup the payment channel. + +Creating and verifying signatures +================================= + +Imagine Alice wants to send a quantity of Ether to Bob, i.e. +Alice is the sender and the Bob is the recipient. + +Alice only needs to send cryptographically signed messages off-chain +(e.g. via email) to Bob and it is similar to writing checks. + +Alice and Bob use signatures to authorise transactions, which is possible with smart contracts on Ethereum. +Alice will build a simple smart contract that lets her transmit Ether, but instead of calling a function herself +to initiate a payment, she will let Bob do that, and therefore pay the transaction fee. + +The contract will work as follows: + + 1. Alice deploys the ``ReceiverPays`` contract, attaching enough Ether to cover the payments that will be made. + 2. Alice authorises a payment by signing a message with their private key. + 3. Alice sends the cryptographically signed message to Bob. The message does not need to be kept secret + (explained later), and the mechanism for sending it does not matter. + 4. Bob claims their payment by presenting the signed message to the smart contract, it verifies the + authenticity of the message and then releases the funds. + +Creating the signature +---------------------- + +Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. +In this tutorial, we will sign messages in the browser using `web3.js <https://github.com/ethereum/web3.js>`_ and `MetaMask <https://metamask.io>`_, using the method described in `EIP-762 <https://github.com/ethereum/EIPs/pull/712>`_, +as it provides a number of other security benefits. + +:: + /// Hashing first makes things easier + var hash = web3.utils.sha3("message to sign"); + web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); }); + +.. note:: + The ``web3.eth.personal.sign`` prepends the length of the message to the signed data. Since we hash first, the message will always be exactly 32 bytes long, and thus this length prefix is always the same. + +What to Sign +------------ + +For a contract that fulfils payments, the signed message must include: + + 1. The recipient's address. + 2. The amount to be transferred. + 3. Protection against replay attacks. + +A replay attack is when a signed message is reused to claim authorization for +a second action. +To avoid replay attacks we use the same as in Ethereum transactions +themselves, a so-called nonce, which is the number of transactions sent by an +account. +The smart contract checks if a nonce is used multiple times. + +Another type of replay attack can occur when the owner deploys a ``ReceiverPays`` smart contract, makes some payments, and then destroys the contract. Later, they decide to deploy the ``RecipientPays`` smart contract again, but the new contract does not know the nonces used in the previous deployment, so the attacker can use the old messages again. + +Alice can protect against this attack by including the contract's address in the message, and only messages containing the contract's address itself will be accepted. You can find an example of this in the first two lines of the ``claimPayment()`` function of the full contract at the end of this section. + +Packing arguments +----------------- + +Now that we have identified what information to include in the signed message, +we are ready to put the message together, hash it, and sign it. For simplicity, +we concatenate the data. The `ethereumjs-abi <https://github.com/ethereumjs/ethereumjs-abi>`_ +library provides a function called ``soliditySHA3`` that mimics the behaviour of +Solidity's ``keccak256`` function applied to arguments encoded using ``abi.encodePacked``. +Here is a JavaScript function that creates the proper signature for the ``ReceiverPays`` example: + +:: + + // recipient is the address that should be paid. + // amount, in wei, specifies how much ether should be sent. + // nonce can be any unique number to prevent replay attacks + // contractAddress is used to prevent cross-contract replay attacks + function signPayment(recipient, amount, nonce, contractAddress, callback) { + var hash = "0x" + abi.soliditySHA3( + ["address", "uint256", "uint256", "address"], + [recipient, amount, nonce, contractAddress] + ).toString("hex"); + + web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback); + } + +Recovering the Message Signer in Solidity +----------------------------------------- + +In general, ECDSA signatures consist of two parameters, ``r`` and ``s``. Signatures in Ethereum include a third parameter called ``v``, that you can use to verify which account's private key was used to sign the message, and the transaction's sender. Solidity provides a built-in function `ecrecover <mathematical-and-cryptographic-functions>`_ that accepts a message along with the ``r``, ``s`` and ``v`` parameters and returns the address that was used to sign the message. + +Extracting the Signature Parameters +----------------------------------- + +Signatures produced by web3.js are the concatenation of ``r``, ``s`` and ``v``, so the first step is to split these parameters apart. You can do this on the client-side, but doing it inside the smart contract means you only need to send one signature parameter rather than three. Splitting apart a byte array into component parts is a mess, so we use `inline assembly <assembly>`_ to do the job in the ``splitSignature`` function (the third function in the full contract at the end of this section). + +Computing the Message Hash +-------------------------- + +The smart contract needs to know exactly what parameters were signed, and so it +must recreate the message from the parameters and use that for signature verification. +The functions ``prefixed`` and ``recoverSigner`` do this in the ``claimPayment`` function. + +The full contract +----------------- + +:: + + pragma solidity >=0.4.24 <0.6.0; + + contract ReceiverPays { + address owner = msg.sender; + + mapping(uint256 => bool) usedNonces; + + constructor() public payable {} + + function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) public { + require(!usedNonces[nonce]); + usedNonces[nonce] = true; + + // this recreates the message that was signed on the client + bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this))); + + require(recoverSigner(message, signature) == owner); + + msg.sender.transfer(amount); + } + + /// destroy the contract and reclaim the leftover funds. + function kill() public { + require(msg.sender == owner); + selfdestruct(msg.sender); + } + + /// signature methods. + function splitSignature(bytes memory sig) + internal + pure + returns (uint8 v, bytes32 r, bytes32 s) + { + require(sig.length == 65); + + assembly { + // first 32 bytes, after the length prefix. + r := mload(add(sig, 32)) + // second 32 bytes. + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes). + v := byte(0, mload(add(sig, 96))) + } + + return (v, r, s); + } + + function recoverSigner(bytes32 message, bytes memory sig) + internal + pure + returns (address) + { + (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); + + return ecrecover(message, v, r, s); + } + + /// builds a prefixed hash to mimic the behavior of eth_sign. + function prefixed(bytes32 hash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + } + + +Writing a Simple Payment Channel +================================ + +Alice now builds a simple but complete implementation of a payment channel. Payment channels use cryptographic signatures to make repeated transfers of Ether securely, instantaneously, and without transaction fees. + +What is a Payment Channel? +-------------------------- + +Payment channels allow participants to make repeated transfers of Ether without using transactions. This means that you can avoid the delays and fees associated with transactions. We are going to explore a simple unidirectional payment channel between two parties (Alice and Bob). It involves three steps: + + 1. Alice funds a smart contract with Ether. This "opens" the payment channel. + 2. Alice signs messages that specify how much of that Ether is owed to the recipient. This step is repeated for each payment. + 3. Bob "closes" the payment channel, withdrawing their portion of the Ether and sending the remainder back to the sender. + +.. note:: + Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender transmits a cryptographically signed message to the recipient via off chain methods (e.g. email). This means only two transactions are required to support any number of transfers. + +Bob is guaranteed to receive their funds because the smart contract escrows the Ether and honours a valid signed message. The smart contract also enforces a timeout, so Alice is guaranteed to eventually recover their funds even if the recipient refuses to close the channel. It is up to the participants in a payment channel to decide how long to keep it open. For a short-lived transaction, such as paying an internet café for each minute of network access, or for a longer relationship, such as paying an employee an hourly wage, a payment could last for months or years. + +Opening the Payment Channel +--------------------------- + +To open the payment channel, Alice deploys the smart contract, attaching the Ether to be escrowed and specifying the intended recipient and a maximum duration for the channel to exist. This is the function ``SimplePaymentChannel`` in the contract, at the end of this section. + +Making Payments +--------------- + +Alice makes payments by sending signed messages to Bob. +This step is performed entirely outside of the Ethereum network. +Messages are cryptographically signed by the sender and then transmitted directly to the recipient. + +Each message includes the following information: + + * The smart contract's address, used to prevent cross-contract replay attacks. + * The total amount of Ether that is owed the recipient so far. + +A payment channel is closed just once, at the end of a series of transfers. +Because of this, only one of the messages sent is redeemed. This is why +each message specifies a cumulative total amount of Ether owed, rather than the +amount of the individual micropayment. The recipient will naturally choose to +redeem the most recent message because that is the one with the highest total. +The nonce per-message is not needed anymore, because the smart contract only honors a single message. The address of the smart contract is still used +to prevent a message intended for one payment channel from being used for a different channel. + +Here is the modified JavaScript code to cryptographically sign a message from the previous section: + +:: + + function constructPaymentMessage(contractAddress, amount) { + return abi.soliditySHA3( + ["address", "uint256"], + [contractAddress, amount] + ); + } + + function signMessage(message, callback) { + web3.eth.personal.sign( + "0x" + message.toString("hex"), + web3.eth.defaultAccount, + callback + ); + } + + // contractAddress is used to prevent cross-contract replay attacks. + // amount, in wei, specifies how much Ether should be sent. + + function signPayment(contractAddress, amount, callback) { + var message = constructPaymentMessage(contractAddress, amount); + signMessage(message, callback); + } + + +Closing the Payment Channel +--------------------------- + +When Bob is ready to receive their funds, it is time to +close the payment channel by calling a ``close`` function on the smart contract. +Closing the channel pays the recipient the Ether they are owed and destroys the contract, sending any remaining Ether back to Alice. To close the channel, Bob needs to provide a message signed by Alice. + +The smart contract must verify that the message contains a valid signature from the sender. +The process for doing this verification is the same as the process the recipient uses. +The Solidity functions ``isValidSignature`` and ``recoverSigner`` work just like their +JavaScript counterparts in the previous section, with the latter function borrowed from the ``ReceiverPays`` contract. + +Only the payment channel recipient can call the ``close`` function, +who naturally passes the most recent payment message because that message +carries the highest total owed. If the sender were allowed to call this function, +they could provide a message with a lower amount and cheat the recipient out of what they are owed. + +The function verifies the signed message matches the given parameters. +If everything checks out, the recipient is sent their portion of the Ether, +and the sender is sent the rest via a ``selfdestruct``. +You can see the ``close`` function in the full contract. + +Channel Expiration +------------------- + +Bob can close the payment channel at any time, but if they fail to do so, +Alice needs a way to recover their escrowed funds. An *expiration* time was set +at the time of contract deployment. Once that time is reached, Alice can call +``claimTimeout`` to recover their funds. You can see the ``claimTimeout`` function in the full contract. + +After this function is called, Bob can no longer receive any Ether, +so it is important that Bob closes the channel before the expiration is reached. + +The full contract +----------------- + +:: + + pragma solidity >=0.4.24 <0.6.0; + + contract SimplePaymentChannel { + address payable public sender; // The account sending payments. + address payable public recipient; // The account receiving the payments. + uint256 public expiration; // Timeout in case the recipient never closes. + + constructor (address payable _recipient, uint256 duration) + public + payable + { + sender = msg.sender; + recipient = _recipient; + expiration = now + duration; + } + + function isValidSignature(uint256 amount, bytes memory signature) + internal + view + returns (bool) + { + bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount))); + + // check that the signature is from the payment sender + return recoverSigner(message, signature) == sender; + } + + /// the recipient can close the channel at any time by presenting a + /// signed amount from the sender. the recipient will be sent that amount, + /// and the remainder will go back to the sender + function close(uint256 amount, bytes memory signature) public { + require(msg.sender == recipient); + require(isValidSignature(amount, signature)); + + recipient.transfer(amount); + selfdestruct(sender); + } + + /// the sender can extend the expiration at any time + function extend(uint256 newExpiration) public { + require(msg.sender == sender); + require(newExpiration > expiration); + + expiration = newExpiration; + } + + /// if the timeout is reached without the recipient closing the channel, + /// then the Ether is released back to the sender. + function claimTimeout() public { + require(now >= expiration); + selfdestruct(sender); + } + + /// All functions below this are just taken from the chapter + /// 'creating and verifying signatures' chapter. + + function splitSignature(bytes memory sig) + internal + pure + returns (uint8 v, bytes32 r, bytes32 s) + { + require(sig.length == 65); + + assembly { + // first 32 bytes, after the length prefix + r := mload(add(sig, 32)) + // second 32 bytes + s := mload(add(sig, 64)) + // final byte (first byte of the next 32 bytes) + v := byte(0, mload(add(sig, 96))) + } + + return (v, r, s); + } + + function recoverSigner(bytes32 message, bytes memory sig) + internal + pure + returns (address) + { + (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); + + return ecrecover(message, v, r, s); + } + + /// builds a prefixed hash to mimic the behavior of eth_sign. + function prefixed(bytes32 hash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); + } + } + + +.. note:: + The function ``splitSignature`` does not use all security + checks. A real implementation should use a more rigorously tested library, + such as openzepplin's `version <https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ECRecovery.sol>`_ of this code. + +Verifying Payments +------------------ + +Unlike in the previous section, messages in a payment channel aren't +redeemed right away. The recipient keeps track of the latest message and +redeems it when it's time to close the payment channel. This means it's +critical that the recipient perform their own verification of each message. +Otherwise there is no guarantee that the recipient will be able to get paid +in the end. + +The recipient should verify each message using the following process: + + 1. Verify that the contact address in the message matches the payment channel. + 2. Verify that the new total is the expected amount. + 3. Verify that the new total does not exceed the amount of Ether escrowed. + 4. Verify that the signature is valid and comes from the payment channel sender. + +We'll use the `ethereumjs-util <https://github.com/ethereumjs/ethereumjs-util>`_ +library to write this verification. The final step can be done a number of ways, +and we use JavaScript. The following code borrows the `constructMessage` function from the signing **JavaScript code** above: + +:: + + // this mimics the prefixing behavior of the eth_sign JSON-RPC method. + function prefixed(hash) { + return ethereumjs.ABI.soliditySHA3( + ["string", "bytes32"], + ["\x19Ethereum Signed Message:\n32", hash] + ); + } + + function recoverSigner(message, signature) { + var split = ethereumjs.Util.fromRpcSig(signature); + var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s); + var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex"); + return signer; + } + + function isValidSignature(contractAddress, amount, signature, expectedSigner) { + var message = prefixed(constructPaymentMessage(contractAddress, amount)); + var signer = recoverSigner(message, signature); + return signer.toLowerCase() == + ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase(); + } diff --git a/docs/examples/safe-remote.rst b/docs/examples/safe-remote.rst new file mode 100644 index 00000000..cfc63a24 --- /dev/null +++ b/docs/examples/safe-remote.rst @@ -0,0 +1,107 @@ +.. index:: purchase, remote purchase, escrow + +******************** +Safe Remote Purchase +******************** + +:: + + pragma solidity >=0.4.22 <0.6.0; + + contract Purchase { + uint public value; + address payable public seller; + address payable public buyer; + enum State { Created, Locked, Inactive } + 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. + constructor() public payable { + seller = msg.sender; + value = msg.value / 2; + require((2 * value) == msg.value, "Value has to be even."); + } + + modifier condition(bool _condition) { + require(_condition); + _; + } + + modifier onlyBuyer() { + require( + msg.sender == buyer, + "Only buyer can call this." + ); + _; + } + + modifier onlySeller() { + require( + msg.sender == seller, + "Only seller can call this." + ); + _; + } + + modifier inState(State _state) { + require( + state == _state, + "Invalid state." + ); + _; + } + + event Aborted(); + event PurchaseConfirmed(); + event ItemReceived(); + + /// Abort the purchase and reclaim the ether. + /// Can only be called by the seller before + /// the contract is locked. + function abort() + public + onlySeller + inState(State.Created) + { + emit Aborted(); + state = State.Inactive; + seller.transfer(address(this).balance); + } + + /// Confirm the purchase as buyer. + /// Transaction has to include `2 * value` ether. + /// The ether will be locked until confirmReceived + /// is called. + function confirmPurchase() + public + inState(State.Created) + condition(msg.value == (2 * value)) + payable + { + emit PurchaseConfirmed(); + buyer = msg.sender; + state = State.Locked; + } + + /// Confirm that you (the buyer) received the item. + /// This will release the locked ether. + function confirmReceived() + public + onlyBuyer + inState(State.Locked) + { + emit ItemReceived(); + // It is important to change the state first because + // otherwise, the contracts called using `send` below + // can call in again here. + state = State.Inactive; + + // NOTE: This actually allows both the buyer and the seller to + // block the refund - the withdraw pattern should be used. + + buyer.transfer(value); + seller.transfer(address(this).balance); + } + }
\ No newline at end of file diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst new file mode 100644 index 00000000..73ace87d --- /dev/null +++ b/docs/examples/voting.rst @@ -0,0 +1,191 @@ +.. index:: voting, ballot + +.. _voting: + +****** +Voting +****** + +The following contract is quite complex, but showcases +a lot of Solidity's features. It implements a voting +contract. Of course, the main problems of electronic +voting is how to assign voting rights to the correct +persons and how to prevent manipulation. We will not +solve all problems here, but at least we will show +how delegated voting can be done so that vote counting +is **automatic and completely transparent** at the +same time. + +The idea is to create one contract per ballot, +providing a short name for each option. +Then the creator of the contract who serves as +chairperson will give the right to vote to each +address individually. + +The persons behind the addresses can then choose +to either vote themselves or to delegate their +vote to a person they trust. + +At the end of the voting time, ``winningProposal()`` +will return the proposal with the largest number +of votes. + +:: + + pragma solidity >=0.4.22 <0.6.0; + + /// @title Voting with delegation. + contract Ballot { + // This declares a new complex type which will + // be used for variables later. + // It will represent a single voter. + struct Voter { + uint weight; // weight is accumulated by delegation + bool voted; // if true, that person already voted + address delegate; // person delegated to + uint vote; // index of the voted proposal + } + + // This is a type for a single proposal. + struct Proposal { + bytes32 name; // short name (up to 32 bytes) + uint voteCount; // number of accumulated votes + } + + address public chairperson; + + // This declares a state variable that + // stores a `Voter` struct for each possible address. + mapping(address => Voter) public voters; + + // A dynamically-sized array of `Proposal` structs. + Proposal[] public proposals; + + /// Create a new ballot to choose one of `proposalNames`. + constructor(bytes32[] memory proposalNames) public { + chairperson = msg.sender; + voters[chairperson].weight = 1; + + // For each of the provided proposal names, + // create a new proposal object and add it + // to the end of the array. + for (uint i = 0; i < proposalNames.length; i++) { + // `Proposal({...})` creates a temporary + // Proposal object and `proposals.push(...)` + // appends it to the end of `proposals`. + proposals.push(Proposal({ + name: proposalNames[i], + voteCount: 0 + })); + } + } + + // Give `voter` the right to vote on this ballot. + // May only be called by `chairperson`. + function giveRightToVote(address voter) public { + // If the first argument of `require` evaluates + // to `false`, execution terminates and all + // changes to the state and to Ether balances + // are reverted. + // This used to consume all gas in old EVM versions, but + // not anymore. + // It is often a good idea to use `require` to check if + // functions are called correctly. + // As a second argument, you can also provide an + // explanation about what went wrong. + require( + msg.sender == chairperson, + "Only chairperson can give right to vote." + ); + require( + !voters[voter].voted, + "The voter already voted." + ); + require(voters[voter].weight == 0); + voters[voter].weight = 1; + } + + /// Delegate your vote to the voter `to`. + function delegate(address to) public { + // assigns reference + Voter storage sender = voters[msg.sender]; + require(!sender.voted, "You already voted."); + + require(to != msg.sender, "Self-delegation is disallowed."); + + // Forward the delegation as long as + // `to` also delegated. + // In general, such loops are very dangerous, + // because if they run too long, they might + // need more gas than is available in a block. + // In this case, the delegation will not be executed, + // but in other situations, such loops might + // cause a contract to get "stuck" completely. + while (voters[to].delegate != address(0)) { + to = voters[to].delegate; + + // We found a loop in the delegation, not allowed. + require(to != msg.sender, "Found loop in delegation."); + } + + // Since `sender` is a reference, this + // modifies `voters[msg.sender].voted` + sender.voted = true; + sender.delegate = to; + Voter storage delegate_ = voters[to]; + if (delegate_.voted) { + // If the delegate already voted, + // directly add to the number of votes + proposals[delegate_.vote].voteCount += sender.weight; + } else { + // If the delegate did not vote yet, + // add to her weight. + delegate_.weight += sender.weight; + } + } + + /// Give your vote (including votes delegated to you) + /// to proposal `proposals[proposal].name`. + function vote(uint proposal) public { + Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "Has no right to vote"); + require(!sender.voted, "Already voted."); + sender.voted = true; + sender.vote = proposal; + + // If `proposal` is out of the range of the array, + // this will throw automatically and revert all + // changes. + proposals[proposal].voteCount += sender.weight; + } + + /// @dev Computes the winning proposal taking all + /// previous votes into account. + function winningProposal() public view + returns (uint winningProposal_) + { + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winningProposal_ = p; + } + } + } + + // Calls winningProposal() function to get the index + // of the winner contained in the proposals array and then + // returns the name of the winner + function winnerName() public view + returns (bytes32 winnerName_) + { + winnerName_ = proposals[winningProposal()].name; + } + } + + +Possible Improvements +===================== + +Currently, many transactions are needed to assign the rights +to vote to all participants. Can you think of a better way? diff --git a/docs/frequently-asked-questions.rst b/docs/frequently-asked-questions.rst index d263e0c6..00d9e043 100644 --- a/docs/frequently-asked-questions.rst +++ b/docs/frequently-asked-questions.rst @@ -16,11 +16,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 later. -How do structs work? -==================== - -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 are some examples of basic string manipulation (``substring``, ``indexOf``, ``charAt``, etc)? ================================================================================================== @@ -59,44 +54,10 @@ Yes, you can use ``abi.encodePacked``:: } } - -Why is the low-level function ``.call()`` less favorable than instantiating a contract with a variable (``ContractB b;``) and executing its functions (``b.doSomething();``)? -============================================================================================================================================================================= - -If you use actual functions, the compiler will tell you if the types -or your arguments do not match, if the function does not exist -or is not visible and it will do the packing of the -arguments for you. - -See `ping.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/45_ping.sol>`_ and -`pong.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/45_pong.sol>`_. - -Are comments included with deployed contracts and do they increase deployment gas? -================================================================================== - -No, everything that is not needed for execution is removed during compilation. -This includes, among others, comments, variable names and type names. - -What happens if you send ether along with a function call to a contract? -======================================================================== - -It gets added to the total balance of the contract, just like when you send ether when creating a contract. -You can only send ether along to a function that has the ``payable`` modifier, -otherwise an exception is thrown. - ****************** Advanced Questions ****************** -How do you get a random number in a contract? (Implement a self-returning gambling contract.) -============================================================================================= - -Getting randomness right is often the crucial part in a crypto project and -most failures result from bad random number generators. - -If you do not want it to be safe, you build something similar to the `coin flipper <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/35_coin_flipper.sol>`_ -but otherwise, rather use a contract that supplies randomness, like the `RANDAO <https://github.com/randao/randao>`_. - Get return value from non-constant function from another contract ================================================================= @@ -105,23 +66,6 @@ The key point is that the calling contract needs to know about the function it i See `ping.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/45_ping.sol>`_ and `pong.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/45_pong.sol>`_. -How do you create 2-dimensional arrays? -======================================= - -See `2D_array.sol <https://github.com/fivedogit/solidity-baby-steps/blob/master/contracts/55_2D_array.sol>`_. - -Note that filling a 10x10 square of ``uint8`` + contract creation took more than ``800,000`` -gas at the time of this writing. 17x17 took ``2,000,000`` gas. With the limit at -3.14 million... well, there’s a pretty low ceiling for what you can create right -now. - -Note that merely "creating" the array is free, the costs are in filling it. - -Note2: Optimizing storage access can pull the gas costs down considerably, because -32 ``uint8`` values can be stored in a single slot. The problem is that these optimizations -currently do not work across loops and also have a problem with bounds checking. -You might get much better results in the future, though. - How do I initialize a contract with only a specific amount of wei? ================================================================== @@ -131,7 +75,7 @@ In the case of a ``contract A`` calling a new instance of ``contract B``, parent You will need to make sure that you have both contracts aware of each other's presence and that ``contract B`` has a ``payable`` constructor. In this example:: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract B { constructor() public payable {} @@ -145,69 +89,6 @@ In this example:: } } -Can a contract pass an array (static size) or string or ``bytes`` (dynamic size) to another contract? -===================================================================================================== - -Sure. Take care that if you cross the memory / storage boundary, -independent copies will be created:: - - pragma solidity >=0.4.16 <0.6.0; - - contract C { - uint[20] x; - - function f() public { - g(x); - h(x); - } - - function g(uint[20] memory y) internal pure { - y[2] = 3; - } - - function h(uint[20] storage y) internal { - y[3] = 4; - } - } - -The call to ``g(x)`` will not have an effect on ``x`` because it needs -to create an independent copy of the storage value in memory. -On the other hand, ``h(x)`` successfully modifies ``x`` because only -a reference and not a copy is passed. - -What does the following strange check do in the Custom Token contract? -====================================================================== - -:: - - require((balanceOf[_to] + _value) >= balanceOf[_to]); - -Integers in Solidity (and most other machine-related programming languages) are restricted to a certain range. -For ``uint256``, this is ``0`` up to ``2**256 - 1``. If the result of some operation on those numbers -does not fit inside this range, it is truncated. These truncations can have -`serious consequences <https://en.bitcoin.it/wiki/Value_overflow_incident>`_, so code like the one -above is necessary to avoid certain attacks. - - -Why are explicit conversions between fixed-size bytes types and integer types failing? -====================================================================================== - -Since version 0.5.0 explicit conversions between fixed-size byte arrays and integers are only allowed, -if both types have the same size. This prevents unexpected behaviour when truncating or padding. -Such conversions are still possible, but intermediate casts are required that make the desired -truncation and padding convention explicit. See :ref:`types-conversion-elementary-types` for a full -explanation and examples. - - -Why can number literals not be converted to fixed-size bytes types? -=================================================================== - -Since version 0.5.0 only hexadecimal number literals can be converted to fixed-size bytes -types and only if the number of hex digits matches the size of the type. See :ref:`types-conversion-literals` -for a full explanation and examples. - - - More Questions? =============== diff --git a/docs/index.rst b/docs/index.rst index ed931163..16745c07 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,6 +19,8 @@ user-defined types among other features. With Solidity you can create contracts for uses such as voting, crowdfunding, blind auctions, and multi-signature wallets. +When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number [to indicate this fast pace of change](https://semver.org/#spec-item-4). + Language Documentation ---------------------- diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 7daae06a..0cce690b 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -81,7 +81,7 @@ registering with username and password — all you need is an Ethereum keypair. :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; contract Coin { // The keyword "public" makes those variables diff --git a/docs/layout-of-source-files.rst b/docs/layout-of-source-files.rst index fa36fc6a..235f4dd4 100644 --- a/docs/layout-of-source-files.rst +++ b/docs/layout-of-source-files.rst @@ -37,12 +37,12 @@ breaking changes, those releases will always have versions of the form The version pragma is used as follows:: - pragma solidity ^0.4.0; + pragma solidity ^0.5.2; -Such a source file will not compile with a compiler earlier than version 0.4.0 -and it will also not work on a compiler starting from version 0.5.0 (this +Such a source file will not compile with a compiler earlier than version 0.5.2 +and it will also not work on a compiler starting from version 0.6.0 (this second condition is added by using ``^``). The idea behind this is that -there will be no breaking changes until version ``0.5.0``, so we can always +there will be no breaking changes until version ``0.6.0``, so we can always be sure that our code will compile the way we intended it to. We do not fix the exact version of the compiler, so that bugfix releases are still possible. diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 5a6f3875..69124c77 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -385,6 +385,8 @@ Global Variables - ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure +- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`. +- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`. .. note:: Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness, @@ -445,7 +447,7 @@ These keywords are reserved in Solidity. They might become part of the syntax in ``abstract``, ``after``, ``alias``, ``apply``, ``auto``, ``case``, ``catch``, ``copyof``, ``default``, ``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``, ``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``, -``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``type``, ``typedef``, ``typeof``, +``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``, ``unchecked``. Language Grammar diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index bd06276b..ebc39ad0 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -183,7 +183,7 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; // THIS CONTRACT CONTAINS A BUG - DO NOT USE contract TxUserWallet { @@ -203,7 +203,7 @@ Now someone tricks you into sending ether to the address of this attack wallet: :: - pragma solidity >0.4.99 <0.6.0; + pragma solidity ^0.5.0; interface TxUserWallet { function transferTo(address payable dest, uint amount) external; @@ -223,7 +223,7 @@ Now someone tricks you into sending ether to the address of this attack wallet: If your wallet had checked ``msg.sender`` for authorization, it would get the address of the attack wallet, instead of the owner address. But by checking ``tx.origin``, it gets the original address that kicked off the transaction, which is still the owner address. The attack wallet instantly drains all your funds. - +.. _underflow-overflow: Two's Complement / Underflows / Overflows ========================================= @@ -241,9 +241,11 @@ more special edge cases for signed numbers. Try to use ``require`` to limit the size of inputs to a reasonable range and use the :ref:`SMT checker<smt_checker>` to find potential overflows, or use a library like -`SafeMath<https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol>` +`SafeMath <https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/math/SafeMath.sol>`_ if you want all overflows to cause a revert. +Code such as ``require((balanceOf[_to] + _value) >= balanceOf[_to])`` can also help you check if values are what you expect. + Minor Details ============= diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 0041e80c..3e54ec28 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -2,1073 +2,9 @@ Solidity by Example ################### -.. index:: voting, ballot +.. include:: examples/voting.rst +.. include:: examples/blind-auction.rst -.. _voting: +.. include:: examples/safe-remote.rst -****** -Voting -****** - -The following contract is quite complex, but showcases -a lot of Solidity's features. It implements a voting -contract. Of course, the main problems of electronic -voting is how to assign voting rights to the correct -persons and how to prevent manipulation. We will not -solve all problems here, but at least we will show -how delegated voting can be done so that vote counting -is **automatic and completely transparent** at the -same time. - -The idea is to create one contract per ballot, -providing a short name for each option. -Then the creator of the contract who serves as -chairperson will give the right to vote to each -address individually. - -The persons behind the addresses can then choose -to either vote themselves or to delegate their -vote to a person they trust. - -At the end of the voting time, ``winningProposal()`` -will return the proposal with the largest number -of votes. - -:: - - pragma solidity >=0.4.22 <0.6.0; - - /// @title Voting with delegation. - contract Ballot { - // This declares a new complex type which will - // be used for variables later. - // It will represent a single voter. - struct Voter { - uint weight; // weight is accumulated by delegation - bool voted; // if true, that person already voted - address delegate; // person delegated to - uint vote; // index of the voted proposal - } - - // This is a type for a single proposal. - struct Proposal { - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes - } - - address public chairperson; - - // This declares a state variable that - // stores a `Voter` struct for each possible address. - mapping(address => Voter) public voters; - - // A dynamically-sized array of `Proposal` structs. - Proposal[] public proposals; - - /// Create a new ballot to choose one of `proposalNames`. - constructor(bytes32[] memory proposalNames) public { - chairperson = msg.sender; - voters[chairperson].weight = 1; - - // For each of the provided proposal names, - // create a new proposal object and add it - // to the end of the array. - for (uint i = 0; i < proposalNames.length; i++) { - // `Proposal({...})` creates a temporary - // Proposal object and `proposals.push(...)` - // appends it to the end of `proposals`. - proposals.push(Proposal({ - name: proposalNames[i], - voteCount: 0 - })); - } - } - - // Give `voter` the right to vote on this ballot. - // May only be called by `chairperson`. - function giveRightToVote(address voter) public { - // If the first argument of `require` evaluates - // to `false`, execution terminates and all - // changes to the state and to Ether balances - // are reverted. - // This used to consume all gas in old EVM versions, but - // not anymore. - // It is often a good idea to use `require` to check if - // functions are called correctly. - // As a second argument, you can also provide an - // explanation about what went wrong. - require( - msg.sender == chairperson, - "Only chairperson can give right to vote." - ); - require( - !voters[voter].voted, - "The voter already voted." - ); - require(voters[voter].weight == 0); - voters[voter].weight = 1; - } - - /// Delegate your vote to the voter `to`. - function delegate(address to) public { - // assigns reference - Voter storage sender = voters[msg.sender]; - require(!sender.voted, "You already voted."); - - require(to != msg.sender, "Self-delegation is disallowed."); - - // Forward the delegation as long as - // `to` also delegated. - // In general, such loops are very dangerous, - // because if they run too long, they might - // need more gas than is available in a block. - // In this case, the delegation will not be executed, - // but in other situations, such loops might - // cause a contract to get "stuck" completely. - while (voters[to].delegate != address(0)) { - to = voters[to].delegate; - - // We found a loop in the delegation, not allowed. - require(to != msg.sender, "Found loop in delegation."); - } - - // Since `sender` is a reference, this - // modifies `voters[msg.sender].voted` - sender.voted = true; - sender.delegate = to; - Voter storage delegate_ = voters[to]; - if (delegate_.voted) { - // If the delegate already voted, - // directly add to the number of votes - proposals[delegate_.vote].voteCount += sender.weight; - } else { - // If the delegate did not vote yet, - // add to her weight. - delegate_.weight += sender.weight; - } - } - - /// Give your vote (including votes delegated to you) - /// to proposal `proposals[proposal].name`. - function vote(uint proposal) public { - Voter storage sender = voters[msg.sender]; - require(sender.weight != 0, "Has no right to vote"); - require(!sender.voted, "Already voted."); - sender.voted = true; - sender.vote = proposal; - - // If `proposal` is out of the range of the array, - // this will throw automatically and revert all - // changes. - proposals[proposal].voteCount += sender.weight; - } - - /// @dev Computes the winning proposal taking all - /// previous votes into account. - function winningProposal() public view - returns (uint winningProposal_) - { - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winningProposal_ = p; - } - } - } - - // Calls winningProposal() function to get the index - // of the winner contained in the proposals array and then - // returns the name of the winner - function winnerName() public view - returns (bytes32 winnerName_) - { - winnerName_ = proposals[winningProposal()].name; - } - } - - -Possible Improvements -===================== - -Currently, many transactions are needed to assign the rights -to vote to all participants. Can you think of a better way? - -.. index:: auction;blind, auction;open, blind auction, open auction - -************* -Blind Auction -************* - -In this section, we will show how easy it is to create a -completely blind auction contract on Ethereum. -We will start with an open auction where everyone -can see the bids that are made and then extend this -contract into a blind auction where it is not -possible to see the actual bid until the bidding -period ends. - -.. _simple_auction: - -Simple Open Auction -=================== - -The general idea of the following simple auction contract -is that everyone can send their bids during -a bidding period. The bids already include sending -money / ether in order to bind the bidders to their -bid. If the highest bid is raised, the previously -highest bidder gets her money back. -After the end of the bidding period, the -contract has to be called manually for the -beneficiary to receive their money - contracts cannot -activate themselves. - -:: - - pragma solidity >=0.4.22 <0.6.0; - - contract SimpleAuction { - // Parameters of the auction. Times are either - // absolute unix timestamps (seconds since 1970-01-01) - // or time periods in seconds. - address payable public beneficiary; - uint public auctionEndTime; - - // Current state of the auction. - address public highestBidder; - uint public highestBid; - - // Allowed withdrawals of previous bids - mapping(address => uint) pendingReturns; - - // Set to true at the end, disallows any change. - // By default initialized to `false`. - bool ended; - - // Events that will be emitted on changes. - event HighestBidIncreased(address bidder, uint amount); - event AuctionEnded(address winner, uint amount); - - // The following is a so-called natspec comment, - // recognizable by the three slashes. - // It will be shown when the user is asked to - // confirm a transaction. - - /// Create a simple auction with `_biddingTime` - /// seconds bidding time on behalf of the - /// beneficiary address `_beneficiary`. - constructor( - uint _biddingTime, - address payable _beneficiary - ) public { - beneficiary = _beneficiary; - auctionEndTime = now + _biddingTime; - } - - /// Bid on the auction with the value sent - /// together with this transaction. - /// The value will only be refunded if the - /// auction is not won. - function bid() public payable { - // No arguments are necessary, all - // information is already part of - // the transaction. The keyword payable - // is required for the function to - // be able to receive Ether. - - // Revert the call if the bidding - // period is over. - require( - now <= auctionEndTime, - "Auction already ended." - ); - - // If the bid is not higher, send the - // money back. - require( - msg.value > highestBid, - "There already is a higher bid." - ); - - if (highestBid != 0) { - // Sending back the money by simply using - // highestBidder.send(highestBid) is a security risk - // because it could execute an untrusted contract. - // It is always safer to let the recipients - // withdraw their money themselves. - pendingReturns[highestBidder] += highestBid; - } - highestBidder = msg.sender; - highestBid = msg.value; - emit HighestBidIncreased(msg.sender, msg.value); - } - - /// Withdraw a bid that was overbid. - function withdraw() public returns (bool) { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `send` returns. - pendingReturns[msg.sender] = 0; - - if (!msg.sender.send(amount)) { - // No need to call throw here, just reset the amount owing - pendingReturns[msg.sender] = amount; - return false; - } - } - return true; - } - - /// End the auction and send the highest bid - /// to the beneficiary. - function auctionEnd() public { - // It is a good guideline to structure functions that interact - // with other contracts (i.e. they call functions or send Ether) - // into three phases: - // 1. checking conditions - // 2. performing actions (potentially changing conditions) - // 3. interacting with other contracts - // If these phases are mixed up, the other contract could call - // back into the current contract and modify the state or cause - // effects (ether payout) to be performed multiple times. - // If functions called internally include interaction with external - // contracts, they also have to be considered interaction with - // external contracts. - - // 1. Conditions - require(now >= auctionEndTime, "Auction not yet ended."); - require(!ended, "auctionEnd has already been called."); - - // 2. Effects - ended = true; - emit AuctionEnded(highestBidder, highestBid); - - // 3. Interaction - beneficiary.transfer(highestBid); - } - } - -Blind Auction -============= - -The previous open auction is extended to a blind auction -in the following. The advantage of a blind auction is -that there is no time pressure towards the end of -the bidding period. Creating a blind auction on a -transparent computing platform might sound like a -contradiction, but cryptography comes to the rescue. - -During the **bidding period**, a bidder does not -actually send her bid, but only a hashed version of it. -Since it is currently considered practically impossible -to find two (sufficiently long) values whose hash -values are equal, the bidder commits to the bid by that. -After the end of the bidding period, the bidders have -to reveal their bids: They send their values -unencrypted and the contract checks that the hash value -is the same as the one provided during the bidding period. - -Another challenge is how to make the auction -**binding and blind** at the same time: The only way to -prevent the bidder from just not sending the money -after they won the auction is to make her send it -together with the bid. Since value transfers cannot -be blinded in Ethereum, anyone can see the value. - -The following contract solves this problem by -accepting any value that is larger than the highest -bid. Since this can of course only be checked during -the reveal phase, some bids might be **invalid**, and -this is on purpose (it even provides an explicit -flag to place invalid bids with high value transfers): -Bidders can confuse competition by placing several -high or low invalid bids. - - -:: - - pragma solidity >0.4.23 <0.6.0; - - contract BlindAuction { - struct Bid { - bytes32 blindedBid; - uint deposit; - } - - address payable public beneficiary; - uint public biddingEnd; - uint public revealEnd; - bool public ended; - - mapping(address => Bid[]) public bids; - - address public highestBidder; - uint public highestBid; - - // Allowed withdrawals of previous bids - mapping(address => uint) pendingReturns; - - event AuctionEnded(address winner, uint highestBid); - - /// Modifiers are a convenient way to validate inputs to - /// functions. `onlyBefore` is applied to `bid` below: - /// The new function body is the modifier's body where - /// `_` is replaced by the old function body. - modifier onlyBefore(uint _time) { require(now < _time); _; } - modifier onlyAfter(uint _time) { require(now > _time); _; } - - constructor( - uint _biddingTime, - uint _revealTime, - address payable _beneficiary - ) public { - beneficiary = _beneficiary; - biddingEnd = now + _biddingTime; - revealEnd = biddingEnd + _revealTime; - } - - /// Place a blinded bid with `_blindedBid` = - /// keccak256(abi.encodePacked(value, fake, secret)). - /// The sent ether is only refunded if the bid is correctly - /// revealed in the revealing phase. The bid is valid if the - /// ether sent together with the bid is at least "value" and - /// "fake" is not true. Setting "fake" to true and sending - /// not the exact amount are ways to hide the real bid but - /// still make the required deposit. The same address can - /// place multiple bids. - function bid(bytes32 _blindedBid) - public - payable - onlyBefore(biddingEnd) - { - bids[msg.sender].push(Bid({ - blindedBid: _blindedBid, - deposit: msg.value - })); - } - - /// Reveal your blinded bids. You will get a refund for all - /// correctly blinded invalid bids and for all bids except for - /// the totally highest. - function reveal( - uint[] memory _values, - bool[] memory _fake, - bytes32[] memory _secret - ) - public - onlyAfter(biddingEnd) - onlyBefore(revealEnd) - { - uint length = bids[msg.sender].length; - require(_values.length == length); - require(_fake.length == length); - require(_secret.length == length); - - uint refund; - for (uint i = 0; i < length; i++) { - Bid storage bidToCheck = bids[msg.sender][i]; - (uint value, bool fake, bytes32 secret) = - (_values[i], _fake[i], _secret[i]); - if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) { - // Bid was not actually revealed. - // Do not refund deposit. - continue; - } - refund += bidToCheck.deposit; - if (!fake && bidToCheck.deposit >= value) { - if (placeBid(msg.sender, value)) - refund -= value; - } - // Make it impossible for the sender to re-claim - // the same deposit. - bidToCheck.blindedBid = bytes32(0); - } - msg.sender.transfer(refund); - } - - // This is an "internal" function which means that it - // can only be called from the contract itself (or from - // derived contracts). - function placeBid(address bidder, uint value) internal - returns (bool success) - { - if (value <= highestBid) { - return false; - } - if (highestBidder != address(0)) { - // Refund the previously highest bidder. - pendingReturns[highestBidder] += highestBid; - } - highestBid = value; - highestBidder = bidder; - return true; - } - - /// Withdraw a bid that was overbid. - function withdraw() public { - uint amount = pendingReturns[msg.sender]; - if (amount > 0) { - // It is important to set this to zero because the recipient - // can call this function again as part of the receiving call - // before `transfer` returns (see the remark above about - // conditions -> effects -> interaction). - pendingReturns[msg.sender] = 0; - - msg.sender.transfer(amount); - } - } - - /// End the auction and send the highest bid - /// to the beneficiary. - function auctionEnd() - public - onlyAfter(revealEnd) - { - require(!ended); - emit AuctionEnded(highestBidder, highestBid); - ended = true; - beneficiary.transfer(highestBid); - } - } - - -.. index:: purchase, remote purchase, escrow - -******************** -Safe Remote Purchase -******************** - -:: - - pragma solidity >=0.4.22 <0.6.0; - - contract Purchase { - uint public value; - address payable public seller; - address payable public buyer; - enum State { Created, Locked, Inactive } - 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. - constructor() public payable { - seller = msg.sender; - value = msg.value / 2; - require((2 * value) == msg.value, "Value has to be even."); - } - - modifier condition(bool _condition) { - require(_condition); - _; - } - - modifier onlyBuyer() { - require( - msg.sender == buyer, - "Only buyer can call this." - ); - _; - } - - modifier onlySeller() { - require( - msg.sender == seller, - "Only seller can call this." - ); - _; - } - - modifier inState(State _state) { - require( - state == _state, - "Invalid state." - ); - _; - } - - event Aborted(); - event PurchaseConfirmed(); - event ItemReceived(); - - /// Abort the purchase and reclaim the ether. - /// Can only be called by the seller before - /// the contract is locked. - function abort() - public - onlySeller - inState(State.Created) - { - emit Aborted(); - state = State.Inactive; - seller.transfer(address(this).balance); - } - - /// Confirm the purchase as buyer. - /// Transaction has to include `2 * value` ether. - /// The ether will be locked until confirmReceived - /// is called. - function confirmPurchase() - public - inState(State.Created) - condition(msg.value == (2 * value)) - payable - { - emit PurchaseConfirmed(); - buyer = msg.sender; - state = State.Locked; - } - - /// Confirm that you (the buyer) received the item. - /// This will release the locked ether. - function confirmReceived() - public - onlyBuyer - inState(State.Locked) - { - emit ItemReceived(); - // It is important to change the state first because - // otherwise, the contracts called using `send` below - // can call in again here. - state = State.Inactive; - - // NOTE: This actually allows both the buyer and the seller to - // block the refund - the withdraw pattern should be used. - - buyer.transfer(value); - seller.transfer(address(this).balance); - } - } - -******************** -Micropayment Channel -******************** - -In this section we will learn how to build an example implementation -of a payment channel. It uses cryptographic signatures to make -repeated transfers of Ether between the same parties secure, instantaneous, and -without transaction fees. For the example, we need to understand how to -sign and verify signatures, and setup the payment channel. - -Creating and verifying signatures -================================= - -Imagine Alice wants to send a quantity of Ether to Bob, i.e. -Alice is the sender and the Bob is the recipient. - -Alice only needs to send cryptographically signed messages off-chain -(e.g. via email) to Bob and it is similar to writing checks. - -Alice and Bob use signatures to authorise transactions, which is possible with smart contracts on Ethereum. -Alice will build a simple smart contract that lets her transmit Ether, but instead of calling a function herself -to initiate a payment, she will let Bob do that, and therefore pay the transaction fee. - -The contract will work as follows: - - 1. Alice deploys the ``ReceiverPays`` contract, attaching enough Ether to cover the payments that will be made. - 2. Alice authorises a payment by signing a message with their private key. - 3. Alice sends the cryptographically signed message to Bob. The message does not need to be kept secret - (explained later), and the mechanism for sending it does not matter. - 4. Bob claims their payment by presenting the signed message to the smart contract, it verifies the - authenticity of the message and then releases the funds. - -Creating the signature ----------------------- - -Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. -In this tutorial, we will sign messages in the browser using `web3.js <https://github.com/ethereum/web3.js>`_ and `MetaMask <https://metamask.io>`_, using the method described in `EIP-762 <https://github.com/ethereum/EIPs/pull/712>`_, -as it provides a number of other security benefits. - -:: - /// Hashing first makes things easier - var hash = web3.utils.sha3("message to sign"); - web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); }); - -.. note:: - The ``web3.eth.personal.sign`` prepends the length of the message to the signed data. Since we hash first, the message will always be exactly 32 bytes long, and thus this length prefix is always the same. - -What to Sign ------------- - -For a contract that fulfils payments, the signed message must include: - - 1. The recipient's address. - 2. The amount to be transferred. - 3. Protection against replay attacks. - -A replay attack is when a signed message is reused to claim authorization for -a second action. -To avoid replay attacks we use the same as in Ethereum transactions -themselves, a so-called nonce, which is the number of transactions sent by an -account. -The smart contract checks if a nonce is used multiple times. - -Another type of replay attack can occur when the owner deploys a ``ReceiverPays`` smart contract, makes some payments, and then destroys the contract. Later, they decide to deploy the ``RecipientPays`` smart contract again, but the new contract does not know the nonces used in the previous deployment, so the attacker can use the old messages again. - -Alice can protect against this attack by including the contract's address in the message, and only messages containing the contract's address itself will be accepted. You can find an example of this in the first two lines of the ``claimPayment()`` function of the full contract at the end of this section. - -Packing arguments ------------------ - -Now that we have identified what information to include in the signed message, -we are ready to put the message together, hash it, and sign it. For simplicity, -we concatenate the data. The `ethereumjs-abi <https://github.com/ethereumjs/ethereumjs-abi>`_ -library provides a function called ``soliditySHA3`` that mimics the behaviour of -Solidity's ``keccak256`` function applied to arguments encoded using ``abi.encodePacked``. -Here is a JavaScript function that creates the proper signature for the ``ReceiverPays`` example: - -:: - - // recipient is the address that should be paid. - // amount, in wei, specifies how much ether should be sent. - // nonce can be any unique number to prevent replay attacks - // contractAddress is used to prevent cross-contract replay attacks - function signPayment(recipient, amount, nonce, contractAddress, callback) { - var hash = "0x" + abi.soliditySHA3( - ["address", "uint256", "uint256", "address"], - [recipient, amount, nonce, contractAddress] - ).toString("hex"); - - web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback); - } - -Recovering the Message Signer in Solidity ------------------------------------------ - -In general, ECDSA signatures consist of two parameters, ``r`` and ``s``. Signatures in Ethereum include a third parameter called ``v``, that you can use to verify which account's private key was used to sign the message, and the transaction's sender. Solidity provides a built-in function `ecrecover <mathematical-and-cryptographic-functions>`_ that accepts a message along with the ``r``, ``s`` and ``v`` parameters and returns the address that was used to sign the message. - -Extracting the Signature Parameters ------------------------------------ - -Signatures produced by web3.js are the concatenation of ``r``, ``s`` and ``v``, so the first step is to split these parameters apart. You can do this on the client-side, but doing it inside the smart contract means you only need to send one signature parameter rather than three. Splitting apart a byte array into component parts is a mess, so we use `inline assembly <assembly>`_ to do the job in the ``splitSignature`` function (the third function in the full contract at the end of this section). - -Computing the Message Hash --------------------------- - -The smart contract needs to know exactly what parameters were signed, and so it -must recreate the message from the parameters and use that for signature verification. -The functions ``prefixed`` and ``recoverSigner`` do this in the ``claimPayment`` function. - -The full contract ------------------ - -:: - - pragma solidity >=0.4.24 <0.6.0; - - contract ReceiverPays { - address owner = msg.sender; - - mapping(uint256 => bool) usedNonces; - - constructor() public payable {} - - function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) public { - require(!usedNonces[nonce]); - usedNonces[nonce] = true; - - // this recreates the message that was signed on the client - bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this))); - - require(recoverSigner(message, signature) == owner); - - msg.sender.transfer(amount); - } - - /// destroy the contract and reclaim the leftover funds. - function kill() public { - require(msg.sender == owner); - selfdestruct(msg.sender); - } - - /// signature methods. - function splitSignature(bytes memory sig) - internal - pure - returns (uint8 v, bytes32 r, bytes32 s) - { - require(sig.length == 65); - - assembly { - // first 32 bytes, after the length prefix. - r := mload(add(sig, 32)) - // second 32 bytes. - s := mload(add(sig, 64)) - // final byte (first byte of the next 32 bytes). - v := byte(0, mload(add(sig, 96))) - } - - return (v, r, s); - } - - function recoverSigner(bytes32 message, bytes memory sig) - internal - pure - returns (address) - { - (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); - - return ecrecover(message, v, r, s); - } - - /// builds a prefixed hash to mimic the behavior of eth_sign. - function prefixed(bytes32 hash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); - } - } - - -Writing a Simple Payment Channel -================================ - -Alice now builds a simple but complete implementation of a payment channel. Payment channels use cryptographic signatures to make repeated transfers of Ether securely, instantaneously, and without transaction fees. - -What is a Payment Channel? --------------------------- - -Payment channels allow participants to make repeated transfers of Ether without using transactions. This means that you can avoid the delays and fees associated with transactions. We are going to explore a simple unidirectional payment channel between two parties (Alice and Bob). It involves three steps: - - 1. Alice funds a smart contract with Ether. This "opens" the payment channel. - 2. Alice signs messages that specify how much of that Ether is owed to the recipient. This step is repeated for each payment. - 3. Bob "closes" the payment channel, withdrawing their portion of the Ether and sending the remainder back to the sender. - -.. note:: - Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender transmits a cryptographically signed message to the recipient via off chain methods (e.g. email). This means only two transactions are required to support any number of transfers. - -Bob is guaranteed to receive their funds because the smart contract escrows the Ether and honours a valid signed message. The smart contract also enforces a timeout, so Alice is guaranteed to eventually recover their funds even if the recipient refuses to close the channel. It is up to the participants in a payment channel to decide how long to keep it open. For a short-lived transaction, such as paying an internet café for each minute of network access, or for a longer relationship, such as paying an employee an hourly wage, a payment could last for months or years. - -Opening the Payment Channel ---------------------------- - -To open the payment channel, Alice deploys the smart contract, attaching the Ether to be escrowed and specifying the intended recipient and a maximum duration for the channel to exist. This is the function ``SimplePaymentChannel`` in the contract, at the end of this section. - -Making Payments ---------------- - -Alice makes payments by sending signed messages to Bob. -This step is performed entirely outside of the Ethereum network. -Messages are cryptographically signed by the sender and then transmitted directly to the recipient. - -Each message includes the following information: - - * The smart contract's address, used to prevent cross-contract replay attacks. - * The total amount of Ether that is owed the recipient so far. - -A payment channel is closed just once, at the end of a series of transfers. -Because of this, only one of the messages sent is redeemed. This is why -each message specifies a cumulative total amount of Ether owed, rather than the -amount of the individual micropayment. The recipient will naturally choose to -redeem the most recent message because that is the one with the highest total. -The nonce per-message is not needed anymore, because the smart contract only honors a single message. The address of the smart contract is still used -to prevent a message intended for one payment channel from being used for a different channel. - -Here is the modified JavaScript code to cryptographically sign a message from the previous section: - -:: - - function constructPaymentMessage(contractAddress, amount) { - return abi.soliditySHA3( - ["address", "uint256"], - [contractAddress, amount] - ); - } - - function signMessage(message, callback) { - web3.eth.personal.sign( - "0x" + message.toString("hex"), - web3.eth.defaultAccount, - callback - ); - } - - // contractAddress is used to prevent cross-contract replay attacks. - // amount, in wei, specifies how much Ether should be sent. - - function signPayment(contractAddress, amount, callback) { - var message = constructPaymentMessage(contractAddress, amount); - signMessage(message, callback); - } - - -Closing the Payment Channel ---------------------------- - -When Bob is ready to receive their funds, it is time to -close the payment channel by calling a ``close`` function on the smart contract. -Closing the channel pays the recipient the Ether they are owed and destroys the contract, sending any remaining Ether back to Alice. To close the channel, Bob needs to provide a message signed by Alice. - -The smart contract must verify that the message contains a valid signature from the sender. -The process for doing this verification is the same as the process the recipient uses. -The Solidity functions ``isValidSignature`` and ``recoverSigner`` work just like their -JavaScript counterparts in the previous section, with the latter function borrowed from the ``ReceiverPays`` contract. - -Only the payment channel recipient can call the ``close`` function, -who naturally passes the most recent payment message because that message -carries the highest total owed. If the sender were allowed to call this function, -they could provide a message with a lower amount and cheat the recipient out of what they are owed. - -The function verifies the signed message matches the given parameters. -If everything checks out, the recipient is sent their portion of the Ether, -and the sender is sent the rest via a ``selfdestruct``. -You can see the ``close`` function in the full contract. - -Channel Expiration -------------------- - -Bob can close the payment channel at any time, but if they fail to do so, -Alice needs a way to recover their escrowed funds. An *expiration* time was set -at the time of contract deployment. Once that time is reached, Alice can call -``claimTimeout`` to recover their funds. You can see the ``claimTimeout`` function in the full contract. - -After this function is called, Bob can no longer receive any Ether, -so it is important that Bob closes the channel before the expiration is reached. - -The full contract ------------------ - -:: - - pragma solidity >=0.4.24 <0.6.0; - - contract SimplePaymentChannel { - address payable public sender; // The account sending payments. - address payable public recipient; // The account receiving the payments. - uint256 public expiration; // Timeout in case the recipient never closes. - - constructor (address payable _recipient, uint256 duration) - public - payable - { - sender = msg.sender; - recipient = _recipient; - expiration = now + duration; - } - - function isValidSignature(uint256 amount, bytes memory signature) - internal - view - returns (bool) - { - bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount))); - - // check that the signature is from the payment sender - return recoverSigner(message, signature) == sender; - } - - /// the recipient can close the channel at any time by presenting a - /// signed amount from the sender. the recipient will be sent that amount, - /// and the remainder will go back to the sender - function close(uint256 amount, bytes memory signature) public { - require(msg.sender == recipient); - require(isValidSignature(amount, signature)); - - recipient.transfer(amount); - selfdestruct(sender); - } - - /// the sender can extend the expiration at any time - function extend(uint256 newExpiration) public { - require(msg.sender == sender); - require(newExpiration > expiration); - - expiration = newExpiration; - } - - /// if the timeout is reached without the recipient closing the channel, - /// then the Ether is released back to the sender. - function claimTimeout() public { - require(now >= expiration); - selfdestruct(sender); - } - - /// All functions below this are just taken from the chapter - /// 'creating and verifying signatures' chapter. - - function splitSignature(bytes memory sig) - internal - pure - returns (uint8 v, bytes32 r, bytes32 s) - { - require(sig.length == 65); - - assembly { - // first 32 bytes, after the length prefix - r := mload(add(sig, 32)) - // second 32 bytes - s := mload(add(sig, 64)) - // final byte (first byte of the next 32 bytes) - v := byte(0, mload(add(sig, 96))) - } - - return (v, r, s); - } - - function recoverSigner(bytes32 message, bytes memory sig) - internal - pure - returns (address) - { - (uint8 v, bytes32 r, bytes32 s) = splitSignature(sig); - - return ecrecover(message, v, r, s); - } - - /// builds a prefixed hash to mimic the behavior of eth_sign. - function prefixed(bytes32 hash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); - } - } - - -.. note:: - The function ``splitSignature`` does not use all security - checks. A real implementation should use a more rigorously tested library, - such as openzepplin's `version <https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ECRecovery.sol>`_ of this code. - -Verifying Payments ------------------- - -Unlike in the previous section, messages in a payment channel aren't -redeemed right away. The recipient keeps track of the latest message and -redeems it when it's time to close the payment channel. This means it's -critical that the recipient perform their own verification of each message. -Otherwise there is no guarantee that the recipient will be able to get paid -in the end. - -The recipient should verify each message using the following process: - - 1. Verify that the contact address in the message matches the payment channel. - 2. Verify that the new total is the expected amount. - 3. Verify that the new total does not exceed the amount of Ether escrowed. - 4. Verify that the signature is valid and comes from the payment channel sender. - -We'll use the `ethereumjs-util <https://github.com/ethereumjs/ethereumjs-util>`_ -library to write this verification. The final step can be done a number of ways, -and we use JavaScript. The following code borrows the `constructMessage` function from the signing **JavaScript code** above: - -:: - - // this mimics the prefixing behavior of the eth_sign JSON-RPC method. - function prefixed(hash) { - return ethereumjs.ABI.soliditySHA3( - ["string", "bytes32"], - ["\x19Ethereum Signed Message:\n32", hash] - ); - } - - function recoverSigner(message, signature) { - var split = ethereumjs.Util.fromRpcSig(signature); - var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s); - var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex"); - return signer; - } - - function isValidSignature(contractAddress, amount, signature, expectedSigner) { - var message = prefixed(constructPaymentMessage(contractAddress, amount)); - var signer = recoverSigner(message, signature); - return signer.toLowerCase() == - ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase(); - } +.. include:: examples/micropayment.rst
\ No newline at end of file diff --git a/docs/types.rst b/docs/types.rst index 08fbd7b3..b9c06f6c 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -18,1356 +18,12 @@ declared variables always have a :ref:`default value<default-value>` dependent on its type. To handle any unexpected values, you should use the :ref:`revert function<assert-and-require>` to revert the whole transaction, or return a tuple with a second `bool` value denoting success. -.. index:: ! value type, ! type;value -.. _value-types: +.. include:: types/value-types.rst -Value Types -=========== +.. include:: types/reference-types.rst -The following types are also called value types because variables of these -types will always be passed by value, i.e. they are always copied when they -are used as function arguments or in assignments. +.. include:: types/mapping-types.rst -.. index:: ! bool, ! true, ! false +.. include:: types/operators.rst -Booleans --------- - -``bool``: The possible values are constants ``true`` and ``false``. - -Operators: - -* ``!`` (logical negation) -* ``&&`` (logical conjunction, "and") -* ``||`` (logical disjunction, "or") -* ``==`` (equality) -* ``!=`` (inequality) - -The operators ``||`` and ``&&`` apply the common short-circuiting rules. This means that in the expression ``f(x) || g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects. - -.. index:: ! uint, ! int, ! integer - -Integers --------- - -``int`` / ``uint``: Signed and unsigned integers of various sizes. Keywords ``uint8`` to ``uint256`` in steps of ``8`` (unsigned of 8 up to 256 bits) and ``int8`` to ``int256``. ``uint`` and ``int`` are aliases for ``uint256`` and ``int256``, respectively. - -Operators: - -* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) -* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) -* Shift operators: ``<<`` (left shift), ``>>`` (right shift) -* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo), ``**`` (exponentiation) - - -Comparisons -^^^^^^^^^^^ - -The value of a comparison is the one obtained by comparing the integer value. - -Bit operations -^^^^^^^^^^^^^^ - -Bit operations are performed on the two's complement representation of the number. -This means that, for example ``~int256(0) == int256(-1)``. - -Shifts -^^^^^^ - -The result of a shift operation has the type of the left operand. The -expression ``x << y`` is equivalent to ``x * 2**y``, and, for positive integers, -``x >> y`` is equivalent to ``x / 2**y``. For negative ``x``, ``x >> y`` -is equivalent to dividing by a power of ``2`` while rounding down (towards negative infinity). -Shifting by a negative amount throws a runtime exception. - -.. warning:: - Before version ``0.5.0`` a right shift ``x >> y`` for negative ``x`` was equivalent to ``x / 2**y``, - i.e. right shifts used rounding towards zero instead of rounding towards negative infinity. - -Addition, Subtraction and Multiplication -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Addition, subtraction and multiplication have the usual semantics. -They wrap in two's complement representation, meaning that -for example ``uint256(0) - uint256(1) == 2**256 - 1``. You have to take these overflows -into account when designing safe smart contracts. - -The expression ``-x`` is equivalent to ``(T(0) - x)`` where -``T`` is the type of ``x``. This means that ``-x`` will not be negative -if the type of ``x`` is an unsigned integer type. Also, ``-x`` can be -positive if ``x`` is negative. There is another caveat also resulting -from two's complement representation:: - - int x = -2**255; - assert(-x == x); - -This means that even if a number is negative, you cannot assume that -its negation will be positive. - - -Division -^^^^^^^^ - -Since the type of the result of an operation is always the type of one of -the operands, division on integers always results in an integer. -In Solidity, division rounds towards zero. This mean that ``int256(-5) / int256(2) == int256(-2)``. - -Note that in contrast, division on :ref:`literals<rational_literals>` results in fractional values -of arbitrary precision. - -.. note:: - Division by zero causes a failing assert. - -Modulo -^^^^^^ - -The modulo operation ``a % n`` yields the remainder ``r`` after the division of the operand ``a`` -by the operand ``n``, where ``q = int(a / n)`` and ``r = a - (n * q)``. This means that modulo -results in the same sign as its left operand (or zero) and ``a % n == -(abs(a) % n)`` holds for negative ``a``: - - * ``int256(5) % int256(2) == int256(1)`` - * ``int256(5) % int256(-2) == int256(1)`` - * ``int256(-5) % int256(2) == int256(-1)`` - * ``int256(-5) % int256(-2) == int256(-1)`` - -.. note:: - Modulo with zero causes a failing assert. - -Exponentiation -^^^^^^^^^^^^^^ - -Exponentiation is only available for unsigned types. Please take care that the types -you are using are large enough to hold the result and prepare for potential wrapping behaviour. - -.. note:: - Note that ``0**0`` is defined by the EVM as ``1``. - -.. 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`` represents the number of bits taken by -the type and ``N`` represents 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 ``ufixed128x18`` and ``fixed128x18``, respectively. - -Operators: - -* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) -* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo) - -.. 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, staticcall, transfer - -.. _address: - -Address -------- - -The address type comes in two flavours, which are largely identical: - - - ``address``: Holds a 20 byte value (size of an Ethereum address). - - ``address payable``: Same as ``address``, but with the additional members ``transfer`` and ``send``. - -The idea behind this distinction is that ``address payable`` is an address you can send Ether to, -while a plain ``address`` cannot be sent Ether. - -Type conversions: - -Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are -not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``). - -:ref:`Address literals<address_literals>` can be implicitly converted to ``address payable``. - -Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following -caveat: -Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)`` -has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function. -If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``. -In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type. - -.. note:: - It might very well be that you do not need to care about the distinction between ``address`` - and ``address payable`` and just use ``address`` everywhere. For example, - if you are using the :ref:`withdrawal pattern<withdrawal_pattern>`, you can (and should) store the - address itself as ``address``, because you invoke the ``transfer`` function on - ``msg.sender``, which is an ``address payable``. - -Operators: - -* ``<=``, ``<``, ``==``, ``!=``, ``>=`` and ``>`` - -.. warning:: - If you convert a type that uses a larger byte size to an ``address``, for example ``bytes32``, then the ``address`` is truncated. - To reduce conversion ambiguity version 0.4.24 and higher of the compiler force you make the truncation explicit in the conversion. - Take for example the address ``0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC``. - - You can use ``address(uint160(bytes20(b)))``, which results in ``0x111122223333444455556666777788889999aAaa``, - or you can use ``address(uint160(uint256(b)))``, which results in ``0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc``. - -.. note:: - The distinction between ``address`` and ``address payable`` was introduced with version 0.5.0. - Also starting from that version, contracts do not derive from the address type, but can still be explicitly converted to - ``address`` or to ``address payable``, if they have a payable fallback function. - -.. _members-of-addresses: - -Members of Addresses -^^^^^^^^^^^^^^^^^^^^ - -For a quick reference of all members of address, see :ref:`address_related`. - -* ``balance`` and ``transfer`` - -It is possible to query the balance of an address using the property ``balance`` -and to send Ether (in units of wei) to a payable address using the ``transfer`` function: - -:: - - address payable x = address(0x123); - address myAddress = address(this); - if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); - -The ``transfer`` function fails if the balance of the current contract is not large enough -or if the Ether transfer is rejected by the receiving account. The ``transfer`` function -reverts on failure. - -.. note:: - If ``x`` is a contract address, its code (more specifically: its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. - -* ``send`` - -Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. - -.. warning:: - There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 - (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order - to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: - use a pattern where the recipient withdraws the money. - -* ``call``, ``delegatecall`` and ``staticcall`` - -In order to interface with contracts that do not adhere to the ABI, -or to get more direct control over the encoding, -the functions ``call``, ``delegatecall`` and ``staticcall`` are provided. -They all take a single ``bytes memory`` parameter and -return the success condition (as a ``bool``) and the returned data -(``bytes memory``). -The functions ``abi.encode``, ``abi.encodePacked``, ``abi.encodeWithSelector`` -and ``abi.encodeWithSignature`` can be used to encode structured data. - -Example:: - - bytes memory payload = abi.encodeWithSignature("register(string)", "MyName"); - (bool success, bytes memory returnData) = address(nameReg).call(payload); - require(success); - -.. warning:: - All these functions are low-level functions and should be used with care. - Specifically, any unknown contract might be malicious and if you call it, you - hand over control to that contract which could in turn call back into - your contract, so be prepared for changes to your state variables - when the call returns. The regular way to interact with other contracts - is to call a function on a contract object (``x.f()``). - -.. note:: - Previous versions of Solidity allowed these functions to receive - arbitrary arguments and would also handle a first argument of type - ``bytes4`` differently. These edge cases were removed in version 0.5.0. - -It is possible to adjust the supplied gas with the ``.gas()`` modifier:: - - address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName")); - -Similarly, the supplied Ether value can be controlled too:: - - address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName")); - -Lastly, these modifiers can be combined. Their order does not matter:: - - address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName")); - -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. - -.. note:: - 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. This function was removed in version 0.5.0. - -Since byzantium ``staticcall`` can be used as well. This is basically the same as ``call``, but will revert if the called function modifies the state in any way. - -All three functions ``call``, ``delegatecall`` and ``staticcall`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity. - -The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``. - -.. note:: - All contracts can be converted to ``address`` type, so it is possible to query the balance of the - current contract using ``address(this).balance``. - -.. index:: ! contract type, ! type; contract - -.. _contract_types: - -Contract Types --------------- - -Every :ref:`contract<contracts>` defines its own type. -You can implicitly convert contracts to contracts they inherit from. -Contracts can be explicitly converted to and from all other contract types -and the ``address`` type. - -Explicit conversion to and from the ``address payable`` type -is only possible if the contract type has a payable fallback function. -The conversion is still performed using ``address(x)`` and not -using ``address payable(x)``. You can find more information in the section about -the :ref:`address type<address>`. - -.. note:: - Before version 0.5.0, contracts directly derived from the address type - and there was no distinction between ``address`` and ``address payable``. - -If you declare a local variable of contract type (`MyContract c`), you can call -functions on that contract. Take care to assign it from somewhere that is the -same contract type. - -You can also instantiate contracts (which means they are newly created). You -can find more details in the :ref:`'Contracts via new'<creating-contracts>` -section. - -The data representation of a contract is identical to that of the ``address`` -type and this type is also used in the :ref:`ABI<ABI>`. - -Contracts do not support any operators. - -The members of contract types are the external functions of the contract -including public state variables. - -.. index:: byte array, bytes32 - -Fixed-size byte arrays ----------------------- - -The value types ``bytes1``, ``bytes2``, ``bytes3``, ..., ``bytes32`` -hold a sequence of bytes from one to up to 32. -``byte`` is an alias for ``bytes1``. - -Operators: - -* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) -* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) -* Shift operators: ``<<`` (left shift), ``>>`` (right shift) -* Index access: If ``x`` is of type ``bytesI``, then ``x[k]`` for ``0 <= k < I`` returns the ``k`` th byte (read-only). - -The shifting operator works with any integer type as right operand (but -returns the type of the left operand), which denotes the number of bits to shift by. -Shifting by a negative amount causes a runtime exception. - -Members: - -* ``.length`` yields the fixed length of the byte array (read-only). - -.. note:: - The type ``byte[]`` is an array of bytes, but due to padding rules, it wastes - 31 bytes of space for each element (except in storage). It is better to use the ``bytes`` - type instead. - -Dynamically-sized byte array ----------------------------- - -``bytes``: - Dynamically-sized byte array, see :ref:`arrays`. Not a value-type! -``string``: - Dynamically-sized UTF-8-encoded string, see :ref:`arrays`. Not a value-type! - -.. index:: address, literal;address - -.. _address_literals: - -Address Literals ----------------- - -Hexadecimal literals that pass the address checksum test, for example -``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address payable`` type. -Hexadecimal literals that are between 39 and 41 digits -long and do not pass the checksum test produce -a warning and are treated as regular rational number literals. - -.. note:: - The mixed-case address checksum format is defined in `EIP-55 <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>`_. - -.. index:: literal, literal;rational - -.. _rational_literals: - -Rational and Integer Literals ------------------------------ - -Integer literals are formed from a sequence of numbers in the range 0-9. -They are interpreted as decimals. For example, ``69`` means sixty nine. -Octal literals do not exist in Solidity and leading zeros are invalid. - -Decimal fraction literals are formed by a ``.`` with at least one number on -one side. Examples include ``1.``, ``.1`` and ``1.3``. - -Scientific notation is also supported, where the base can have fractions, while the exponent cannot. -Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. - -Underscores can be used to separate the digits of a numeric literal to aid readability. -For example, decimal ``123_000``, hexadecimal ``0x2eff_abde``, scientific decimal notation ``1_2e345_678`` are all valid. -Underscores are only allowed between two digits and only one consecutive underscore is allowed. -There is no additional semantic meaning added to a number literal containing underscores, -the underscores are ignored. - -Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by -using them together with a non-literal expression or by explicit conversion). -This means that computations do not overflow and divisions do not truncate -in number literal expressions. - -For example, ``(2**800 + 1) - 2**800`` results in the constant ``1`` (of type ``uint8``) -although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results -in the integer ``4`` (although non-integers were used in between). - -Any operator that can be applied to integers can also be applied to number literal expressions as -long as the operands are integers. If any of the two is fractional, bit operations are disallowed -and exponentiation is disallowed if the exponent is fractional (because that might result in -a non-rational number). - -.. note:: - Solidity has a number literal type for each rational number. - Integer literals and rational number literals belong to number literal types. - Moreover, all number literal expressions (i.e. the expressions that - contain only number literals and operators) belong to number literal - types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both - belong to the same number literal type for the rational number three. - -.. warning:: - Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. - -.. note:: - Number literal expressions are converted into a non-literal type as soon as they are used with non-literal - expressions. Disregarding types, the value of the expression assigned to ``b`` - below evaluates to an integer. Because ``a`` is of type ``uint128``, the - expression ``2.5 + a`` has to have a proper type, though. Since there is no common type - for the type of ``2.5`` and ``uint128``, the Solidity compiler does not accept - this code. - -:: - - uint128 a = 1; - uint128 b = 2.5 + a + 0.5; - -.. index:: literal, literal;string, string -.. _string_literals: - -String Literals and Types -------------------------- - -String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. - -For example, with ``bytes32 samevar = "stringliteral"`` the string literal is interpreted in its raw byte form when assigned to a ``bytes32`` type. - -String literals support the following escape characters: - - - ``\<newline>`` (escapes an actual newline) - - ``\\`` (backslash) - - ``\'`` (single quote) - - ``\"`` (double quote) - - ``\b`` (backspace) - - ``\f`` (form feed) - - ``\n`` (newline) - - ``\r`` (carriage return) - - ``\t`` (tab) - - ``\v`` (vertical tab) - - ``\xNN`` (hex escape, see below) - - ``\uNNNN`` (unicode escape, see below) - -``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence. - -The string in the following example has a length of ten bytes. -It starts with a newline byte, followed by a double quote, a single -quote a backslash character and then (without separator) the -character sequence ``abcdef``. - -:: - - "\n\"\'\\abc\ - def" - -Any unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to -terminate the string literal. Newline only terminates the string literal if it is not preceded by a ``\``. - -.. index:: literal, bytes - -Hexadecimal Literals --------------------- - -Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``). Their content must be a hexadecimal string and their value will be the binary representation of those values. - -Hexadecimal literals behave like :ref:`string literals <string_literals>` and have the same convertibility restrictions. - -.. index:: enum - -.. _enums: - -Enums ------ - -Enums are one way to create a user-defined type in Solidity. They are explicitly convertible -to and from all integer types but implicit conversion is not allowed. The explicit conversion -from integer checks at runtime that the value lies inside the range of the enum and causes a failing assert otherwise. -Enums needs at least one member. - -The data representation is the same as for enums in C: The options are represented by -subsequent unsigned integer values starting from ``0``. - - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract test { - enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } - ActionChoices choice; - ActionChoices constant defaultChoice = ActionChoices.GoStraight; - - function setGoStraight() public { - choice = ActionChoices.GoStraight; - } - - // Since enum types are not part of the ABI, the signature of "getChoice" - // will automatically be changed to "getChoice() returns (uint8)" - // for all matters external to Solidity. The integer type used is just - // large enough to hold all enum values, i.e. if you have more than 256 values, - // `uint16` will be used and so on. - function getChoice() public view returns (ActionChoices) { - return choice; - } - - function getDefaultChoice() public pure returns (uint) { - return uint(defaultChoice); - } - } - -.. index:: ! function type, ! type; function - -.. _function_types: - -Function Types --------------- - -Function types are the types of functions. Variables of function type -can be assigned from functions and function parameters of function type -can be used to pass functions to and return functions from function calls. -Function types come in two flavours - *internal* and *external* functions: - -Internal functions can only be called inside the current contract (more specifically, -inside the current code unit, which also includes internal library functions -and inherited functions) because they cannot be executed outside of the -context of the current contract. Calling an internal function is realized -by jumping to its entry label, just like when calling a function of the current -contract internally. - -External functions consist of an address and a function signature and they can -be passed via and returned from external function calls. - -Function types are notated as follows:: - - function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)] - -In contrast to the parameter types, the return types cannot be empty - if the -function type should not return anything, the whole ``returns (<return types>)`` -part has to be omitted. - -By default, function types are internal, so the ``internal`` keyword can be -omitted. Note that this only applies to function types. Visibility has -to be specified explicitly for functions defined in contracts, they -do not have a default. - -Conversions: - -A value of external function type can be explicitly converted to ``address`` -resulting in the address of the contract of the function. - -A function type ``A`` is implicitly convertible to a function type ``B`` if and only if -their parameter types are identical, their return types are identical, -their internal/external property is identical and the state mutability of ``A`` -is not more restrictive than the state mutability of ``B``. In particular: - - - ``pure`` functions can be converted to ``view`` and ``non-payable`` functions - - ``view`` functions can be converted to ``non-payable`` functions - - ``payable`` functions can be converted to ``non-payable`` functions - -No other conversions between function types are possible. - -The rule about ``payable`` and ``non-payable`` might be a little -confusing, but in essence, if a function is ``payable``, this means that it -also accepts a payment of zero Ether, so it also is ``non-payable``. -On the other hand, a ``non-payable`` function will reject Ether sent to it, -so ``non-payable`` functions cannot be converted to ``payable`` functions. - -If a function type variable is not initialised, calling it results -in a failed assertion. The same happens if you call a function after using ``delete`` -on it. - -If external function types are used outside of the context of Solidity, -they are treated as the ``function`` type, which encodes the address -followed by the function identifier together in a single ``bytes24`` type. - -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, -just use ``f``, if you want to use its external form, use ``this.f``. - -Members: - -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.16 <0.6.0; - - contract Selector { - function f() public pure returns (bytes4) { - return this.f.selector; - } - } - -Example that shows how to use internal function types:: - - pragma solidity >=0.4.16 <0.6.0; - - library ArrayUtils { - // internal functions can be used in internal library functions because - // they will be part of the same code context - function map(uint[] memory self, function (uint) pure returns (uint) f) - internal - pure - returns (uint[] memory r) - { - r = new uint[](self.length); - for (uint i = 0; i < self.length; i++) { - r[i] = f(self[i]); - } - } - function reduce( - uint[] memory self, - function (uint, uint) pure returns (uint) f - ) - internal - pure - returns (uint r) - { - r = self[0]; - for (uint i = 1; i < self.length; i++) { - r = f(r, self[i]); - } - } - function range(uint length) internal pure returns (uint[] memory r) { - r = new uint[](length); - for (uint i = 0; i < r.length; i++) { - r[i] = i; - } - } - } - - contract Pyramid { - using ArrayUtils for *; - function pyramid(uint l) public pure returns (uint) { - return ArrayUtils.range(l).map(square).reduce(sum); - } - function square(uint x) internal pure returns (uint) { - return x * x; - } - function sum(uint x, uint y) internal pure returns (uint) { - return x + y; - } - } - -Another example that uses external function types:: - - pragma solidity >=0.4.22 <0.6.0; - - contract Oracle { - struct Request { - bytes data; - function(uint) external callback; - } - Request[] requests; - event NewRequest(uint); - function query(bytes memory data, function(uint) external callback) public { - requests.push(Request(data, callback)); - emit NewRequest(requests.length - 1); - } - function reply(uint requestID, uint response) public { - // Here goes the check that the reply comes from a trusted source - requests[requestID].callback(response); - } - } - - contract OracleUser { - Oracle constant oracle = Oracle(0x1234567); // known contract - uint exchangeRate; - function buySomething() public { - oracle.query("USD", this.oracleResponse); - } - function oracleResponse(uint response) public { - require( - msg.sender == address(oracle), - "Only oracle can call this." - ); - exchangeRate = response; - } - } - -.. note:: - Lambda or inline functions are planned but not yet supported. - -.. index:: ! type;reference, ! reference type, storage, memory, location, array, struct - -.. _reference-types: - -Reference Types -=============== - -Values of reference type can be modified through multiple different names. -Contrast this with value types where you get an independent copy whenever -a variable of value type is used. Because of that, reference types have to be handled -more carefully than value types. Currently, reference types comprise structs, -arrays and mappings. If you use a reference type, you always have to explicitly -provide the data area where the type is stored: ``memory`` (whose lifetime is limited -to a function call), ``storage`` (the location where the state variables are stored) -or ``calldata`` (special data location that contains the function arguments, -only available for external function call parameters). - -An assignment or type conversion that changes the data location will always incur an automatic copy operation, -while assignments inside the same data location only copy in some cases for storage types. - -.. _data-location: - -Data location -------------- - -Every reference type, i.e. *arrays* and *structs*, has an additional -annotation, the "data location", about where it is stored. There are three data locations: -``memory``, ``storage`` and ``calldata``. Calldata is only valid for parameters of external contract -functions and is required for this type of parameter. Calldata is a non-modifiable, -non-persistent area where function arguments are stored, and behaves mostly like memory. - - -.. note:: - Prior to version 0.5.0 the data location could be omitted, and would default to different locations - depending on the kind of variable, function type, etc., but all complex types must now give an explicit - data location. - -.. _data-location-assignment: - -Data location and assignment behaviour -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Data locations are not only relevant for persistency of data, but also for the semantics of assignments: - -* Assignments between ``storage`` and ``memory`` (or from ``calldata``) always create an independent copy. -* Assignments from ``memory`` to ``memory`` only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data. -* Assignments from ``storage`` to a local storage variable also only assign a reference. -* All other assignments to ``storage`` always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference. - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract C { - uint[] x; // the data location of x is storage - - // the data location of memoryArray is memory - function f(uint[] memory memoryArray) public { - x = memoryArray; // works, copies the whole array to storage - uint[] storage y = x; // works, assigns a pointer, data location of y is storage - y[7]; // fine, returns the 8th element - y.length = 2; // fine, modifies x through y - delete x; // fine, clears the array, also modifies y - // The following does not work; it would need to create a new temporary / - // unnamed array in storage, but storage is "statically" allocated: - // y = memoryArray; - // This does not work either, since it would "reset" the pointer, but there - // is no sensible location it could point to. - // delete y; - g(x); // calls g, handing over a reference to x - h(x); // calls h and creates an independent, temporary copy in memory - } - - function g(uint[] storage) internal pure {} - function h(uint[] memory) public pure {} - } - -.. index:: ! array - -.. _arrays: - -Arrays ------- - -Arrays can have a compile-time fixed size, or they can have a dynamic size. - -The type of an array of fixed size ``k`` and element type ``T`` is written as ``T[k]``, -and an array of dynamic size as ``T[]``. - -For example, an array of 5 dynamic arrays of ``uint`` is written as -``uint[][5]``. The notation is reversed compared to some other languages. In -Solidity, ``X[3]`` is always an array containing three elements of type ``X``, -even if ``X`` is itself an array. This is not the case in other languages such -as C. - -Indices are zero-based, and access is in the opposite direction of the -declaration. - -For example, if you have a variable ``uint[][5] x memory``, you access the -second ``uint`` in the third dynamic array using ``x[2][1]``, and to access the -third dynamic array, use ``x[2]``. Again, -if you have an array ``T[5] a`` for a type ``T`` that can also be an array, -then ``a[2]`` always has type ``T``. - -Array elements can be of any type, including mapping or struct. The general -restrictions for types apply, in that mappings can only be stored in the -``storage`` data location and publicly-visible functions need parameters that are :ref:`ABI types <ABI>`. - -Accessing an array past its end causes a failing assertion. You can use the ``.push()`` method to append a new element at the end or assign to the ``.length`` :ref:`member <array-members>` to change the size (see below for caveats). -method or increase the ``.length`` :ref:`member <array-members>` to add elements. - -Variables of type ``bytes`` and ``string`` are special arrays. A ``bytes`` is similar to ``byte[]``, -but it is packed tightly in calldata and memory. ``string`` is equal to ``bytes`` but does not allow -length or index access. - -You should use ``bytes`` over ``byte[]`` because it is cheaper, since ``byte[]`` adds 31 padding bytes between the elements. As a general rule, -use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length -string (UTF-8) data. If you can limit the length to a certain number of bytes, -always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper. - -.. note:: - If you want to access the byte-representation of a string ``s``, use - ``bytes(s).length`` / ``bytes(s)[7] = 'x';``. Keep in mind - that you are accessing the low-level bytes of the UTF-8 representation, - and not the individual characters. - -It is possible to mark arrays ``public`` and have Solidity create a :ref:`getter <visibility-and-getters>`. -The numeric index becomes a required parameter for the getter. - -.. index:: ! array;allocating, new - -Allocating Memory Arrays -^^^^^^^^^^^^^^^^^^^^^^^^ - -You can use the ``new`` keyword to create arrays with a runtime-dependent length in memory. -As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to -the ``.length`` member). You either have to calculate the required size in advance -or create a new memory array and copy every element. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract C { - function f(uint len) public pure { - uint[] memory a = new uint[](7); - bytes memory b = new bytes(len); - assert(a.length == 7); - assert(b.length == len); - a[6] = 8; - } - } - -.. index:: ! array;literals, ! inline;arrays - -Array Literals -^^^^^^^^^^^^^^ - -An array literal is a comma-separated list of one or more expressions, enclosed -in square brackets (``[...]``). For example ``[1, a, f(3)]``. There must be a -common type all elements can be implicitly converted to. This is the elementary -type of the array. - -Array literals are always statically-sized memory arrays. - -In the example below, the type of ``[1, 2, 3]`` is -``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if you want the result to be a ``uint[3] memory`` type, you need to convert the first element to ``uint``. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract C { - function f() public pure { - g([uint(1), 2, 3]); - } - function g(uint[3] memory) public pure { - // ... - } - } - -Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: - -:: - - pragma solidity >=0.4.0 <0.6.0; - - // This will not compile. - contract C { - function f() public { - // The next line creates a type error because uint[3] memory - // cannot be converted to uint[] memory. - uint[] memory x = [uint(1), 3, 4]; - } - } - -It is planned to remove this restriction in the future, but it creates some -complications because of how arrays are passed in the ABI. - -.. index:: ! array;length, length, push, pop, !array;push, !array;pop - -.. _array-members: - -Array Members -^^^^^^^^^^^^^ - -**length**: - Arrays have a ``length`` member that contains their number of elements. - The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. - For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. - Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. - Increasing the length adds new zero-initialised elements to the array. - Reducing the length performs an implicit :ref:``delete`` on each of the - removed elements. If you try to resize a non-dynamic array that isn't in - storage, you receive a ``Value must be an lvalue`` error. -**push**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. -**pop**: - Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element. - -.. warning:: - If you use ``.length--`` on an empty array, it causes an underflow and - thus sets the length to ``2**256-1``. - -.. note:: - Increasing the length of a storage array has constant gas costs because - storage is assumed to be zero-initialised, while decreasing - the length has at least linear cost (but in most cases worse than linear), - because it includes explicitly clearing the removed - elements similar to calling :ref:``delete`` on them. - -.. note:: - It is not yet possible to use arrays of arrays in external functions - (but they are supported in public functions). - -.. note:: - In EVM versions before Byzantium, it was not possible to access - dynamic arrays return from function calls. If you call functions - that return dynamic arrays, make sure to use an EVM that is set to - Byzantium mode. - -:: - - pragma solidity >=0.4.16 <0.6.0; - - contract ArrayContract { - uint[2**20] m_aLotOfIntegers; - // Note that the following is not a pair of dynamic arrays but a - // dynamic array of pairs (i.e. of fixed size arrays of length two). - // Because of that, T[] is always a dynamic array of T, even if T - // itself is an array. - // Data location for all state variables is storage. - bool[2][] m_pairsOfFlags; - - // newPairs is stored in memory - the only possibility - // for public contract function arguments - function setAllFlagPairs(bool[2][] memory newPairs) public { - // assignment to a storage array performs a copy of ``newPairs`` and - // replaces the complete array ``m_pairsOfFlags``. - m_pairsOfFlags = newPairs; - } - - struct StructType { - uint[] contents; - uint moreInfo; - } - StructType s; - - function f(uint[] memory c) public { - // stores a reference to ``s`` in ``g`` - StructType storage g = s; - // also changes ``s.moreInfo``. - g.moreInfo = 2; - // assigns a copy because ``g.contents`` - // is not a local variable, but a member of - // a local variable. - g.contents = c; - } - - function setFlagPair(uint index, bool flagA, bool flagB) public { - // access to a non-existing index will throw an exception - m_pairsOfFlags[index][0] = flagA; - m_pairsOfFlags[index][1] = flagB; - } - - function changeFlagArraySize(uint newSize) public { - // if the new size is smaller, removed array elements will be cleared - m_pairsOfFlags.length = newSize; - } - - function clear() public { - // these clear the arrays completely - delete m_pairsOfFlags; - delete m_aLotOfIntegers; - // identical effect here - m_pairsOfFlags.length = 0; - } - - bytes m_byteData; - - function byteArrays(bytes memory data) public { - // byte arrays ("bytes") are different as they are stored without padding, - // but can be treated identical to "uint8[]" - m_byteData = data; - m_byteData.length += 7; - m_byteData[3] = 0x08; - delete m_byteData[2]; - } - - function addFlag(bool[2] memory flag) public returns (uint) { - return m_pairsOfFlags.push(flag); - } - - function createMemoryArray(uint size) public pure returns (bytes memory) { - // Dynamic memory arrays are created using `new`: - uint[2][] memory arrayOfPairs = new uint[2][](size); - - // Inline arrays are always statically-sized and if you only - // use literals, you have to provide at least one type. - arrayOfPairs[0] = [uint(1), 2]; - - // Create a dynamic byte array: - bytes memory b = new bytes(200); - for (uint i = 0; i < b.length; i++) - b[i] = byte(uint8(i)); - return b; - } - } - - -.. index:: ! struct, ! type;struct - -.. _structs: - -Structs -------- - -Solidity provides a way to define new types in the form of structs, which is -shown in the following example: - -:: - - pragma solidity >=0.4.11 <0.6.0; - - contract CrowdFunding { - // Defines a new type with two fields. - struct Funder { - address addr; - uint amount; - } - - struct Campaign { - address payable beneficiary; - uint fundingGoal; - uint numFunders; - uint amount; - mapping (uint => Funder) funders; - } - - uint numCampaigns; - mapping (uint => Campaign) campaigns; - - function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) { - campaignID = numCampaigns++; // campaignID is return variable - // Creates new struct in memory and copies it to storage. - // We leave out the mapping type, because it is not valid in memory. - // If structs are copied (even from storage to storage), mapping types - // are always omitted, because they cannot be enumerated. - campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); - } - - function contribute(uint campaignID) public payable { - Campaign storage c = campaigns[campaignID]; - // Creates a new temporary memory struct, initialised with the given values - // and copies it over to storage. - // Note that you can also use Funder(msg.sender, msg.value) to initialise. - c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); - c.amount += msg.value; - } - - function checkGoalReached(uint campaignID) public returns (bool reached) { - Campaign storage c = campaigns[campaignID]; - if (c.amount < c.fundingGoal) - return false; - uint amount = c.amount; - c.amount = 0; - c.beneficiary.transfer(amount); - return true; - } - } - -The contract does not provide the full functionality of a crowdfunding -contract, but it contains the basic concepts necessary to understand structs. -Struct types can be used inside mappings and arrays and they can itself -contain mappings and arrays. - -It is not possible for a struct to contain a member of its own type, -although the struct itself can be the value type of a mapping member -or it can contain a dynamically-sized array of its type. -This restriction is necessary, as the size of the struct has to be finite. - -Note how in all the functions, a struct type is assigned to a local variable -with data location ``storage``. -This does not copy the struct but only stores a reference so that assignments to -members of the local variable actually write to the state. - -Of course, you can also directly access the members of the struct without -assigning it to a local variable, as in -``campaigns[campaignID].amount = 0``. - -.. index:: !mapping -.. _mapping-types: - -Mapping Types -============= - -You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``. -The ``_KeyType`` can be any elementary type. This means it can be any of -the built-in value types plus ``bytes`` and ``string``. User-defined -or complex types like contract types, enums, mappings, structs and any array type -apart from ``bytes`` and ``string`` are not allowed. -``_ValueType`` can be any type, including mappings. - -You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised -such that every possible key exists and is mapped to a value whose -byte-representation is all zeros, a type's :ref:`default value <default-value>`. The similarity ends there, the key data is not stored in a -mapping, only its ``keccak256`` hash is used to look up the value. - -Because of this, mappings do not have a length or a concept of a key or -value being set. - -Mappings can only have a data location of ``storage`` and thus -are allowed for state variables, as storage reference types -in functions, or as parameters for library functions. -They cannot be used as parameters or return parameters -of contract functions that are publicly visible. - -You can mark variables of mapping type as ``public`` and Solidity creates a -:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a -parameter for the getter. If ``_ValueType`` is a value type or a struct, -the getter returns ``_ValueType``. -If ``_ValueType`` is an array or a mapping, the getter has one parameter for -each ``_KeyType``, recursively. For example with a mapping: - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract MappingExample { - mapping(address => uint) public balances; - - function update(uint newBalance) public { - balances[msg.sender] = newBalance; - } - } - - contract MappingUser { - function f() public returns (uint) { - MappingExample m = new MappingExample(); - m.update(100); - return m.balances(address(this)); - } - } - - -.. note:: - Mappings are not iterable, but it is possible to implement a data structure - on top of them. For an example, see `iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_. - -.. index:: assignment, ! delete, lvalue - -Operators Involving LValues -=========================== - -If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the following operators are available as shorthands: - -``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change. - -delete ------- - -``delete a`` assigns the initial value for the type to ``a``. I.e. for integers it is -equivalent to ``a = 0``, but it can also be used on arrays, where it assigns a dynamic -array of length zero or a static array of the same length with all elements set to their -initial value. ``delete a[x]`` deletes the item at index ``x`` of the array and leaves -all other elements and the length of the array untouched. This especially means that it leaves -a gap in the array. If you plan to remove items, a mapping is probably a better choice. - -For structs, it assigns a struct with all members reset. In other words, the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared without assignment, with the following caveat: - -``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If ``a`` is a mapping, then ``delete a[x]`` will delete the value stored at ``x``. - -It is important to note that ``delete a`` really behaves like an assignment to ``a``, i.e. it stores a new object in ``a``. -This distinction is visible when ``a`` is reference variable: It will only reset ``a`` itself, not the -value it referred to previously. - -:: - - pragma solidity >=0.4.0 <0.6.0; - - contract DeleteExample { - uint data; - uint[] dataArray; - - function f() public { - uint x = data; - delete x; // sets x to 0, does not affect data - delete data; // sets data to 0, does not affect x - uint[] storage y = dataArray; - delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also - // y is affected which is an alias to the storage object - // On the other hand: "delete y" is not valid, as assignments to local variables - // referencing storage objects can only be made from existing storage objects. - assert(y.length == 0); - } - } - -.. index:: ! type;conversion, ! cast - -.. _types-conversion-elementary-types: - -Conversions between Elementary Types -==================================== - -Implicit Conversions --------------------- - -If an operator is applied to different types, the compiler tries to -implicitly convert one of the operands to the type of the other (the same is -true for assignments). In general, an implicit conversion between value-types -is possible if it -makes sense semantically and no information is lost: ``uint8`` is convertible to -``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256`` -(because ``uint256`` cannot hold e.g. ``-1``). - -For more details, please consult the sections about the types themselves. - -Explicit Conversions --------------------- - -If the compiler does not allow implicit conversion but you know what you are -doing, an explicit type conversion is sometimes possible. Note that this may -give you some unexpected behaviour and allows you to bypass some security -features of the compiler, so be sure to test that the -result is what you want! Take the following example where you are converting -a negative ``int8`` to a ``uint``: - -:: - - int8 y = -3; - uint x = uint(y); - -At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex -characters), which is -3 in the two's complement representation of 256 bits. - -If an integer is explicitly converted to a smaller type, higher-order bits are -cut off:: - - uint32 a = 0x12345678; - uint16 b = uint16(a); // b will be 0x5678 now - -If an integer is explicitly converted to a larger type, it is padded on the left (i.e. at the higher order end). -The result of the conversion will compare equal to the original integer. - - uint16 a = 0x1234; - uint32 b = uint32(a); // b will be 0x00001234 now - assert(a == b); - -Fixed-size bytes types behave differently during conversions. They can be thought of as -sequences of individual bytes and converting to a smaller type will cut off the -sequence:: - - bytes2 a = 0x1234; - bytes1 b = bytes1(a); // b will be 0x12 - -If a fixed-size bytes type is explicitly converted to a larger type, it is padded on -the right. Accessing the byte at a fixed index will result in the same value before and -after the conversion (if the index is still in range):: - - bytes2 a = 0x1234; - bytes4 b = bytes4(a); // b will be 0x12340000 - assert(a[0] == b[0]); - assert(a[1] == b[1]); - -Since integers and fixed-size byte arrays behave differently when truncating or -padding, explicit conversions between integers and fixed-size byte arrays are only allowed, -if both have the same size. If you want to convert between integers and fixed-size byte arrays of -different size, you have to use intermediate conversions that make the desired truncation and padding -rules explicit:: - - bytes2 a = 0x1234; - uint32 b = uint16(a); // b will be 0x00001234 - uint32 c = uint32(bytes4(a)); // c will be 0x12340000 - uint8 d = uint8(uint16(a)); // d will be 0x34 - uint8 e = uint8(bytes1(a)); // d will be 0x12 - -.. _types-conversion-literals: - -Conversions between Literals and Elementary Types -================================================= - -Integer Types -------------- - -Decimal and hexadecimal number literals can be implicitly converted to any integer type -that is large enough to represent it without truncation:: - - uint8 a = 12; // fine - uint32 b = 1234; // fine - uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456 - -Fixed-Size Byte Arrays ----------------------- - -Decimal number literals cannot be implicitly converted to fixed-size byte arrays. Hexadecimal -number literals can be, but only if the number of hex digits exactly fits the size of the bytes -type. As an exception both decimal and hexadecimal literals which have a value of zero can be -converted to any fixed-size bytes type:: - - bytes2 a = 54321; // not allowed - bytes2 b = 0x12; // not allowed - bytes2 c = 0x123; // not allowed - bytes2 d = 0x1234; // fine - bytes2 e = 0x0012; // fine - bytes4 f = 0; // fine - bytes4 g = 0x0; // fine - -String literals and hex string literals can be implicitly converted to fixed-size byte arrays, -if their number of characters matches the size of the bytes type:: - - bytes2 a = hex"1234"; // fine - bytes2 b = "xy"; // fine - bytes2 c = hex"12"; // not allowed - bytes2 d = hex"123"; // not allowed - bytes2 e = "x"; // not allowed - bytes2 f = "xyz"; // not allowed - -Addresses ---------- - -As described in :ref:`address_literals`, hex literals of the correct size that pass the checksum -test are of ``address`` type. No other literals can be implicitly converted to the ``address`` type. - -Explicit conversions from ``bytes20`` or any integer type to ``address`` result in ``address payable``. +.. include:: types/conversion.rst
\ No newline at end of file diff --git a/docs/types/conversion.rst b/docs/types/conversion.rst new file mode 100644 index 00000000..5a9f84c0 --- /dev/null +++ b/docs/types/conversion.rst @@ -0,0 +1,127 @@ +.. index:: ! type;conversion, ! cast + +.. _types-conversion-elementary-types: + +Conversions between Elementary Types +==================================== + +Implicit Conversions +-------------------- + +If an operator is applied to different types, the compiler tries to +implicitly convert one of the operands to the type of the other (the same is +true for assignments). In general, an implicit conversion between value-types +is possible if it +makes sense semantically and no information is lost: ``uint8`` is convertible to +``uint16`` and ``int128`` to ``int256``, but ``int8`` is not convertible to ``uint256`` +(because ``uint256`` cannot hold e.g. ``-1``). + +For more details, please consult the sections about the types themselves. + +Explicit Conversions +-------------------- + +If the compiler does not allow implicit conversion but you know what you are +doing, an explicit type conversion is sometimes possible. Note that this may +give you some unexpected behaviour and allows you to bypass some security +features of the compiler, so be sure to test that the +result is what you want! Take the following example where you are converting +a negative ``int8`` to a ``uint``: + +:: + + int8 y = -3; + uint x = uint(y); + +At the end of this code snippet, ``x`` will have the value ``0xfffff..fd`` (64 hex +characters), which is -3 in the two's complement representation of 256 bits. + +If an integer is explicitly converted to a smaller type, higher-order bits are +cut off:: + + uint32 a = 0x12345678; + uint16 b = uint16(a); // b will be 0x5678 now + +If an integer is explicitly converted to a larger type, it is padded on the left (i.e. at the higher order end). +The result of the conversion will compare equal to the original integer:: + + uint16 a = 0x1234; + uint32 b = uint32(a); // b will be 0x00001234 now + assert(a == b); + +Fixed-size bytes types behave differently during conversions. They can be thought of as +sequences of individual bytes and converting to a smaller type will cut off the +sequence:: + + bytes2 a = 0x1234; + bytes1 b = bytes1(a); // b will be 0x12 + +If a fixed-size bytes type is explicitly converted to a larger type, it is padded on +the right. Accessing the byte at a fixed index will result in the same value before and +after the conversion (if the index is still in range):: + + bytes2 a = 0x1234; + bytes4 b = bytes4(a); // b will be 0x12340000 + assert(a[0] == b[0]); + assert(a[1] == b[1]); + +Since integers and fixed-size byte arrays behave differently when truncating or +padding, explicit conversions between integers and fixed-size byte arrays are only allowed, +if both have the same size. If you want to convert between integers and fixed-size byte arrays of +different size, you have to use intermediate conversions that make the desired truncation and padding +rules explicit:: + + bytes2 a = 0x1234; + uint32 b = uint16(a); // b will be 0x00001234 + uint32 c = uint32(bytes4(a)); // c will be 0x12340000 + uint8 d = uint8(uint16(a)); // d will be 0x34 + uint8 e = uint8(bytes1(a)); // e will be 0x12 + +.. _types-conversion-literals: + +Conversions between Literals and Elementary Types +================================================= + +Integer Types +------------- + +Decimal and hexadecimal number literals can be implicitly converted to any integer type +that is large enough to represent it without truncation:: + + uint8 a = 12; // fine + uint32 b = 1234; // fine + uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456 + +Fixed-Size Byte Arrays +---------------------- + +Decimal number literals cannot be implicitly converted to fixed-size byte arrays. Hexadecimal +number literals can be, but only if the number of hex digits exactly fits the size of the bytes +type. As an exception both decimal and hexadecimal literals which have a value of zero can be +converted to any fixed-size bytes type:: + + bytes2 a = 54321; // not allowed + bytes2 b = 0x12; // not allowed + bytes2 c = 0x123; // not allowed + bytes2 d = 0x1234; // fine + bytes2 e = 0x0012; // fine + bytes4 f = 0; // fine + bytes4 g = 0x0; // fine + +String literals and hex string literals can be implicitly converted to fixed-size byte arrays, +if their number of characters matches the size of the bytes type:: + + bytes2 a = hex"1234"; // fine + bytes2 b = "xy"; // fine + bytes2 c = hex"12"; // not allowed + bytes2 d = hex"123"; // not allowed + bytes2 e = "x"; // not allowed + bytes2 f = "xyz"; // not allowed + +Addresses +--------- + +As described in :ref:`address_literals`, hex literals of the correct size that pass the checksum +test are of ``address`` type. No other literals can be implicitly converted to the ``address`` type. + +Explicit conversions from ``bytes20`` or any integer type to ``address`` result in ``address payable``. diff --git a/docs/types/mapping-types.rst b/docs/types/mapping-types.rst new file mode 100644 index 00000000..935ed6b4 --- /dev/null +++ b/docs/types/mapping-types.rst @@ -0,0 +1,58 @@ +.. index:: !mapping +.. _mapping-types: + +Mapping Types +============= + +You declare mapping types with the syntax ``mapping(_KeyType => _ValueType)``. +The ``_KeyType`` can be any elementary type. This means it can be any of +the built-in value types plus ``bytes`` and ``string``. User-defined +or complex types like contract types, enums, mappings, structs and any array type +apart from ``bytes`` and ``string`` are not allowed. +``_ValueType`` can be any type, including mappings. + +You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised +such that every possible key exists and is mapped to a value whose +byte-representation is all zeros, a type's :ref:`default value <default-value>`. The similarity ends there, the key data is not stored in a +mapping, only its ``keccak256`` hash is used to look up the value. + +Because of this, mappings do not have a length or a concept of a key or +value being set. + +Mappings can only have a data location of ``storage`` and thus +are allowed for state variables, as storage reference types +in functions, or as parameters for library functions. +They cannot be used as parameters or return parameters +of contract functions that are publicly visible. + +You can mark variables of mapping type as ``public`` and Solidity creates a +:ref:`getter <visibility-and-getters>` for you. The ``_KeyType`` becomes a +parameter for the getter. If ``_ValueType`` is a value type or a struct, +the getter returns ``_ValueType``. +If ``_ValueType`` is an array or a mapping, the getter has one parameter for +each ``_KeyType``, recursively. For example with a mapping: + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract MappingExample { + mapping(address => uint) public balances; + + function update(uint newBalance) public { + balances[msg.sender] = newBalance; + } + } + + contract MappingUser { + function f() public returns (uint) { + MappingExample m = new MappingExample(); + m.update(100); + return m.balances(address(this)); + } + } + + +.. note:: + Mappings are not iterable, but it is possible to implement a data structure + on top of them. For an example, see `iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_. diff --git a/docs/types/operators.rst b/docs/types/operators.rst new file mode 100644 index 00000000..9851f214 --- /dev/null +++ b/docs/types/operators.rst @@ -0,0 +1,47 @@ +.. index:: assignment, ! delete, lvalue + +Operators Involving LValues +=========================== + +If ``a`` is an LValue (i.e. a variable or something that can be assigned to), the following operators are available as shorthands: + +``a += e`` is equivalent to ``a = a + e``. The operators ``-=``, ``*=``, ``/=``, ``%=``, ``|=``, ``&=`` and ``^=`` are defined accordingly. ``a++`` and ``a--`` are equivalent to ``a += 1`` / ``a -= 1`` but the expression itself still has the previous value of ``a``. In contrast, ``--a`` and ``++a`` have the same effect on ``a`` but return the value after the change. + +delete +------ + +``delete a`` assigns the initial value for the type to ``a``. I.e. for integers it is +equivalent to ``a = 0``, but it can also be used on arrays, where it assigns a dynamic +array of length zero or a static array of the same length with all elements set to their +initial value. ``delete a[x]`` deletes the item at index ``x`` of the array and leaves +all other elements and the length of the array untouched. This especially means that it leaves +a gap in the array. If you plan to remove items, a mapping is probably a better choice. + +For structs, it assigns a struct with all members reset. In other words, the value of ``a`` after ``delete a`` is the same as if ``a`` would be declared without assignment, with the following caveat: + +``delete`` has no effect on mappings (as the keys of mappings may be arbitrary and are generally unknown). So if you delete a struct, it will reset all members that are not mappings and also recurse into the members unless they are mappings. However, individual keys and what they map to can be deleted: If ``a`` is a mapping, then ``delete a[x]`` will delete the value stored at ``x``. + +It is important to note that ``delete a`` really behaves like an assignment to ``a``, i.e. it stores a new object in ``a``. +This distinction is visible when ``a`` is reference variable: It will only reset ``a`` itself, not the +value it referred to previously. + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract DeleteExample { + uint data; + uint[] dataArray; + + function f() public { + uint x = data; + delete x; // sets x to 0, does not affect data + delete data; // sets data to 0, does not affect x + uint[] storage y = dataArray; + delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also + // y is affected which is an alias to the storage object + // On the other hand: "delete y" is not valid, as assignments to local variables + // referencing storage objects can only be made from existing storage objects. + assert(y.length == 0); + } + } diff --git a/docs/types/reference-types.rst b/docs/types/reference-types.rst new file mode 100644 index 00000000..b9fd9194 --- /dev/null +++ b/docs/types/reference-types.rst @@ -0,0 +1,409 @@ +.. index:: ! type;reference, ! reference type, storage, memory, location, array, struct + +.. _reference-types: + +Reference Types +=============== + +Values of reference type can be modified through multiple different names. +Contrast this with value types where you get an independent copy whenever +a variable of value type is used. Because of that, reference types have to be handled +more carefully than value types. Currently, reference types comprise structs, +arrays and mappings. If you use a reference type, you always have to explicitly +provide the data area where the type is stored: ``memory`` (whose lifetime is limited +to a function call), ``storage`` (the location where the state variables are stored) +or ``calldata`` (special data location that contains the function arguments, +only available for external function call parameters). + +An assignment or type conversion that changes the data location will always incur an automatic copy operation, +while assignments inside the same data location only copy in some cases for storage types. + +.. _data-location: + +Data location +------------- + +Every reference type, i.e. *arrays* and *structs*, has an additional +annotation, the "data location", about where it is stored. There are three data locations: +``memory``, ``storage`` and ``calldata``. Calldata is only valid for parameters of external contract +functions and is required for this type of parameter. Calldata is a non-modifiable, +non-persistent area where function arguments are stored, and behaves mostly like memory. + + +.. note:: + Prior to version 0.5.0 the data location could be omitted, and would default to different locations + depending on the kind of variable, function type, etc., but all complex types must now give an explicit + data location. + +.. _data-location-assignment: + +Data location and assignment behaviour +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Data locations are not only relevant for persistency of data, but also for the semantics of assignments: + +* Assignments between ``storage`` and ``memory`` (or from ``calldata``) always create an independent copy. +* Assignments from ``memory`` to ``memory`` only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data. +* Assignments from ``storage`` to a local storage variable also only assign a reference. +* All other assignments to ``storage`` always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference. + +:: + + pragma solidity >=0.4.0 <0.6.0; + + contract C { + uint[] x; // the data location of x is storage + + // the data location of memoryArray is memory + function f(uint[] memory memoryArray) public { + x = memoryArray; // works, copies the whole array to storage + uint[] storage y = x; // works, assigns a pointer, data location of y is storage + y[7]; // fine, returns the 8th element + y.length = 2; // fine, modifies x through y + delete x; // fine, clears the array, also modifies y + // The following does not work; it would need to create a new temporary / + // unnamed array in storage, but storage is "statically" allocated: + // y = memoryArray; + // This does not work either, since it would "reset" the pointer, but there + // is no sensible location it could point to. + // delete y; + g(x); // calls g, handing over a reference to x + h(x); // calls h and creates an independent, temporary copy in memory + } + + function g(uint[] storage) internal pure {} + function h(uint[] memory) public pure {} + } + +.. index:: ! array + +.. _arrays: + +Arrays +------ + +Arrays can have a compile-time fixed size, or they can have a dynamic size. + +The type of an array of fixed size ``k`` and element type ``T`` is written as ``T[k]``, +and an array of dynamic size as ``T[]``. + +For example, an array of 5 dynamic arrays of ``uint`` is written as +``uint[][5]``. The notation is reversed compared to some other languages. In +Solidity, ``X[3]`` is always an array containing three elements of type ``X``, +even if ``X`` is itself an array. This is not the case in other languages such +as C. + +Indices are zero-based, and access is in the opposite direction of the +declaration. + +For example, if you have a variable ``uint[][5] x memory``, you access the +second ``uint`` in the third dynamic array using ``x[2][1]``, and to access the +third dynamic array, use ``x[2]``. Again, +if you have an array ``T[5] a`` for a type ``T`` that can also be an array, +then ``a[2]`` always has type ``T``. + +Array elements can be of any type, including mapping or struct. The general +restrictions for types apply, in that mappings can only be stored in the +``storage`` data location and publicly-visible functions need parameters that are :ref:`ABI types <ABI>`. + +Accessing an array past its end causes a failing assertion. You can use the ``.push()`` method to append a new element at the end or assign to the ``.length`` :ref:`member <array-members>` to change the size (see below for caveats). +method or increase the ``.length`` :ref:`member <array-members>` to add elements. + +Variables of type ``bytes`` and ``string`` are special arrays. A ``bytes`` is similar to ``byte[]``, +but it is packed tightly in calldata and memory. ``string`` is equal to ``bytes`` but does not allow +length or index access. + +You should use ``bytes`` over ``byte[]`` because it is cheaper, since ``byte[]`` adds 31 padding bytes between the elements. As a general rule, +use ``bytes`` for arbitrary-length raw byte data and ``string`` for arbitrary-length +string (UTF-8) data. If you can limit the length to a certain number of bytes, +always use one of the value types ``bytes1`` to ``bytes32`` because they are much cheaper. + +.. note:: + If you want to access the byte-representation of a string ``s``, use + ``bytes(s).length`` / ``bytes(s)[7] = 'x';``. Keep in mind + that you are accessing the low-level bytes of the UTF-8 representation, + and not the individual characters. + +It is possible to mark arrays ``public`` and have Solidity create a :ref:`getter <visibility-and-getters>`. +The numeric index becomes a required parameter for the getter. + +.. index:: ! array;allocating, new + +Allocating Memory Arrays +^^^^^^^^^^^^^^^^^^^^^^^^ + +You must use the ``new`` keyword to create arrays with a runtime-dependent length in memory. +As opposed to storage arrays, it is **not** possible to resize memory arrays (e.g. by assigning to +the ``.length`` member). You either have to calculate the required size in advance +or create a new memory array and copy every element. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract C { + function f(uint len) public pure { + uint[] memory a = new uint[](7); + bytes memory b = new bytes(len); + assert(a.length == 7); + assert(b.length == len); + a[6] = 8; + } + } + +.. index:: ! array;literals, ! inline;arrays + +Array Literals +^^^^^^^^^^^^^^ + +An array literal is a comma-separated list of one or more expressions, enclosed +in square brackets (``[...]``). For example ``[1, a, f(3)]``. There must be a +common type all elements can be implicitly converted to. This is the elementary +type of the array. + +Array literals are always statically-sized memory arrays. + +In the example below, the type of ``[1, 2, 3]`` is +``uint8[3] memory``. Because the type of each of these constants is ``uint8``, if you want the result to be a ``uint[3] memory`` type, you need to convert the first element to ``uint``. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract C { + function f() public pure { + g([uint(1), 2, 3]); + } + function g(uint[3] memory) public pure { + // ... + } + } + +Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible: + +:: + + pragma solidity >=0.4.0 <0.6.0; + + // This will not compile. + contract C { + function f() public { + // The next line creates a type error because uint[3] memory + // cannot be converted to uint[] memory. + uint[] memory x = [uint(1), 3, 4]; + } + } + +It is planned to remove this restriction in the future, but it creates some +complications because of how arrays are passed in the ABI. + +.. index:: ! array;length, length, push, pop, !array;push, !array;pop + +.. _array-members: + +Array Members +^^^^^^^^^^^^^ + +**length**: + Arrays have a ``length`` member that contains their number of elements. + The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created. + For dynamically-sized arrays (only available for storage), this member can be assigned to resize the array. + Accessing elements outside the current length does not automatically resize the array and instead causes a failing assertion. + Increasing the length adds new zero-initialised elements to the array. + Reducing the length performs an implicit :ref:``delete`` on each of the + removed elements. If you try to resize a non-dynamic array that isn't in + storage, you receive a ``Value must be an lvalue`` error. +**push**: + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``push`` that you can use to append an element at the end of the array. The element will be zero-initialised. The function returns the new length. +**pop**: + Dynamic storage arrays and ``bytes`` (not ``string``) have a member function called ``pop`` that you can use to remove an element from the end of the array. This also implicitly calls :ref:``delete`` on the removed element. + +.. warning:: + If you use ``.length--`` on an empty array, it causes an underflow and + thus sets the length to ``2**256-1``. + +.. note:: + Increasing the length of a storage array has constant gas costs because + storage is assumed to be zero-initialised, while decreasing + the length has at least linear cost (but in most cases worse than linear), + because it includes explicitly clearing the removed + elements similar to calling :ref:``delete`` on them. + +.. note:: + It is not yet possible to use arrays of arrays in external functions + (but they are supported in public functions). + +.. note:: + In EVM versions before Byzantium, it was not possible to access + dynamic arrays return from function calls. If you call functions + that return dynamic arrays, make sure to use an EVM that is set to + Byzantium mode. + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract ArrayContract { + uint[2**20] m_aLotOfIntegers; + // Note that the following is not a pair of dynamic arrays but a + // dynamic array of pairs (i.e. of fixed size arrays of length two). + // Because of that, T[] is always a dynamic array of T, even if T + // itself is an array. + // Data location for all state variables is storage. + bool[2][] m_pairsOfFlags; + + // newPairs is stored in memory - the only possibility + // for public contract function arguments + function setAllFlagPairs(bool[2][] memory newPairs) public { + // assignment to a storage array performs a copy of ``newPairs`` and + // replaces the complete array ``m_pairsOfFlags``. + m_pairsOfFlags = newPairs; + } + + struct StructType { + uint[] contents; + uint moreInfo; + } + StructType s; + + function f(uint[] memory c) public { + // stores a reference to ``s`` in ``g`` + StructType storage g = s; + // also changes ``s.moreInfo``. + g.moreInfo = 2; + // assigns a copy because ``g.contents`` + // is not a local variable, but a member of + // a local variable. + g.contents = c; + } + + function setFlagPair(uint index, bool flagA, bool flagB) public { + // access to a non-existing index will throw an exception + m_pairsOfFlags[index][0] = flagA; + m_pairsOfFlags[index][1] = flagB; + } + + function changeFlagArraySize(uint newSize) public { + // if the new size is smaller, removed array elements will be cleared + m_pairsOfFlags.length = newSize; + } + + function clear() public { + // these clear the arrays completely + delete m_pairsOfFlags; + delete m_aLotOfIntegers; + // identical effect here + m_pairsOfFlags.length = 0; + } + + bytes m_byteData; + + function byteArrays(bytes memory data) public { + // byte arrays ("bytes") are different as they are stored without padding, + // but can be treated identical to "uint8[]" + m_byteData = data; + m_byteData.length += 7; + m_byteData[3] = 0x08; + delete m_byteData[2]; + } + + function addFlag(bool[2] memory flag) public returns (uint) { + return m_pairsOfFlags.push(flag); + } + + function createMemoryArray(uint size) public pure returns (bytes memory) { + // Dynamic memory arrays are created using `new`: + uint[2][] memory arrayOfPairs = new uint[2][](size); + + // Inline arrays are always statically-sized and if you only + // use literals, you have to provide at least one type. + arrayOfPairs[0] = [uint(1), 2]; + + // Create a dynamic byte array: + bytes memory b = new bytes(200); + for (uint i = 0; i < b.length; i++) + b[i] = byte(uint8(i)); + return b; + } + } + + +.. index:: ! struct, ! type;struct + +.. _structs: + +Structs +------- + +Solidity provides a way to define new types in the form of structs, which is +shown in the following example: + +:: + + pragma solidity >=0.4.11 <0.6.0; + + contract CrowdFunding { + // Defines a new type with two fields. + struct Funder { + address addr; + uint amount; + } + + struct Campaign { + address payable beneficiary; + uint fundingGoal; + uint numFunders; + uint amount; + mapping (uint => Funder) funders; + } + + uint numCampaigns; + mapping (uint => Campaign) campaigns; + + function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) { + campaignID = numCampaigns++; // campaignID is return variable + // Creates new struct in memory and copies it to storage. + // We leave out the mapping type, because it is not valid in memory. + // If structs are copied (even from storage to storage), mapping types + // are always omitted, because they cannot be enumerated. + campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); + } + + function contribute(uint campaignID) public payable { + Campaign storage c = campaigns[campaignID]; + // Creates a new temporary memory struct, initialised with the given values + // and copies it over to storage. + // Note that you can also use Funder(msg.sender, msg.value) to initialise. + c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); + c.amount += msg.value; + } + + function checkGoalReached(uint campaignID) public returns (bool reached) { + Campaign storage c = campaigns[campaignID]; + if (c.amount < c.fundingGoal) + return false; + uint amount = c.amount; + c.amount = 0; + c.beneficiary.transfer(amount); + return true; + } + } + +The contract does not provide the full functionality of a crowdfunding +contract, but it contains the basic concepts necessary to understand structs. +Struct types can be used inside mappings and arrays and they can itself +contain mappings and arrays. + +It is not possible for a struct to contain a member of its own type, +although the struct itself can be the value type of a mapping member +or it can contain a dynamically-sized array of its type. +This restriction is necessary, as the size of the struct has to be finite. + +Note how in all the functions, a struct type is assigned to a local variable +with data location ``storage``. +This does not copy the struct but only stores a reference so that assignments to +members of the local variable actually write to the state. + +Of course, you can also directly access the members of the struct without +assigning it to a local variable, as in +``campaigns[campaignID].amount = 0``. diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst new file mode 100644 index 00000000..b85863dd --- /dev/null +++ b/docs/types/value-types.rst @@ -0,0 +1,716 @@ +.. index:: ! value type, ! type;value +.. _value-types: + +Value Types +=========== + +The following types are also called value types because variables of these +types will always be passed by value, i.e. they are always copied when they +are used as function arguments or in assignments. + +.. index:: ! bool, ! true, ! false + +Booleans +-------- + +``bool``: The possible values are constants ``true`` and ``false``. + +Operators: + +* ``!`` (logical negation) +* ``&&`` (logical conjunction, "and") +* ``||`` (logical disjunction, "or") +* ``==`` (equality) +* ``!=`` (inequality) + +The operators ``||`` and ``&&`` apply the common short-circuiting rules. This means that in the expression ``f(x) || g(y)``, if ``f(x)`` evaluates to ``true``, ``g(y)`` will not be evaluated even if it may have side-effects. + +.. index:: ! uint, ! int, ! integer + +Integers +-------- + +``int`` / ``uint``: Signed and unsigned integers of various sizes. Keywords ``uint8`` to ``uint256`` in steps of ``8`` (unsigned of 8 up to 256 bits) and ``int8`` to ``int256``. ``uint`` and ``int`` are aliases for ``uint256`` and ``int256``, respectively. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) +* Shift operators: ``<<`` (left shift), ``>>`` (right shift) +* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo), ``**`` (exponentiation) + +.. warning:: + + Integers in Solidity are restricted to a certain range. For example, with ``uint32``, this is ``0`` up to ``2**32 - 1``. + If the result of some operation on those numbers does not fit inside this range, it is truncated. These truncations can have + serious consequences that you should :ref:`be aware of and mitigate against<underflow-overflow>`. + +Comparisons +^^^^^^^^^^^ + +The value of a comparison is the one obtained by comparing the integer value. + +Bit operations +^^^^^^^^^^^^^^ + +Bit operations are performed on the two's complement representation of the number. +This means that, for example ``~int256(0) == int256(-1)``. + +Shifts +^^^^^^ + +The result of a shift operation has the type of the left operand. The +expression ``x << y`` is equivalent to ``x * 2**y``, and, for positive integers, +``x >> y`` is equivalent to ``x / 2**y``. For negative ``x``, ``x >> y`` +is equivalent to dividing by a power of ``2`` while rounding down (towards negative infinity). +Shifting by a negative amount throws a runtime exception. + +.. warning:: + Before version ``0.5.0`` a right shift ``x >> y`` for negative ``x`` was equivalent to ``x / 2**y``, + i.e. right shifts used rounding towards zero instead of rounding towards negative infinity. + +Addition, Subtraction and Multiplication +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Addition, subtraction and multiplication have the usual semantics. +They wrap in two's complement representation, meaning that +for example ``uint256(0) - uint256(1) == 2**256 - 1``. You have to take these overflows +into account when designing safe smart contracts. + +The expression ``-x`` is equivalent to ``(T(0) - x)`` where +``T`` is the type of ``x``. This means that ``-x`` will not be negative +if the type of ``x`` is an unsigned integer type. Also, ``-x`` can be +positive if ``x`` is negative. There is another caveat also resulting +from two's complement representation:: + + int x = -2**255; + assert(-x == x); + +This means that even if a number is negative, you cannot assume that +its negation will be positive. + + +Division +^^^^^^^^ + +Since the type of the result of an operation is always the type of one of +the operands, division on integers always results in an integer. +In Solidity, division rounds towards zero. This mean that ``int256(-5) / int256(2) == int256(-2)``. + +Note that in contrast, division on :ref:`literals<rational_literals>` results in fractional values +of arbitrary precision. + +.. note:: + Division by zero causes a failing assert. + +Modulo +^^^^^^ + +The modulo operation ``a % n`` yields the remainder ``r`` after the division of the operand ``a`` +by the operand ``n``, where ``q = int(a / n)`` and ``r = a - (n * q)``. This means that modulo +results in the same sign as its left operand (or zero) and ``a % n == -(abs(a) % n)`` holds for negative ``a``: + + * ``int256(5) % int256(2) == int256(1)`` + * ``int256(5) % int256(-2) == int256(1)`` + * ``int256(-5) % int256(2) == int256(-1)`` + * ``int256(-5) % int256(-2) == int256(-1)`` + +.. note:: + Modulo with zero causes a failing assert. + +Exponentiation +^^^^^^^^^^^^^^ + +Exponentiation is only available for unsigned types. Please take care that the types +you are using are large enough to hold the result and prepare for potential wrapping behaviour. + +.. note:: + Note that ``0**0`` is defined by the EVM as ``1``. + +.. 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`` represents the number of bits taken by +the type and ``N`` represents 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 ``ufixed128x18`` and ``fixed128x18``, respectively. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Arithmetic operators: ``+``, ``-``, unary ``-``, ``*``, ``/``, ``%`` (modulo) + +.. 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, staticcall, transfer + +.. _address: + +Address +------- + +The address type comes in two flavours, which are largely identical: + + - ``address``: Holds a 20 byte value (size of an Ethereum address). + - ``address payable``: Same as ``address``, but with the additional members ``transfer`` and ``send``. + +The idea behind this distinction is that ``address payable`` is an address you can send Ether to, +while a plain ``address`` cannot be sent Ether. + +Type conversions: + +Implicit conversions from ``address payable`` to ``address`` are allowed, whereas conversions from ``address`` to ``address payable`` are +not possible (the only way to perform such a conversion is by using an intermediate conversion to ``uint160``). + +:ref:`Address literals<address_literals>` can be implicitly converted to ``address payable``. + +Explicit conversions to and from ``address`` are allowed for integers, integer literals, ``bytes20`` and contract types with the following +caveat: +Conversions of the form ``address payable(x)`` are not allowed. Instead the result of a conversion of the form ``address(x)`` +has the type ``address payable``, if ``x`` is of integer or fixed bytes type, a literal or a contract with a payable fallback function. +If ``x`` is a contract without payable fallback function, then ``address(x)`` will be of type ``address``. +In external function signatures ``address`` is used for both the ``address`` and the ``address payable`` type. + +.. note:: + It might very well be that you do not need to care about the distinction between ``address`` + and ``address payable`` and just use ``address`` everywhere. For example, + if you are using the :ref:`withdrawal pattern<withdrawal_pattern>`, you can (and should) store the + address itself as ``address``, because you invoke the ``transfer`` function on + ``msg.sender``, which is an ``address payable``. + +Operators: + +* ``<=``, ``<``, ``==``, ``!=``, ``>=`` and ``>`` + +.. warning:: + If you convert a type that uses a larger byte size to an ``address``, for example ``bytes32``, then the ``address`` is truncated. + To reduce conversion ambiguity version 0.4.24 and higher of the compiler force you make the truncation explicit in the conversion. + Take for example the address ``0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC``. + + You can use ``address(uint160(bytes20(b)))``, which results in ``0x111122223333444455556666777788889999aAaa``, + or you can use ``address(uint160(uint256(b)))``, which results in ``0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc``. + +.. note:: + The distinction between ``address`` and ``address payable`` was introduced with version 0.5.0. + Also starting from that version, contracts do not derive from the address type, but can still be explicitly converted to + ``address`` or to ``address payable``, if they have a payable fallback function. + +.. _members-of-addresses: + +Members of Addresses +^^^^^^^^^^^^^^^^^^^^ + +For a quick reference of all members of address, see :ref:`address_related`. + +* ``balance`` and ``transfer`` + +It is possible to query the balance of an address using the property ``balance`` +and to send Ether (in units of wei) to a payable address using the ``transfer`` function: + +:: + + address payable x = address(0x123); + address myAddress = address(this); + if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); + +The ``transfer`` function fails if the balance of the current contract is not large enough +or if the Ether transfer is rejected by the receiving account. The ``transfer`` function +reverts on failure. + +.. note:: + If ``x`` is a contract address, its code (more specifically: its :ref:`fallback-function`, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + +* ``send`` + +Send is the low-level counterpart of ``transfer``. If the execution fails, the current contract will not stop with an exception, but ``send`` will return ``false``. + +.. warning:: + There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 + (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order + to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: + use a pattern where the recipient withdraws the money. + +* ``call``, ``delegatecall`` and ``staticcall`` + +In order to interface with contracts that do not adhere to the ABI, +or to get more direct control over the encoding, +the functions ``call``, ``delegatecall`` and ``staticcall`` are provided. +They all take a single ``bytes memory`` parameter and +return the success condition (as a ``bool``) and the returned data +(``bytes memory``). +The functions ``abi.encode``, ``abi.encodePacked``, ``abi.encodeWithSelector`` +and ``abi.encodeWithSignature`` can be used to encode structured data. + +Example:: + + bytes memory payload = abi.encodeWithSignature("register(string)", "MyName"); + (bool success, bytes memory returnData) = address(nameReg).call(payload); + require(success); + +.. warning:: + All these functions are low-level functions and should be used with care. + Specifically, any unknown contract might be malicious and if you call it, you + hand over control to that contract which could in turn call back into + your contract, so be prepared for changes to your state variables + when the call returns. The regular way to interact with other contracts + is to call a function on a contract object (``x.f()``). + +.. note:: + Previous versions of Solidity allowed these functions to receive + arbitrary arguments and would also handle a first argument of type + ``bytes4`` differently. These edge cases were removed in version 0.5.0. + +It is possible to adjust the supplied gas with the ``.gas()`` modifier:: + + address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName")); + +Similarly, the supplied Ether value can be controlled too:: + + address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName")); + +Lastly, these modifiers can be combined. Their order does not matter:: + + address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName")); + +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. + +.. note:: + 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. This function was removed in version 0.5.0. + +Since byzantium ``staticcall`` can be used as well. This is basically the same as ``call``, but will revert if the called function modifies the state in any way. + +All three functions ``call``, ``delegatecall`` and ``staticcall`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity. + +The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``. + +.. note:: + All contracts can be converted to ``address`` type, so it is possible to query the balance of the + current contract using ``address(this).balance``. + +.. index:: ! contract type, ! type; contract + +.. _contract_types: + +Contract Types +-------------- + +Every :ref:`contract<contracts>` defines its own type. +You can implicitly convert contracts to contracts they inherit from. +Contracts can be explicitly converted to and from all other contract types +and the ``address`` type. + +Explicit conversion to and from the ``address payable`` type +is only possible if the contract type has a payable fallback function. +The conversion is still performed using ``address(x)`` and not +using ``address payable(x)``. You can find more information in the section about +the :ref:`address type<address>`. + +.. note:: + Before version 0.5.0, contracts directly derived from the address type + and there was no distinction between ``address`` and ``address payable``. + +If you declare a local variable of contract type (`MyContract c`), you can call +functions on that contract. Take care to assign it from somewhere that is the +same contract type. + +You can also instantiate contracts (which means they are newly created). You +can find more details in the :ref:`'Contracts via new'<creating-contracts>` +section. + +The data representation of a contract is identical to that of the ``address`` +type and this type is also used in the :ref:`ABI<ABI>`. + +Contracts do not support any operators. + +The members of contract types are the external functions of the contract +including public state variables. + +For a contract ``C`` you can use ``type(C)`` to access +:ref:`type information<meta-type>` about the contract. + +.. index:: byte array, bytes32 + +Fixed-size byte arrays +---------------------- + +The value types ``bytes1``, ``bytes2``, ``bytes3``, ..., ``bytes32`` +hold a sequence of bytes from one to up to 32. +``byte`` is an alias for ``bytes1``. + +Operators: + +* Comparisons: ``<=``, ``<``, ``==``, ``!=``, ``>=``, ``>`` (evaluate to ``bool``) +* Bit operators: ``&``, ``|``, ``^`` (bitwise exclusive or), ``~`` (bitwise negation) +* Shift operators: ``<<`` (left shift), ``>>`` (right shift) +* Index access: If ``x`` is of type ``bytesI``, then ``x[k]`` for ``0 <= k < I`` returns the ``k`` th byte (read-only). + +The shifting operator works with any integer type as right operand (but +returns the type of the left operand), which denotes the number of bits to shift by. +Shifting by a negative amount causes a runtime exception. + +Members: + +* ``.length`` yields the fixed length of the byte array (read-only). + +.. note:: + The type ``byte[]`` is an array of bytes, but due to padding rules, it wastes + 31 bytes of space for each element (except in storage). It is better to use the ``bytes`` + type instead. + +Dynamically-sized byte array +---------------------------- + +``bytes``: + Dynamically-sized byte array, see :ref:`arrays`. Not a value-type! +``string``: + Dynamically-sized UTF-8-encoded string, see :ref:`arrays`. Not a value-type! + +.. index:: address, literal;address + +.. _address_literals: + +Address Literals +---------------- + +Hexadecimal literals that pass the address checksum test, for example +``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address payable`` type. +Hexadecimal literals that are between 39 and 41 digits +long and do not pass the checksum test produce +a warning and are treated as regular rational number literals. + +.. note:: + The mixed-case address checksum format is defined in `EIP-55 <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>`_. + +.. index:: literal, literal;rational + +.. _rational_literals: + +Rational and Integer Literals +----------------------------- + +Integer literals are formed from a sequence of numbers in the range 0-9. +They are interpreted as decimals. For example, ``69`` means sixty nine. +Octal literals do not exist in Solidity and leading zeros are invalid. + +Decimal fraction literals are formed by a ``.`` with at least one number on +one side. Examples include ``1.``, ``.1`` and ``1.3``. + +Scientific notation is also supported, where the base can have fractions, while the exponent cannot. +Examples include ``2e10``, ``-2e10``, ``2e-10``, ``2.5e1``. + +Underscores can be used to separate the digits of a numeric literal to aid readability. +For example, decimal ``123_000``, hexadecimal ``0x2eff_abde``, scientific decimal notation ``1_2e345_678`` are all valid. +Underscores are only allowed between two digits and only one consecutive underscore is allowed. +There is no additional semantic meaning added to a number literal containing underscores, +the underscores are ignored. + +Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by +using them together with a non-literal expression or by explicit conversion). +This means that computations do not overflow and divisions do not truncate +in number literal expressions. + +For example, ``(2**800 + 1) - 2**800`` results in the constant ``1`` (of type ``uint8``) +although intermediate results would not even fit the machine word size. Furthermore, ``.5 * 8`` results +in the integer ``4`` (although non-integers were used in between). + +Any operator that can be applied to integers can also be applied to number literal expressions as +long as the operands are integers. If any of the two is fractional, bit operations are disallowed +and exponentiation is disallowed if the exponent is fractional (because that might result in +a non-rational number). + +.. note:: + Solidity has a number literal type for each rational number. + Integer literals and rational number literals belong to number literal types. + Moreover, all number literal expressions (i.e. the expressions that + contain only number literals and operators) belong to number literal + types. So the number literal expressions ``1 + 2`` and ``2 + 1`` both + belong to the same number literal type for the rational number three. + +.. warning:: + Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. ``5 / 2`` is not equal to ``2``, but to ``2.5``. + +.. note:: + Number literal expressions are converted into a non-literal type as soon as they are used with non-literal + expressions. Disregarding types, the value of the expression assigned to ``b`` + below evaluates to an integer. Because ``a`` is of type ``uint128``, the + expression ``2.5 + a`` has to have a proper type, though. Since there is no common type + for the type of ``2.5`` and ``uint128``, the Solidity compiler does not accept + this code. + +:: + + uint128 a = 1; + uint128 b = 2.5 + a + 0.5; + +.. index:: literal, literal;string, string +.. _string_literals: + +String Literals and Types +------------------------- + +String literals are written with either double or single-quotes (``"foo"`` or ``'bar'``). They do not imply trailing zeroes as in C; ``"foo"`` represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to ``bytes1``, ..., ``bytes32``, if they fit, to ``bytes`` and to ``string``. + +For example, with ``bytes32 samevar = "stringliteral"`` the string literal is interpreted in its raw byte form when assigned to a ``bytes32`` type. + +String literals support the following escape characters: + + - ``\<newline>`` (escapes an actual newline) + - ``\\`` (backslash) + - ``\'`` (single quote) + - ``\"`` (double quote) + - ``\b`` (backspace) + - ``\f`` (form feed) + - ``\n`` (newline) + - ``\r`` (carriage return) + - ``\t`` (tab) + - ``\v`` (vertical tab) + - ``\xNN`` (hex escape, see below) + - ``\uNNNN`` (unicode escape, see below) + +``\xNN`` takes a hex value and inserts the appropriate byte, while ``\uNNNN`` takes a Unicode codepoint and inserts an UTF-8 sequence. + +The string in the following example has a length of ten bytes. +It starts with a newline byte, followed by a double quote, a single +quote a backslash character and then (without separator) the +character sequence ``abcdef``. + +:: + + "\n\"\'\\abc\ + def" + +Any unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to +terminate the string literal. Newline only terminates the string literal if it is not preceded by a ``\``. + +.. index:: literal, bytes + +Hexadecimal Literals +-------------------- + +Hexadecimal literals are prefixed with the keyword ``hex`` and are enclosed in double or single-quotes (``hex"001122FF"``). Their content must be a hexadecimal string and their value will be the binary representation of those values. + +Hexadecimal literals behave like :ref:`string literals <string_literals>` and have the same convertibility restrictions. + +.. index:: enum + +.. _enums: + +Enums +----- + +Enums are one way to create a user-defined type in Solidity. They are explicitly convertible +to and from all integer types but implicit conversion is not allowed. The explicit conversion +from integer checks at runtime that the value lies inside the range of the enum and causes a failing assert otherwise. +Enums needs at least one member. + +The data representation is the same as for enums in C: The options are represented by +subsequent unsigned integer values starting from ``0``. + + +:: + + pragma solidity >=0.4.16 <0.6.0; + + contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } + ActionChoices choice; + ActionChoices constant defaultChoice = ActionChoices.GoStraight; + + function setGoStraight() public { + choice = ActionChoices.GoStraight; + } + + // Since enum types are not part of the ABI, the signature of "getChoice" + // will automatically be changed to "getChoice() returns (uint8)" + // for all matters external to Solidity. The integer type used is just + // large enough to hold all enum values, i.e. if you have more than 256 values, + // `uint16` will be used and so on. + function getChoice() public view returns (ActionChoices) { + return choice; + } + + function getDefaultChoice() public pure returns (uint) { + return uint(defaultChoice); + } + } + +.. index:: ! function type, ! type; function + +.. _function_types: + +Function Types +-------------- + +Function types are the types of functions. Variables of function type +can be assigned from functions and function parameters of function type +can be used to pass functions to and return functions from function calls. +Function types come in two flavours - *internal* and *external* functions: + +Internal functions can only be called inside the current contract (more specifically, +inside the current code unit, which also includes internal library functions +and inherited functions) because they cannot be executed outside of the +context of the current contract. Calling an internal function is realized +by jumping to its entry label, just like when calling a function of the current +contract internally. + +External functions consist of an address and a function signature and they can +be passed via and returned from external function calls. + +Function types are notated as follows:: + + function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)] + +In contrast to the parameter types, the return types cannot be empty - if the +function type should not return anything, the whole ``returns (<return types>)`` +part has to be omitted. + +By default, function types are internal, so the ``internal`` keyword can be +omitted. Note that this only applies to function types. Visibility has +to be specified explicitly for functions defined in contracts, they +do not have a default. + +Conversions: + +A value of external function type can be explicitly converted to ``address`` +resulting in the address of the contract of the function. + +A function type ``A`` is implicitly convertible to a function type ``B`` if and only if +their parameter types are identical, their return types are identical, +their internal/external property is identical and the state mutability of ``A`` +is not more restrictive than the state mutability of ``B``. In particular: + + - ``pure`` functions can be converted to ``view`` and ``non-payable`` functions + - ``view`` functions can be converted to ``non-payable`` functions + - ``payable`` functions can be converted to ``non-payable`` functions + +No other conversions between function types are possible. + +The rule about ``payable`` and ``non-payable`` might be a little +confusing, but in essence, if a function is ``payable``, this means that it +also accepts a payment of zero Ether, so it also is ``non-payable``. +On the other hand, a ``non-payable`` function will reject Ether sent to it, +so ``non-payable`` functions cannot be converted to ``payable`` functions. + +If a function type variable is not initialised, calling it results +in a failed assertion. The same happens if you call a function after using ``delete`` +on it. + +If external function types are used outside of the context of Solidity, +they are treated as the ``function`` type, which encodes the address +followed by the function identifier together in a single ``bytes24`` type. + +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, +just use ``f``, if you want to use its external form, use ``this.f``. + +Members: + +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.16 <0.6.0; + + contract Selector { + function f() public pure returns (bytes4) { + return this.f.selector; + } + } + +Example that shows how to use internal function types:: + + pragma solidity >=0.4.16 <0.6.0; + + library ArrayUtils { + // internal functions can be used in internal library functions because + // they will be part of the same code context + function map(uint[] memory self, function (uint) pure returns (uint) f) + internal + pure + returns (uint[] memory r) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } + } + function reduce( + uint[] memory self, + function (uint, uint) pure returns (uint) f + ) + internal + pure + returns (uint r) + { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } + } + function range(uint length) internal pure returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } + } + } + + contract Pyramid { + using ArrayUtils for *; + function pyramid(uint l) public pure returns (uint) { + return ArrayUtils.range(l).map(square).reduce(sum); + } + function square(uint x) internal pure returns (uint) { + return x * x; + } + function sum(uint x, uint y) internal pure returns (uint) { + return x + y; + } + } + +Another example that uses external function types:: + + pragma solidity >=0.4.22 <0.6.0; + + contract Oracle { + struct Request { + bytes data; + function(uint) external callback; + } + Request[] requests; + event NewRequest(uint); + function query(bytes memory data, function(uint) external callback) public { + requests.push(Request(data, callback)); + emit NewRequest(requests.length - 1); + } + function reply(uint requestID, uint response) public { + // Here goes the check that the reply comes from a trusted source + requests[requestID].callback(response); + } + } + + contract OracleUser { + Oracle constant oracle = Oracle(0x1234567); // known contract + uint exchangeRate; + function buySomething() public { + oracle.query("USD", this.oracleResponse); + } + function oracleResponse(uint response) public { + require( + msg.sender == address(oracle), + "Only oracle can call this." + ); + exchangeRate = response; + } + } + +.. note:: + Lambda or inline functions are planned but not yet supported.
\ No newline at end of file diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 336aaf77..ce7706c1 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -103,11 +103,11 @@ Block and Transaction Properties values will be zero. .. note:: - The function ``blockhash`` was previously known as ``block.blockhash``. It was deprecated in + The function ``blockhash`` was previously known as ``block.blockhash``, which was deprecated in version 0.4.22 and removed in version 0.5.0. .. note:: - The function ``gasleft`` was previously known as ``msg.gas``. It was deprecated in + The function ``gasleft`` was previously known as ``msg.gas``, which was deprecated in version 0.4.21 and removed in version 0.5.0. .. index:: abi, encoding, packed @@ -200,6 +200,10 @@ Members of Address Types For more information, see the section on :ref:`address`. .. warning:: + You should avoid using ``.call()`` whenever possible when executing another contract function as it bypasses type checking, + function existence check, and argument packing. + +.. warning:: There are some dangers in using ``send``: The transfer fails if the call stack depth is at 1024 (this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order to make safe Ether transfers, always check the return value of ``send``, use ``transfer`` or even better: @@ -240,3 +244,33 @@ Furthermore, all functions of the current contract are callable directly includi .. note:: Prior to version 0.5.0, there was a function called ``suicide`` with the same semantics as ``selfdestruct``. + +.. index:: type, creationCode, runtimeCode + +.. _meta-type: + +Type Information +---------------- + +The expression ``type(X)`` can be used to retrieve information about the +type ``X``. Currently, there is limited support for this feature, but +it might be expanded in the future. The following properties are +available for a conract type ``C``: + +``type(C).creationCode``: + Memory byte array that contains the creation bytecode of the contract. + This can be used in inline assembly to build custom creation routines, + especially by using the ``create2`` opcode. + This property can **not** be accessed in the contract itself or any + derived contract. It causes the bytecode to be included in the bytecode + of the call site and thus circular references like that are not possible. + +``type(C).runtimeCode``: + Memory byte array that contains the runtime bytecode of the contract. + This is the code that is usually deployed by the constructor of ``C``. + If ``C`` has a constructor that uses inline assembly, this might be + different from the actually deployed bytecode. Also note that libraries + modify their runtime bytecode at time of deployment to guard against + regular calls. + The same restrictions as with ``.creationCode`` also apply for this + property. diff --git a/docs/yul.rst b/docs/yul.rst index 9e50f126..627e6e7c 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -130,9 +130,10 @@ Restrictions on the Grammar --------------------------- Switches must have at least one case (including the default case). -If all possible values of the expression is covered, the default case should -not be allowed (i.e. a switch with a ``bool`` expression and having both a -true and false case should not allow a default case). +If all possible values of the expression are covered, a default case should +not be allowed (i.e. a switch with a ``bool`` expression that has both a +true and a false case should not allow a default case). All case values need to +have the same type. Every expression evaluates to zero or more values. Identifiers and Literals evaluate to exactly @@ -171,7 +172,7 @@ As an exception, identifiers defined in the "init" part of the for-loop (the first block) are visible in all other parts of the for-loop (but not outside of the loop). Identifiers declared in the other parts of the for loop respect the regular -syntatical scoping rules. +syntactical scoping rules. The parameters and return parameters of functions are visible in the function body and their names cannot overlap. diff --git a/libdevcore/Algorithms.h b/libdevcore/Algorithms.h index 7fe2472d..34a4dfe5 100644 --- a/libdevcore/Algorithms.h +++ b/libdevcore/Algorithms.h @@ -75,4 +75,47 @@ private: V const* m_firstCycleVertex = nullptr; }; +/** + * Generic breadth first search. + * + * Example: Gather all (recursive) children in a graph starting at (and including) ``root``: + * + * Node const* root = ...; + * std::set<Node> allNodes = BreadthFirstSearch<Node>{{root}}.run([](Node const& _node, auto&& _addChild) { + * // Potentially process ``_node``. + * for (Node const& _child: _node.children()) + * // Potentially filter the children to be visited. + * _addChild(_child); + * }).visited; + * + * Note that the order of the traversal is *non-deterministic* (the children are stored in a std::set of pointers). + */ +template<typename V> +struct BreadthFirstSearch +{ + /// Runs the breadth first search. The verticesToTraverse member of the struct needs to be initialized. + /// @param _forEachChild is a callable of the form [...](V const& _node, auto&& _addChild) { ... } + /// that is called for each visited node and is supposed to call _addChild(childNode) for every child + /// node of _node. + template<typename ForEachChild> + BreadthFirstSearch& run(ForEachChild&& _forEachChild) + { + while (!verticesToTraverse.empty()) + { + V const* v = *verticesToTraverse.begin(); + verticesToTraverse.erase(verticesToTraverse.begin()); + visited.insert(v); + + _forEachChild(*v, [this](V const& _vertex) { + if (!visited.count(&_vertex)) + verticesToTraverse.insert(&_vertex); + }); + } + return *this; + } + + std::set<V const*> verticesToTraverse; + std::set<V const*> visited{}; +}; + } diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index e68ac10a..193fa41d 100644 --- a/libdevcore/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -1,7 +1,6 @@ set(sources Algorithms.h Assertions.h - boost_multiprecision_number_compare_bug_workaround.hpp Common.h CommonData.cpp CommonData.h diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 6208424e..e02cf7fa 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -39,26 +39,13 @@ #include <libdevcore/vector_ref.h> -#if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#endif // defined(__GNUC__) - -// See https://github.com/ethereum/libweb3core/commit/90680a8c25bfb48b24371b4abcacde56c181517c -// See https://svn.boost.org/trac/boost/ticket/11328 -// Bob comment - perhaps we should just HARD FAIL here with Boost-1.58.00? -// It is quite old now, and requiring end-users to use a newer Boost release is probably not unreasonable. #include <boost/version.hpp> -#if (BOOST_VERSION == 105800) - #include "boost_multiprecision_number_compare_bug_workaround.hpp" -#endif // (BOOST_VERSION == 105800) +#if (BOOST_VERSION < 106500) +#error "Unsupported Boost version. At least 1.65 required." +#endif #include <boost/multiprecision/cpp_int.hpp> -#if defined(__GNUC__) -#pragma GCC diagnostic pop -#endif // defined(__GNUC__) - #include <map> #include <vector> #include <functional> diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 7c59c505..98331936 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -263,6 +263,59 @@ void iterateReplacing(std::vector<T>& _vector, const F& _f) _vector = std::move(modifiedVector); } + +namespace detail +{ +template <typename T, typename F, std::size_t... I> +void iterateReplacingWindow(std::vector<T>& _vector, F const& _f, std::index_sequence<I...>) +{ + // Concept: _f must be Callable, must accept sizeof...(I) parameters of type T&, must return optional<vector<T>> + bool useModified = false; + std::vector<T> modifiedVector; + size_t i = 0; + for (; i + sizeof...(I) <= _vector.size(); ++i) + { + if (boost::optional<std::vector<T>> r = _f(_vector[i + I]...)) + { + if (!useModified) + { + std::move(_vector.begin(), _vector.begin() + i, back_inserter(modifiedVector)); + useModified = true; + } + modifiedVector += std::move(*r); + i += sizeof...(I) - 1; + } + else if (useModified) + modifiedVector.emplace_back(std::move(_vector[i])); + } + if (useModified) + { + for (; i < _vector.size(); ++i) + modifiedVector.emplace_back(std::move(_vector[i])); + _vector = std::move(modifiedVector); + } +} + +} + +/// Function that iterates over the vector @param _vector, +/// calling the function @param _f on sequences of @tparam N of its +/// elements. If @param _f returns a vector, these elements are replaced by +/// the returned vector and the iteration continues with the next @tparam N elements. +/// If the function does not return a vector, the iteration continues with an overlapping +/// sequence of @tparam N elements that starts with the second element of the previous +/// iteration. +/// During the iteration, the original vector is only valid +/// on the current element and after that. The actual replacement takes +/// place at the end, but already visited elements might be invalidated. +/// If nothing is replaced, no copy is performed. +template <std::size_t N, typename T, typename F> +void iterateReplacingWindow(std::vector<T>& _vector, F const& _f) +{ + // Concept: _f must be Callable, must accept N parameters of type T&, must return optional<vector<T>> + detail::iterateReplacingWindow(_vector, _f, std::make_index_sequence<N>{}); +} + /// @returns true iff @a _str passess the hex address checksum test. /// @param _strict if false, hex strings with only uppercase or only lowercase letters /// are considered valid. @@ -275,4 +328,10 @@ std::string getChecksummedAddress(std::string const& _addr); bool isValidHex(std::string const& _string); bool isValidDecimal(std::string const& _string); +template<typename Container, typename Compare> +bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _compare) +{ + return std::equal(std::begin(_lhs), std::end(_lhs), std::begin(_rhs), std::end(_rhs), std::forward<Compare>(_compare)); +} + } diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index cc730575..29b31ebc 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -128,26 +128,6 @@ int dev::readStandardInputChar() return cin.get(); } -boost::filesystem::path dev::weaklyCanonicalFilesystemPath(boost::filesystem::path const &_path) -{ - if (boost::filesystem::exists(_path)) - return boost::filesystem::canonical(_path); - else - { - boost::filesystem::path head(_path); - boost::filesystem::path tail; - for (auto it = --_path.end(); !head.empty(); --it) - { - if (boost::filesystem::exists(head)) - break; - tail = (*it) / tail; - head.remove_filename(); - } - head = boost::filesystem::canonical(head); - return head / tail; - } -} - string dev::absolutePath(string const& _path, string const& _reference) { boost::filesystem::path p(_path); diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index b9f941ea..0d8aca79 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -50,10 +50,6 @@ std::string toString(_T const& _t) return o.str(); } -/// Partial implementation of boost::filesystem::weakly_canonical (available in boost>=1.60). -/// Should be replaced by the boost implementation as soon as support for boost<1.60 can be dropped. -boost::filesystem::path weaklyCanonicalFilesystemPath(boost::filesystem::path const &_path); - /// @returns the absolute path corresponding to @a _path relative to @a _reference. std::string absolutePath(std::string const& _path, std::string const& _reference); diff --git a/libdevcore/boost_multiprecision_number_compare_bug_workaround.hpp b/libdevcore/boost_multiprecision_number_compare_bug_workaround.hpp deleted file mode 100644 index 2568e17d..00000000 --- a/libdevcore/boost_multiprecision_number_compare_bug_workaround.hpp +++ /dev/null @@ -1,520 +0,0 @@ - -// This is a copy of boost/multiprecision/detail/number_compare.hpp from boost 1.59 to replace buggy version from 1.58. - -#ifdef BOOST_MP_COMPARE_HPP -#error This bug workaround header must be included before original boost/multiprecision/detail/number_compare.hpp -#endif - -/////////////////////////////////////////////////////////////////////////////// -// Copyright 2012 John Maddock. Distributed under the Boost -// Software License, Version 1.0. (See accompanying file -// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef BOOST_MP_COMPARE_HPP -#define BOOST_MP_COMPARE_HPP - -// A copy of boost/multiprecision/traits/is_backend.hpp -#ifndef BOOST_MP_IS_BACKEND_HPP -#define BOOST_MP_IS_BACKEND_HPP - -#include <boost/mpl/has_xxx.hpp> -#include <boost/type_traits/conditional.hpp> -#include <boost/type_traits/is_convertible.hpp> -#include <boost/multiprecision/detail/number_base.hpp> -#include <boost/multiprecision/detail/generic_interconvert.hpp> - -namespace boost{ namespace multiprecision{ namespace detail{ - - BOOST_MPL_HAS_XXX_TRAIT_DEF(signed_types) - BOOST_MPL_HAS_XXX_TRAIT_DEF(unsigned_types) - BOOST_MPL_HAS_XXX_TRAIT_DEF(float_types) - - template <class T> - struct is_backend - { - static const bool value = has_signed_types<T>::value && has_unsigned_types<T>::value && has_float_types<T>::value; - }; - - template <class Backend> - struct other_backend - { - typedef typename boost::conditional< - boost::is_same<number<Backend>, number<Backend, et_on> >::value, - number<Backend, et_off>, number<Backend, et_on> >::type type; - }; - - template <class B, class V> - struct number_from_backend - { - typedef typename boost::conditional < - boost::is_convertible<V, number<B> >::value, - number<B>, - typename other_backend<B>::type > ::type type; - }; - - template <bool b, class T, class U> - struct is_first_backend_imp{ static const bool value = false; }; - template <class T, class U> - struct is_first_backend_imp<true, T, U>{ static const bool value = is_convertible<U, number<T, et_on> >::value || is_convertible<U, number<T, et_off> >::value; }; - - template <class T, class U> - struct is_first_backend : is_first_backend_imp<is_backend<T>::value, T, U> {}; - - template <bool b, class T, class U> - struct is_second_backend_imp{ static const bool value = false; }; - template <class T, class U> - struct is_second_backend_imp<true, T, U>{ static const bool value = is_convertible<T, number<U> >::value || is_convertible<T, number<U, et_off> >::value; }; - - template <class T, class U> - struct is_second_backend : is_second_backend_imp<is_backend<U>::value, T, U> {}; - -} -} -} - -#endif // BOOST_MP_IS_BACKEND_HPP - -// -// Comparison operators for number. -// - -namespace boost{ namespace multiprecision{ - -namespace default_ops{ - -template <class B> -inline bool eval_eq(const B& a, const B& b) -{ - return a.compare(b) == 0; -} -template <class T, class U> -inline typename enable_if_c<boost::multiprecision::detail::is_first_backend<T, U>::value, bool>::type eval_eq(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend<T, U>::type t(b); - return eval_eq(a, t.backend()); -} -template <class T, class U> -inline typename enable_if_c<boost::multiprecision::detail::is_second_backend<T, U>::value, bool>::type eval_eq(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend<U, T>::type t(a); - return eval_eq(t.backend(), b); -} - -template <class B> -inline bool eval_lt(const B& a, const B& b) -{ - return a.compare(b) < 0; -} -template <class T, class U> -inline typename enable_if_c<boost::multiprecision::detail::is_first_backend<T, U>::value, bool>::type eval_lt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend<T, U>::type t(b); - return eval_lt(a, t.backend()); -} -template <class T, class U> -inline typename enable_if_c<boost::multiprecision::detail::is_second_backend<T, U>::value, bool>::type eval_lt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend<U, T>::type t(a); - return eval_lt(t.backend(), b); -} - -template <class B> -inline bool eval_gt(const B& a, const B& b) -{ - return a.compare(b) > 0; -} -template <class T, class U> -inline typename enable_if_c<boost::multiprecision::detail::is_first_backend<T, U>::value, bool>::type eval_gt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend<T, U>::type t(b); - return eval_gt(a, t.backend()); -} -template <class T, class U> -inline typename enable_if_c<boost::multiprecision::detail::is_second_backend<T, U>::value, bool>::type eval_gt(const T& a, const U& b) -{ - typename boost::multiprecision::detail::number_from_backend<U, T>::type t(a); - return eval_gt(t.backend(), b); -} - -} // namespace default_ops - -namespace detail{ - -template <class Num, class Val> -struct is_valid_mixed_compare : public mpl::false_ {}; - -template <class B, expression_template_option ET, class Val> -struct is_valid_mixed_compare<number<B, ET>, Val> : public is_convertible<Val, number<B, ET> > {}; - -template <class B, expression_template_option ET> -struct is_valid_mixed_compare<number<B, ET>, number<B, ET> > : public mpl::false_ {}; - -template <class B, expression_template_option ET, class tag, class Arg1, class Arg2, class Arg3, class Arg4> -struct is_valid_mixed_compare<number<B, ET>, expression<tag, Arg1, Arg2, Arg3, Arg4> > - : public mpl::bool_<is_convertible<expression<tag, Arg1, Arg2, Arg3, Arg4>, number<B, ET> >::value> {}; - -template <class tag, class Arg1, class Arg2, class Arg3, class Arg4, class B, expression_template_option ET> -struct is_valid_mixed_compare<expression<tag, Arg1, Arg2, Arg3, Arg4>, number<B, ET> > - : public mpl::bool_<is_convertible<expression<tag, Arg1, Arg2, Arg3, Arg4>, number<B, ET> >::value> {}; - -template <class Backend, expression_template_option ExpressionTemplates> -inline BOOST_CONSTEXPR typename boost::enable_if_c<number_category<Backend>::value != number_kind_floating_point, bool>::type is_unordered_value(const number<Backend, ExpressionTemplates>&) -{ - return false; -} -template <class Backend, expression_template_option ExpressionTemplates> -inline BOOST_CONSTEXPR typename boost::enable_if_c<number_category<Backend>::value == number_kind_floating_point, bool>::type is_unordered_value(const number<Backend, ExpressionTemplates>& a) -{ - using default_ops::eval_fpclassify; - return eval_fpclassify(a.backend()) == FP_NAN; -} - -template <class Arithmetic> -inline BOOST_CONSTEXPR typename boost::enable_if_c<number_category<Arithmetic>::value != number_kind_floating_point, bool>::type is_unordered_value(const Arithmetic&) -{ - return false; -} -template <class Arithmetic> -inline BOOST_CONSTEXPR typename boost::enable_if_c<number_category<Arithmetic>::value == number_kind_floating_point, bool>::type is_unordered_value(const Arithmetic& a) -{ - return (boost::math::isnan)(a); -} - -template <class T, class U> -inline BOOST_CONSTEXPR bool is_unordered_comparison(const T& a, const U& b) -{ - return is_unordered_value(a) || is_unordered_value(b); -} - -} - -template <class Backend, expression_template_option ExpressionTemplates, class Backend2, expression_template_option ExpressionTemplates2> -inline bool operator == (const number<Backend, ExpressionTemplates>& a, const number<Backend2, ExpressionTemplates2>& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_eq(a.backend(), b.backend()); -} -template <class Backend, expression_template_option ExpressionTemplates, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator == (const number<Backend, ExpressionTemplates>& a, const Arithmetic& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_eq(a.backend(), number<Backend, ExpressionTemplates>::canonical_value(b)); -} -template <class Arithmetic, class Backend, expression_template_option ExpressionTemplates> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator == (const Arithmetic& a, const number<Backend, ExpressionTemplates>& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_eq(b.backend(), number<Backend, ExpressionTemplates>::canonical_value(a)); -} -template <class Arithmetic, class Tag, class A1, class A2, class A3, class A4> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator == (const Arithmetic& a, const detail::expression<Tag, A1, A2, A3, A4>& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_eq; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return eval_eq(t.backend(), result_type::canonical_value(a)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator == (const detail::expression<Tag, A1, A2, A3, A4>& a, const Arithmetic& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_eq; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return eval_eq(t.backend(), result_type::canonical_value(b)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Tagb, class A1b, class A2b, class A3b, class A4b> -inline typename enable_if<is_same<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type>, bool>::type - operator == (const detail::expression<Tag, A1, A2, A3, A4>& a, const detail::expression<Tagb, A1b, A2b, A3b, A4b>& b) -{ - using default_ops::eval_eq; - typename detail::expression<Tag, A1, A2, A3, A4>::result_type t(a); - typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return eval_eq(t.backend(), t2.backend()); -} - -template <class Backend, expression_template_option ExpressionTemplates, class Backend2, expression_template_option ExpressionTemplates2> -inline bool operator != (const number<Backend, ExpressionTemplates>& a, const number<Backend2, ExpressionTemplates2>& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return true; - return !eval_eq(a.backend(), b.backend()); -} -template <class Backend, expression_template_option ExpressionTemplates, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator != (const number<Backend, ExpressionTemplates>& a, const Arithmetic& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return true; - return !eval_eq(a.backend(), number<Backend, et_on>::canonical_value(b)); -} -template <class Arithmetic, class Backend, expression_template_option ExpressionTemplates> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator != (const Arithmetic& a, const number<Backend, ExpressionTemplates>& b) -{ - using default_ops::eval_eq; - if(detail::is_unordered_comparison(a, b)) return true; - return !eval_eq(b.backend(), number<Backend, et_on>::canonical_value(a)); -} -template <class Arithmetic, class Tag, class A1, class A2, class A3, class A4> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator != (const Arithmetic& a, const detail::expression<Tag, A1, A2, A3, A4>& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_eq; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return true; - return !eval_eq(t.backend(), result_type::canonical_value(a)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator != (const detail::expression<Tag, A1, A2, A3, A4>& a, const Arithmetic& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_eq; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return true; - return !eval_eq(t.backend(), result_type::canonical_value(b)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Tagb, class A1b, class A2b, class A3b, class A4b> -inline typename enable_if<is_same<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type>, bool>::type - operator != (const detail::expression<Tag, A1, A2, A3, A4>& a, const detail::expression<Tagb, A1b, A2b, A3b, A4b>& b) -{ - using default_ops::eval_eq; - typename detail::expression<Tag, A1, A2, A3, A4>::result_type t(a); - typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return true; - return !eval_eq(t.backend(), t2.backend()); -} - -template <class Backend, expression_template_option ExpressionTemplates, class Backend2, expression_template_option ExpressionTemplates2> -inline bool operator < (const number<Backend, ExpressionTemplates>& a, const number<Backend2, ExpressionTemplates2>& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_lt(a.backend(), b.backend()); -} -template <class Backend, expression_template_option ExpressionTemplates, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator < (const number<Backend, ExpressionTemplates>& a, const Arithmetic& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_lt(a.backend(), number<Backend, ExpressionTemplates>::canonical_value(b)); -} -template <class Arithmetic, class Backend, expression_template_option ExpressionTemplates> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator < (const Arithmetic& a, const number<Backend, ExpressionTemplates>& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_gt(b.backend(), number<Backend, ExpressionTemplates>::canonical_value(a)); -} -template <class Arithmetic, class Tag, class A1, class A2, class A3, class A4> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator < (const Arithmetic& a, const detail::expression<Tag, A1, A2, A3, A4>& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_gt; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return eval_gt(t.backend(), result_type::canonical_value(a)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator < (const detail::expression<Tag, A1, A2, A3, A4>& a, const Arithmetic& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_lt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return eval_lt(t.backend(), result_type::canonical_value(b)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Tagb, class A1b, class A2b, class A3b, class A4b> -inline typename enable_if<is_same<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type>, bool>::type - operator < (const detail::expression<Tag, A1, A2, A3, A4>& a, const detail::expression<Tagb, A1b, A2b, A3b, A4b>& b) -{ - using default_ops::eval_lt; - typename detail::expression<Tag, A1, A2, A3, A4>::result_type t(a); - typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return eval_lt(t.backend(), t2.backend()); -} - -template <class Backend, expression_template_option ExpressionTemplates, class Backend2, expression_template_option ExpressionTemplates2> -inline bool operator > (const number<Backend, ExpressionTemplates>& a, const number<Backend2, ExpressionTemplates2>& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_gt(a.backend(), b.backend()); -} -template <class Backend, expression_template_option ExpressionTemplates, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator > (const number<Backend, ExpressionTemplates>& a, const Arithmetic& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_gt(a.backend(), number<Backend, ExpressionTemplates>::canonical_value(b)); -} -template <class Arithmetic, class Backend, expression_template_option ExpressionTemplates> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator > (const Arithmetic& a, const number<Backend, ExpressionTemplates>& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return eval_lt(b.backend(), number<Backend, ExpressionTemplates>::canonical_value(a)); -} -template <class Arithmetic, class Tag, class A1, class A2, class A3, class A4> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator > (const Arithmetic& a, const detail::expression<Tag, A1, A2, A3, A4>& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_lt; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return a > t; -} -template <class Tag, class A1, class A2, class A3, class A4, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator > (const detail::expression<Tag, A1, A2, A3, A4>& a, const Arithmetic& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_gt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return t > b; -} -template <class Tag, class A1, class A2, class A3, class A4, class Tagb, class A1b, class A2b, class A3b, class A4b> -inline typename enable_if<is_same<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type>, bool>::type - operator > (const detail::expression<Tag, A1, A2, A3, A4>& a, const detail::expression<Tagb, A1b, A2b, A3b, A4b>& b) -{ - using default_ops::eval_gt; - typename detail::expression<Tag, A1, A2, A3, A4>::result_type t(a); - typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return t > t2; -} - -template <class Backend, expression_template_option ExpressionTemplates, class Backend2, expression_template_option ExpressionTemplates2> -inline bool operator <= (const number<Backend, ExpressionTemplates>& a, const number<Backend2, ExpressionTemplates2>& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_gt(a.backend(), b.backend()); -} -template <class Backend, expression_template_option ExpressionTemplates, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator <= (const number<Backend, ExpressionTemplates>& a, const Arithmetic& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_gt(a.backend(), number<Backend, ExpressionTemplates>::canonical_value(b)); -} -template <class Arithmetic, class Backend, expression_template_option ExpressionTemplates> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator <= (const Arithmetic& a, const number<Backend, ExpressionTemplates>& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_lt(b.backend(), number<Backend, ExpressionTemplates>::canonical_value(a)); -} -template <class Arithmetic, class Tag, class A1, class A2, class A3, class A4> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator <= (const Arithmetic& a, const detail::expression<Tag, A1, A2, A3, A4>& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_lt; - if(detail::is_unordered_value(a) || detail::is_unordered_value(b)) - return false; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return !eval_lt(t.backend(), result_type::canonical_value(a)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator <= (const detail::expression<Tag, A1, A2, A3, A4>& a, const Arithmetic& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_gt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return !eval_gt(t.backend(), result_type::canonical_value(b)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Tagb, class A1b, class A2b, class A3b, class A4b> -inline typename enable_if<is_same<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type>, bool>::type - operator <= (const detail::expression<Tag, A1, A2, A3, A4>& a, const detail::expression<Tagb, A1b, A2b, A3b, A4b>& b) -{ - using default_ops::eval_gt; - typename detail::expression<Tag, A1, A2, A3, A4>::result_type t(a); - typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return !eval_gt(t.backend(), t2.backend()); -} - -template <class Backend, expression_template_option ExpressionTemplates, class Backend2, expression_template_option ExpressionTemplates2> -inline bool operator >= (const number<Backend, ExpressionTemplates>& a, const number<Backend2, ExpressionTemplates2>& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_lt(a.backend(), b.backend()); -} -template <class Backend, expression_template_option ExpressionTemplates, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator >= (const number<Backend, ExpressionTemplates>& a, const Arithmetic& b) -{ - using default_ops::eval_lt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_lt(a.backend(), number<Backend, ExpressionTemplates>::canonical_value(b)); -} -template <class Arithmetic, class Backend, expression_template_option ExpressionTemplates> -inline typename enable_if_c<detail::is_valid_mixed_compare<number<Backend, ExpressionTemplates>, Arithmetic>::value, bool>::type - operator >= (const Arithmetic& a, const number<Backend, ExpressionTemplates>& b) -{ - using default_ops::eval_gt; - if(detail::is_unordered_comparison(a, b)) return false; - return !eval_gt(b.backend(), number<Backend, ExpressionTemplates>::canonical_value(a)); -} -template <class Arithmetic, class Tag, class A1, class A2, class A3, class A4> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator >= (const Arithmetic& a, const detail::expression<Tag, A1, A2, A3, A4>& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_gt; - result_type t(b); - if(detail::is_unordered_comparison(a, t)) return false; - return !eval_gt(t.backend(), result_type::canonical_value(a)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Arithmetic> -inline typename enable_if_c<detail::is_valid_mixed_compare<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, Arithmetic>::value, bool>::type - operator >= (const detail::expression<Tag, A1, A2, A3, A4>& a, const Arithmetic& b) -{ - typedef typename detail::expression<Tag, A1, A2, A3, A4>::result_type result_type; - using default_ops::eval_lt; - result_type t(a); - if(detail::is_unordered_comparison(t, b)) return false; - return !eval_lt(t.backend(), result_type::canonical_value(b)); -} -template <class Tag, class A1, class A2, class A3, class A4, class Tagb, class A1b, class A2b, class A3b, class A4b> -inline typename enable_if<is_same<typename detail::expression<Tag, A1, A2, A3, A4>::result_type, typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type>, bool>::type - operator >= (const detail::expression<Tag, A1, A2, A3, A4>& a, const detail::expression<Tagb, A1b, A2b, A3b, A4b>& b) -{ - using default_ops::eval_lt; - typename detail::expression<Tag, A1, A2, A3, A4>::result_type t(a); - typename detail::expression<Tagb, A1b, A2b, A3b, A4b>::result_type t2(b); - if(detail::is_unordered_comparison(t, t2)) return false; - return !eval_lt(t.backend(), t2.backend()); -} - - -}} // namespaces - -#endif // BOOST_MP_COMPARE_HPP diff --git a/libevmasm/RuleList.h b/libevmasm/RuleList.h index 874a8929..01e9b984 100644 --- a/libevmasm/RuleList.h +++ b/libevmasm/RuleList.h @@ -44,7 +44,7 @@ template <class S> S modWorkaround(S const& _a, S const& _b) return (S)(bigint(_a) % bigint(_b)); } -// This part of simplificationRuleList below was split out to prevent +// simplificationRuleList below was split up into parts to prevent // stack overflows in the JavaScript optimizer for emscripten builds // that affected certain browser versions. template <class Pattern> @@ -52,8 +52,8 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1( Pattern A, Pattern B, Pattern C, - Pattern X, - Pattern Y + Pattern, + Pattern ) { return std::vector<SimplificationRule<Pattern>> { @@ -96,8 +96,20 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1( if (A.d() > 255) return u256(0); return B.d() >> unsigned(A.d()); - }, false}, + }, false} + }; +} +template <class Pattern> +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern +) +{ + return std::vector<SimplificationRule<Pattern>> { // invariants involving known constants {{Instruction::ADD, {X, 0}}, [=]{ return X; }, false}, {{Instruction::ADD, {0, X}}, [=]{ return X; }, false}, @@ -128,7 +140,19 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1( {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }, true}, {{Instruction::EQ, {X, 0}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, {{Instruction::EQ, {0, X}}, [=]() -> Pattern { return {Instruction::ISZERO, {X}}; }, false }, + }; +} +template <class Pattern> +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart3( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern +) +{ + return std::vector<SimplificationRule<Pattern>> { // operations involving an expression and itself {{Instruction::AND, {X, X}}, [=]{ return X; }, true}, {{Instruction::OR, {X, X}}, [=]{ return X; }, true}, @@ -139,8 +163,20 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1( {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }, true}, {{Instruction::GT, {X, X}}, [=]{ return u256(0); }, true}, {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }, true}, - {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }, true}, + {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }, true} + }; +} +template <class Pattern> +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart4( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + return std::vector<SimplificationRule<Pattern>> { // logical instruction combinations {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }, false}, {{Instruction::XOR, {X, {Instruction::XOR, {X, Y}}}}, [=]{ return Y; }, true}, @@ -163,16 +199,13 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart1( } -// This part of simplificationRuleList below was split out to prevent -// stack overflows in the JavaScript optimizer for emscripten builds -// that affected certain browser versions. template <class Pattern> -std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2( - Pattern A, - Pattern B, +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart5( + Pattern, + Pattern, Pattern, Pattern X, - Pattern Y + Pattern ) { std::vector<SimplificationRule<Pattern>> rules; @@ -207,7 +240,19 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2( false }); } + return rules; +} +template <class Pattern> +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart6( + Pattern, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + std::vector<SimplificationRule<Pattern>> rules; // Double negation of opcodes with boolean result for (auto const& op: std::vector<Instruction>{ Instruction::EQ, @@ -234,6 +279,19 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2( false }); + return rules; +} + +template <class Pattern> +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart7( + Pattern A, + Pattern B, + Pattern, + Pattern X, + Pattern Y +) +{ + std::vector<SimplificationRule<Pattern>> rules; // Associative operations for (auto const& opFun: std::vector<std::pair<Instruction,std::function<u256(u256 const&,u256 const&)>>>{ {Instruction::ADD, std::plus<u256>()}, @@ -274,6 +332,20 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleListPart2( } } + return rules; +} + +template <class Pattern> +std::vector<SimplificationRule<Pattern>> simplificationRuleListPart8( + Pattern A, + Pattern, + Pattern, + Pattern X, + Pattern Y +) +{ + std::vector<SimplificationRule<Pattern>> rules; + // move constants across subtractions rules += std::vector<SimplificationRule<Pattern>>{ { @@ -322,6 +394,12 @@ std::vector<SimplificationRule<Pattern>> simplificationRuleList( std::vector<SimplificationRule<Pattern>> rules; rules += simplificationRuleListPart1(A, B, C, X, Y); rules += simplificationRuleListPart2(A, B, C, X, Y); + rules += simplificationRuleListPart3(A, B, C, X, Y); + rules += simplificationRuleListPart4(A, B, C, X, Y); + rules += simplificationRuleListPart5(A, B, C, X, Y); + rules += simplificationRuleListPart6(A, B, C, X, Y); + rules += simplificationRuleListPart7(A, B, C, X, Y); + rules += simplificationRuleListPart8(A, B, C, X, Y); return rules; } diff --git a/liblangutil/SourceLocation.h b/liblangutil/SourceLocation.h index 840891c2..c461909f 100644 --- a/liblangutil/SourceLocation.h +++ b/liblangutil/SourceLocation.h @@ -49,6 +49,26 @@ struct SourceLocation bool isEmpty() const { return start == -1 && end == -1; } + /// @returns the smallest SourceLocation that contains both @param _a and @param _b. + /// Assumes that @param _a and @param _b refer to the same source (exception: if the source of either one + /// is unset, the source of the other will be used for the result, even if that is unset as well). + /// Invalid start and end positions (with value of -1) are ignored (if start or end are -1 for both @param _a and + /// @param _b, then start resp. end of the result will be -1 as well). + static SourceLocation smallestCovering(SourceLocation _a, SourceLocation const& _b) + { + if (!_a.source) + _a.source = _b.source; + + if (_a.start < 0) + _a.start = _b.start; + else if (_b.start >= 0 && _b.start < _a.start) + _a.start = _b.start; + if (_b.end > _a.end) + _a.end = _b.end; + + return _a; + } + int start = -1; int end = -1; std::shared_ptr<CharStream> source; diff --git a/liblangutil/SourceReferenceExtractor.cpp b/liblangutil/SourceReferenceExtractor.cpp index 4502bb23..1a6dbdb3 100644 --- a/liblangutil/SourceReferenceExtractor.cpp +++ b/liblangutil/SourceReferenceExtractor.cpp @@ -58,7 +58,9 @@ SourceReference SourceReferenceExtractor::extract(SourceLocation const* _locatio int locationLength = isMultiline ? line.length() - start.column : end.column - start.column; if (locationLength > 150) { - line = line.substr(0, start.column + 35) + " ... " + line.substr(end.column - 35); + int const lhs = start.column + 35; + int const rhs = (isMultiline ? line.length() : end.column) - 35; + line = line.substr(0, lhs) + " ... " + line.substr(rhs); end.column = start.column + 75; locationLength = 75; } diff --git a/liblangutil/Token.h b/liblangutil/Token.h index f832fdf7..b3a1acb1 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -180,6 +180,7 @@ namespace langutil K(CallData, "calldata", 0) \ K(Struct, "struct", 0) \ K(Throw, "throw", 0) \ + K(Type, "type", 0) \ K(Using, "using", 0) \ K(Var, "var", 0) \ K(View, "view", 0) \ @@ -256,7 +257,6 @@ namespace langutil K(Supports, "supports", 0) \ K(Switch, "switch", 0) \ K(Try, "try", 0) \ - K(Type, "type", 0) \ K(Typedef, "typedef", 0) \ K(TypeOf, "typeof", 0) \ K(Unchecked, "unchecked", 0) \ diff --git a/libsolc/CMakeLists.txt b/libsolc/CMakeLists.txt index dfdf8162..7fb4aa05 100644 --- a/libsolc/CMakeLists.txt +++ b/libsolc/CMakeLists.txt @@ -1,4 +1,7 @@ if (EMSCRIPTEN) + # Specify which functions to export in soljson.js. + # Note that additional Emscripten-generated methods needed by solc-js are + # defined to be exported in cmake/EthCompilerSettings.cmake. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_solidity_license\",\"_solidity_version\",\"_solidity_compile\"]' -s RESERVED_FUNCTION_POINTERS=20") add_executable(soljson libsolc.cpp libsolc.h) target_link_libraries(soljson PRIVATE solidity) diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp index 3adf6318..b801547f 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.cpp +++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp @@ -18,6 +18,7 @@ #include <libsolidity/analysis/ControlFlowAnalyzer.h> #include <liblangutil/SourceLocation.h> +#include <libdevcore/Algorithms.h> #include <boost/range/algorithm/sort.hpp> using namespace std; @@ -36,6 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function) { auto const& functionFlow = m_cfg.functionFlow(_function); checkUninitializedAccess(functionFlow.entry, functionFlow.exit); + checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert); } return false; } @@ -145,3 +147,35 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod } } } + +void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const +{ + // collect all nodes reachable from the entry point + std::set<CFGNode const*> reachable = BreadthFirstSearch<CFGNode>{{_entry}}.run( + [](CFGNode const& _node, auto&& _addChild) { + for (CFGNode const* exit: _node.exits) + _addChild(*exit); + } + ).visited; + + // traverse all paths backwards from exit and revert + // and extract (valid) source locations of unreachable nodes into sorted set + std::set<SourceLocation> unreachable; + BreadthFirstSearch<CFGNode>{{_exit, _revert}}.run( + [&](CFGNode const& _node, auto&& _addChild) { + if (!reachable.count(&_node) && !_node.location.isEmpty()) + unreachable.insert(_node.location); + for (CFGNode const* entry: _node.entries) + _addChild(*entry); + } + ); + + for (auto it = unreachable.begin(); it != unreachable.end();) + { + SourceLocation location = *it++; + // Extend the location, as long as the next location overlaps (unreachable is sorted). + for (; it != unreachable.end() && it->start <= location.end; ++it) + location.end = std::max(location.end, it->end); + m_errorReporter.warning(location, "Unreachable code."); + } +} diff --git a/libsolidity/analysis/ControlFlowAnalyzer.h b/libsolidity/analysis/ControlFlowAnalyzer.h index 7761817a..e1585740 100644 --- a/libsolidity/analysis/ControlFlowAnalyzer.h +++ b/libsolidity/analysis/ControlFlowAnalyzer.h @@ -38,6 +38,9 @@ public: private: /// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit. void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const; + /// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert + /// that can not be reached from @param _entry. + void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const; CFG const& m_cfg; langutil::ErrorReporter& m_errorReporter; diff --git a/libsolidity/analysis/ControlFlowBuilder.cpp b/libsolidity/analysis/ControlFlowBuilder.cpp index 3dab8b16..66664245 100644 --- a/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/libsolidity/analysis/ControlFlowBuilder.cpp @@ -18,6 +18,7 @@ #include <libsolidity/analysis/ControlFlowBuilder.h> using namespace dev; +using namespace langutil; using namespace solidity; using namespace std; @@ -53,6 +54,7 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation) case Token::Or: case Token::And: { + visitNode(_operation); appendControlFlow(_operation.leftExpression()); auto nodes = splitFlow<2>(); @@ -62,14 +64,14 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation) return false; } default: - break; + return ASTConstVisitor::visit(_operation); } - return ASTConstVisitor::visit(_operation); } bool ControlFlowBuilder::visit(Conditional const& _conditional) { solAssert(!!m_currentNode, ""); + visitNode(_conditional); _conditional.condition().accept(*this); @@ -86,6 +88,7 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional) bool ControlFlowBuilder::visit(IfStatement const& _ifStatement) { solAssert(!!m_currentNode, ""); + visitNode(_ifStatement); _ifStatement.condition().accept(*this); @@ -106,6 +109,7 @@ bool ControlFlowBuilder::visit(IfStatement const& _ifStatement) bool ControlFlowBuilder::visit(ForStatement const& _forStatement) { solAssert(!!m_currentNode, ""); + visitNode(_forStatement); if (_forStatement.initializationExpression()) _forStatement.initializationExpression()->accept(*this); @@ -139,6 +143,7 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement) bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement) { solAssert(!!m_currentNode, ""); + visitNode(_whileStatement); if (_whileStatement.isDoWhile()) { @@ -183,28 +188,31 @@ bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement) return false; } -bool ControlFlowBuilder::visit(Break const&) +bool ControlFlowBuilder::visit(Break const& _break) { solAssert(!!m_currentNode, ""); solAssert(!!m_breakJump, ""); + visitNode(_break); connect(m_currentNode, m_breakJump); m_currentNode = newLabel(); return false; } -bool ControlFlowBuilder::visit(Continue const&) +bool ControlFlowBuilder::visit(Continue const& _continue) { solAssert(!!m_currentNode, ""); solAssert(!!m_continueJump, ""); + visitNode(_continue); connect(m_currentNode, m_continueJump); m_currentNode = newLabel(); return false; } -bool ControlFlowBuilder::visit(Throw const&) +bool ControlFlowBuilder::visit(Throw const& _throw) { solAssert(!!m_currentNode, ""); solAssert(!!m_revertNode, ""); + visitNode(_throw); connect(m_currentNode, m_revertNode); m_currentNode = newLabel(); return false; @@ -232,6 +240,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) { case FunctionType::Kind::Revert: solAssert(!!m_revertNode, ""); + visitNode(_functionCall); _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); connect(m_currentNode, m_revertNode); @@ -241,6 +250,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall) case FunctionType::Kind::Assert: { solAssert(!!m_revertNode, ""); + visitNode(_functionCall); _functionCall.expression().accept(*this); ASTNode::listAccept(_functionCall.arguments(), *this); connect(m_currentNode, m_revertNode); @@ -314,6 +324,7 @@ bool ControlFlowBuilder::visit(Return const& _return) { solAssert(!!m_currentNode, ""); solAssert(!!m_returnNode, ""); + visitNode(_return); if (_return.expression()) { appendControlFlow(*_return.expression()); @@ -327,11 +338,12 @@ bool ControlFlowBuilder::visit(Return const& _return) } connect(m_currentNode, m_returnNode); m_currentNode = newLabel(); - return true; + return false; } -bool ControlFlowBuilder::visit(FunctionTypeName const&) +bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName) { + visitNode(_functionTypeName); // Do not visit the parameters and return values of a function type name. // We do not want to consider them as variable declarations for the control flow graph. return false; @@ -340,6 +352,7 @@ bool ControlFlowBuilder::visit(FunctionTypeName const&) bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) { solAssert(!!m_currentNode, ""); + visitNode(_inlineAssembly); for (auto const& ref: _inlineAssembly.annotation().externalReferences) { if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration)) @@ -355,6 +368,7 @@ bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) { solAssert(!!m_currentNode, ""); + visitNode(_variableDeclaration); m_currentNode->variableOccurrences.emplace_back( _variableDeclaration, @@ -382,6 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration) bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { solAssert(!!m_currentNode, ""); + visitNode(_variableDeclarationStatement); for (auto const& var: _variableDeclarationStatement.declarations()) if (var) @@ -417,6 +432,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl bool ControlFlowBuilder::visit(Identifier const& _identifier) { solAssert(!!m_currentNode, ""); + visitNode(_identifier); if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) m_currentNode->variableOccurrences.emplace_back( @@ -430,7 +446,12 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier) return true; } - +bool ControlFlowBuilder::visitNode(ASTNode const& _node) +{ + solAssert(!!m_currentNode, ""); + m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, _node.location()); + return true; +} void ControlFlowBuilder::appendControlFlow(ASTNode const& _node) { diff --git a/libsolidity/analysis/ControlFlowBuilder.h b/libsolidity/analysis/ControlFlowBuilder.h index f196e5fc..b1748395 100644 --- a/libsolidity/analysis/ControlFlowBuilder.h +++ b/libsolidity/analysis/ControlFlowBuilder.h @@ -66,6 +66,11 @@ private: bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; bool visit(Identifier const& _identifier) override; +protected: + bool visitNode(ASTNode const&) override; + +private: + /// Appends the control flow of @a _node to the current control flow. void appendControlFlow(ASTNode const& _node); @@ -77,9 +82,6 @@ private: /// Creates an arc from @a _from to @a _to. static void connect(CFGNode* _from, CFGNode* _to); - -private: - /// Splits the control flow starting at the current node into n paths. /// m_currentNode is set to nullptr and has to be set manually or /// using mergeFlow later. diff --git a/libsolidity/analysis/ControlFlowGraph.h b/libsolidity/analysis/ControlFlowGraph.h index cc0113d8..a55b96e8 100644 --- a/libsolidity/analysis/ControlFlowGraph.h +++ b/libsolidity/analysis/ControlFlowGraph.h @@ -20,6 +20,7 @@ #include <libsolidity/ast/AST.h> #include <libsolidity/ast/ASTVisitor.h> #include <liblangutil/ErrorReporter.h> +#include <liblangutil/SourceLocation.h> #include <map> #include <memory> @@ -98,6 +99,8 @@ struct CFGNode /// Variable occurrences in the node. std::vector<VariableOccurrence> variableOccurrences; + // Source location of this control flow block. + langutil::SourceLocation location; }; /** Describes the control flow of a function. */ diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index cd5fe07d..2276d783 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -61,7 +61,14 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{ make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), - make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)) + make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), + make_shared<MagicVariableDeclaration>("type", make_shared<FunctionType>( + strings{"address"} /* accepts any contract type, handled by the type checker */, + strings{} /* returns a MagicType, handled by the type checker */, + FunctionType::Kind::MetaType, + false, + StateMutability::Pure + )), }) { } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index aaaa4f9f..11ed6a4f 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -32,6 +32,56 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; +/** + * Helper class that determines whether a contract's constructor uses inline assembly. + */ +class dev::solidity::ConstructorUsesAssembly +{ +public: + /// @returns true if and only if the contract's or any of its bases' constructors + /// use inline assembly. + bool check(ContractDefinition const& _contract) + { + for (auto const* base: _contract.annotation().linearizedBaseContracts) + if (checkInternal(*base)) + return true; + return false; + } + + +private: + class Checker: public ASTConstVisitor + { + public: + Checker(FunctionDefinition const& _f) { _f.accept(*this); } + bool visit(InlineAssembly const&) override { assemblySeen = true; return false; } + bool assemblySeen = false; + }; + + bool checkInternal(ContractDefinition const& _contract) + { + if (!m_usesAssembly.count(&_contract)) + { + bool usesAssembly = false; + if (_contract.constructor()) + usesAssembly = Checker{*_contract.constructor()}.assemblySeen; + m_usesAssembly[&_contract] = usesAssembly; + } + return m_usesAssembly[&_contract]; + } + + map<ContractDefinition const*, bool> m_usesAssembly; +}; + +StaticAnalyzer::StaticAnalyzer(ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) +{ +} + +StaticAnalyzer::~StaticAnalyzer() +{ +} + bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); @@ -152,6 +202,18 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\"" ); + else if (type->kind() == MagicType::Kind::MetaType && _memberAccess.memberName() == "runtimeCode") + { + if (!m_constructorUsesAssembly) + m_constructorUsesAssembly = make_unique<ConstructorUsesAssembly>(); + ContractType const& contract = dynamic_cast<ContractType const&>(*type->typeArgument()); + if (m_constructorUsesAssembly->check(contract.contractDefinition())) + m_errorReporter.warning( + _memberAccess.location(), + "The constructor of the contract (or its base) uses inline assembly. " + "Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode." + ); + } } if (_memberAccess.memberName() == "callcode") diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index ff33fa3a..3daf83b3 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -38,6 +38,8 @@ namespace dev namespace solidity { +class ConstructorUsesAssembly; + /** * The module that performs static analysis on the AST. @@ -49,7 +51,8 @@ class StaticAnalyzer: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter); + ~StaticAnalyzer(); /// Performs static analysis on the given source unit and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings @@ -85,6 +88,10 @@ private: /// when traversing. std::map<std::pair<size_t, VariableDeclaration const*>, int> m_localVarUseCount; + /// Cache that holds information about whether a contract's constructor + /// uses inline assembly. + std::unique_ptr<ConstructorUsesAssembly> m_constructorUsesAssembly; + FunctionDefinition const* m_currentFunction = nullptr; /// Flag that indicates a constructor. diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp index 066b5004..7b8aa0f2 100644 --- a/libsolidity/analysis/SyntaxChecker.cpp +++ b/libsolidity/analysis/SyntaxChecker.cpp @@ -262,7 +262,7 @@ bool SyntaxChecker::visit(PlaceholderStatement const&) bool SyntaxChecker::visit(ContractDefinition const& _contract) { - m_isInterface = _contract.contractKind() == ContractDefinition::ContractKind::Interface; + m_isInterface = _contract.isInterface(); ASTString const& contractName = _contract.name(); for (FunctionDefinition const* function: _contract.definedFunctions()) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 507a2c94..6d887c45 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -199,12 +199,44 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c return components; } +TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall) +{ + vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); + if (arguments.size() != 1) + { + m_errorReporter.typeError( + _functionCall.location(), + "This function takes one argument, but " + + toString(arguments.size()) + + " were provided." + ); + return {}; + } + TypePointer firstArgType = type(*arguments.front()); + if ( + firstArgType->category() != Type::Category::TypeType || + dynamic_cast<TypeType const&>(*firstArgType).actualType()->category() != TypeType::Category::Contract + ) + { + m_errorReporter.typeError( + arguments.front()->location(), + "Invalid type for argument in function call. " + "Contract type required, but " + + type(*arguments.front())->toString(true) + + " provided." + ); + return {}; + } + + return {MagicType::metaType(dynamic_cast<TypeType const&>(*firstArgType).actualType())}; +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); solAssert(base, "Base contract not available."); - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + if (m_scope->isInterface()) m_errorReporter.typeError(_inheritance.location(), "Interfaces cannot inherit."); if (base->isLibrary()) @@ -212,7 +244,7 @@ void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) auto const& arguments = _inheritance.arguments(); TypePointers parameterTypes; - if (base->contractKind() != ContractDefinition::ContractKind::Interface) + if (!base->isInterface()) // Interfaces do not have constructors, so there are zero parameters. parameterTypes = ContractType(*base).newExpressionType()->parameterTypes(); @@ -299,33 +331,50 @@ bool TypeChecker::visit(FunctionDefinition const& _function) if (!_function.isConstructor() && !_function.isFallback() && !_function.isPartOfExternalInterface()) m_errorReporter.typeError(_function.location(), "Internal functions cannot be payable."); } - for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters()) - { - if (type(*var)->category() == Type::Category::Mapping) + auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) { + if (type(var)->category() == Type::Category::Mapping) { - if (!type(*var)->dataStoredIn(DataLocation::Storage)) - m_errorReporter.typeError(var->location(), "Mapping types can only have a data location of \"storage\"." ); + if (!type(var)->dataStoredIn(DataLocation::Storage)) + m_errorReporter.typeError(var.location(), "Mapping types can only have a data location of \"storage\"." ); else if (!isLibraryFunction && _function.isPublic()) - m_errorReporter.typeError(var->location(), "Mapping types for parameters or return variables can only be used in internal or library functions."); + m_errorReporter.typeError(var.location(), "Mapping types for parameters or return variables can only be used in internal or library functions."); } else { - if (!type(*var)->canLiveOutsideStorage() && _function.isPublic()) - m_errorReporter.typeError(var->location(), "Type is required to live outside storage."); - if (_function.isPublic() && !(type(*var)->interfaceType(isLibraryFunction))) - m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions."); + if (!type(var)->canLiveOutsideStorage() && _function.isPublic()) + m_errorReporter.typeError(var.location(), "Type is required to live outside storage."); + if (_function.isPublic() && !(type(var)->interfaceType(isLibraryFunction))) + m_errorReporter.fatalTypeError(var.location(), "Internal or recursive type is not allowed for public or external functions."); } if ( _function.isPublic() && !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) && - !typeSupportedByOldABIEncoder(*type(*var)) + !typeSupportedByOldABIEncoder(*type(var)) ) m_errorReporter.typeError( - var->location(), + var.location(), "This type is only supported in the new experimental ABI encoder. " "Use \"pragma experimental ABIEncoderV2;\" to enable the feature." ); + }; + for (ASTPointer<VariableDeclaration> const& var: _function.parameters()) + { + TypePointer baseType = type(*var); + while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get())) + baseType = arrayType->baseType(); + if ( + !m_scope->isInterface() && + baseType->category() == Type::Category::Struct && + baseType->dataStoredIn(DataLocation::CallData) + ) + m_errorReporter.typeError(var->location(), "Calldata structs are not yet supported."); + checkArgumentAndReturnParameter(*var); + var->accept(*this); + } + for (ASTPointer<VariableDeclaration> const& var: _function.returnParameters()) + { + checkArgumentAndReturnParameter(*var); var->accept(*this); } set<Declaration const*> modifiers; @@ -346,7 +395,7 @@ bool TypeChecker::visit(FunctionDefinition const& _function) else modifiers.insert(decl); } - if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface) + if (m_scope->isInterface()) { if (_function.isImplemented()) m_errorReporter.typeError(_function.location(), "Functions in interfaces cannot have an implementation."); @@ -375,7 +424,7 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) // * a function's input/output parameters, // * or inside of a struct definition. if ( - m_scope->contractKind() == ContractDefinition::ContractKind::Interface + m_scope->isInterface() && !_variable.isCallableParameter() && !m_insideStruct ) @@ -1482,7 +1531,16 @@ void TypeChecker::typeCheckABIEncodeFunctions( if (argType->category() == Type::Category::RationalNumber) { - if (!argType->mobileType()) + auto const& rationalType = dynamic_cast<RationalNumberType const&>(*argType); + if (rationalType.isFractional()) + { + m_errorReporter.typeError( + arguments[i]->location(), + "Fractional numbers cannot yet be encoded." + ); + continue; + } + else if (!argType->mobileType()) { m_errorReporter.typeError( arguments[i]->location(), @@ -1822,6 +1880,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } + case FunctionType::Kind::MetaType: + returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall); + break; default: { typeCheckFunctionCall(_functionCall, functionType); @@ -1862,7 +1923,7 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) if (!contract) m_errorReporter.fatalTypeError(_newExpression.location(), "Identifier is not a contract."); - if (contract->contractKind() == ContractDefinition::ContractKind::Interface) + if (contract->isInterface()) m_errorReporter.fatalTypeError(_newExpression.location(), "Cannot instantiate an interface."); if (!contract->annotation().unimplementedFunctions.empty()) { @@ -2062,8 +2123,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (tt->actualType()->category() == Type::Category::Enum) annotation.isPure = true; if (auto magicType = dynamic_cast<MagicType const*>(exprType.get())) + { if (magicType->kind() == MagicType::Kind::ABI) annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::MetaType && ( + memberName == "creationCode" || memberName == "runtimeCode" + )) + { + annotation.isPure = true; + m_scope->annotation().contractDependencies.insert( + &dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition() + ); + if (contractDependenciesAreCyclic(*m_scope)) + m_errorReporter.typeError( + _memberAccess.location(), + "Circular reference for contract code access." + ); + } + } return false; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index b60c571a..d5f3645c 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -81,6 +81,8 @@ private: bool _abiEncoderV2 ); + TypePointers typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall); + /// Performs type checks and determines result types for type conversion FunctionCall nodes. TypePointer typeCheckTypeConversionAndRetrieveReturnType( FunctionCall const& _functionCall diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index eb019481..7df7ac17 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -338,7 +338,9 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::ABI, "encodeWithSignature"}, {MagicType::Kind::Block, "blockhash"}, {MagicType::Kind::Message, "data"}, - {MagicType::Kind::Message, "sig"} + {MagicType::Kind::Message, "sig"}, + {MagicType::Kind::MetaType, "creationCode"}, + {MagicType::Kind::MetaType, "runtimeCode"} }; set<MagicMember> static const payableMembers{ {MagicType::Kind::Message, "value"} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index f379d758..4cf8b1e8 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -138,6 +138,11 @@ bool ContractDefinition::constructorIsPublic() const return !f || f->isPublic(); } +bool ContractDefinition::canBeDeployed() const +{ + return constructorIsPublic() && annotation().unimplementedFunctions.empty(); +} + FunctionDefinition const* ContractDefinition::fallbackFunction() const { for (ContractDefinition const* contract: annotation().linearizedBaseContracts) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 9ac065ea..f6fdc441 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -394,6 +394,7 @@ public: std::vector<FunctionDefinition const*> definedFunctions() const { return filteredNodes<FunctionDefinition>(m_subNodes); } std::vector<EventDefinition const*> events() const { return filteredNodes<EventDefinition>(m_subNodes); } std::vector<EventDefinition const*> const& interfaceEvents() const; + bool isInterface() const { return m_contractKind == ContractKind::Interface; } bool isLibrary() const { return m_contractKind == ContractKind::Library; } /// @returns a map of canonical function signatures to FunctionDefinitions @@ -408,6 +409,10 @@ public: FunctionDefinition const* constructor() const; /// @returns true iff the constructor of this contract is public (or non-existing). bool constructorIsPublic() const; + /// @returns true iff the contract can be deployed, i.e. is not abstract and has a + /// public constructor. + /// Should only be called after the type checker has run. + bool canBeDeployed() const; /// Returns the fallback function or nullptr if no fallback function was specified. FunctionDefinition const* fallbackFunction() const; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index cc978b4a..3a8c9878 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2525,7 +2525,7 @@ FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _c strings parameterNames; StateMutability stateMutability = StateMutability::NonPayable; - solAssert(_contract.contractKind() != ContractDefinition::ContractKind::Interface, ""); + solAssert(!_contract.isInterface(), ""); if (constructor) { @@ -2626,6 +2626,7 @@ string FunctionType::richIdentifier() const case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; case Kind::ABIDecode: id += "abidecode"; break; + case Kind::MetaType: id += "metatype"; break; } id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); @@ -3037,7 +3038,8 @@ bool FunctionType::isPure() const m_kind == Kind::ABIEncodePacked || m_kind == Kind::ABIEncodeWithSelector || m_kind == Kind::ABIEncodeWithSignature || - m_kind == Kind::ABIDecode; + m_kind == Kind::ABIDecode || + m_kind == Kind::MetaType; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -3305,6 +3307,14 @@ string ModuleType::toString(bool) const return string("module \"") + m_sourceUnit.annotation().path + string("\""); } +shared_ptr<MagicType> MagicType::metaType(TypePointer _type) +{ + solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now."); + auto t = make_shared<MagicType>(Kind::MetaType); + t->m_typeArgument = std::move(_type); + return t; +} + string MagicType::richIdentifier() const { switch (m_kind) @@ -3317,6 +3327,9 @@ string MagicType::richIdentifier() const return "t_magic_transaction"; case Kind::ABI: return "t_magic_abi"; + case Kind::MetaType: + solAssert(m_typeArgument, ""); + return "t_magic_meta_type_" + m_typeArgument->richIdentifier(); } return ""; } @@ -3403,12 +3416,27 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const StateMutability::Pure )} }); - default: - solAssert(false, "Unknown kind of magic."); + case Kind::MetaType: + { + solAssert( + m_typeArgument && m_typeArgument->category() == Type::Category::Contract, + "Only contracts supported for now" + ); + ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_typeArgument).contractDefinition(); + if (contract.canBeDeployed()) + return MemberList::MemberMap({ + {"creationCode", std::make_shared<ArrayType>(DataLocation::Memory)}, + {"runtimeCode", std::make_shared<ArrayType>(DataLocation::Memory)} + }); + else + return {}; + } } + solAssert(false, "Unknown kind of magic."); + return {}; } -string MagicType::toString(bool) const +string MagicType::toString(bool _short) const { switch (m_kind) { @@ -3420,7 +3448,17 @@ string MagicType::toString(bool) const return "tx"; case Kind::ABI: return "abi"; - default: - solAssert(false, "Unknown kind of magic."); + case Kind::MetaType: + solAssert(m_typeArgument, ""); + return "type(" + m_typeArgument->toString(_short) + ")"; } + solAssert(false, "Unknown kind of magic."); + return {}; +} + +TypePointer MagicType::typeArgument() const +{ + solAssert(m_kind == Kind::MetaType, ""); + solAssert(m_typeArgument, ""); + return m_typeArgument; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index bee00661..53109de1 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -989,6 +989,7 @@ public: ABIEncodeWithSignature, ABIDecode, GasLeft, ///< gasleft() + MetaType ///< type(...) }; Category category() const override { return Category::Function; } @@ -1299,16 +1300,23 @@ private: }; /** - * Special type for magic variables (block, msg, tx), similar to a struct but without any reference - * (it always references a global singleton by name). + * Special type for magic variables (block, msg, tx, type(...)), similar to a struct but without any reference. */ class MagicType: public Type { public: - enum class Kind { Block, Message, Transaction, ABI }; + enum class Kind { + Block, ///< "block" + Message, ///< "msg" + Transaction, ///< "tx" + ABI, ///< "abi" + MetaType ///< "type(...)" + }; Category category() const override { return Category::Magic; } explicit MagicType(Kind _kind): m_kind(_kind) {} + /// Factory function for meta type + static std::shared_ptr<MagicType> metaType(TypePointer _type); TypeResult binaryOperatorResult(Token, TypePointer const&) const override { @@ -1327,8 +1335,13 @@ public: Kind kind() const { return m_kind; } + TypePointer typeArgument() const; + private: Kind m_kind; + /// Contract type used for contract metadata magic. + TypePointer m_typeArgument; + }; /** diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index c1ab03e3..c0fa81ce 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -39,14 +39,19 @@ string ABIFunctions::tupleEncoder( bool _encodeAsLibraryTypes ) { + EncodingOptions options; + options.encodeAsLibraryTypes = _encodeAsLibraryTypes; + options.encodeFunctionFromStack = true; + options.padded = true; + options.dynamicInplace = false; + 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"; + functionName += options.toFunctionNameSuffix(); return createExternallyUsedFunction(functionName, [&]() { solAssert(!_givenTypes.empty(), ""); @@ -90,7 +95,7 @@ string ABIFunctions::tupleEncoder( ); elementTempl("values", valueNames); elementTempl("pos", to_string(headPos)); - elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], _encodeAsLibraryTypes, true)); + elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); encodeElements += elementTempl.render(); headPos += dynamic ? 0x20 : _targetTypes[i]->calldataEncodedSize(); } @@ -184,6 +189,20 @@ pair<string, set<string>> ABIFunctions::requestedFunctions() return make_pair(result, std::move(m_externallyUsedFunctions)); } +string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const +{ + string suffix; + if (!padded) + suffix += "_nonPadded"; + if (dynamicInplace) + suffix += "_inplace"; + if (encodeFunctionFromStack) + suffix += "_fromStack"; + if (encodeAsLibraryTypes) + suffix += "_library"; + return suffix; +} + string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure) { string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier(); @@ -490,32 +509,31 @@ string ABIFunctions::splitExternalFunctionIdFunction() string ABIFunctions::abiEncodingFunction( Type const& _from, Type const& _to, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ) { - TypePointer toInterface = _to.fullEncodingType(_encodeAsLibraryTypes, true, false); + TypePointer toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false); solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented."); Type const& to = *toInterface; if (_from.category() == Type::Category::StringLiteral) - return abiEncodingFunctionStringLiteral(_from, to, _encodeAsLibraryTypes); + return abiEncodingFunctionStringLiteral(_from, to, _options); else if (auto toArray = dynamic_cast<ArrayType const*>(&to)) { solAssert(_from.category() == Type::Category::Array, ""); solAssert(to.dataStoredIn(DataLocation::Memory), ""); ArrayType const& fromArray = dynamic_cast<ArrayType const&>(_from); if (fromArray.location() == DataLocation::CallData) - return abiEncodingFunctionCalldataArray(fromArray, *toArray, _encodeAsLibraryTypes); + return abiEncodingFunctionCalldataArray(fromArray, *toArray, _options); else if (!fromArray.isByteArray() && ( fromArray.location() == DataLocation::Memory || fromArray.baseType()->storageBytes() > 16 )) - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _encodeAsLibraryTypes); + return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); else if (fromArray.location() == DataLocation::Memory) - return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _encodeAsLibraryTypes); + return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); else if (fromArray.location() == DataLocation::Storage) - return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _encodeAsLibraryTypes); + return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); else solAssert(false, ""); } @@ -523,14 +541,13 @@ string ABIFunctions::abiEncodingFunction( { StructType const* fromStruct = dynamic_cast<StructType const*>(&_from); solAssert(fromStruct, ""); - return abiEncodingFunctionStruct(*fromStruct, *toStruct, _encodeAsLibraryTypes); + return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options); } else if (_from.category() == Type::Category::Function) return abiEncodingFunctionFunctionType( dynamic_cast<FunctionType const&>(_from), to, - _encodeAsLibraryTypes, - _fromStack + _options ); solAssert(_from.sizeOnStack() == 1, ""); @@ -541,7 +558,7 @@ string ABIFunctions::abiEncodingFunction( _from.identifier() + "_to_" + to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { solAssert(!to.isDynamicallyEncoded(), ""); @@ -556,7 +573,7 @@ string ABIFunctions::abiEncodingFunction( { // special case: convert storage reference type to value type - this is only // possible for library calls where we just forward the storage reference - solAssert(_encodeAsLibraryTypes, ""); + solAssert(_options.encodeAsLibraryTypes, ""); solAssert(to == IntegerType::uint256(), ""); templ("cleanupConvert", "value"); } @@ -574,7 +591,7 @@ string ABIFunctions::abiEncodingFunction( string ABIFunctions::abiEncodingFunctionCalldataArray( Type const& _from, Type const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { solAssert(_to.isDynamicallySized(), ""); @@ -596,7 +613,7 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { solUnimplementedAssert(fromArrayType.isByteArray(), "Only byte arrays can be encoded from calldata currently."); // TODO if this is not a byte array, we might just copy byte-by-byte anyway, @@ -622,7 +639,7 @@ string ABIFunctions::abiEncodingFunctionCalldataArray( string ABIFunctions::abiEncodingFunctionSimpleArray( ArrayType const& _from, ArrayType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -630,7 +647,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); @@ -691,11 +708,13 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( templ("storeLength", ""); templ("dataAreaFun", arrayDataAreaFunction(_from)); templ("elementEncodedSize", toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize())); + + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; templ("encodeToMemoryFun", abiEncodingFunction( *_from.baseType(), *_to.baseType(), - _encodeAsLibraryTypes, - false + subOptions )); templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" ); templ("nextArrayElement", nextArrayElementFunction(_from)); @@ -706,7 +725,7 @@ string ABIFunctions::abiEncodingFunctionSimpleArray( string ABIFunctions::abiEncodingFunctionMemoryByteArray( ArrayType const& _from, ArrayType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -714,7 +733,7 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); @@ -742,7 +761,7 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray( string ABIFunctions::abiEncodingFunctionCompactStorageArray( ArrayType const& _from, ArrayType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -750,7 +769,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); solAssert(_from.length() == _to.length(), ""); @@ -840,11 +859,13 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( templ("itemsPerSlot", to_string(itemsPerSlot)); string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize()); templ("elementEncodedSize", elementEncodedSize); + + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; string encodeToMemoryFun = abiEncodingFunction( *_from.baseType(), *_to.baseType(), - _encodeAsLibraryTypes, - false + subOptions ); templ("encodeToMemoryFun", encodeToMemoryFun); std::vector<std::map<std::string, std::string>> items(itemsPerSlot); @@ -859,7 +880,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray( string ABIFunctions::abiEncodingFunctionStruct( StructType const& _from, StructType const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { string functionName = @@ -867,7 +888,7 @@ string ABIFunctions::abiEncodingFunctionStruct( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported."); solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); @@ -904,7 +925,7 @@ string ABIFunctions::abiEncodingFunctionStruct( solAssert(member.type, ""); if (!member.type->canLiveOutsideStorage()) continue; - TypePointer memberTypeTo = member.type->fullEncodingType(_encodeAsLibraryTypes, true, false); + TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false); solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented."); auto memberTypeFrom = _from.memberType(member.name); solAssert(memberTypeFrom, ""); @@ -958,7 +979,10 @@ string ABIFunctions::abiEncodingFunctionStruct( } memberTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); encodingOffset += dynamicMember ? 0x20 : memberTypeTo->calldataEncodedSize(); - memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, _encodeAsLibraryTypes, false)); + + EncodingOptions subOptions(_options); + subOptions.encodeFunctionFromStack = false; + memberTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); members.push_back({}); members.back()["encode"] = memberTempl.render(); @@ -973,7 +997,7 @@ string ABIFunctions::abiEncodingFunctionStruct( string ABIFunctions::abiEncodingFunctionStringLiteral( Type const& _from, Type const& _to, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ) { solAssert(_from.category() == Type::Category::StringLiteral, ""); @@ -983,7 +1007,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( _from.identifier() + "_to_" + _to.identifier() + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); return createFunction(functionName, [&]() { auto const& strType = dynamic_cast<StringLiteralType const&>(_from); string const& value = strType.value(); @@ -1034,8 +1058,7 @@ string ABIFunctions::abiEncodingFunctionStringLiteral( string ABIFunctions::abiEncodingFunctionFunctionType( FunctionType const& _from, Type const& _to, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ) { solAssert(_from.kind() == FunctionType::Kind::External, ""); @@ -1046,10 +1069,9 @@ string ABIFunctions::abiEncodingFunctionFunctionType( _from.identifier() + "_to_" + _to.identifier() + - (_fromStack ? "_fromStack" : "") + - (_encodeAsLibraryTypes ? "_library" : ""); + _options.toFunctionNameSuffix(); - if (_fromStack) + if (_options.encodeFunctionFromStack) return createFunction(functionName, [&]() { return Whiskers(R"( function <functionName>(addr, function_id, pos) { @@ -1716,3 +1738,4 @@ size_t ABIFunctions::headSize(TypePointers const& _targetTypes) return headSize; } + diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h index 1e0cf7fa..c5443236 100644 --- a/libsolidity/codegen/ABIFunctions.h +++ b/libsolidity/codegen/ABIFunctions.h @@ -87,6 +87,23 @@ public: std::pair<std::string, std::set<std::string>> requestedFunctions(); private: + struct EncodingOptions + { + /// Pad/signextend value types and bytes/string to multiples of 32 bytes. + bool padded = true; + /// Store arrays and structs in place without "data pointer" and do not store the length. + bool dynamicInplace = false; + /// Only for external function types: The value is a pair of address / function id instead + /// of a memory pointer to the compression representation. + bool encodeFunctionFromStack = false; + /// Encode storage pointers as storage pointers (we are targeting a library call). + bool encodeAsLibraryTypes = false; + + /// @returns a string to uniquely identify the encoding options for the encoding + /// function name. Skips everything that has its default value. + std::string toFunctionNameSuffix() const; + }; + /// @returns the name of the cleanup function for the given type and /// adds its implementation to the requested functions. /// @param _revertOnFailure if true, causes revert on invalid data, @@ -115,40 +132,39 @@ private: std::string abiEncodingFunction( Type const& _givenType, Type const& _targetType, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given calldata array. std::string abiEncodingFunctionCalldataArray( Type const& _givenType, Type const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given memory array or /// a given storage array with one item per slot. std::string abiEncodingFunctionSimpleArray( ArrayType const& _givenType, ArrayType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); std::string abiEncodingFunctionMemoryByteArray( ArrayType const& _givenType, ArrayType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for array target type and given storage array /// where multiple items are packed into the same storage slot. std::string abiEncodingFunctionCompactStorageArray( ArrayType const& _givenType, ArrayType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); /// Part of @a abiEncodingFunction for struct types. std::string abiEncodingFunctionStruct( StructType const& _givenType, StructType const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); // @returns the name of the ABI encoding function with the given type @@ -157,14 +173,13 @@ private: std::string abiEncodingFunctionStringLiteral( Type const& _givenType, Type const& _targetType, - bool _encodeAsLibraryTypes + EncodingOptions const& _options ); std::string abiEncodingFunctionFunctionType( FunctionType const& _from, Type const& _to, - bool _encodeAsLibraryTypes, - bool _fromStack + EncodingOptions const& _options ); /// @returns the name of the ABI decoding function for the given type diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index a22e6e9d..72efed33 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -31,22 +31,28 @@ using namespace dev::solidity; void Compiler::compileContract( ContractDefinition const& _contract, - std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts, + std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers, bytes const& _metadata ) { ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns); - runtimeCompiler.compileContract(_contract, _contracts); + runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); // This might modify m_runtimeContext because it can access runtime functions at // creation time. ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1); - m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_context.optimise(m_optimize, m_optimizeRuns); } +std::shared_ptr<eth::Assembly> Compiler::runtimeAssemblyPtr() const +{ + solAssert(m_context.runtimeContext(), ""); + return m_context.runtimeContext()->assemblyPtr(); +} + eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const { return m_runtimeContext.functionEntryLabelIfExists(_function); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 784d7f8c..c21de96d 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -45,11 +45,15 @@ public: /// @arg _metadata contains the to be injected metadata CBOR void compileContract( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts, + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers, bytes const& _metadata ); /// @returns Entire assembly. eth::Assembly const& assembly() const { return m_context.assembly(); } + /// @returns Entire assembly as a shared pointer to non-const. + std::shared_ptr<eth::Assembly> assemblyPtr() const { return m_context.assemblyPtr(); } + /// @returns Runtime assembly. + std::shared_ptr<eth::Assembly> runtimeAssemblyPtr() const; /// @returns The entire assembled object (with constructor). eth::LinkerObject assembledObject() const { return m_context.assembledObject(); } /// @returns Only the runtime object (without constructor). diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index be681b2e..861b1c98 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -167,11 +167,18 @@ unsigned CompilerContext::numberOfLocalVariables() const return m_localVariables.size(); } -eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const +shared_ptr<eth::Assembly> CompilerContext::compiledContract(ContractDefinition const& _contract) const { - auto ret = m_compiledContracts.find(&_contract); - solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); - return *ret->second; + auto ret = m_otherCompilers.find(&_contract); + solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); + return ret->second->assemblyPtr(); +} + +shared_ptr<eth::Assembly> CompilerContext::compiledContractRuntime(ContractDefinition const& _contract) const +{ + auto ret = m_otherCompilers.find(&_contract); + solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); + return ret->second->runtimeAssemblyPtr(); } bool CompilerContext::isLocalVariable(Declaration const* _declaration) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index dedcd95f..e5ddfbc5 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -41,6 +41,7 @@ namespace dev { namespace solidity { +class Compiler; /** * Context to be shared by all units that compile the same contract. @@ -74,8 +75,9 @@ public: /// Returns the number of currently allocated local variables. unsigned numberOfLocalVariables() const; - void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } - eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; + void setOtherCompilers(std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers) { m_otherCompilers = _otherCompilers; } + std::shared_ptr<eth::Assembly> compiledContract(ContractDefinition const& _contract) const; + std::shared_ptr<eth::Assembly> compiledContractRuntime(ContractDefinition const& _contract) const; void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } @@ -222,15 +224,15 @@ public: void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. - CompilerContext* runtimeContext() { return m_runtimeContext; } + CompilerContext* runtimeContext() const { return m_runtimeContext; } /// @returns the identifier of the runtime subroutine. size_t runtimeSub() const { return m_runtimeSub; } /// @returns a const reference to the underlying assembly. eth::Assembly const& assembly() const { return *m_asm; } - /// @returns non-const reference to the underlying assembly. Should be avoided in favour of - /// wrappers in this class. - eth::Assembly& nonConstAssembly() { return *m_asm; } + /// @returns a shared pointer to the assembly. + /// Should be avoided except when adding sub-assemblies. + std::shared_ptr<eth::Assembly> assemblyPtr() const { return m_asm; } /// @arg _sourceCodes is the map of input files to source code strings std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const @@ -307,7 +309,7 @@ private: /// Activated experimental features. std::set<ExperimentalFeature> m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. - std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts; + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers; /// Storage offsets of state variables std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index bbc703c7..6cfb0777 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -1199,6 +1199,29 @@ void CompilerUtils::computeHashStatic() m_context << u256(32) << u256(0) << Instruction::KECCAK256; } +void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation) +{ + string which = _creation ? "Creation" : "Runtime"; + m_context.callLowLevelFunction( + "$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(), + 1, + 1, + [&contract, _creation](CompilerContext& _context) + { + // copy the contract's code into memory + shared_ptr<eth::Assembly> assembly = + _creation ? + _context.compiledContract(contract) : + _context.compiledContractRuntime(contract); + // pushes size + auto subroutine = _context.addSubroutine(assembly); + _context << Instruction::DUP1 << subroutine; + _context << Instruction::DUP4 << Instruction::CODECOPY; + _context << Instruction::ADD; + } + ); +} + void CompilerUtils::storeStringData(bytesConstRef _data) { //@todo provide both alternatives to the optimiser diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 7e4f47ba..6bde2e8b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -263,6 +263,13 @@ public: /// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type. void computeHashStatic(); + /// Apppends code that copies the code of the given contract to memory. + /// Stack pre: Memory position + /// Stack post: Updated memory position + /// @param creation if true, copies creation code, if false copies runtime code. + /// @note the contract has to be compiled already, so beware of cyclic dependencies! + void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode); + /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. static const unsigned dataStartOffset; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index b051d260..f843e07a 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -60,7 +60,7 @@ private: void ContractCompiler::compileContract( ContractDefinition const& _contract, - std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts + map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -70,7 +70,7 @@ void ContractCompiler::compileContract( // This has to be the first code in the contract. appendDelegatecallCheck(); - initializeContext(_contract, _contracts); + initializeContext(_contract, _otherCompilers); // This generates the dispatch function for externally visible functions // and adds the function to the compilation queue. Additionally internal functions, // which are referenced directly or indirectly will be added. @@ -81,7 +81,7 @@ void ContractCompiler::compileContract( size_t ContractCompiler::compileConstructor( ContractDefinition const& _contract, - std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts + std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -89,18 +89,18 @@ size_t ContractCompiler::compileConstructor( return deployLibrary(_contract); else { - initializeContext(_contract, _contracts); + initializeContext(_contract, _otherCompilers); return packIntoContractCreator(_contract); } } void ContractCompiler::initializeContext( ContractDefinition const& _contract, - map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts + map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers ) { m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); - m_context.setCompiledContracts(_compiledContracts); + m_context.setOtherCompilers(_otherCompilers); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); CompilerUtils(m_context).initialiseFreeMemoryPointer(); registerStateVariables(_contract); @@ -716,7 +716,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) CodeGenerator::assemble( _inlineAssembly.operations(), *_inlineAssembly.annotation().analysisInfo, - m_context.nonConstAssembly(), + *m_context.assemblyPtr(), identifierAccess ); m_context.setStackOffset(startStackHeight); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 40871f0d..9ab006f6 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -49,13 +49,13 @@ public: void compileContract( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers ); /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers ); private: @@ -63,7 +63,7 @@ private: /// information about the contract like the AST annotations. void initializeContext( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers ); /// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// with a new and initialized context. Adds the constructor code. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index be2709ae..e6bb163d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -594,22 +594,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } ContractDefinition const* contract = &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition(); - m_context.callLowLevelFunction( - "$copyContractCreationCodeToMemory_" + contract->type()->identifier(), - 0, - 1, - [contract](CompilerContext& _context) - { - // copy the contract's code into memory - eth::Assembly const& assembly = _context.compiledContract(*contract); - CompilerUtils(_context).fetchFreeMemoryPointer(); - // pushes size - auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly)); - _context << Instruction::DUP1 << subroutine; - _context << Instruction::DUP4 << Instruction::CODECOPY; - _context << Instruction::ADD; - } - ); + utils().fetchFreeMemoryPointer(); + utils().copyContractCodeToMemory(*contract, true); utils().abiEncode(argumentTypes, function.parameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment @@ -1107,6 +1093,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::GasLeft: m_context << Instruction::GAS; break; + case FunctionType::Kind::MetaType: + // No code to generate. + break; } } return false; @@ -1348,6 +1337,23 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "Gas has been removed."); else if (member == "blockhash") solAssert(false, "Blockhash has been removed."); + else if (member == "creationCode" || member == "runtimeCode") + { + TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition(); + utils().fetchFreeMemoryPointer(); + m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; + utils().copyContractCodeToMemory(contract, member == "creationCode"); + // Stack: start end + m_context.appendInlineAssembly( + Whiskers(R"({ + mstore(start, sub(end, add(start, 0x20))) + mstore(<free>, end) + })")("free", to_string(CompilerUtils::freeMemoryPointer)).render(), + {"start", "end"} + ); + m_context << Instruction::POP; + } else solAssert(false, "Unknown magic member."); break; diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 35c1e2f1..500b610f 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -386,7 +386,7 @@ void SMTChecker::endVisit(BinaryOperation const& _op) void SMTChecker::endVisit(FunctionCall const& _funCall) { solAssert(_funCall.annotation().kind != FunctionCallKind::Unset, ""); - if (_funCall.annotation().kind != FunctionCallKind::FunctionCall) + if (_funCall.annotation().kind == FunctionCallKind::StructConstructorCall) { m_errorReporter.warning( _funCall.location(), @@ -395,6 +395,12 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) return; } + if (_funCall.annotation().kind == FunctionCallKind::TypeConversion) + { + visitTypeConversion(_funCall); + return; + } + FunctionType const& funType = dynamic_cast<FunctionType const&>(*_funCall.expression().annotation().type); std::vector<ASTPointer<Expression const>> const args = _funCall.arguments(); @@ -412,6 +418,10 @@ void SMTChecker::endVisit(FunctionCall const& _funCall) case FunctionType::Kind::Internal: inlineFunctionCall(_funCall); break; + case FunctionType::Kind::External: + resetStateVariables(); + resetStorageReferences(); + break; case FunctionType::Kind::KECCAK256: case FunctionType::Kind::ECRecover: case FunctionType::Kind::SHA256: @@ -571,6 +581,43 @@ void SMTChecker::endVisit(Identifier const& _identifier) } } +void SMTChecker::visitTypeConversion(FunctionCall const& _funCall) +{ + solAssert(_funCall.annotation().kind == FunctionCallKind::TypeConversion, ""); + solAssert(_funCall.arguments().size() == 1, ""); + auto argument = _funCall.arguments().at(0); + unsigned argSize = argument->annotation().type->storageBytes(); + unsigned castSize = _funCall.annotation().type->storageBytes(); + if (argSize == castSize) + defineExpr(_funCall, expr(*argument)); + else + { + createExpr(_funCall); + setUnknownValue(*m_expressions.at(&_funCall)); + auto const& funCallCategory = _funCall.annotation().type->category(); + // TODO: truncating and bytesX needs a different approach because of right padding. + if (funCallCategory == Type::Category::Integer || funCallCategory == Type::Category::Address) + { + if (argSize < castSize) + defineExpr(_funCall, expr(*argument)); + else + { + auto const& intType = dynamic_cast<IntegerType const&>(*m_expressions.at(&_funCall)->type()); + defineExpr(_funCall, smt::Expression::ite( + expr(*argument) >= minValue(intType) && expr(*argument) <= maxValue(intType), + expr(*argument), + expr(_funCall) + )); + } + } + + m_errorReporter.warning( + _funCall.location(), + "Type conversion is not yet fully supported and might yield false positives." + ); + } +} + void SMTChecker::visitFunctionIdentifier(Identifier const& _identifier) { auto const& fType = dynamic_cast<FunctionType const&>(*_identifier.annotation().type); @@ -1151,25 +1198,35 @@ void SMTChecker::removeLocalVariables() } } +void SMTChecker::resetVariable(VariableDeclaration const& _variable) +{ + newValue(_variable); + setUnknownValue(_variable); +} + void SMTChecker::resetStateVariables() { - for (auto const& variable: m_variables) - { - if (variable.first->isStateVariable()) - { - newValue(*variable.first); - setUnknownValue(*variable.first); - } - } + resetVariables([&](VariableDeclaration const& _variable) { return _variable.isStateVariable(); }); +} + +void SMTChecker::resetStorageReferences() +{ + resetVariables([&](VariableDeclaration const& _variable) { return _variable.hasReferenceOrMappingType(); }); } void SMTChecker::resetVariables(vector<VariableDeclaration const*> _variables) { for (auto const* decl: _variables) + resetVariable(*decl); +} + +void SMTChecker::resetVariables(function<bool(VariableDeclaration const&)> const& _filter) +{ + for_each(begin(m_variables), end(m_variables), [&](auto _variable) { - newValue(*decl); - setUnknownValue(*decl); - } + if (_filter(*_variable.first)) + this->resetVariable(*_variable.first); + }); } void SMTChecker::mergeVariables(vector<VariableDeclaration const*> const& _variables, smt::Expression const& _condition, VariableIndices const& _indicesEndTrue, VariableIndices const& _indicesEndFalse) diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index f14d2ac0..a85933c8 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -86,6 +86,7 @@ private: void visitAssert(FunctionCall const& _funCall); void visitRequire(FunctionCall const& _funCall); void visitGasLeft(FunctionCall const& _funCall); + void visitTypeConversion(FunctionCall const& _funCall); /// Visits the FunctionDefinition of the called function /// if available and inlines the return value. void inlineFunctionCall(FunctionCall const& _funCall); @@ -146,8 +147,11 @@ private: void initializeLocalVariables(FunctionDefinition const& _function); void initializeFunctionCallParameters(FunctionDefinition const& _function, std::vector<smt::Expression> const& _callArgs); + void resetVariable(VariableDeclaration const& _variable); void resetStateVariables(); + void resetStorageReferences(); void resetVariables(std::vector<VariableDeclaration const*> _variables); + void resetVariables(std::function<bool(VariableDeclaration const&)> const& _filter); /// Given two different branches and the touched variables, /// merge the touched variables into after-branch ite variables /// using the branch condition as guard. diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index 69bceefc..6d30f5f7 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -134,7 +134,7 @@ void AssemblyStack::optimize(yul::Object& _object) for (auto& subNode: _object.subObjects) if (auto subObject = dynamic_cast<yul::Object*>(subNode.get())) optimize(*subObject); - yul::OptimiserSuite::run(*_object.code, *_object.analysisInfo); + yul::OptimiserSuite::run(*languageToDialect(m_language), *_object.code, *_object.analysisInfo); } MachineAssemblyObject AssemblyStack::assemble(Machine _machine, bool _optimize) const diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index f9d889e7..9e4da62d 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -343,12 +343,12 @@ bool CompilerStack::compile() return false; // Only compile contracts individually which have been requested. - map<ContractDefinition const*, eth::Assembly const*> compiledContracts; + map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers; for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) if (isRequestedContract(*contract)) - compileContract(*contract, compiledContracts); + compileContract(*contract, otherCompilers); m_stackState = CompilationSuccessful; this->link(); return true; @@ -795,19 +795,15 @@ bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& featu void CompilerStack::compileContract( ContractDefinition const& _contract, - map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts + map<ContractDefinition const*, shared_ptr<Compiler const>>& _otherCompilers ) { solAssert(m_stackState >= AnalysisSuccessful, ""); - if ( - _compiledContracts.count(&_contract) || - !_contract.annotation().unimplementedFunctions.empty() || - !_contract.constructorIsPublic() - ) + if (_otherCompilers.count(&_contract) || !_contract.canBeDeployed()) return; for (auto const* dependency: _contract.annotation().contractDependencies) - compileContract(*dependency, _compiledContracts); + compileContract(*dependency, _otherCompilers); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); @@ -825,7 +821,7 @@ void CompilerStack::compileContract( try { // Run optimiser and compile the contract. - compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); + compiler->compileContract(_contract, _otherCompilers, cborEncodedMetadata); } catch(eth::OptimizerException const&) { @@ -852,7 +848,7 @@ void CompilerStack::compileContract( solAssert(false, "Assembly exception for deployed bytecode"); } - _compiledContracts[compiledContract.contract] = &compiler->assembly(); + _otherCompilers[compiledContract.contract] = compiler; } CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 81d5009f..79927850 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -293,10 +293,12 @@ private: /// @returns true if the contract is requested to be compiled. bool isRequestedContract(ContractDefinition const& _contract) const; - /// Compile a single contract and put the result in @a _compiledContracts. + /// Compile a single contract. + /// @param _otherCompilers provides access to compilers of other contracts, to get + /// their bytecode if needed. Only filled after they have been compiled. void compileContract( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>>& _otherCompilers ); /// Links all the known library addresses in the available objects. Any unknown diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 8a6bc343..35476a76 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1551,6 +1551,12 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() nodeFactory.markEndPosition(); expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance()); break; + case Token::Type: + // Inside expressions "type" is the name of a special, globally-available function. + nodeFactory.markEndPosition(); + m_scanner->next(); + expression = nodeFactory.createNode<Identifier>(make_shared<ASTString>("type")); + break; case Token::LParen: case Token::LBrack: { diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index 0ecc5a30..a5552c51 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -24,6 +24,7 @@ #include <libyul/AsmScopeFiller.h> #include <libyul/AsmScope.h> #include <libyul/AsmAnalysisInfo.h> +#include <libyul/Utilities.h> #include <liblangutil/ErrorReporter.h> @@ -299,11 +300,14 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) bool success = true; size_t parameters = 0; size_t returns = 0; + bool needsLiteralArguments = false; if (BuiltinFunction const* f = m_dialect->builtin(_funCall.functionName.name)) { // TODO: compare types, too parameters = f->parameters.size(); returns = f->returns.size(); + if (f->literalArguments) + needsLiteralArguments = true; } else if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor( [&](Scope::Variable const&) @@ -347,8 +351,15 @@ bool AsmAnalyzer::operator()(FunctionCall const& _funCall) } for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) + { if (!expectExpression(arg)) success = false; + else if (needsLiteralArguments && arg.type() != typeid(Literal)) + m_errorReporter.typeError( + _funCall.functionName.location, + "Function expects direct literals as arguments." + ); + } // Use argument size instead of parameter count to avoid misleading errors. m_stackHeight += int(returns) - int(_funCall.arguments.size()); m_info.stackHeightInfo[&_funCall] = m_stackHeight; @@ -380,7 +391,29 @@ bool AsmAnalyzer::operator()(Switch const& _switch) if (!expectExpression(*_switch.expression)) success = false; - set<tuple<LiteralKind, YulString>> cases; + if (m_dialect->flavour == AsmFlavour::Yul) + { + YulString caseType; + bool mismatchingTypes = false; + for (auto const& _case: _switch.cases) + if (_case.value) + { + if (caseType.empty()) + caseType = _case.value->type; + else if (caseType != _case.value->type) + { + mismatchingTypes = true; + break; + } + } + if (mismatchingTypes) + m_errorReporter.typeError( + _switch.location, + "Switch cases have non-matching types." + ); + } + + set<Literal const*, Less<Literal*>> cases; for (auto const& _case: _switch.cases) { if (_case.value) @@ -394,12 +427,11 @@ bool AsmAnalyzer::operator()(Switch const& _switch) m_stackHeight--; /// Note: the parser ensures there is only one default case - auto val = make_tuple(_case.value->kind, _case.value->value); - if (!cases.insert(val).second) + if (!cases.insert(_case.value.get()).second) { m_errorReporter.declarationError( _case.location, - "Duplicate case defined" + "Duplicate case defined." ); success = false; } diff --git a/libyul/AsmData.h b/libyul/AsmData.h index 86c373a4..fd8eab38 100644 --- a/libyul/AsmData.h +++ b/libyul/AsmData.h @@ -59,25 +59,25 @@ struct StackAssignment { langutil::SourceLocation location; Identifier variableN /// 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 { langutil::SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Expression> value; }; +struct Assignment { langutil::SourceLocation location; std::vector<Identifier> variableNames; std::unique_ptr<Expression> value; }; /// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))" struct FunctionalInstruction { langutil::SourceLocation location; dev::solidity::Instruction instruction; std::vector<Expression> arguments; }; struct FunctionCall { langutil::SourceLocation location; Identifier functionName; std::vector<Expression> arguments; }; /// Statement that contains only a single expression struct ExpressionStatement { langutil::SourceLocation location; Expression expression; }; /// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted -struct VariableDeclaration { langutil::SourceLocation location; TypedNameList variables; std::shared_ptr<Expression> value; }; +struct VariableDeclaration { langutil::SourceLocation location; TypedNameList variables; std::unique_ptr<Expression> value; }; /// Block that creates a scope (frees declared stack variables) struct Block { langutil::SourceLocation location; std::vector<Statement> statements; }; /// Function definition ("function f(a, b) -> (d, e) { ... }") struct FunctionDefinition { langutil::SourceLocation location; YulString name; TypedNameList parameters; TypedNameList returnVariables; Block body; }; /// Conditional execution without "else" part. -struct If { langutil::SourceLocation location; std::shared_ptr<Expression> condition; Block body; }; +struct If { langutil::SourceLocation location; std::unique_ptr<Expression> condition; Block body; }; /// Switch case or default case -struct Case { langutil::SourceLocation location; std::shared_ptr<Literal> value; Block body; }; +struct Case { langutil::SourceLocation location; std::unique_ptr<Literal> value; Block body; }; /// Switch statement -struct Switch { langutil::SourceLocation location; std::shared_ptr<Expression> expression; std::vector<Case> cases; }; -struct ForLoop { langutil::SourceLocation location; Block pre; std::shared_ptr<Expression> condition; Block post; Block body; }; +struct Switch { langutil::SourceLocation location; std::unique_ptr<Expression> expression; std::vector<Case> cases; }; +struct ForLoop { langutil::SourceLocation location; Block pre; std::unique_ptr<Expression> condition; Block post; Block body; }; struct LocationExtractor: boost::static_visitor<langutil::SourceLocation> { diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp index f3ca6cd0..217c838a 100644 --- a/libyul/AsmParser.cpp +++ b/libyul/AsmParser.cpp @@ -81,15 +81,15 @@ Statement Parser::parseStatement() { If _if = createWithLocation<If>(); m_scanner->next(); - _if.condition = make_shared<Expression>(parseExpression()); + _if.condition = make_unique<Expression>(parseExpression()); _if.body = parseBlock(); - return _if; + return Statement{move(_if)}; } case Token::Switch: { Switch _switch = createWithLocation<Switch>(); m_scanner->next(); - _switch.expression = make_shared<Expression>(parseExpression()); + _switch.expression = make_unique<Expression>(parseExpression()); while (m_scanner->currentToken() == Token::Case) _switch.cases.emplace_back(parseCase()); if (m_scanner->currentToken() == Token::Default) @@ -101,7 +101,7 @@ Statement Parser::parseStatement() if (_switch.cases.empty()) fatalParserError("Switch statement without any cases."); _switch.location.end = _switch.cases.back().body.location.end; - return _switch; + return Statement{move(_switch)}; } case Token::For: return parseForLoop(); @@ -120,7 +120,7 @@ Statement Parser::parseStatement() fatalParserError("Identifier expected, got instruction name."); assignment.location.end = endPosition(); expectToken(Token::Identifier); - return assignment; + return Statement{move(assignment)}; } default: break; @@ -163,7 +163,7 @@ Statement Parser::parseStatement() assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; - return assignment; + return Statement{std::move(assignment)}; } case Token::Colon: { @@ -184,7 +184,7 @@ Statement Parser::parseStatement() assignment.variableNames.emplace_back(identifier); assignment.value.reset(new Expression(parseExpression())); assignment.location.end = locationOf(*assignment.value).end; - return assignment; + return Statement{std::move(assignment)}; } else { @@ -230,7 +230,7 @@ Case Parser::parseCase() ElementaryOperation literal = parseElementaryOperation(); if (literal.type() != typeid(Literal)) fatalParserError("Literal expected."); - _case.value = make_shared<Literal>(boost::get<Literal>(std::move(literal))); + _case.value = make_unique<Literal>(boost::get<Literal>(std::move(literal))); } else fatalParserError("Case or default case expected."); @@ -245,7 +245,7 @@ ForLoop Parser::parseForLoop() ForLoop forLoop = createWithLocation<ForLoop>(); expectToken(Token::For); forLoop.pre = parseBlock(); - forLoop.condition = make_shared<Expression>(parseExpression()); + forLoop.condition = make_unique<Expression>(parseExpression()); forLoop.post = parseBlock(); forLoop.body = parseBlock(); forLoop.location.end = forLoop.body.location.end; @@ -443,7 +443,7 @@ VariableDeclaration Parser::parseVariableDeclaration() { expectToken(Token::Colon); expectToken(Token::Assign); - varDecl.value.reset(new Expression(parseExpression())); + varDecl.value = make_unique<Expression>(parseExpression()); varDecl.location.end = locationOf(*varDecl.value).end; } else diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 52c4ac8e..259f43f8 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -20,6 +20,8 @@ add_library(yul Object.h ObjectParser.cpp ObjectParser.h + Utilities.cpp + Utilities.h YulString.h backends/evm/AbstractAssembly.h backends/evm/EVMAssembly.cpp @@ -42,6 +44,10 @@ add_library(yul optimiser/DataFlowAnalyzer.h optimiser/Disambiguator.cpp optimiser/Disambiguator.h + optimiser/EquivalentFunctionDetector.cpp + optimiser/EquivalentFunctionDetector.h + optimiser/EquivalentFunctionCombiner.cpp + optimiser/EquivalentFunctionCombiner.h optimiser/ExpressionInliner.cpp optimiser/ExpressionInliner.h optimiser/ExpressionJoiner.cpp @@ -68,10 +74,14 @@ add_library(yul optimiser/NameCollector.h optimiser/NameDispenser.cpp optimiser/NameDispenser.h + optimiser/OptimizerUtilities.cpp + optimiser/OptimizerUtilities.h optimiser/RedundantAssignEliminator.cpp optimiser/RedundantAssignEliminator.h optimiser/Rematerialiser.cpp optimiser/Rematerialiser.h + optimiser/SSAReverser.cpp + optimiser/SSAReverser.h optimiser/SSATransform.cpp optimiser/SSATransform.h optimiser/SSAValueTracker.cpp @@ -90,8 +100,6 @@ add_library(yul optimiser/SyntacticalEquality.h optimiser/UnusedPruner.cpp optimiser/UnusedPruner.h - optimiser/Utilities.cpp - optimiser/Utilities.h optimiser/VarDeclInitializer.cpp optimiser/VarDeclInitializer.h ) diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 01fd98df..e06a6826 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -44,7 +44,13 @@ struct BuiltinFunction YulString name; std::vector<Type> parameters; std::vector<Type> returns; - bool movable; + /// If true, calls to this function can be freely moved and copied (as long as their + /// arguments are either variables or also movable) without altering the semantics. + /// This means the function cannot depend on storage or memory, cannot have any side-effects, + /// but it can depend on state that is constant across an EVM-call. + bool movable = false; + /// If true, can only accept literals as arguments and they cannot be moved to voriables. + bool literalArguments = false; }; struct Dialect: boost::noncopyable diff --git a/libyul/Utilities.cpp b/libyul/Utilities.cpp new file mode 100644 index 00000000..e5f4e517 --- /dev/null +++ b/libyul/Utilities.cpp @@ -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/>. +*/ +/** + * Some useful snippets for the optimiser. + */ + +#include <libyul/Utilities.h> + +#include <libyul/AsmData.h> +#include <libyul/Exceptions.h> + +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace yul; + +u256 yul::valueOfNumberLiteral(Literal const& _literal) +{ + assertThrow(_literal.kind == LiteralKind::Number, OptimizerException, ""); + std::string const& literalString = _literal.value.str(); + assertThrow(isValidDecimal(literalString) || isValidHex(literalString), OptimizerException, ""); + return u256(literalString); +} + +template<> +bool Less<Literal>::operator()(Literal const& _lhs, Literal const& _rhs) const +{ + if (std::make_tuple(_lhs.kind, _lhs.type) != std::make_tuple(_rhs.kind, _rhs.type)) + return std::make_tuple(_lhs.kind, _lhs.type) < std::make_tuple(_rhs.kind, _rhs.type); + + if (_lhs.kind == LiteralKind::Number) + return valueOfNumberLiteral(_lhs) < valueOfNumberLiteral(_rhs); + else + return _lhs.value < _rhs.value; +} diff --git a/libyul/Utilities.h b/libyul/Utilities.h new file mode 100644 index 00000000..288bc6ee --- /dev/null +++ b/libyul/Utilities.h @@ -0,0 +1,59 @@ +/* + 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/>. +*/ +/** + * Small useful snippets for the optimiser. + */ + +#pragma once + +#include <libdevcore/Common.h> +#include <libyul/AsmDataForward.h> + +namespace yul +{ + +dev::u256 valueOfNumberLiteral(Literal const& _literal); + +/** + * Linear order on Yul AST nodes. + * + * Defines a linear order on Yul AST nodes to be used in maps and sets. + * Note: the order is total and deterministic, but independent of the semantics, e.g. + * it is not guaranteed that the false Literal is "less" than the true Literal. + */ +template<typename T> +struct Less +{ + bool operator()(T const& _lhs, T const& _rhs) const; +}; + +template<typename T> +struct Less<T*> +{ + bool operator()(T const* _lhs, T const* _rhs) const + { + if (_lhs && _rhs) + return Less<T>{}(*_lhs, *_rhs); + else + return _lhs < _rhs; + } +}; + +template<> bool Less<Literal>::operator()(Literal const& _lhs, Literal const& _rhs) const; +extern template struct Less<Literal>; + +} diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 935f05c6..18502117 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -44,7 +44,7 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess): if (!m_objectAccess) return; - addFunction("datasize", 1, 1, true, [this]( + addFunction("datasize", 1, 1, true, true, [this]( FunctionCall const& _call, AbstractAssembly& _assembly, std::function<void()> @@ -58,7 +58,7 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess): else _assembly.appendDataSize(m_subIDs.at(dataName)); }); - addFunction("dataoffset", 1, 1, true, [this]( + addFunction("dataoffset", 1, 1, true, true, [this]( FunctionCall const& _call, AbstractAssembly& _assembly, std::function<void()> @@ -72,7 +72,7 @@ EVMDialect::EVMDialect(AsmFlavour _flavour, bool _objectAccess): else _assembly.appendDataOffset(m_subIDs.at(dataName)); }); - addFunction("datacopy", 3, 0, false, []( + addFunction("datacopy", 3, 0, false, false, []( FunctionCall const&, AbstractAssembly& _assembly, std::function<void()> _visitArguments @@ -128,6 +128,7 @@ void EVMDialect::addFunction( size_t _params, size_t _returns, bool _movable, + bool _literalArguments, std::function<void(FunctionCall const&, AbstractAssembly&, std::function<void()>)> _generateCode ) { @@ -137,5 +138,6 @@ void EVMDialect::addFunction( f.parameters.resize(_params); f.returns.resize(_returns); f.movable = _movable; + f.literalArguments = _literalArguments; f.generateCode = std::move(_generateCode); } diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index feb00b03..c23833bc 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -72,6 +72,7 @@ private: size_t _params, size_t _returns, bool _movable, + bool _literalArguments, std::function<void(FunctionCall const&, AbstractAssembly&, std::function<void()>)> _generateCode ); diff --git a/libyul/optimiser/ASTCopier.h b/libyul/optimiser/ASTCopier.h index 4d2f18ae..7e5e9c86 100644 --- a/libyul/optimiser/ASTCopier.h +++ b/libyul/optimiser/ASTCopier.h @@ -93,10 +93,11 @@ protected: std::vector<T> translateVector(std::vector<T> const& _values); template <typename T> - std::shared_ptr<T> translate(std::shared_ptr<T> const& _v) + std::unique_ptr<T> translate(std::unique_ptr<T> const& _v) { - return _v ? std::make_shared<T>(translate(*_v)) : nullptr; + return _v ? std::make_unique<T>(translate(*_v)) : nullptr; } + Block translate(Block const& _block); Case translate(Case const& _case); Identifier translate(Identifier const& _identifier); diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index 9b851333..5f182eba 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -25,6 +25,7 @@ #include <libyul/optimiser/SyntacticalEquality.h> #include <libyul/Exceptions.h> #include <libyul/AsmData.h> +#include <libyul/Dialect.h> using namespace std; using namespace dev; @@ -32,12 +33,24 @@ using namespace yul; void CommonSubexpressionEliminator::visit(Expression& _e) { + bool descend = true; + // If this is a function call to a function that requires literal arguments, + // do not try to simplify there. + if (_e.type() == typeid(FunctionCall)) + if (BuiltinFunction const* builtin = m_dialect.builtin(boost::get<FunctionCall>(_e).functionName.name)) + if (builtin->literalArguments) + // We should not modify function arguments that have to be literals + // Note that replacing the function call entirely is fine, + // if the function call is movable. + descend = false; + // We visit the inner expression first to first simplify inner expressions, // which hopefully allows more matches. // Note that the DataFlowAnalyzer itself only has code for visiting Statements, // so this basically invokes the AST walker directly and thus post-visiting // is also fine with regards to data flow analysis. - DataFlowAnalyzer::visit(_e); + if (descend) + DataFlowAnalyzer::visit(_e); if (_e.type() == typeid(Identifier)) { @@ -61,7 +74,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e) { assertThrow(var.second, OptimizerException, ""); assertThrow(inScope(var.first), OptimizerException, ""); - if (SyntacticalEqualityChecker::equal(_e, *var.second)) + if (SyntacticallyEqual{}(_e, *var.second)) { _e = Identifier{locationOf(_e), var.first}; break; diff --git a/libyul/optimiser/CommonSubexpressionEliminator.h b/libyul/optimiser/CommonSubexpressionEliminator.h index ac1ebe3a..9f416d9f 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.h +++ b/libyul/optimiser/CommonSubexpressionEliminator.h @@ -26,6 +26,8 @@ namespace yul { +struct Dialect; + /** * Optimisation stage that replaces expressions known to be the current value of a variable * in scope by a reference to that variable. @@ -34,6 +36,9 @@ namespace yul */ class CommonSubexpressionEliminator: public DataFlowAnalyzer { +public: + CommonSubexpressionEliminator(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} + protected: using ASTModifier::visit; void visit(Expression& _e) override; diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index c8d236dc..b4f2d1f9 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -51,8 +51,10 @@ void DataFlowAnalyzer::operator()(VariableDeclaration& _varDecl) for (auto const& var: _varDecl.variables) names.emplace(var.name); m_variableScopes.back().variables += names; + if (_varDecl.value) visit(*_varDecl.value); + handleAssignment(names, _varDecl.value.get()); } @@ -142,7 +144,7 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres static Expression const zero{Literal{{}, LiteralKind::Number, YulString{"0"}, {}}}; clearValues(_variables); - MovableChecker movableChecker; + MovableChecker movableChecker{m_dialect}; if (_value) movableChecker.visit(*_value); else diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index 4f12ff6a..5fb5db95 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -30,6 +30,7 @@ namespace yul { +struct Dialect; /** * Base class to perform data flow analysis during AST walks. @@ -43,6 +44,8 @@ namespace yul class DataFlowAnalyzer: public ASTModifier { public: + explicit DataFlowAnalyzer(Dialect const& _dialect): m_dialect(_dialect) {} + using ASTModifier::operator(); void operator()(Assignment& _assignment) override; void operator()(VariableDeclaration& _varDecl) override; @@ -84,6 +87,7 @@ protected: }; /// List of scopes. std::vector<Scope> m_variableScopes; + Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/Disambiguator.cpp b/libyul/optimiser/Disambiguator.cpp index fda5895b..cb56ee99 100644 --- a/libyul/optimiser/Disambiguator.cpp +++ b/libyul/optimiser/Disambiguator.cpp @@ -23,6 +23,7 @@ #include <libyul/Exceptions.h> #include <libyul/AsmData.h> #include <libyul/AsmScope.h> +#include <libyul/Dialect.h> using namespace std; using namespace dev; @@ -31,7 +32,7 @@ using namespace dev::solidity; YulString Disambiguator::translateIdentifier(YulString _originalName) { - if ((m_externallyUsedIdentifiers.count(_originalName))) + if (m_dialect.builtin(_originalName) || m_externallyUsedIdentifiers.count(_originalName)) return _originalName; assertThrow(!m_scopes.empty() && m_scopes.back(), OptimizerException, ""); diff --git a/libyul/optimiser/Disambiguator.h b/libyul/optimiser/Disambiguator.h index bb83417b..ec6a0879 100644 --- a/libyul/optimiser/Disambiguator.h +++ b/libyul/optimiser/Disambiguator.h @@ -32,6 +32,7 @@ namespace yul { +struct Dialect; /** * Creates a copy of a Yul AST replacing all identifiers by unique names. @@ -40,10 +41,14 @@ class Disambiguator: public ASTCopier { public: explicit Disambiguator( + Dialect const& _dialect, AsmAnalysisInfo const& _analysisInfo, std::set<YulString> const& _externallyUsedIdentifiers = {} ): - m_info(_analysisInfo), m_externallyUsedIdentifiers(_externallyUsedIdentifiers), m_nameDispenser(m_externallyUsedIdentifiers) + m_info(_analysisInfo), + m_dialect(_dialect), + m_externallyUsedIdentifiers(_externallyUsedIdentifiers), + m_nameDispenser(_dialect, m_externallyUsedIdentifiers) { } @@ -58,6 +63,7 @@ protected: void leaveScopeInternal(Scope& _scope); AsmAnalysisInfo const& m_info; + Dialect const& m_dialect; std::set<YulString> const& m_externallyUsedIdentifiers; std::vector<Scope*> m_scopes; diff --git a/libyul/optimiser/EquivalentFunctionCombiner.cpp b/libyul/optimiser/EquivalentFunctionCombiner.cpp new file mode 100644 index 00000000..939e63d2 --- /dev/null +++ b/libyul/optimiser/EquivalentFunctionCombiner.cpp @@ -0,0 +1,41 @@ +/* + 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/>. +*/ +/** + * Optimiser component that combines syntactically equivalent functions. + */ + +#include <libyul/optimiser/EquivalentFunctionCombiner.h> +#include <libyul/AsmData.h> +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace yul; +using namespace dev::solidity; + +void EquivalentFunctionCombiner::run(Block& _ast) +{ + EquivalentFunctionCombiner{EquivalentFunctionDetector::run(_ast)}(_ast); +} + +void EquivalentFunctionCombiner::operator()(FunctionCall& _funCall) +{ + auto it = m_duplicates.find(_funCall.functionName.name); + if (it != m_duplicates.end()) + _funCall.functionName.name = it->second->name; + ASTModifier::operator()(_funCall); +} diff --git a/libyul/optimiser/EquivalentFunctionCombiner.h b/libyul/optimiser/EquivalentFunctionCombiner.h new file mode 100644 index 00000000..0c766ded --- /dev/null +++ b/libyul/optimiser/EquivalentFunctionCombiner.h @@ -0,0 +1,49 @@ +/* + 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/>. +*/ +/** + * Optimiser component that combines syntactically equivalent functions. + */ +#pragma once + +#include <libyul/optimiser/ASTWalker.h> +#include <libyul/optimiser/EquivalentFunctionDetector.h> +#include <libyul/AsmDataForward.h> + +namespace yul +{ + +/** + * Optimiser component that detects syntactically equivalent functions and replaces all calls to any of them by calls + * to one particular of them. + * + * Prerequisite: Disambiguator, Function Hoister + */ +class EquivalentFunctionCombiner: public ASTModifier +{ +public: + static void run(Block& _ast); + + using ASTModifier::operator(); + void operator()(FunctionCall& _funCall) override; + +private: + EquivalentFunctionCombiner(std::map<YulString, FunctionDefinition const*> _duplicates): m_duplicates(std::move(_duplicates)) {} + std::map<YulString, FunctionDefinition const*> m_duplicates; +}; + + +} diff --git a/libyul/optimiser/EquivalentFunctionDetector.cpp b/libyul/optimiser/EquivalentFunctionDetector.cpp new file mode 100644 index 00000000..d3a697bd --- /dev/null +++ b/libyul/optimiser/EquivalentFunctionDetector.cpp @@ -0,0 +1,63 @@ +/* + 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/>. +*/ +/** + * Optimiser component that combines syntactically equivalent functions. + */ + +#include <libyul/optimiser/EquivalentFunctionDetector.h> +#include <libyul/optimiser/SyntacticalEquality.h> + +#include <libyul/AsmData.h> +#include <libyul/optimiser/Metrics.h> + +using namespace std; +using namespace dev; +using namespace yul; +using namespace solidity; + +void EquivalentFunctionDetector::operator()(FunctionDefinition const& _fun) +{ + RoughHeuristic heuristic(_fun); + auto& candidates = m_candidates[heuristic]; + for (auto const& candidate: candidates) + if (SyntacticallyEqual{}.statementEqual(_fun, *candidate)) + { + m_duplicates[_fun.name] = candidate; + return; + } + candidates.push_back(&_fun); +} + +bool EquivalentFunctionDetector::RoughHeuristic::operator<(EquivalentFunctionDetector::RoughHeuristic const& _rhs) const +{ + if ( + std::make_tuple(m_fun.parameters.size(), m_fun.returnVariables.size()) == + std::make_tuple(_rhs.m_fun.parameters.size(), _rhs.m_fun.returnVariables.size()) + ) + return codeSize() < _rhs.codeSize(); + else + return + std::make_tuple(m_fun.parameters.size(), m_fun.returnVariables.size()) < + std::make_tuple(_rhs.m_fun.parameters.size(), _rhs.m_fun.returnVariables.size()); +} + +size_t EquivalentFunctionDetector::RoughHeuristic::codeSize() const +{ + if (!m_codeSize) + m_codeSize = CodeSize::codeSize(m_fun.body); + return *m_codeSize; +} diff --git a/libyul/optimiser/EquivalentFunctionDetector.h b/libyul/optimiser/EquivalentFunctionDetector.h new file mode 100644 index 00000000..329fd385 --- /dev/null +++ b/libyul/optimiser/EquivalentFunctionDetector.h @@ -0,0 +1,71 @@ +/* + 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/>. +*/ +/** + * Optimiser component that combines syntactically equivalent functions. + */ +#pragma once + +#include <libyul/optimiser/ASTWalker.h> +#include <libyul/AsmDataForward.h> + +namespace yul +{ + +/** + * Optimiser component that detects syntactically equivalent functions. + * + * Prerequisite: Disambiguator + */ +class EquivalentFunctionDetector: public ASTWalker +{ +public: + static std::map<YulString, FunctionDefinition const*> run(Block& _block) + { + EquivalentFunctionDetector detector{}; + detector(_block); + return std::move(detector.m_duplicates); + } + + using ASTWalker::operator(); + void operator()(FunctionDefinition const& _fun) override; + +private: + EquivalentFunctionDetector() = default; + /** + * Fast heuristic to detect distinct, resp. potentially equal functions. + * + * Defines a partial order on function definitions. If two functions + * are comparable (one is "less" than the other), they are distinct. + * If not (neither is "less" than the other), they are *potentially* equal. + */ + class RoughHeuristic + { + public: + RoughHeuristic(FunctionDefinition const& _fun): m_fun(_fun) {} + bool operator<(RoughHeuristic const& _rhs) const; + private: + std::size_t codeSize() const; + FunctionDefinition const& m_fun; + mutable boost::optional<std::size_t> m_codeSize; + // In case the heuristic doesn't turn out to be good enough, we might want to define a hash function for code blocks. + }; + std::map<RoughHeuristic, std::vector<FunctionDefinition const*>> m_candidates; + std::map<YulString, FunctionDefinition const*> m_duplicates; +}; + + +} diff --git a/libyul/optimiser/ExpressionInliner.cpp b/libyul/optimiser/ExpressionInliner.cpp index 27d43ac0..858c8733 100644 --- a/libyul/optimiser/ExpressionInliner.cpp +++ b/libyul/optimiser/ExpressionInliner.cpp @@ -56,7 +56,7 @@ void ExpressionInliner::visit(Expression& _expression) bool movable = boost::algorithm::all_of( funCall.arguments, - [=](Expression const& _arg) { return MovableChecker(_arg).movable(); } + [=](Expression const& _arg) { return MovableChecker(m_dialect, _arg).movable(); } ); if (m_inlinableFunctions.count(funCall.functionName.name) && movable) { diff --git a/libyul/optimiser/ExpressionInliner.h b/libyul/optimiser/ExpressionInliner.h index 14e80c0a..e6e710f8 100644 --- a/libyul/optimiser/ExpressionInliner.h +++ b/libyul/optimiser/ExpressionInliner.h @@ -29,6 +29,7 @@ namespace yul { +struct Dialect; /** * Optimiser component that modifies an AST in place, inlining functions that can be @@ -44,8 +45,8 @@ namespace yul class ExpressionInliner: public ASTModifier { public: - ExpressionInliner(Block& _block): - m_block(_block) + ExpressionInliner(Dialect const& _dialect, Block& _block): + m_block(_block), m_dialect(_dialect) {} void run(); @@ -62,6 +63,7 @@ private: std::set<YulString> m_currentFunctions; Block& m_block; + Dialect const& m_dialect; }; diff --git a/libyul/optimiser/ExpressionJoiner.cpp b/libyul/optimiser/ExpressionJoiner.cpp index de2b5d53..02ac4e45 100644 --- a/libyul/optimiser/ExpressionJoiner.cpp +++ b/libyul/optimiser/ExpressionJoiner.cpp @@ -22,7 +22,7 @@ #include <libyul/optimiser/ExpressionJoiner.h> #include <libyul/optimiser/NameCollector.h> -#include <libyul/optimiser/Utilities.h> +#include <libyul/optimiser/OptimizerUtilities.h> #include <libyul/Exceptions.h> #include <libyul/AsmData.h> diff --git a/libyul/optimiser/ExpressionSimplifier.cpp b/libyul/optimiser/ExpressionSimplifier.cpp index cda44e8e..b26e4245 100644 --- a/libyul/optimiser/ExpressionSimplifier.cpp +++ b/libyul/optimiser/ExpressionSimplifier.cpp @@ -36,7 +36,7 @@ using namespace dev::solidity; void ExpressionSimplifier::visit(Expression& _expression) { ASTModifier::visit(_expression); - while (auto match = SimplificationRules::findFirstMatch(_expression, m_ssaValues)) + while (auto match = SimplificationRules::findFirstMatch(_expression, m_dialect, m_ssaValues)) { // Do not apply the rule if it removes non-constant parts of the expression. // TODO: The check could actually be less strict than "movable". @@ -45,15 +45,15 @@ void ExpressionSimplifier::visit(Expression& _expression) // so if the value of the variable is not movable, the expression that references // the variable still is. - if (match->removesNonConstants && !MovableChecker(_expression).movable()) + if (match->removesNonConstants && !MovableChecker(m_dialect, _expression).movable()) return; _expression = match->action().toExpression(locationOf(_expression)); } } -void ExpressionSimplifier::run(Block& _ast) +void ExpressionSimplifier::run(Dialect const& _dialect, Block& _ast) { SSAValueTracker ssaValues; ssaValues(_ast); - ExpressionSimplifier{ssaValues.values()}(_ast); + ExpressionSimplifier{_dialect, ssaValues.values()}(_ast); } diff --git a/libyul/optimiser/ExpressionSimplifier.h b/libyul/optimiser/ExpressionSimplifier.h index fe3507f8..b1122e91 100644 --- a/libyul/optimiser/ExpressionSimplifier.h +++ b/libyul/optimiser/ExpressionSimplifier.h @@ -26,6 +26,7 @@ namespace yul { +struct Dialect; /** * Applies simplification rules to all expressions. @@ -40,12 +41,13 @@ public: using ASTModifier::operator(); virtual void visit(Expression& _expression); - static void run(Block& _ast); + static void run(Dialect const& _dialect, Block& _ast); private: - explicit ExpressionSimplifier(std::map<YulString, Expression const*> _ssaValues): - m_ssaValues(std::move(_ssaValues)) + explicit ExpressionSimplifier(Dialect const& _dialect, std::map<YulString, Expression const*> _ssaValues): + m_dialect(_dialect), m_ssaValues(std::move(_ssaValues)) {} + Dialect const& m_dialect; std::map<YulString, Expression const*> m_ssaValues; }; diff --git a/libyul/optimiser/ExpressionSplitter.cpp b/libyul/optimiser/ExpressionSplitter.cpp index a3b2dc11..2f80fc32 100644 --- a/libyul/optimiser/ExpressionSplitter.cpp +++ b/libyul/optimiser/ExpressionSplitter.cpp @@ -24,6 +24,7 @@ #include <libyul/optimiser/ASTWalker.h> #include <libyul/AsmData.h> +#include <libyul/Dialect.h> #include <libdevcore/CommonData.h> @@ -43,6 +44,11 @@ void ExpressionSplitter::operator()(FunctionalInstruction& _instruction) void ExpressionSplitter::operator()(FunctionCall& _funCall) { + if (BuiltinFunction const* builtin = m_dialect.builtin(_funCall.functionName.name)) + if (builtin->literalArguments) + // We cannot outline function arguments that have to be literals + return; + for (auto& arg: _funCall.arguments | boost::adaptors::reversed) outlineExpression(arg); } @@ -100,7 +106,7 @@ void ExpressionSplitter::outlineExpression(Expression& _expr) m_statementsToPrefix.emplace_back(VariableDeclaration{ location, {{TypedName{location, var, {}}}}, - make_shared<Expression>(std::move(_expr)) + make_unique<Expression>(std::move(_expr)) }); _expr = Identifier{location, var}; } diff --git a/libyul/optimiser/ExpressionSplitter.h b/libyul/optimiser/ExpressionSplitter.h index d4d2b3f6..a72d14ba 100644 --- a/libyul/optimiser/ExpressionSplitter.h +++ b/libyul/optimiser/ExpressionSplitter.h @@ -31,6 +31,7 @@ namespace yul { class NameCollector; +struct Dialect; /** @@ -57,8 +58,8 @@ class NameCollector; class ExpressionSplitter: public ASTModifier { public: - explicit ExpressionSplitter(NameDispenser& _nameDispenser): - m_nameDispenser(_nameDispenser) + explicit ExpressionSplitter(Dialect const& _dialect, NameDispenser& _nameDispenser): + m_dialect(_dialect), m_nameDispenser(_nameDispenser) { } void operator()(FunctionalInstruction&) override; @@ -77,6 +78,7 @@ private: /// List of statements that should go in front of the currently visited AST element, /// at the statement level. std::vector<Statement> m_statementsToPrefix; + Dialect const& m_dialect; NameDispenser& m_nameDispenser; }; diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index 95360dc3..ca6162ec 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -23,7 +23,7 @@ #include <libyul/optimiser/ASTCopier.h> #include <libyul/optimiser/ASTWalker.h> #include <libyul/optimiser/NameCollector.h> -#include <libyul/optimiser/Utilities.h> +#include <libyul/optimiser/OptimizerUtilities.h> #include <libyul/optimiser/Metrics.h> #include <libyul/optimiser/SSAValueTracker.h> #include <libyul/Exceptions.h> @@ -80,9 +80,9 @@ void FullInliner::run() } } -void FullInliner::updateCodeSize(FunctionDefinition& fun) +void FullInliner::updateCodeSize(FunctionDefinition const& _fun) { - m_functionSizes[fun.name] = CodeSize::codeSize(fun.body); + m_functionSizes[_fun.name] = CodeSize::codeSize(_fun.body); } void FullInliner::handleBlock(YulString _currentFunctionName, Block& _block) @@ -100,8 +100,13 @@ bool FullInliner::shallInline(FunctionCall const& _funCall, YulString _callSite) if (!calledFunction) return false; + // Inline really, really tiny functions + size_t size = m_functionSizes.at(calledFunction->name); + if (size <= 1) + return true; + // Do not inline into already big functions. - if (m_functionSizes.at(_callSite) > 100) + if (m_functionSizes.at(_callSite) > 45) return false; if (m_singleUse.count(calledFunction->name)) @@ -119,8 +124,7 @@ bool FullInliner::shallInline(FunctionCall const& _funCall, YulString _callSite) break; } - size_t size = m_functionSizes.at(calledFunction->name); - return (size < 10 || (constantArg && size < 30)); + return (size < 6 || (constantArg && size < 12)); } void FullInliner::tentativelyUpdateCodeSize(YulString _function, YulString _callSite) @@ -177,9 +181,9 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC variableReplacements[_existingVariable.name] = newName; VariableDeclaration varDecl{_funCall.location, {{_funCall.location, newName, _existingVariable.type}}, {}}; if (_value) - varDecl.value = make_shared<Expression>(std::move(*_value)); + varDecl.value = make_unique<Expression>(std::move(*_value)); else - varDecl.value = make_shared<Expression>(zero); + varDecl.value = make_unique<Expression>(zero); newStatements.emplace_back(std::move(varDecl)); }; @@ -198,7 +202,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC newStatements.emplace_back(Assignment{ _assignment.location, {_assignment.variableNames[i]}, - make_shared<Expression>(Identifier{ + make_unique<Expression>(Identifier{ _assignment.location, variableReplacements.at(function->returnVariables[i].name) }) @@ -210,7 +214,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC newStatements.emplace_back(VariableDeclaration{ _varDecl.location, {std::move(_varDecl.variables[i])}, - make_shared<Expression>(Identifier{ + make_unique<Expression>(Identifier{ _varDecl.location, variableReplacements.at(function->returnVariables[i].name) }) @@ -228,10 +232,10 @@ Statement BodyCopier::operator()(VariableDeclaration const& _varDecl) return ASTCopier::operator()(_varDecl); } -Statement BodyCopier::operator()(FunctionDefinition const& _funDef) +Statement BodyCopier::operator()(FunctionDefinition const&) { assertThrow(false, OptimizerException, "Function hoisting has to be done before function inlining."); - return _funDef; + return {}; } YulString BodyCopier::translateIdentifier(YulString _name) diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index d2dd3229..32664c96 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -91,7 +91,7 @@ public: void tentativelyUpdateCodeSize(YulString _function, YulString _callSite); private: - void updateCodeSize(FunctionDefinition& fun); + void updateCodeSize(FunctionDefinition const& _fun); void handleBlock(YulString _currentFunctionName, Block& _block); /// The AST to be modified. The root block itself will not be modified, because diff --git a/libyul/optimiser/FunctionGrouper.cpp b/libyul/optimiser/FunctionGrouper.cpp index 02ce22cd..b9852fcd 100644 --- a/libyul/optimiser/FunctionGrouper.cpp +++ b/libyul/optimiser/FunctionGrouper.cpp @@ -33,6 +33,9 @@ using namespace dev::solidity; void FunctionGrouper::operator()(Block& _block) { + if (alreadyGrouped(_block)) + return; + vector<Statement> reordered; reordered.emplace_back(Block{_block.location, {}}); @@ -45,3 +48,15 @@ void FunctionGrouper::operator()(Block& _block) } _block.statements = std::move(reordered); } + +bool FunctionGrouper::alreadyGrouped(Block const& _block) +{ + if (_block.statements.empty()) + return false; + if (_block.statements.front().type() != typeid(Block)) + return false; + for (size_t i = 1; i < _block.statements.size(); ++i) + if (_block.statements.at(i).type() != typeid(FunctionDefinition)) + return false; + return true; +} diff --git a/libyul/optimiser/FunctionGrouper.h b/libyul/optimiser/FunctionGrouper.h index 3b3f48a7..4b6abf76 100644 --- a/libyul/optimiser/FunctionGrouper.h +++ b/libyul/optimiser/FunctionGrouper.h @@ -38,6 +38,9 @@ class FunctionGrouper { public: void operator()(Block& _block); + +private: + bool alreadyGrouped(Block const& _block); }; } diff --git a/libyul/optimiser/FunctionHoister.cpp b/libyul/optimiser/FunctionHoister.cpp index bd1c781b..4863b94d 100644 --- a/libyul/optimiser/FunctionHoister.cpp +++ b/libyul/optimiser/FunctionHoister.cpp @@ -21,7 +21,7 @@ */ #include <libyul/optimiser/FunctionHoister.h> -#include <libyul/optimiser/Utilities.h> +#include <libyul/optimiser/OptimizerUtilities.h> #include <libyul/AsmData.h> #include <libdevcore/CommonData.h> diff --git a/libyul/optimiser/InlinableExpressionFunctionFinder.cpp b/libyul/optimiser/InlinableExpressionFunctionFinder.cpp index 662cdf25..f57faa7c 100644 --- a/libyul/optimiser/InlinableExpressionFunctionFinder.cpp +++ b/libyul/optimiser/InlinableExpressionFunctionFinder.cpp @@ -20,7 +20,7 @@ #include <libyul/optimiser/InlinableExpressionFunctionFinder.h> -#include <libyul/optimiser/Utilities.h> +#include <libyul/optimiser/OptimizerUtilities.h> #include <libyul/AsmData.h> using namespace std; diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index 8fc9476e..e13940aa 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -21,7 +21,13 @@ #include <libyul/optimiser/Metrics.h> #include <libyul/AsmData.h> +#include <libyul/Exceptions.h> +#include <libevmasm/Instruction.h> + +#include <libdevcore/Visitor.h> + +using namespace std; using namespace dev; using namespace yul; @@ -50,13 +56,93 @@ void CodeSize::visit(Statement const& _statement) { if (_statement.type() == typeid(FunctionDefinition)) return; + else if (!( + _statement.type() == typeid(Block) || + _statement.type() == typeid(ExpressionStatement) || + _statement.type() == typeid(Assignment) || + _statement.type() == typeid(VariableDeclaration) + )) + ++m_size; - ++m_size; ASTWalker::visit(_statement); } void CodeSize::visit(Expression const& _expression) { - ++m_size; + if (_expression.type() != typeid(Identifier)) + ++m_size; + ASTWalker::visit(_expression); +} + + +size_t CodeCost::codeCost(Expression const& _expr) +{ + CodeCost cc; + cc.visit(_expr); + return cc.m_cost; +} + + +void CodeCost::operator()(FunctionCall const& _funCall) +{ + yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); + m_cost += 49; + ASTWalker::operator()(_funCall); +} + +void CodeCost::operator()(FunctionalInstruction const& _instr) +{ + using namespace dev::solidity; + yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); + Tier gasPriceTier = instructionInfo(_instr.instruction).gasPriceTier; + if (gasPriceTier < Tier::VeryLow) + m_cost -= 1; + else if (gasPriceTier < Tier::High) + m_cost += 1; + else + m_cost += 49; + ASTWalker::operator()(_instr); +} +void CodeCost::operator()(Literal const& _literal) +{ + yulAssert(m_cost >= 1, "Should assign cost one in visit(Expression)."); + size_t cost = 0; + switch (_literal.kind) + { + case LiteralKind::Boolean: + break; + case LiteralKind::Number: + for (u256 n = u256(_literal.value.str()); n >= 0x100; n >>= 8) + cost++; + break; + case LiteralKind::String: + cost = _literal.value.str().size(); + break; + } + + m_cost += cost; +} + +void CodeCost::visit(Statement const& _statement) +{ + ++m_cost; + ASTWalker::visit(_statement); +} + +void CodeCost::visit(Expression const& _expression) +{ + ++m_cost; ASTWalker::visit(_expression); } + +void AssignmentCounter::operator()(Assignment const& _assignment) +{ + for (auto const& variable: _assignment.variableNames) + ++m_assignmentCounters[variable.name]; +} + +size_t AssignmentCounter::assignmentCount(YulString _name) const +{ + auto it = m_assignmentCounters.find(_name); + return (it == m_assignmentCounters.end()) ? 0 : it->second; +} diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index d26ecbd9..5364646e 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -30,6 +30,13 @@ namespace yul * More specifically, the number of AST nodes. * Ignores function definitions while traversing the AST. * If you want to know the size of a function, you have to invoke this on its body. + * + * As an exception, the following AST elements have a cost of zero: + * - expression statement (only the expression inside has a cost) + * - block (only the statements inside have a cost) + * - variable references + * - variable declarations (only the right hand side has a cost) + * - assignments (only the value has a cost) */ class CodeSize: public ASTWalker { @@ -39,6 +46,8 @@ public: static size_t codeSize(Block const& _block); private: + CodeSize() {} + void visit(Statement const& _statement) override; void visit(Expression const& _expression) override; @@ -46,4 +55,40 @@ private: size_t m_size = 0; }; +/** + * Very rough cost that takes the size and execution cost of code into account. + * The cost per AST element is one, except for literals where it is the byte size. + * Function calls cost 50. Instructions cost 0 for 3 or less gas (same as DUP), + * 2 for up to 10 and 50 otherwise. + */ +class CodeCost: public ASTWalker +{ +public: + static size_t codeCost(Expression const& _expression); + +private: + void operator()(FunctionCall const& _funCall) override; + void operator()(FunctionalInstruction const& _instr) override; + void operator()(Literal const& _literal) override; + void visit(Statement const& _statement) override; + void visit(Expression const& _expression) override; + +private: + size_t m_cost = 0; +}; + +/** + * Counts the number of assignments to every variable. + * Only works after running the Disambiguator. + */ +class AssignmentCounter: public ASTWalker +{ +public: + using ASTWalker::operator(); + void operator()(Assignment const& _assignment) override; + std::size_t assignmentCount(YulString _name) const; +private: + std::map<YulString, size_t> m_assignmentCounters; +}; + } diff --git a/libyul/optimiser/NameDispenser.cpp b/libyul/optimiser/NameDispenser.cpp index e7cdc60f..172e6907 100644 --- a/libyul/optimiser/NameDispenser.cpp +++ b/libyul/optimiser/NameDispenser.cpp @@ -22,17 +22,19 @@ #include <libyul/optimiser/NameCollector.h> #include <libyul/AsmData.h> +#include <libyul/Dialect.h> using namespace std; using namespace dev; using namespace yul; -NameDispenser::NameDispenser(Block const& _ast): - NameDispenser(NameCollector(_ast).names()) +NameDispenser::NameDispenser(Dialect const& _dialect, Block const& _ast): + NameDispenser(_dialect, NameCollector(_ast).names()) { } -NameDispenser::NameDispenser(set<YulString> _usedNames): +NameDispenser::NameDispenser(Dialect const& _dialect, set<YulString> _usedNames): + m_dialect(_dialect), m_usedNames(std::move(_usedNames)) { } @@ -51,7 +53,7 @@ YulString NameDispenser::newName(YulString _nameHint, YulString _context) YulString NameDispenser::newNameInternal(YulString _nameHint) { YulString name = _nameHint; - while (name.empty() || m_usedNames.count(name)) + while (name.empty() || m_usedNames.count(name) || m_dialect.builtin(name)) { m_counter++; name = YulString(_nameHint.str() + "_" + to_string(m_counter)); diff --git a/libyul/optimiser/NameDispenser.h b/libyul/optimiser/NameDispenser.h index 664a5265..719743e6 100644 --- a/libyul/optimiser/NameDispenser.h +++ b/libyul/optimiser/NameDispenser.h @@ -27,6 +27,7 @@ namespace yul { +struct Dialect; /** * Optimizer component that can be used to generate new names that @@ -38,9 +39,9 @@ class NameDispenser { public: /// Initialize the name dispenser with all the names used in the given AST. - explicit NameDispenser(Block const& _ast); + explicit NameDispenser(Dialect const& _dialect, Block const& _ast); /// Initialize the name dispenser with the given used names. - explicit NameDispenser(std::set<YulString> _usedNames); + explicit NameDispenser(Dialect const& _dialect, std::set<YulString> _usedNames); /// @returns a currently unused name that should be similar to _nameHint /// and prefixed by _context if present. @@ -51,6 +52,7 @@ public: private: YulString newNameInternal(YulString _nameHint); + Dialect const& m_dialect; std::set<YulString> m_usedNames; size_t m_counter = 0; }; diff --git a/libyul/optimiser/Utilities.cpp b/libyul/optimiser/OptimizerUtilities.cpp index b3b580d5..f9571a4c 100644 --- a/libyul/optimiser/Utilities.cpp +++ b/libyul/optimiser/OptimizerUtilities.cpp @@ -1,4 +1,4 @@ -/*( +/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify @@ -18,10 +18,9 @@ * Some useful snippets for the optimiser. */ -#include <libyul/optimiser/Utilities.h> +#include <libyul/optimiser/OptimizerUtilities.h> #include <libyul/AsmData.h> -#include <libyul/Exceptions.h> #include <libdevcore/CommonData.h> @@ -38,11 +37,3 @@ void yul::removeEmptyBlocks(Block& _block) }; boost::range::remove_erase_if(_block.statements, isEmptyBlock); } - -u256 yul::valueOfNumberLiteral(Literal const& _literal) -{ - assertThrow(_literal.kind == LiteralKind::Number, OptimizerException, ""); - std::string const& literalString = _literal.value.str(); - assertThrow(isValidDecimal(literalString) || isValidHex(literalString), OptimizerException, ""); - return u256(literalString); -} diff --git a/libyul/optimiser/Utilities.h b/libyul/optimiser/OptimizerUtilities.h index 1cfff62b..449a1bc0 100644 --- a/libyul/optimiser/Utilities.h +++ b/libyul/optimiser/OptimizerUtilities.h @@ -29,6 +29,4 @@ namespace yul /// Removes statements that are just empty blocks (non-recursive). void removeEmptyBlocks(Block& _block); -dev::u256 valueOfNumberLiteral(Literal const& _literal); - } diff --git a/libyul/optimiser/RedundantAssignEliminator.cpp b/libyul/optimiser/RedundantAssignEliminator.cpp index 7b18e8ca..f6d7676f 100644 --- a/libyul/optimiser/RedundantAssignEliminator.cpp +++ b/libyul/optimiser/RedundantAssignEliminator.cpp @@ -150,9 +150,9 @@ void RedundantAssignEliminator::operator()(Block const& _block) ASTWalker::operator()(_block); } -void RedundantAssignEliminator::run(Block& _ast) +void RedundantAssignEliminator::run(Dialect const& _dialect, Block& _ast) { - RedundantAssignEliminator rae; + RedundantAssignEliminator rae{_dialect}; rae(_ast); AssignmentRemover remover{rae.m_assignmentsToRemove}; @@ -211,7 +211,7 @@ void RedundantAssignEliminator::finalize(YulString _variable) for (auto& assignment: m_assignments[_variable]) { assertThrow(assignment.second != State::Undecided, OptimizerException, ""); - if (assignment.second == State{State::Unused} && MovableChecker{*assignment.first->value}.movable()) + if (assignment.second == State{State::Unused} && MovableChecker{*m_dialect, *assignment.first->value}.movable()) // TODO the only point where we actually need this // to be a set is for the for loop m_assignmentsToRemove.insert(assignment.first); diff --git a/libyul/optimiser/RedundantAssignEliminator.h b/libyul/optimiser/RedundantAssignEliminator.h index 4f82e7a2..2f4dd380 100644 --- a/libyul/optimiser/RedundantAssignEliminator.h +++ b/libyul/optimiser/RedundantAssignEliminator.h @@ -28,6 +28,7 @@ namespace yul { +struct Dialect; /** * Optimiser component that removes assignments to variables that are not used @@ -98,6 +99,7 @@ namespace yul class RedundantAssignEliminator: public ASTWalker { public: + explicit RedundantAssignEliminator(Dialect const& _dialect): m_dialect(&_dialect) {} RedundantAssignEliminator(RedundantAssignEliminator const&) = default; RedundantAssignEliminator& operator=(RedundantAssignEliminator const&) = default; RedundantAssignEliminator(RedundantAssignEliminator&&) = default; @@ -112,7 +114,7 @@ public: void operator()(ForLoop const&) override; void operator()(Block const& _block) override; - static void run(Block& _ast); + static void run(Dialect const& _dialect, Block& _ast); private: RedundantAssignEliminator() = default; @@ -167,6 +169,7 @@ private: void changeUndecidedTo(YulString _variable, State _newState); void finalize(YulString _variable); + Dialect const* m_dialect; std::set<YulString> m_declaredVariables; // TODO check that this does not cause nondeterminism! // This could also be a pseudo-map from state to assignment. diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index 4180bfc3..56f6e99c 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -22,6 +22,7 @@ #include <libyul/optimiser/Metrics.h> #include <libyul/optimiser/ASTCopier.h> +#include <libyul/optimiser/NameCollector.h> #include <libyul/Exceptions.h> #include <libyul/AsmData.h> @@ -29,6 +30,17 @@ using namespace std; using namespace dev; using namespace yul; +void Rematerialiser::run(Dialect const& _dialect, Block& _ast) +{ + Rematerialiser{_dialect, _ast}(_ast); +} + +Rematerialiser::Rematerialiser(Dialect const& _dialect, Block& _ast): + DataFlowAnalyzer(_dialect), + m_referenceCounts(ReferencesCounter::countReferences(_ast)) +{ +} + void Rematerialiser::visit(Expression& _e) { if (_e.type() == typeid(Identifier)) @@ -37,12 +49,21 @@ void Rematerialiser::visit(Expression& _e) if (m_value.count(identifier.name)) { YulString name = identifier.name; - for (auto const& ref: m_references[name]) - assertThrow(inScope(ref), OptimizerException, ""); assertThrow(m_value.at(name), OptimizerException, ""); auto const& value = *m_value.at(name); - if (CodeSize::codeSize(value) <= 7) + size_t refs = m_referenceCounts[name]; + size_t cost = CodeCost::codeCost(value); + if (refs <= 1 || cost == 0 || (refs <= 5 && cost <= 1)) + { + assertThrow(m_referenceCounts[name] > 0, OptimizerException, ""); + for (auto const& ref: m_references[name]) + assertThrow(inScope(ref), OptimizerException, ""); + // update reference counts + m_referenceCounts[name]--; + for (auto const& ref: ReferencesCounter::countReferences(value)) + m_referenceCounts[ref.first] += ref.second; _e = (ASTCopier{}).translate(value); + } } } DataFlowAnalyzer::visit(_e); diff --git a/libyul/optimiser/Rematerialiser.h b/libyul/optimiser/Rematerialiser.h index b3841519..731697c8 100644 --- a/libyul/optimiser/Rematerialiser.h +++ b/libyul/optimiser/Rematerialiser.h @@ -26,16 +26,27 @@ namespace yul { /** - * Optimisation stage that replaces variables by their most recently assigned expressions. + * Optimisation stage that replaces variables by their most recently assigned expressions, + * but only if the expression is movable and one of the following holds: + * - the variable is referenced exactly once + * - the value is extremely cheap ("cost" of zero like ``caller()``) + * - the variable is referenced at most 5 times and the value is rather cheap + * ("cost" of at most 1 like a constant up to 0xff) * * Prerequisite: Disambiguator */ class Rematerialiser: public DataFlowAnalyzer { +public: + static void run(Dialect const& _dialect, Block& _ast); + protected: + Rematerialiser(Dialect const& _dialect, Block& _ast); + using ASTModifier::visit; void visit(Expression& _e) override; + std::map<YulString, size_t> m_referenceCounts; }; } diff --git a/libyul/optimiser/SSAReverser.cpp b/libyul/optimiser/SSAReverser.cpp new file mode 100644 index 00000000..6548ebb5 --- /dev/null +++ b/libyul/optimiser/SSAReverser.cpp @@ -0,0 +1,114 @@ +/* + 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 <libyul/optimiser/SSAReverser.h> +#include <libyul/optimiser/Metrics.h> +#include <libyul/AsmData.h> +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace yul; + +void SSAReverser::run(Block& _block) +{ + AssignmentCounter assignmentCounter; + assignmentCounter(_block); + SSAReverser{assignmentCounter}(_block); +} + +void SSAReverser::operator()(Block& _block) +{ + walkVector(_block.statements); + iterateReplacingWindow<2>( + _block.statements, + [&](Statement& _stmt1, Statement& _stmt2) -> boost::optional<vector<Statement>> + { + auto* varDecl = boost::get<VariableDeclaration>(&_stmt1); + + if (!varDecl || varDecl->variables.size() != 1 || !varDecl->value) + return {}; + + // Replaces + // let a_1 := E + // a := a_1 + // with + // a := E + // let a_1 := a + if (auto* assignment = boost::get<Assignment>(&_stmt2)) + { + auto* identifier = boost::get<Identifier>(assignment->value.get()); + if ( + assignment->variableNames.size() == 1 && + identifier && + identifier->name == varDecl->variables.front().name + ) + { + vector<Statement> result; + result.emplace_back(Assignment{ + std::move(assignment->location), + assignment->variableNames, + std::move(varDecl->value) + }); + result.emplace_back(VariableDeclaration{ + std::move(varDecl->location), + std::move(varDecl->variables), + std::make_unique<Expression>(std::move(assignment->variableNames.front())) + }); + return { std::move(result) }; + } + } + // Replaces + // let a_1 := E + // let a := a_1 + // with + // let a := E + // let a_1 := a + else if (auto* varDecl2 = boost::get<VariableDeclaration>(&_stmt2)) + { + auto* identifier = boost::get<Identifier>(varDecl2->value.get()); + if ( + varDecl2->variables.size() == 1 && + identifier && + identifier->name == varDecl->variables.front().name && ( + m_assignmentCounter.assignmentCount(varDecl2->variables.front().name) > + m_assignmentCounter.assignmentCount(varDecl->variables.front().name) + ) + ) + { + vector<Statement> result; + auto varIdentifier2 = std::make_unique<Expression>(Identifier{ + varDecl2->variables.front().location, + varDecl2->variables.front().name + }); + result.emplace_back(VariableDeclaration{ + std::move(varDecl2->location), + std::move(varDecl2->variables), + std::move(varDecl->value) + }); + result.emplace_back(VariableDeclaration{ + std::move(varDecl->location), + std::move(varDecl->variables), + std::move(varIdentifier2) + }); + return { std::move(result) }; + } + } + + return {}; + } + ); +} diff --git a/libyul/optimiser/SSAReverser.h b/libyul/optimiser/SSAReverser.h new file mode 100644 index 00000000..67abeb56 --- /dev/null +++ b/libyul/optimiser/SSAReverser.h @@ -0,0 +1,74 @@ +/* + 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 <libyul/optimiser/ASTWalker.h> + +namespace yul +{ + +class AssignmentCounter; + +/** + * Reverses the SSA transformation. + * + * In particular, the SSA transform will rewrite + * + * a := E + * + * to + * + * let a_1 := E + * a := a_1 + * + * To undo this kind of transformation, the SSAReverser changes this back to + * + * a := E + * let a_1 := a + * + * Secondly, the SSA transform will rewrite + * + * let a := E + * to + * + * let a_1 := E + * let a := a_1 + * + * To undo this kind of transformation, the SSAReverser changes this back to + * + * let a := E + * let a_1 := a + * + * After that the CSE can replace references of a_1 by references to a, + * after which the unused pruner can remove the declaration of a_1. + * + * Prerequisites: Disambiguator + * + */ +class SSAReverser: public ASTModifier +{ +public: + using ASTModifier::operator(); + void operator()(Block& _block) override; + + static void run(Block& _block); +private: + SSAReverser(AssignmentCounter const& _assignmentCounter): m_assignmentCounter(_assignmentCounter) {} + AssignmentCounter const& m_assignmentCounter; +}; + +} diff --git a/libyul/optimiser/SSATransform.cpp b/libyul/optimiser/SSATransform.cpp index 928c0859..33c875b5 100644 --- a/libyul/optimiser/SSATransform.cpp +++ b/libyul/optimiser/SSATransform.cpp @@ -66,12 +66,13 @@ void SSATransform::operator()(Block& _block) // Creates a new variable (and returns its declaration) with value _value // and replaces _value by a reference to that new variable. - auto replaceByNew = [&](SourceLocation _loc, YulString _varName, YulString _type, shared_ptr<Expression>& _value) -> VariableDeclaration + + auto replaceByNew = [&](SourceLocation _loc, YulString _varName, YulString _type, unique_ptr<Expression>& _value) -> VariableDeclaration { YulString newName = m_nameDispenser.newName(_varName); m_currentVariableValues[_varName] = newName; variablesToClearAtEnd.emplace(_varName); - shared_ptr<Expression> v = make_shared<Expression>(Identifier{_loc, newName}); + unique_ptr<Expression> v = make_unique<Expression>(Identifier{_loc, newName}); _value.swap(v); return VariableDeclaration{_loc, {TypedName{_loc, std::move(newName), std::move(_type)}}, std::move(v)}; }; @@ -87,14 +88,16 @@ void SSATransform::operator()(Block& _block) visit(*varDecl.value); if (varDecl.variables.size() != 1 || !m_variablesToReplace.count(varDecl.variables.front().name)) return {}; - // Replace "let a := v" by "let a_1 := v let a := v" - VariableDeclaration newVarDecl = replaceByNew( + vector<Statement> v; + // Replace "let a := v" by "let a_1 := v let a := a_1" + v.emplace_back(replaceByNew( varDecl.location, varDecl.variables.front().name, varDecl.variables.front().type, varDecl.value - ); - return vector<Statement>{std::move(newVarDecl), std::move(varDecl)}; + )); + v.emplace_back(move(varDecl)); + return v; } else if (_s.type() == typeid(Assignment)) { @@ -103,14 +106,16 @@ void SSATransform::operator()(Block& _block) if (assignment.variableNames.size() != 1) return {}; assertThrow(m_variablesToReplace.count(assignment.variableNames.front().name), OptimizerException, ""); + vector<Statement> v; // Replace "a := v" by "let a_1 := v a := v" - VariableDeclaration newVarDecl = replaceByNew( + v.emplace_back(replaceByNew( assignment.location, assignment.variableNames.front().name, {}, // TODO determine type assignment.value - ); - return vector<Statement>{std::move(newVarDecl), std::move(assignment)}; + )); + v.emplace_back(move(assignment)); + return v; } else visit(_s); diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 91bb2709..7edf97d7 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -22,6 +22,7 @@ #include <libyul/Exceptions.h> #include <libyul/AsmData.h> +#include <libyul/Dialect.h> #include <libevmasm/SemanticInformation.h> @@ -31,7 +32,13 @@ using namespace std; using namespace dev; using namespace yul; -MovableChecker::MovableChecker(Expression const& _expression) +MovableChecker::MovableChecker(Dialect const& _dialect): + m_dialect(_dialect) +{ +} + +MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expression): + MovableChecker(_dialect) { visit(_expression); } @@ -50,8 +57,14 @@ void MovableChecker::operator()(FunctionalInstruction const& _instr) ASTWalker::operator()(_instr); } -void MovableChecker::operator()(FunctionCall const&) +void MovableChecker::operator()(FunctionCall const& _functionCall) { + if (BuiltinFunction const* f = m_dialect.builtin(_functionCall.functionName.name)) + if (f->movable) + { + ASTWalker::operator()(_functionCall); + return; + } m_movable = false; } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 70c50806..a81a489f 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -26,6 +26,7 @@ namespace yul { +struct Dialect; /** * Specific AST walker that determines whether an expression is movable. @@ -33,8 +34,8 @@ namespace yul class MovableChecker: public ASTWalker { public: - MovableChecker() = default; - explicit MovableChecker(Expression const& _expression); + explicit MovableChecker(Dialect const& _dialect); + MovableChecker(Dialect const& _dialect, Expression const& _expression); void operator()(Identifier const& _identifier) override; void operator()(FunctionalInstruction const& _functionalInstruction) override; @@ -48,6 +49,7 @@ public: std::set<YulString> const& referencedVariables() const { return m_variableReferences; } private: + Dialect const& m_dialect; /// Which variables the current expression references. std::set<YulString> m_variableReferences; /// Is the current expression movable or not. diff --git a/libyul/optimiser/SimplificationRules.cpp b/libyul/optimiser/SimplificationRules.cpp index 45b0ca2c..1b620b64 100644 --- a/libyul/optimiser/SimplificationRules.cpp +++ b/libyul/optimiser/SimplificationRules.cpp @@ -20,11 +20,11 @@ #include <libyul/optimiser/SimplificationRules.h> -#include <libyul/optimiser/Utilities.h> #include <libyul/optimiser/ASTCopier.h> #include <libyul/optimiser/Semantics.h> #include <libyul/optimiser/SyntacticalEquality.h> #include <libyul/AsmData.h> +#include <libyul/Utilities.h> #include <libevmasm/RuleList.h> @@ -36,6 +36,7 @@ using namespace yul; SimplificationRule<Pattern> const* SimplificationRules::findFirstMatch( Expression const& _expr, + Dialect const& _dialect, map<YulString, Expression const*> const& _ssaValues ) { @@ -49,7 +50,7 @@ SimplificationRule<Pattern> const* SimplificationRules::findFirstMatch( for (auto const& rule: rules.m_rules[uint8_t(instruction.instruction)]) { rules.resetMatchGroups(); - if (rule.pattern.matches(_expr, _ssaValues)) + if (rule.pattern.matches(_expr, _dialect, _ssaValues)) return &rule; } return nullptr; @@ -104,7 +105,11 @@ void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _ m_matchGroups = &_matchGroups; } -bool Pattern::matches(Expression const& _expr, map<YulString, Expression const*> const& _ssaValues) const +bool Pattern::matches( + Expression const& _expr, + Dialect const& _dialect, + map<YulString, Expression const*> const& _ssaValues +) const { Expression const* expr = &_expr; @@ -139,7 +144,7 @@ bool Pattern::matches(Expression const& _expr, map<YulString, Expression const*> return false; assertThrow(m_arguments.size() == instr.arguments.size(), OptimizerException, ""); for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i].matches(instr.arguments.at(i), _ssaValues)) + if (!m_arguments[i].matches(instr.arguments.at(i), _dialect, _ssaValues)) return false; } else @@ -166,8 +171,8 @@ bool Pattern::matches(Expression const& _expr, map<YulString, Expression const*> Expression const* firstMatch = (*m_matchGroups)[m_matchGroup]; assertThrow(firstMatch, OptimizerException, "Match set but to null."); return - SyntacticalEqualityChecker::equal(*firstMatch, _expr) && - MovableChecker(_expr).movable(); + SyntacticallyEqual{}(*firstMatch, _expr) && + MovableChecker(_dialect, _expr).movable(); } else if (m_kind == PatternKind::Any) (*m_matchGroups)[m_matchGroup] = &_expr; diff --git a/libyul/optimiser/SimplificationRules.h b/libyul/optimiser/SimplificationRules.h index 16aaba04..8213a185 100644 --- a/libyul/optimiser/SimplificationRules.h +++ b/libyul/optimiser/SimplificationRules.h @@ -33,7 +33,7 @@ namespace yul { - +struct Dialect; class Pattern; /** @@ -49,6 +49,7 @@ public: /// @param _ssaValues values of variables that are assigned exactly once. static SimplificationRule<Pattern> const* findFirstMatch( Expression const& _expr, + Dialect const& _dialect, std::map<YulString, Expression const*> const& _ssaValues ); @@ -93,7 +94,11 @@ public: /// same expression equivalence class. void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups); unsigned matchGroup() const { return m_matchGroup; } - bool matches(Expression const& _expr, std::map<YulString, Expression const*> const& _ssaValues) const; + bool matches( + Expression const& _expr, + Dialect const& _dialect, + std::map<YulString, Expression const*> const& _ssaValues + ) const; std::vector<Pattern> arguments() const { return m_arguments; } diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index bdf4cb2a..727775cb 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -16,8 +16,8 @@ */ #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/Semantics.h> -#include <libyul/optimiser/Utilities.h> #include <libyul/AsmData.h> +#include <libyul/Utilities.h> #include <libdevcore/CommonData.h> #include <libdevcore/Visitor.h> @@ -51,7 +51,11 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements) GenericFallbackReturnsVisitor<OptionalStatements, If, Switch, ForLoop> const visitor( [&](If& _ifStmt) -> OptionalStatements { if (_ifStmt.body.statements.empty()) - return {{makePopExpressionStatement(_ifStmt.location, std::move(*_ifStmt.condition))}}; + { + OptionalStatements s = vector<Statement>{}; + s->emplace_back(makePopExpressionStatement(_ifStmt.location, std::move(*_ifStmt.condition))); + return s; + } if (expressionAlwaysTrue(*_ifStmt.condition)) return {std::move(_ifStmt.body.statements)}; else if (expressionAlwaysFalse(*_ifStmt.condition)) @@ -64,18 +68,24 @@ void StructuralSimplifier::simplify(std::vector<yul::Statement>& _statements) auto& switchCase = _switchStmt.cases.front(); auto loc = locationOf(*_switchStmt.expression); if (switchCase.value) - return {{If{ + { + OptionalStatements s = vector<Statement>{}; + s->emplace_back(If{ std::move(_switchStmt.location), - make_shared<Expression>(FunctionalInstruction{ + make_unique<Expression>(FunctionalInstruction{ std::move(loc), solidity::Instruction::EQ, {std::move(*switchCase.value), std::move(*_switchStmt.expression)} - }), std::move(switchCase.body)}}}; + }), std::move(switchCase.body)}); + return s; + } else - return {{ - makePopExpressionStatement(loc, std::move(*_switchStmt.expression)), - std::move(switchCase.body) - }}; + { + OptionalStatements s = vector<Statement>{}; + s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression))); + s->emplace_back(std::move(switchCase.body)); + return s; + } } else return {}; diff --git a/libyul/optimiser/StructuralSimplifier.h b/libyul/optimiser/StructuralSimplifier.h index bbd8e005..d68a9620 100644 --- a/libyul/optimiser/StructuralSimplifier.h +++ b/libyul/optimiser/StructuralSimplifier.h @@ -38,6 +38,8 @@ namespace yul class StructuralSimplifier: public DataFlowAnalyzer { public: + explicit StructuralSimplifier(Dialect const& _dialect): DataFlowAnalyzer(_dialect) {} + using DataFlowAnalyzer::operator(); void operator()(Block& _block) override; private: diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index bfba8dfc..8cf6e104 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -22,8 +22,10 @@ #include <libyul/optimiser/Disambiguator.h> #include <libyul/optimiser/VarDeclInitializer.h> +#include <libyul/optimiser/BlockFlattener.h> #include <libyul/optimiser/FunctionGrouper.h> #include <libyul/optimiser/FunctionHoister.h> +#include <libyul/optimiser/EquivalentFunctionCombiner.h> #include <libyul/optimiser/ExpressionSplitter.h> #include <libyul/optimiser/ExpressionJoiner.h> #include <libyul/optimiser/ExpressionInliner.h> @@ -33,6 +35,7 @@ #include <libyul/optimiser/UnusedPruner.h> #include <libyul/optimiser/ExpressionSimplifier.h> #include <libyul/optimiser/CommonSubexpressionEliminator.h> +#include <libyul/optimiser/SSAReverser.h> #include <libyul/optimiser/SSATransform.h> #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/RedundantAssignEliminator.h> @@ -47,6 +50,7 @@ using namespace dev; using namespace yul; void OptimiserSuite::run( + Dialect const& _dialect, Block& _ast, AsmAnalysisInfo const& _analysisInfo, set<YulString> const& _externallyUsedIdentifiers @@ -54,66 +58,85 @@ void OptimiserSuite::run( { set<YulString> reservedIdentifiers = _externallyUsedIdentifiers; - Block ast = boost::get<Block>(Disambiguator(_analysisInfo, reservedIdentifiers)(_ast)); + Block ast = boost::get<Block>(Disambiguator(_dialect, _analysisInfo, reservedIdentifiers)(_ast)); (VarDeclInitializer{})(ast); (FunctionHoister{})(ast); + (BlockFlattener{})(ast); (FunctionGrouper{})(ast); + EquivalentFunctionCombiner::run(ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); (ForLoopInitRewriter{})(ast); - StructuralSimplifier{}(ast); + (BlockFlattener{})(ast); + StructuralSimplifier{_dialect}(ast); - NameDispenser dispenser{ast}; + NameDispenser dispenser{_dialect, ast}; for (size_t i = 0; i < 4; i++) { - ExpressionSplitter{dispenser}(ast); + ExpressionSplitter{_dialect, dispenser}(ast); SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(ast); - RedundantAssignEliminator::run(ast); + RedundantAssignEliminator::run(_dialect, ast); + RedundantAssignEliminator::run(_dialect, ast); - CommonSubexpressionEliminator{}(ast); - ExpressionSimplifier::run(ast); - StructuralSimplifier{}(ast); + CommonSubexpressionEliminator{_dialect}(ast); + ExpressionSimplifier::run(_dialect, ast); + StructuralSimplifier{_dialect}(ast); + (BlockFlattener{})(ast); SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(ast); - RedundantAssignEliminator::run(ast); - UnusedPruner::runUntilStabilised(ast, reservedIdentifiers); - CommonSubexpressionEliminator{}(ast); - UnusedPruner::runUntilStabilised(ast, reservedIdentifiers); - SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(ast); - RedundantAssignEliminator::run(ast); + RedundantAssignEliminator::run(_dialect, ast); + RedundantAssignEliminator::run(_dialect, ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + CommonSubexpressionEliminator{_dialect}(ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + + SSAReverser::run(ast); + CommonSubexpressionEliminator{_dialect}(ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); ExpressionJoiner::run(ast); ExpressionJoiner::run(ast); - ExpressionInliner(ast).run(); - UnusedPruner::runUntilStabilised(ast); + ExpressionInliner(_dialect, ast).run(); + UnusedPruner::runUntilStabilised(_dialect, ast); - ExpressionSplitter{dispenser}(ast); + ExpressionSplitter{_dialect, dispenser}(ast); SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(ast); - RedundantAssignEliminator::run(ast); - CommonSubexpressionEliminator{}(ast); + RedundantAssignEliminator::run(_dialect, ast); + RedundantAssignEliminator::run(_dialect, ast); + CommonSubexpressionEliminator{_dialect}(ast); + + (FunctionGrouper{})(ast); + EquivalentFunctionCombiner::run(ast); FullInliner{ast, dispenser}.run(); + SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(ast); - RedundantAssignEliminator::run(ast); - ExpressionSimplifier::run(ast); - StructuralSimplifier{}(ast); - CommonSubexpressionEliminator{}(ast); + RedundantAssignEliminator::run(_dialect, ast); + RedundantAssignEliminator::run(_dialect, ast); + ExpressionSimplifier::run(_dialect, ast); + StructuralSimplifier{_dialect}(ast); + (BlockFlattener{})(ast); + CommonSubexpressionEliminator{_dialect}(ast); SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(ast); - RedundantAssignEliminator::run(ast); - UnusedPruner::runUntilStabilised(ast, reservedIdentifiers); + RedundantAssignEliminator::run(_dialect, ast); + RedundantAssignEliminator::run(_dialect, ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + CommonSubexpressionEliminator{_dialect}(ast); } ExpressionJoiner::run(ast); - UnusedPruner::runUntilStabilised(ast); + Rematerialiser::run(_dialect, ast); + UnusedPruner::runUntilStabilised(_dialect, ast); ExpressionJoiner::run(ast); - UnusedPruner::runUntilStabilised(ast); + UnusedPruner::runUntilStabilised(_dialect, ast); ExpressionJoiner::run(ast); - UnusedPruner::runUntilStabilised(ast); + UnusedPruner::runUntilStabilised(_dialect, ast); + + SSAReverser::run(ast); + CommonSubexpressionEliminator{_dialect}(ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + ExpressionJoiner::run(ast); - UnusedPruner::runUntilStabilised(ast); + Rematerialiser::run(_dialect, ast); + UnusedPruner::runUntilStabilised(_dialect, ast); _ast = std::move(ast); } diff --git a/libyul/optimiser/Suite.h b/libyul/optimiser/Suite.h index 795326b4..376e9889 100644 --- a/libyul/optimiser/Suite.h +++ b/libyul/optimiser/Suite.h @@ -29,6 +29,7 @@ namespace yul { struct AsmAnalysisInfo; +struct Dialect; /** * Optimiser suite that combines all steps and also provides the settings for the heuristics @@ -37,9 +38,9 @@ class OptimiserSuite { public: static void run( + Dialect const& _dialect, Block& _ast, AsmAnalysisInfo const& _analysisInfo, - std::set<YulString> const& _externallyUsedIdentifiers = {} ); }; diff --git a/libyul/optimiser/SyntacticalEquality.cpp b/libyul/optimiser/SyntacticalEquality.cpp index 99ce06e5..53f0b029 100644 --- a/libyul/optimiser/SyntacticalEquality.cpp +++ b/libyul/optimiser/SyntacticalEquality.cpp @@ -22,6 +22,7 @@ #include <libyul/Exceptions.h> #include <libyul/AsmData.h> +#include <libyul/Utilities.h> #include <libdevcore/CommonData.h> @@ -29,48 +30,166 @@ using namespace std; using namespace dev; using namespace yul; -bool SyntacticalEqualityChecker::equal(Expression const& _e1, Expression const& _e2) +bool SyntacticallyEqual::operator()(Expression const& _lhs, Expression const& _rhs) { - if (_e1.type() != _e2.type()) + return boost::apply_visitor([this](auto&& _lhsExpr, auto&& _rhsExpr) -> bool { + // ``this->`` is redundant, but required to work around a bug present in gcc 6.x. + return this->expressionEqual(_lhsExpr, _rhsExpr); + }, _lhs, _rhs); +} + +bool SyntacticallyEqual::operator()(Statement const& _lhs, Statement const& _rhs) +{ + return boost::apply_visitor([this](auto&& _lhsStmt, auto&& _rhsStmt) -> bool { + // ``this->`` is redundant, but required to work around a bug present in gcc 6.x. + return this->statementEqual(_lhsStmt, _rhsStmt); + }, _lhs, _rhs); +} + +bool SyntacticallyEqual::expressionEqual(FunctionalInstruction const& _lhs, FunctionalInstruction const& _rhs) +{ + return + _lhs.instruction == _rhs.instruction && + containerEqual(_lhs.arguments, _rhs.arguments, [this](Expression const& _lhsExpr, Expression const& _rhsExpr) -> bool { + return (*this)(_lhsExpr, _rhsExpr); + }); +} + +bool SyntacticallyEqual::expressionEqual(FunctionCall const& _lhs, FunctionCall const& _rhs) +{ + return + expressionEqual(_lhs.functionName, _rhs.functionName) && + containerEqual(_lhs.arguments, _rhs.arguments, [this](Expression const& _lhsExpr, Expression const& _rhsExpr) -> bool { + return (*this)(_lhsExpr, _rhsExpr); + }); +} + +bool SyntacticallyEqual::expressionEqual(Identifier const& _lhs, Identifier const& _rhs) +{ + auto lhsIt = m_identifiersLHS.find(_lhs.name); + auto rhsIt = m_identifiersRHS.find(_rhs.name); + return + (lhsIt == m_identifiersLHS.end() && rhsIt == m_identifiersRHS.end() && _lhs.name == _rhs.name) || + (lhsIt != m_identifiersLHS.end() && rhsIt != m_identifiersRHS.end() && lhsIt->second == rhsIt->second); +} +bool SyntacticallyEqual::expressionEqual(Literal const& _lhs, Literal const& _rhs) +{ + if (_lhs.kind != _rhs.kind || _lhs.type != _rhs.type) return false; - // TODO This somehow calls strcmp - WHERE? - - // TODO This should be replaced by some kind of AST walker as soon as it gets - // more complex. - if (_e1.type() == typeid(FunctionalInstruction)) - { - auto const& e1 = boost::get<FunctionalInstruction>(_e1); - auto const& e2 = boost::get<FunctionalInstruction>(_e2); - return - e1.instruction == e2.instruction && - equalVector(e1.arguments, e2.arguments); - } - else if (_e1.type() == typeid(FunctionCall)) - { - auto const& e1 = boost::get<FunctionCall>(_e1); - auto const& e2 = boost::get<FunctionCall>(_e2); - return - equal(e1.functionName, e2.functionName) && - equalVector(e1.arguments, e2.arguments); - } - else if (_e1.type() == typeid(Identifier)) - return boost::get<Identifier>(_e1).name == boost::get<Identifier>(_e2).name; - else if (_e1.type() == typeid(Literal)) - { - auto const& e1 = boost::get<Literal>(_e1); - auto const& e2 = boost::get<Literal>(_e2); - return e1.kind == e2.kind && e1.value == e2.value && e1.type == e2.type; - } + if (_lhs.kind == LiteralKind::Number) + return valueOfNumberLiteral(_lhs) == valueOfNumberLiteral(_rhs); else - { - assertThrow(false, OptimizerException, "Invalid expression"); - } - return false; + return _lhs.value == _rhs.value; } -bool SyntacticalEqualityChecker::equalVector(vector<Expression> const& _e1, vector<Expression> const& _e2) +bool SyntacticallyEqual::statementEqual(ExpressionStatement const& _lhs, ExpressionStatement const& _rhs) { - return _e1.size() == _e2.size() && - std::equal(begin(_e1), end(_e1), begin(_e2), SyntacticalEqualityChecker::equal); + return (*this)(_lhs.expression, _rhs.expression); +} +bool SyntacticallyEqual::statementEqual(Assignment const& _lhs, Assignment const& _rhs) +{ + return containerEqual( + _lhs.variableNames, + _rhs.variableNames, + [this](Identifier const& _lhsVarName, Identifier const& _rhsVarName) -> bool { + return this->expressionEqual(_lhsVarName, _rhsVarName); + } + ) && (*this)(*_lhs.value, *_rhs.value); +} + +bool SyntacticallyEqual::statementEqual(VariableDeclaration const& _lhs, VariableDeclaration const& _rhs) +{ + // first visit expression, then variable declarations + if (!compareUniquePtr<Expression, &SyntacticallyEqual::operator()>(_lhs.value, _rhs.value)) + return false; + return containerEqual(_lhs.variables, _rhs.variables, [this](TypedName const& _lhsVarName, TypedName const& _rhsVarName) -> bool { + return this->visitDeclaration(_lhsVarName, _rhsVarName); + }); +} +bool SyntacticallyEqual::statementEqual(FunctionDefinition const& _lhs, FunctionDefinition const& _rhs) +{ + auto compare = [this](TypedName const& _lhsVarName, TypedName const& _rhsVarName) -> bool { + return this->visitDeclaration(_lhsVarName, _rhsVarName); + }; + // first visit parameter declarations, then body + if (!containerEqual(_lhs.parameters, _rhs.parameters, compare)) + return false; + if (!containerEqual(_lhs.returnVariables, _rhs.returnVariables, compare)) + return false; + return statementEqual(_lhs.body, _rhs.body); +} + +bool SyntacticallyEqual::statementEqual(If const& _lhs, If const& _rhs) +{ + return + compareUniquePtr<Expression, &SyntacticallyEqual::operator()>(_lhs.condition, _rhs.condition) && + statementEqual(_lhs.body, _rhs.body); +} + +bool SyntacticallyEqual::statementEqual(Switch const& _lhs, Switch const& _rhs) +{ + static auto const sortCasesByValue = [](Case const* _lhsCase, Case const* _rhsCase) -> bool { + return Less<Literal*>{}(_lhsCase->value.get(), _rhsCase->value.get()); + }; + std::set<Case const*, decltype(sortCasesByValue)> lhsCases(sortCasesByValue); + std::set<Case const*, decltype(sortCasesByValue)> rhsCases(sortCasesByValue); + for (auto const& lhsCase: _lhs.cases) + lhsCases.insert(&lhsCase); + for (auto const& rhsCase: _rhs.cases) + rhsCases.insert(&rhsCase); + return + compareUniquePtr<Expression, &SyntacticallyEqual::operator()>(_lhs.expression, _rhs.expression) && + containerEqual(lhsCases, rhsCases, [this](Case const* _lhsCase, Case const* _rhsCase) -> bool { + return this->switchCaseEqual(*_lhsCase, *_rhsCase); + }); +} + + +bool SyntacticallyEqual::switchCaseEqual(Case const& _lhs, Case const& _rhs) +{ + return + compareUniquePtr<Literal, &SyntacticallyEqual::expressionEqual>(_lhs.value, _rhs.value) && + statementEqual(_lhs.body, _rhs.body); +} + +bool SyntacticallyEqual::statementEqual(ForLoop const& _lhs, ForLoop const& _rhs) +{ + return + statementEqual(_lhs.pre, _rhs.pre) && + compareUniquePtr<Expression, &SyntacticallyEqual::operator()>(_lhs.condition, _rhs.condition) && + statementEqual(_lhs.body, _rhs.body) && + statementEqual(_lhs.post, _rhs.post); +} + +bool SyntacticallyEqual::statementEqual(Instruction const&, Instruction const&) +{ + assertThrow(false, OptimizerException, ""); +} + +bool SyntacticallyEqual::statementEqual(Label const&, Label const&) +{ + assertThrow(false, OptimizerException, ""); +} + +bool SyntacticallyEqual::statementEqual(StackAssignment const&, StackAssignment const&) +{ + assertThrow(false, OptimizerException, ""); +} + +bool SyntacticallyEqual::statementEqual(Block const& _lhs, Block const& _rhs) +{ + return containerEqual(_lhs.statements, _rhs.statements, [this](Statement const& _lhsStmt, Statement const& _rhsStmt) -> bool { + return (*this)(_lhsStmt, _rhsStmt); + }); +} + +bool SyntacticallyEqual::visitDeclaration(TypedName const& _lhs, TypedName const& _rhs) +{ + if (_lhs.type != _rhs.type) + return false; + std::size_t id = m_idsUsed++; + m_identifiersLHS[_lhs.name] = id; + m_identifiersRHS[_rhs.name] = id; + return true; } diff --git a/libyul/optimiser/SyntacticalEquality.h b/libyul/optimiser/SyntacticalEquality.h index 63c51b4f..af240717 100644 --- a/libyul/optimiser/SyntacticalEquality.h +++ b/libyul/optimiser/SyntacticalEquality.h @@ -21,27 +21,69 @@ #pragma once #include <libyul/AsmDataForward.h> +#include <libyul/YulString.h> -#include <vector> +#include <map> +#include <type_traits> namespace yul { + /** * Component that can compare ASTs for equality on a syntactic basis. - * Ignores source locations but requires exact matches otherwise. + * Ignores source locations and allows for different variable names but requires exact matches otherwise. * - * TODO: Only implemented for Expressions for now. - * A future version might also recognize renamed variables and thus could be used to - * remove duplicate functions. + * Prerequisite: Disambiguator (unless only expressions are compared) */ -class SyntacticalEqualityChecker +class SyntacticallyEqual { public: - static bool equal(Expression const& _e1, Expression const& _e2); + bool operator()(Expression const& _lhs, Expression const& _rhs); + bool operator()(Statement const& _lhs, Statement const& _rhs); + + bool expressionEqual(FunctionalInstruction const& _lhs, FunctionalInstruction const& _rhs); + bool expressionEqual(FunctionCall const& _lhs, FunctionCall const& _rhs); + bool expressionEqual(Identifier const& _lhs, Identifier const& _rhs); + bool expressionEqual(Literal const& _lhs, Literal const& _rhs); + + bool statementEqual(ExpressionStatement const& _lhs, ExpressionStatement const& _rhs); + bool statementEqual(Assignment const& _lhs, Assignment const& _rhs); + bool statementEqual(VariableDeclaration const& _lhs, VariableDeclaration const& _rhs); + bool statementEqual(FunctionDefinition const& _lhs, FunctionDefinition const& _rhs); + bool statementEqual(If const& _lhs, If const& _rhs); + bool statementEqual(Switch const& _lhs, Switch const& _rhs); + bool switchCaseEqual(Case const& _lhs, Case const& _rhs); + bool statementEqual(ForLoop const& _lhs, ForLoop const& _rhs); + bool statementEqual(Block const& _lhs, Block const& _rhs); +private: + bool statementEqual(Instruction const& _lhs, Instruction const& _rhs); + bool statementEqual(Label const& _lhs, Label const& _rhs); + bool statementEqual(StackAssignment const& _lhs, StackAssignment const& _rhs); + + bool visitDeclaration(TypedName const& _lhs, TypedName const& _rhs); + + template<typename U, typename V> + bool expressionEqual(U const&, V const&, std::enable_if_t<!std::is_same<U, V>::value>* = nullptr) + { + return false; + } + + template<typename U, typename V> + bool statementEqual(U const&, V const&, std::enable_if_t<!std::is_same<U, V>::value>* = nullptr) + { + return false; + } + + template<typename T, bool (SyntacticallyEqual::*CompareMember)(T const&, T const&)> + bool compareUniquePtr(std::unique_ptr<T> const& _lhs, std::unique_ptr<T> const& _rhs) + { + return (_lhs == _rhs) || (_lhs && _rhs && (this->*CompareMember)(*_lhs, *_rhs)); + } -protected: - static bool equalVector(std::vector<Expression> const& _e1, std::vector<Expression> const& _e2); + std::size_t m_idsUsed = 0; + std::map<YulString, std::size_t> m_identifiersLHS; + std::map<YulString, std::size_t> m_identifiersRHS; }; } diff --git a/libyul/optimiser/UnusedPruner.cpp b/libyul/optimiser/UnusedPruner.cpp index 31aead82..365b255c 100644 --- a/libyul/optimiser/UnusedPruner.cpp +++ b/libyul/optimiser/UnusedPruner.cpp @@ -22,7 +22,7 @@ #include <libyul/optimiser/NameCollector.h> #include <libyul/optimiser/Semantics.h> -#include <libyul/optimiser/Utilities.h> +#include <libyul/optimiser/OptimizerUtilities.h> #include <libyul/Exceptions.h> #include <libyul/AsmData.h> @@ -32,7 +32,8 @@ using namespace std; using namespace dev; using namespace yul; -UnusedPruner::UnusedPruner(Block& _ast, set<YulString> const& _externallyUsedFunctions) +UnusedPruner::UnusedPruner(Dialect const& _dialect, Block& _ast, set<YulString> const& _externallyUsedFunctions): + m_dialect(_dialect) { ReferencesCounter counter; counter(_ast); @@ -69,7 +70,7 @@ void UnusedPruner::operator()(Block& _block) { if (!varDecl.value) statement = Block{std::move(varDecl.location), {}}; - else if (MovableChecker(*varDecl.value).movable()) + else if (MovableChecker(m_dialect, *varDecl.value).movable()) { subtractReferences(ReferencesCounter::countReferences(*varDecl.value)); statement = Block{std::move(varDecl.location), {}}; @@ -87,7 +88,7 @@ void UnusedPruner::operator()(Block& _block) else if (statement.type() == typeid(ExpressionStatement)) { ExpressionStatement& exprStmt = boost::get<ExpressionStatement>(statement); - if (MovableChecker(exprStmt.expression).movable()) + if (MovableChecker(m_dialect, exprStmt.expression).movable()) { // pop(x) should be movable! subtractReferences(ReferencesCounter::countReferences(exprStmt.expression)); @@ -100,11 +101,15 @@ void UnusedPruner::operator()(Block& _block) ASTModifier::operator()(_block); } -void UnusedPruner::runUntilStabilised(Block& _ast, set<YulString> const& _externallyUsedFunctions) +void UnusedPruner::runUntilStabilised( + Dialect const& _dialect, + Block& _ast, + set<YulString> const& _externallyUsedFunctions +) { while (true) { - UnusedPruner pruner(_ast, _externallyUsedFunctions); + UnusedPruner pruner(_dialect, _ast, _externallyUsedFunctions); pruner(_ast); if (!pruner.shouldRunAgain()) return; diff --git a/libyul/optimiser/UnusedPruner.h b/libyul/optimiser/UnusedPruner.h index 64e02b35..72279d4a 100644 --- a/libyul/optimiser/UnusedPruner.h +++ b/libyul/optimiser/UnusedPruner.h @@ -28,6 +28,7 @@ namespace yul { +struct Dialect; /** * Optimisation stage that removes unused variables and functions and also @@ -40,7 +41,11 @@ namespace yul class UnusedPruner: public ASTModifier { public: - explicit UnusedPruner(Block& _ast, std::set<YulString> const& _externallyUsedFunctions = {}); + UnusedPruner( + Dialect const& _dialect, + Block& _ast, + std::set<YulString> const& _externallyUsedFunctions = {} + ); using ASTModifier::operator(); void operator()(Block& _block) override; @@ -49,12 +54,17 @@ public: bool shouldRunAgain() const { return m_shouldRunAgain; } // Run the pruner until the code does not change anymore. - static void runUntilStabilised(Block& _ast, std::set<YulString> const& _externallyUsedFunctions = {}); + static void runUntilStabilised( + Dialect const& _dialect, + Block& _ast, + std::set<YulString> const& _externallyUsedFunctions = {} + ); private: bool used(YulString _name) const; void subtractReferences(std::map<YulString, size_t> const& _subtrahend); + Dialect const& m_dialect; bool m_shouldRunAgain = false; std::map<YulString, size_t> m_references; }; diff --git a/libyul/optimiser/VarDeclInitializer.cpp b/libyul/optimiser/VarDeclInitializer.cpp index 4a26757f..7009cc9b 100644 --- a/libyul/optimiser/VarDeclInitializer.cpp +++ b/libyul/optimiser/VarDeclInitializer.cpp @@ -39,7 +39,7 @@ void VarDeclInitializer::operator()(Block& _block) return {}; else if (_varDecl.variables.size() == 1) { - _varDecl.value = make_shared<Expression>(zero); + _varDecl.value = make_unique<Expression>(zero); return {}; } else @@ -47,7 +47,7 @@ void VarDeclInitializer::operator()(Block& _block) OptionalStatements ret{vector<Statement>{}}; langutil::SourceLocation loc{std::move(_varDecl.location)}; for (auto& var: _varDecl.variables) - ret->push_back(VariableDeclaration{loc, {std::move(var)}, make_shared<Expression>(zero)}); + ret->push_back(VariableDeclaration{loc, {std::move(var)}, make_unique<Expression>(zero)}); return ret; } } diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index 14c497ae..dbd41113 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -30,5 +30,5 @@ set -e if [[ "$OSTYPE" != "darwin"* ]]; then ./scripts/travis-emscripten/install_deps.sh - docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.37.21-64bit ./scripts/travis-emscripten/build_emscripten.sh + docker run -v $(pwd):/root/project -w /root/project trzeci/emscripten:sdk-tag-1.38.22-64bit ./scripts/travis-emscripten/build_emscripten.sh fi diff --git a/scripts/create_source_tarball.sh b/scripts/create_source_tarball.sh index 632c1daa..7e3c238d 100755 --- a/scripts/create_source_tarball.sh +++ b/scripts/create_source_tarball.sh @@ -33,6 +33,6 @@ REPO_ROOT="$(dirname "$0")"/.. mkdir -p "$SOLDIR/deps/downloads/" 2>/dev/null || true wget -O "$SOLDIR/deps/downloads/jsoncpp-1.8.4.tar.gz" https://github.com/open-source-parsers/jsoncpp/archive/1.8.4.tar.gz mkdir -p "$REPO_ROOT/upload" - tar czf "$REPO_ROOT/upload/solidity_$versionstring.tar.gz" -C "$TEMPDIR" "solidity_$versionstring" + tar --owner 0 --group 0 -czf "$REPO_ROOT/upload/solidity_$versionstring.tar.gz" -C "$TEMPDIR" "solidity_$versionstring" rm -r "$TEMPDIR" ) diff --git a/scripts/install_deps.cmake b/scripts/install_deps.cmake index d1284b9e..0cb0ed62 100644 --- a/scripts/install_deps.cmake +++ b/scripts/install_deps.cmake @@ -90,10 +90,10 @@ function(prepare_package_source NAME VERSION URL) endfunction() set(INSTALL_DIR "${ROOT_DIR}/install") -set(SERVER "https://github.com/ethereum/cpp-dependencies/releases/download/vc140/") +set(SERVER "https://github.com/ethereum/cpp-dependencies/releases/download/vs2017/") function(download_and_install PACKAGE_NAME) download_and_unpack("${SERVER}${PACKAGE_NAME}.tar.gz" ${INSTALL_DIR}) endfunction(download_and_install) -download_and_install("boost-1.61") +download_and_install("boost-1.67.0") diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 09d5a249..0d1620c4 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -352,7 +352,7 @@ case $(uname -s) in # CentOS needs some more testing. This is the general idea of packages # needed, but some tweaking/improvements can definitely happen #------------------------------------------------------------------------------ - CentOS) + CentOS*) read -p "This script will heavily modify your system in order to allow for compilation of Solidity. Are you sure? [Y/N]" -n 1 -r if [[ $REPLY =~ ^[Yy]$ ]]; then # Make Sure we have the EPEL repos diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 4ba0e644..6cba7dff 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -53,7 +53,7 @@ packagename=solc static_build_distribution=cosmic -for distribution in xenial bionic cosmic STATIC +for distribution in bionic cosmic STATIC do cd /tmp/ rm -rf $distribution diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 32899903..0b6c5346 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -49,16 +49,16 @@ fi WORKSPACE=/root/project # Increase nodejs stack size -if [ -e ~/.emscripten ] +if ! [ -e /emsdk_portable/node/bin/node_orig ] then - sed -i -e 's/NODE_JS="nodejs"/NODE_JS=["nodejs", "--stack_size=8192"]/' ~/.emscripten -else - echo 'NODE_JS=["nodejs", "--stack_size=8192"]' > ~/.emscripten + mv /emsdk_portable/node/bin/node /emsdk_portable/node/bin/node_orig + echo -e '#!/bin/sh\nexec /emsdk_portable/node/bin/node_orig --stack-size=8192 $@' > /emsdk_portable/node/bin/node + chmod 755 /emsdk_portable/node/bin/node fi # Boost echo -en 'travis_fold:start:compiling_boost\\r' -cd "$WORKSPACE"/boost_1_67_0 +cd "$WORKSPACE"/boost_1_68_0 # if b2 exists, it is a fresh checkout, otherwise it comes from the cache # and is already compiled test -e b2 && ( @@ -79,24 +79,25 @@ cd $WORKSPACE mkdir -p build cd build cmake \ - -DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake \ + -DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/emscripten.cmake \ -DCMAKE_BUILD_TYPE=Release \ - -DEMSCRIPTEN=1 \ -DBoost_FOUND=1 \ -DBoost_USE_STATIC_LIBS=1 \ -DBoost_USE_STATIC_RUNTIME=1 \ - -DBoost_INCLUDE_DIR="$WORKSPACE"/boost_1_67_0/ \ - -DBoost_FILESYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_67_0/libboost_filesystem.a \ - -DBoost_PROGRAM_OPTIONS_LIBRARY_RELEASE="$WORKSPACE"/boost_1_67_0/libboost_program_options.a \ - -DBoost_REGEX_LIBRARY_RELEASE="$WORKSPACE"/boost_1_67_0/libboost_regex.a \ - -DBoost_SYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_67_0/libboost_system.a \ - -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY_RELEASE="$WORKSPACE"/boost_1_67_0/libboost_unit_test_framework.a \ + -DBoost_INCLUDE_DIR="$WORKSPACE"/boost_1_68_0/ \ + -DBoost_FILESYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_filesystem.a \ + -DBoost_PROGRAM_OPTIONS_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_program_options.a \ + -DBoost_REGEX_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_regex.a \ + -DBoost_SYSTEM_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_system.a \ + -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY_RELEASE="$WORKSPACE"/boost_1_68_0/libboost_unit_test_framework.a \ -DTESTS=0 \ .. make -j 4 cd .. mkdir -p upload +# Patch soljson.js to provide backwards-compatibility with older emscripten versions +echo ";/* backwards compatibility */ Module['Runtime'] = Module;" >> build/libsolc/soljson.js cp build/libsolc/soljson.js upload/ cp build/libsolc/soljson.js ./ diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index 155506e4..5959ef12 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -30,15 +30,15 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' -test -e boost_1_67_0 -a -e boost_1_67_0/boost || ( -rm -rf boost_1_67_0 +test -e boost_1_68_0 -a -e boost_1_68_0/boost || ( +rm -rf boost_1_68_0 rm -f boost.tar.xz -wget -q 'https://sourceforge.net/projects/boost/files/boost/1.67.0/boost_1_67_0.tar.gz/download'\ +wget -q 'https://sourceforge.net/projects/boost/files/boost/1.68.0/boost_1_68_0.tar.gz/download'\ -O boost.tar.xz -test "$(shasum boost.tar.xz)" = "77e73c9fd7bf85b14067767b9e8fdc39b49ee0f2 boost.tar.xz" +test "$(shasum boost.tar.xz)" = "a78cf6ebb111a48385dd0c135e145a6819a8c856 boost.tar.xz" tar -xzf boost.tar.xz rm boost.tar.xz -cd boost_1_67_0 +cd boost_1_68_0 ./bootstrap.sh wget -q 'https://raw.githubusercontent.com/tee3/boost-build-emscripten/master/emscripten.jam' test "$(shasum emscripten.jam)" = "a7e13fc2c1e53b0e079ef440622f879aa6da3049 emscripten.jam" diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index bda1b78a..4bf24901 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -731,7 +731,7 @@ bool CommandLineInterface::processInput() try { auto path = boost::filesystem::path(_path); - auto canonicalPath = weaklyCanonicalFilesystemPath(path); + auto canonicalPath = boost::filesystem::weakly_canonical(path); bool isAllowed = false; for (auto const& allowedDir: m_allowedDirectories) { diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 9d2ffa5f..1ef1b320 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -28,14 +28,14 @@ set -e +## GLOBAL VARIABLES + REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) -echo $REPO_ROOT SOLC="$REPO_ROOT/build/solc/solc" FULLARGS="--optimize --ignore-missing --combined-json abi,asm,ast,bin,bin-runtime,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc" -echo "Checking that the bug list is up to date..." -"$REPO_ROOT"/scripts/update_bugs_by_version.py +## FUNCTIONS if [ "$CIRCLECI" ] then @@ -93,24 +93,10 @@ function compileFull() fi } -printTask "Testing unknown options..." -( - set +e - output=$("$SOLC" --allow=test 2>&1) - failed=$? - set -e - - if [ "$output" == "unrecognised option '--allow=test'" ] && [ $failed -ne 0 ] ; then - echo "Passed" - else - printError "Incorrect response to unknown options: $STDERR" - exit 1 - fi -) - # General helper function for testing SOLC behaviour, based on file name, compile opts, exit code, stdout and stderr. # An failure is expected. -test_solc_behaviour() { +function test_solc_behaviour() +{ local filename="${1}" local solc_args="${2}" local solc_stdin="${3}" @@ -122,7 +108,8 @@ test_solc_behaviour() { if [[ "$exit_code_expected" = "" ]]; then exit_code_expected="0"; fi set +e - if [[ "$solc_stdin" = "" ]]; then + if [[ "$solc_stdin" = "" ]] + then "$SOLC" "${filename}" ${solc_args} 1>$stdout_path 2>$stderr_path else "$SOLC" "${filename}" ${solc_args} <$solc_stdin 1>$stdout_path 2>$stderr_path @@ -130,7 +117,8 @@ test_solc_behaviour() { exitCode=$? set -e - if [[ "$solc_args" == *"--standard-json"* ]]; then + if [[ "$solc_args" == *"--standard-json"* ]] + then sed -i -e 's/{[^{]*Warning: This is a pre-release compiler version[^}]*},\{0,1\}//' "$stdout_path" sed -i -e 's/"errors":\[\],\{0,1\}//' "$stdout_path" else @@ -138,28 +126,35 @@ test_solc_behaviour() { sed -i -e 's/ Consider adding "pragma .*$//' "$stderr_path" fi - if [[ $exitCode -ne "$exit_code_expected" ]]; then + if [[ $exitCode -ne "$exit_code_expected" ]] + then printError "Incorrect exit code. Expected $exit_code_expected but got $exitCode." rm -f $stdout_path $stderr_path exit 1 fi - if [[ "$(cat $stdout_path)" != "${stdout_expected}" ]]; then + if [[ "$(cat $stdout_path)" != "${stdout_expected}" ]] + then printError "Incorrect output on stdout received. Expected:" echo -e "${stdout_expected}" printError "But got:" cat $stdout_path + printError "When running $SOLC ${filename} ${solc_args} <$solc_stdin" + rm -f $stdout_path $stderr_path exit 1 fi - if [[ "$(cat $stderr_path)" != "${stderr_expected}" ]]; then + if [[ "$(cat $stderr_path)" != "${stderr_expected}" ]] + then printError "Incorrect output on stderr received. Expected:" echo -e "${stderr_expected}" printError "But got:" cat $stderr_path + printError "When running $SOLC ${filename} ${solc_args} <$solc_stdin" + rm -f $stdout_path $stderr_path exit 1 fi @@ -167,6 +162,49 @@ test_solc_behaviour() { rm -f $stdout_path $stderr_path } + +function test_solc_assembly_output() +{ + local input="${1}" + local expected="${2}" + local solc_args="${3}" + + local expected_object="object \"object\" { code "${expected}" }" + + output=$(echo "${input}" | "$SOLC" - ${solc_args} 2>/dev/null) + empty=$(echo $output | sed -ne '/'"${expected_object}"'/p') + if [ -z "$empty" ] + then + printError "Incorrect assembly output. Expected: " + echo -e ${expected} + printError "with arguments ${solc_args}, but got:" + echo "${output}" + exit 1 + fi +} + +## RUN + +echo "Checking that the bug list is up to date..." +"$REPO_ROOT"/scripts/update_bugs_by_version.py + +printTask "Testing unknown options..." +( + set +e + output=$("$SOLC" --allow=test 2>&1) + failed=$? + set -e + + if [ "$output" == "unrecognised option '--allow=test'" ] && [ $failed -ne 0 ] + then + echo "Passed" + else + printError "Incorrect response to unknown options: $STDERR" + exit 1 + fi +) + + printTask "Testing passing files that are not found..." test_solc_behaviour "file_not_found.sol" "" "" "" 1 "\"file_not_found.sol\" is not found." @@ -177,48 +215,40 @@ printTask "Testing passing empty remappings..." test_solc_behaviour "${0}" "=/some/remapping/target" "" "" 1 "Invalid remapping: \"=/some/remapping/target\"." test_solc_behaviour "${0}" "ctx:=/some/remapping/target" "" "" 1 "Invalid remapping: \"ctx:=/some/remapping/target\"." -printTask "Running standard JSON commandline tests..." -( -cd "$REPO_ROOT"/test/cmdlineTests/ -for file in *.json -do - args="--standard-json" - stdin="$REPO_ROOT/test/cmdlineTests/$file" - stdout=$(cat $file.stdout 2>/dev/null || true) - exitCode=$(cat $file.exit 2>/dev/null || true) - err=$(cat $file.err 2>/dev/null || true) - printTask " - $file" - test_solc_behaviour "" "$args" "$stdin" "$stdout" "$exitCode" "$err" -done -) - printTask "Running general commandline tests..." ( -cd "$REPO_ROOT"/test/cmdlineTests/ -for file in *.sol -do - args=$(cat $file.args 2>/dev/null || true) - stdout=$(cat $file.stdout 2>/dev/null || true) - exitCode=$(cat $file.exit 2>/dev/null || true) - err=$(cat $file.err 2>/dev/null || true) - printTask " - $file" - test_solc_behaviour "$file" "$args" "" "$stdout" "$exitCode" "$err" -done + cd "$REPO_ROOT"/test/cmdlineTests/ + for tdir in */ + do + if [ -e "${tdir}/input.json" ] + then + inputFile="" + stdin="${tdir}/input.json" + stdout=$(cat ${tdir}/output.json 2>/dev/null || true) + args="--standard-json "$(cat ${tdir}/args 2>/dev/null || true) + else + inputFile="${tdir}input.sol" + stdin="" + stdout=$(cat ${tdir}/output 2>/dev/null || true) + args=$(cat ${tdir}/args 2>/dev/null || true) + fi + exitCode=$(cat ${tdir}/exit 2>/dev/null || true) + err=$(cat ${tdir}/err 2>/dev/null || true) + printTask " - ${tdir}" + test_solc_behaviour "$inputFile" "$args" "$stdin" "$stdout" "$exitCode" "$err" + done ) printTask "Compiling various other contracts and libraries..." ( -cd "$REPO_ROOT"/test/compilationTests/ -for dir in * -do - if [ "$dir" != "README.md" ] - then + cd "$REPO_ROOT"/test/compilationTests/ + for dir in */ + do echo " - $dir" cd "$dir" compileFull -w *.sol */*.sol cd .. - fi -done + done ) printTask "Compiling all examples from the documentation..." @@ -293,25 +323,6 @@ SOLTMPDIR=$(mktemp -d) ) rm -rf "$SOLTMPDIR" -test_solc_assembly_output() { - local input="${1}" - local expected="${2}" - local solc_args="${3}" - - local expected_object="object \"object\" { code "${expected}" }" - - output=$(echo "${input}" | "$SOLC" - ${solc_args} 2>/dev/null) - empty=$(echo $output | sed -ne '/'"${expected_object}"'/p') - if [ -z "$empty" ] - then - printError "Incorrect assembly output. Expected: " - echo -e ${expected} - printError "with arguments ${solc_args}, but got:" - echo "${output}" - exit 1 - fi -} - printTask "Testing assemble, yul, strict-assembly and optimize..." ( echo '{}' | "$SOLC" - --assemble &>/dev/null @@ -342,7 +353,8 @@ SOLTMPDIR=$(mktemp -d) set -e # This should fail - if [[ !("$output" =~ "No input files given") || ($result == 0) ]] ; then + if [[ !("$output" =~ "No input files given") || ($result == 0) ]] + then printError "Incorrect response to empty input arg list: $STDERR" exit 1 fi @@ -353,7 +365,8 @@ SOLTMPDIR=$(mktemp -d) set -e # The contract should be compiled - if [[ "$result" != 0 ]] ; then + if [[ "$result" != 0 ]] + then exit 1 fi @@ -361,7 +374,8 @@ SOLTMPDIR=$(mktemp -d) set +e output=$(echo '' | "$SOLC" --ast - 2>/dev/null) set -e - if [[ $? != 0 ]] ; then + if [[ $? != 0 ]] + then exit 1 fi ) @@ -379,14 +393,16 @@ SOLTMPDIR=$(mktemp -d) do set +e "$REPO_ROOT"/build/test/tools/solfuzzer --quiet < "$f" - if [ $? -ne 0 ]; then + if [ $? -ne 0 ] + then printError "Fuzzer failed on:" cat "$f" exit 1 fi "$REPO_ROOT"/build/test/tools/solfuzzer --without-optimizer --quiet < "$f" - if [ $? -ne 0 ]; then + if [ $? -ne 0 ] + then printError "Fuzzer (without optimizer) failed on:" cat "$f" exit 1 @@ -395,4 +411,5 @@ SOLTMPDIR=$(mktemp -d) done ) rm -rf "$SOLTMPDIR" + echo "Commandline tests successful." diff --git a/test/cmdlineTests/data_storage.sol.args b/test/cmdlineTests/data_storage/args index 3684987e..3684987e 100644 --- a/test/cmdlineTests/data_storage.sol.args +++ b/test/cmdlineTests/data_storage/args diff --git a/test/cmdlineTests/data_storage.sol b/test/cmdlineTests/data_storage/input.sol index cc602cc9..cc602cc9 100644 --- a/test/cmdlineTests/data_storage.sol +++ b/test/cmdlineTests/data_storage/input.sol diff --git a/test/cmdlineTests/data_storage.sol.stdout b/test/cmdlineTests/data_storage/output index 4a5250f7..e0dae4bd 100644 --- a/test/cmdlineTests/data_storage.sol.stdout +++ b/test/cmdlineTests/data_storage/output @@ -1,5 +1,5 @@ -======= data_storage.sol:C ======= +======= data_storage/input.sol:C ======= Gas estimation: construction: 306 + 264400 = 264706 diff --git a/test/cmdlineTests/gas_test_dispatch.sol.args b/test/cmdlineTests/gas_test_dispatch/args index 3684987e..3684987e 100644 --- a/test/cmdlineTests/gas_test_dispatch.sol.args +++ b/test/cmdlineTests/gas_test_dispatch/args diff --git a/test/cmdlineTests/gas_test_dispatch.sol b/test/cmdlineTests/gas_test_dispatch/input.sol index a5ca9e7d..a5ca9e7d 100644 --- a/test/cmdlineTests/gas_test_dispatch.sol +++ b/test/cmdlineTests/gas_test_dispatch/input.sol diff --git a/test/cmdlineTests/gas_test_dispatch.sol.stdout b/test/cmdlineTests/gas_test_dispatch/output index 117affad..d817e85c 100644 --- a/test/cmdlineTests/gas_test_dispatch.sol.stdout +++ b/test/cmdlineTests/gas_test_dispatch/output @@ -1,5 +1,5 @@ -======= gas_test_dispatch.sol:Large ======= +======= gas_test_dispatch/input.sol:Large ======= Gas estimation: construction: 1034 + 998400 = 999434 @@ -27,7 +27,7 @@ external: g8(uint256): 20721 g9(uint256): 20678 -======= gas_test_dispatch.sol:Medium ======= +======= gas_test_dispatch/input.sol:Medium ======= Gas estimation: construction: 411 + 376600 = 377011 @@ -42,7 +42,7 @@ external: g8(uint256): 20699 g9(uint256): 20655 -======= gas_test_dispatch.sol:Small ======= +======= gas_test_dispatch/input.sol:Small ======= Gas estimation: construction: 153 + 107800 = 107953 diff --git a/test/cmdlineTests/gas_test_dispatch_optimize.sol.args b/test/cmdlineTests/gas_test_dispatch_optimize/args index 814e0591..814e0591 100644 --- a/test/cmdlineTests/gas_test_dispatch_optimize.sol.args +++ b/test/cmdlineTests/gas_test_dispatch_optimize/args diff --git a/test/cmdlineTests/gas_test_dispatch_optimize.sol b/test/cmdlineTests/gas_test_dispatch_optimize/input.sol index a5ca9e7d..a5ca9e7d 100644 --- a/test/cmdlineTests/gas_test_dispatch_optimize.sol +++ b/test/cmdlineTests/gas_test_dispatch_optimize/input.sol diff --git a/test/cmdlineTests/gas_test_dispatch_optimize.sol.stdout b/test/cmdlineTests/gas_test_dispatch_optimize/output index 76fa086e..fd8e9e93 100644 --- a/test/cmdlineTests/gas_test_dispatch_optimize.sol.stdout +++ b/test/cmdlineTests/gas_test_dispatch_optimize/output @@ -1,5 +1,5 @@ -======= gas_test_dispatch_optimize.sol:Large ======= +======= gas_test_dispatch_optimize/input.sol:Large ======= Gas estimation: construction: 300 + 262000 = 262300 @@ -27,7 +27,7 @@ external: g8(uint256): 20980 g9(uint256): 20826 -======= gas_test_dispatch_optimize.sol:Medium ======= +======= gas_test_dispatch_optimize/input.sol:Medium ======= Gas estimation: construction: 190 + 143000 = 143190 @@ -42,7 +42,7 @@ external: g8(uint256): 20870 g9(uint256): 20826 -======= gas_test_dispatch_optimize.sol:Small ======= +======= gas_test_dispatch_optimize/input.sol:Small ======= Gas estimation: construction: 117 + 67400 = 67517 diff --git a/test/cmdlineTests/standard_binaries_requested.json b/test/cmdlineTests/standard_binaries_requested/input.json index 65f19543..65f19543 100644 --- a/test/cmdlineTests/standard_binaries_requested.json +++ b/test/cmdlineTests/standard_binaries_requested/input.json diff --git a/test/cmdlineTests/standard_binaries_requested.json.stdout b/test/cmdlineTests/standard_binaries_requested/output.json index 2baef22a..2baef22a 100644 --- a/test/cmdlineTests/standard_binaries_requested.json.stdout +++ b/test/cmdlineTests/standard_binaries_requested/output.json diff --git a/test/cmdlineTests/standard.json.exit b/test/cmdlineTests/standard_default_success/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard.json.exit +++ b/test/cmdlineTests/standard_default_success/exit diff --git a/test/cmdlineTests/standard.json b/test/cmdlineTests/standard_default_success/input.json index 826253b8..826253b8 100644 --- a/test/cmdlineTests/standard.json +++ b/test/cmdlineTests/standard_default_success/input.json diff --git a/test/cmdlineTests/standard.json.stdout b/test/cmdlineTests/standard_default_success/output.json index 59b90c8c..59b90c8c 100644 --- a/test/cmdlineTests/standard.json.stdout +++ b/test/cmdlineTests/standard_default_success/output.json diff --git a/test/cmdlineTests/standard_methodIdentifiersRequested.json b/test/cmdlineTests/standard_method_identifiers_requested/input.json index 79a3c75d..79a3c75d 100644 --- a/test/cmdlineTests/standard_methodIdentifiersRequested.json +++ b/test/cmdlineTests/standard_method_identifiers_requested/input.json diff --git a/test/cmdlineTests/standard_methodIdentifiersRequested.json.stdout b/test/cmdlineTests/standard_method_identifiers_requested/output.json index 7e3f139f..7e3f139f 100644 --- a/test/cmdlineTests/standard_methodIdentifiersRequested.json.stdout +++ b/test/cmdlineTests/standard_method_identifiers_requested/output.json diff --git a/test/cmdlineTests/standard_only_ast_requested.json b/test/cmdlineTests/standard_only_ast_requested/input.json index 7abd6da5..7abd6da5 100644 --- a/test/cmdlineTests/standard_only_ast_requested.json +++ b/test/cmdlineTests/standard_only_ast_requested/input.json diff --git a/test/cmdlineTests/standard_only_ast_requested.json.stdout b/test/cmdlineTests/standard_only_ast_requested/output.json index b884ab7d..b884ab7d 100644 --- a/test/cmdlineTests/standard_only_ast_requested.json.stdout +++ b/test/cmdlineTests/standard_only_ast_requested/output.json diff --git a/test/cmdlineTests/standard_wrong_key_auxiliary_input.json.exit b/test/cmdlineTests/standard_wrong_key_auxiliary_input/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_key_auxiliary_input.json.exit +++ b/test/cmdlineTests/standard_wrong_key_auxiliary_input/exit diff --git a/test/cmdlineTests/standard_wrong_key_auxiliary_input.json b/test/cmdlineTests/standard_wrong_key_auxiliary_input/input.json index 51dbce41..51dbce41 100644 --- a/test/cmdlineTests/standard_wrong_key_auxiliary_input.json +++ b/test/cmdlineTests/standard_wrong_key_auxiliary_input/input.json diff --git a/test/cmdlineTests/standard_wrong_key_auxiliary_input.json.stdout b/test/cmdlineTests/standard_wrong_key_auxiliary_input/output.json index 077ac47e..077ac47e 100644 --- a/test/cmdlineTests/standard_wrong_key_auxiliary_input.json.stdout +++ b/test/cmdlineTests/standard_wrong_key_auxiliary_input/output.json diff --git a/test/cmdlineTests/standard_wrong_key_metadata.json.exit b/test/cmdlineTests/standard_wrong_key_metadata/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_key_metadata.json.exit +++ b/test/cmdlineTests/standard_wrong_key_metadata/exit diff --git a/test/cmdlineTests/standard_wrong_key_metadata.json b/test/cmdlineTests/standard_wrong_key_metadata/input.json index 490e489a..490e489a 100644 --- a/test/cmdlineTests/standard_wrong_key_metadata.json +++ b/test/cmdlineTests/standard_wrong_key_metadata/input.json diff --git a/test/cmdlineTests/standard_wrong_key_metadata.json.stdout b/test/cmdlineTests/standard_wrong_key_metadata/output.json index 077ac47e..077ac47e 100644 --- a/test/cmdlineTests/standard_wrong_key_metadata.json.stdout +++ b/test/cmdlineTests/standard_wrong_key_metadata/output.json diff --git a/test/cmdlineTests/standard_wrong_key_optimizer.json.exit b/test/cmdlineTests/standard_wrong_key_optimizer/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_key_optimizer.json.exit +++ b/test/cmdlineTests/standard_wrong_key_optimizer/exit diff --git a/test/cmdlineTests/standard_wrong_key_optimizer.json b/test/cmdlineTests/standard_wrong_key_optimizer/input.json index c28c3a92..c28c3a92 100644 --- a/test/cmdlineTests/standard_wrong_key_optimizer.json +++ b/test/cmdlineTests/standard_wrong_key_optimizer/input.json diff --git a/test/cmdlineTests/standard_wrong_key_optimizer.json.stdout b/test/cmdlineTests/standard_wrong_key_optimizer/output.json index 077ac47e..077ac47e 100644 --- a/test/cmdlineTests/standard_wrong_key_optimizer.json.stdout +++ b/test/cmdlineTests/standard_wrong_key_optimizer/output.json diff --git a/test/cmdlineTests/standard_wrong_key_root.json.exit b/test/cmdlineTests/standard_wrong_key_root/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_key_root.json.exit +++ b/test/cmdlineTests/standard_wrong_key_root/exit diff --git a/test/cmdlineTests/standard_wrong_key_root.json b/test/cmdlineTests/standard_wrong_key_root/input.json index 4689c50c..4689c50c 100644 --- a/test/cmdlineTests/standard_wrong_key_root.json +++ b/test/cmdlineTests/standard_wrong_key_root/input.json diff --git a/test/cmdlineTests/standard_wrong_key_root.json.stdout b/test/cmdlineTests/standard_wrong_key_root/output.json index 077ac47e..077ac47e 100644 --- a/test/cmdlineTests/standard_wrong_key_root.json.stdout +++ b/test/cmdlineTests/standard_wrong_key_root/output.json diff --git a/test/cmdlineTests/standard_wrong_key_settings.json.exit b/test/cmdlineTests/standard_wrong_key_settings/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_key_settings.json.exit +++ b/test/cmdlineTests/standard_wrong_key_settings/exit diff --git a/test/cmdlineTests/standard_wrong_key_settings.json b/test/cmdlineTests/standard_wrong_key_settings/input.json index d7809b1c..d7809b1c 100644 --- a/test/cmdlineTests/standard_wrong_key_settings.json +++ b/test/cmdlineTests/standard_wrong_key_settings/input.json diff --git a/test/cmdlineTests/standard_wrong_key_settings.json.stdout b/test/cmdlineTests/standard_wrong_key_settings/output.json index 077ac47e..077ac47e 100644 --- a/test/cmdlineTests/standard_wrong_key_settings.json.stdout +++ b/test/cmdlineTests/standard_wrong_key_settings/output.json diff --git a/test/cmdlineTests/standard_wrong_key_source.json.exit b/test/cmdlineTests/standard_wrong_key_source/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_key_source.json.exit +++ b/test/cmdlineTests/standard_wrong_key_source/exit diff --git a/test/cmdlineTests/standard_wrong_key_source.json b/test/cmdlineTests/standard_wrong_key_source/input.json index d8a8aa16..d8a8aa16 100644 --- a/test/cmdlineTests/standard_wrong_key_source.json +++ b/test/cmdlineTests/standard_wrong_key_source/input.json diff --git a/test/cmdlineTests/standard_wrong_key_source.json.stdout b/test/cmdlineTests/standard_wrong_key_source/output.json index 077ac47e..077ac47e 100644 --- a/test/cmdlineTests/standard_wrong_key_source.json.stdout +++ b/test/cmdlineTests/standard_wrong_key_source/output.json diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input.json.exit b/test/cmdlineTests/standard_wrong_type_auxiliary_input/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input.json.exit +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input/exit diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input.json b/test/cmdlineTests/standard_wrong_type_auxiliary_input/input.json index 8d2c7593..8d2c7593 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input.json +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input/input.json diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input.json.stdout b/test/cmdlineTests/standard_wrong_type_auxiliary_input/output.json index 046cb6d9..046cb6d9 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input/output.json diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses.json.exit b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses.json.exit +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses/exit diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses.json b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses/input.json index 9175050f..9175050f 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses.json +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses/input.json diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses.json.stdout b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses/output.json index 3efaea20..3efaea20 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses/output.json diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member.json.exit b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member.json.exit +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member/exit diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member.json b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member/input.json index aa7d451b..aa7d451b 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member.json +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member/input.json diff --git a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member.json.stdout b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member/output.json index a05176be..a05176be 100644 --- a/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_auxiliary_input_smtlib2responses_member/output.json diff --git a/test/cmdlineTests/standard_wrong_type_metadata.json.exit b/test/cmdlineTests/standard_wrong_type_metadata/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_metadata.json.exit +++ b/test/cmdlineTests/standard_wrong_type_metadata/exit diff --git a/test/cmdlineTests/standard_wrong_type_metadata.json b/test/cmdlineTests/standard_wrong_type_metadata/input.json index d4dd06a1..d4dd06a1 100644 --- a/test/cmdlineTests/standard_wrong_type_metadata.json +++ b/test/cmdlineTests/standard_wrong_type_metadata/input.json diff --git a/test/cmdlineTests/standard_wrong_type_metadata.json.stdout b/test/cmdlineTests/standard_wrong_type_metadata/output.json index 7b997cec..7b997cec 100644 --- a/test/cmdlineTests/standard_wrong_type_metadata.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_metadata/output.json diff --git a/test/cmdlineTests/standard_wrong_type_optimizer.json.exit b/test/cmdlineTests/standard_wrong_type_optimizer/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_optimizer.json.exit +++ b/test/cmdlineTests/standard_wrong_type_optimizer/exit diff --git a/test/cmdlineTests/standard_wrong_type_optimizer.json b/test/cmdlineTests/standard_wrong_type_optimizer/input.json index b42ca550..b42ca550 100644 --- a/test/cmdlineTests/standard_wrong_type_optimizer.json +++ b/test/cmdlineTests/standard_wrong_type_optimizer/input.json diff --git a/test/cmdlineTests/standard_wrong_type_optimizer.json.stdout b/test/cmdlineTests/standard_wrong_type_optimizer/output.json index d43b6470..d43b6470 100644 --- a/test/cmdlineTests/standard_wrong_type_optimizer.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_optimizer/output.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection.json.exit b/test/cmdlineTests/standard_wrong_type_output_selection/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection.json.exit +++ b/test/cmdlineTests/standard_wrong_type_output_selection/exit diff --git a/test/cmdlineTests/standard_wrong_type_output_selection.json b/test/cmdlineTests/standard_wrong_type_output_selection/input.json index a7b615d1..a7b615d1 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection.json +++ b/test/cmdlineTests/standard_wrong_type_output_selection/input.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection.json.stdout b/test/cmdlineTests/standard_wrong_type_output_selection/output.json index 39e74882..39e74882 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_output_selection/output.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_contract.json.exit b/test/cmdlineTests/standard_wrong_type_output_selection_contract/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_contract.json.exit +++ b/test/cmdlineTests/standard_wrong_type_output_selection_contract/exit diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_contract.json b/test/cmdlineTests/standard_wrong_type_output_selection_contract/input.json index 9840a97e..9840a97e 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_contract.json +++ b/test/cmdlineTests/standard_wrong_type_output_selection_contract/input.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_contract.json.stdout b/test/cmdlineTests/standard_wrong_type_output_selection_contract/output.json index a4ba320e..a4ba320e 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_contract.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_output_selection_contract/output.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_file.json.exit b/test/cmdlineTests/standard_wrong_type_output_selection_file/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_file.json.exit +++ b/test/cmdlineTests/standard_wrong_type_output_selection_file/exit diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_file.json b/test/cmdlineTests/standard_wrong_type_output_selection_file/input.json index 7ab12ba8..7ab12ba8 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_file.json +++ b/test/cmdlineTests/standard_wrong_type_output_selection_file/input.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_file.json.stdout b/test/cmdlineTests/standard_wrong_type_output_selection_file/output.json index 8874e636..8874e636 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_file.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_output_selection_file/output.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_output.json.exit b/test/cmdlineTests/standard_wrong_type_output_selection_output/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_output.json.exit +++ b/test/cmdlineTests/standard_wrong_type_output_selection_output/exit diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_output.json b/test/cmdlineTests/standard_wrong_type_output_selection_output/input.json index 3e5cd661..3e5cd661 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_output.json +++ b/test/cmdlineTests/standard_wrong_type_output_selection_output/input.json diff --git a/test/cmdlineTests/standard_wrong_type_output_selection_output.json.stdout b/test/cmdlineTests/standard_wrong_type_output_selection_output/output.json index a4ba320e..a4ba320e 100644 --- a/test/cmdlineTests/standard_wrong_type_output_selection_output.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_output_selection_output/output.json diff --git a/test/cmdlineTests/standard_wrong_type_remappings.json.exit b/test/cmdlineTests/standard_wrong_type_remappings/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_remappings.json.exit +++ b/test/cmdlineTests/standard_wrong_type_remappings/exit diff --git a/test/cmdlineTests/standard_wrong_type_remappings.json b/test/cmdlineTests/standard_wrong_type_remappings/input.json index 1436e014..1436e014 100644 --- a/test/cmdlineTests/standard_wrong_type_remappings.json +++ b/test/cmdlineTests/standard_wrong_type_remappings/input.json diff --git a/test/cmdlineTests/standard_wrong_type_remappings.json.stdout b/test/cmdlineTests/standard_wrong_type_remappings/output.json index b5e4ea5c..b5e4ea5c 100644 --- a/test/cmdlineTests/standard_wrong_type_remappings.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_remappings/output.json diff --git a/test/cmdlineTests/standard_wrong_type_remappings_entry.json.exit b/test/cmdlineTests/standard_wrong_type_remappings_entry/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_remappings_entry.json.exit +++ b/test/cmdlineTests/standard_wrong_type_remappings_entry/exit diff --git a/test/cmdlineTests/standard_wrong_type_remappings_entry.json b/test/cmdlineTests/standard_wrong_type_remappings_entry/input.json index c96611f3..c96611f3 100644 --- a/test/cmdlineTests/standard_wrong_type_remappings_entry.json +++ b/test/cmdlineTests/standard_wrong_type_remappings_entry/input.json diff --git a/test/cmdlineTests/standard_wrong_type_remappings_entry.json.stdout b/test/cmdlineTests/standard_wrong_type_remappings_entry/output.json index 0fc71ded..0fc71ded 100644 --- a/test/cmdlineTests/standard_wrong_type_remappings_entry.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_remappings_entry/output.json diff --git a/test/cmdlineTests/standard_wrong_type_root.json.exit b/test/cmdlineTests/standard_wrong_type_root/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_root.json.exit +++ b/test/cmdlineTests/standard_wrong_type_root/exit diff --git a/test/cmdlineTests/standard_wrong_type_root.json b/test/cmdlineTests/standard_wrong_type_root/input.json index 4763607a..4763607a 100644 --- a/test/cmdlineTests/standard_wrong_type_root.json +++ b/test/cmdlineTests/standard_wrong_type_root/input.json diff --git a/test/cmdlineTests/standard_wrong_type_root.json.stdout b/test/cmdlineTests/standard_wrong_type_root/output.json index 15c12e77..15c12e77 100644 --- a/test/cmdlineTests/standard_wrong_type_root.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_root/output.json diff --git a/test/cmdlineTests/standard_wrong_type_settings.json.exit b/test/cmdlineTests/standard_wrong_type_settings/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_settings.json.exit +++ b/test/cmdlineTests/standard_wrong_type_settings/exit diff --git a/test/cmdlineTests/standard_wrong_type_settings.json b/test/cmdlineTests/standard_wrong_type_settings/input.json index 7cdb0881..7cdb0881 100644 --- a/test/cmdlineTests/standard_wrong_type_settings.json +++ b/test/cmdlineTests/standard_wrong_type_settings/input.json diff --git a/test/cmdlineTests/standard_wrong_type_settings.json.stdout b/test/cmdlineTests/standard_wrong_type_settings/output.json index c78c6086..c78c6086 100644 --- a/test/cmdlineTests/standard_wrong_type_settings.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_settings/output.json diff --git a/test/cmdlineTests/standard_wrong_type_source.json.exit b/test/cmdlineTests/standard_wrong_type_source/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_source.json.exit +++ b/test/cmdlineTests/standard_wrong_type_source/exit diff --git a/test/cmdlineTests/standard_wrong_type_source.json b/test/cmdlineTests/standard_wrong_type_source/input.json index d58504fe..d58504fe 100644 --- a/test/cmdlineTests/standard_wrong_type_source.json +++ b/test/cmdlineTests/standard_wrong_type_source/input.json diff --git a/test/cmdlineTests/standard_wrong_type_source.json.stdout b/test/cmdlineTests/standard_wrong_type_source/output.json index 98fe32fd..98fe32fd 100644 --- a/test/cmdlineTests/standard_wrong_type_source.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_source/output.json diff --git a/test/cmdlineTests/standard_wrong_type_sources.json.exit b/test/cmdlineTests/standard_wrong_type_sources/exit index 573541ac..573541ac 100644 --- a/test/cmdlineTests/standard_wrong_type_sources.json.exit +++ b/test/cmdlineTests/standard_wrong_type_sources/exit diff --git a/test/cmdlineTests/standard_wrong_type_sources.json b/test/cmdlineTests/standard_wrong_type_sources/input.json index 76e1ae7d..76e1ae7d 100644 --- a/test/cmdlineTests/standard_wrong_type_sources.json +++ b/test/cmdlineTests/standard_wrong_type_sources/input.json diff --git a/test/cmdlineTests/standard_wrong_type_sources.json.stdout b/test/cmdlineTests/standard_wrong_type_sources/output.json index ac6c613f..ac6c613f 100644 --- a/test/cmdlineTests/standard_wrong_type_sources.json.stdout +++ b/test/cmdlineTests/standard_wrong_type_sources/output.json diff --git a/test/cmdlineTests/too_long_line.sol.err b/test/cmdlineTests/too_long_line/err index 55cd1935..bfbc8e1e 100644 --- a/test/cmdlineTests/too_long_line.sol.err +++ b/test/cmdlineTests/too_long_line/err @@ -1,6 +1,6 @@ -too_long_line.sol:1:1: Warning: Source file does not specify required compiler version! +too_long_line/input.sol:1:1: Warning: Source file does not specify required compiler version! contract C { ^ (Relevant source part starts here and spans across multiple lines). -too_long_line.sol:2:164: Error: Identifier not found or not unique. +too_long_line/input.sol:2:164: Error: Identifier not found or not unique. ... ffffffffffffffffffffffffffffffffff(announcementType Type, string Announcement, string ... ^--------------^ diff --git a/test/cmdlineTests/too_long_line.sol.exit b/test/cmdlineTests/too_long_line/exit index d00491fd..d00491fd 100644 --- a/test/cmdlineTests/too_long_line.sol.exit +++ b/test/cmdlineTests/too_long_line/exit diff --git a/test/cmdlineTests/too_long_line.sol b/test/cmdlineTests/too_long_line/input.sol index 7df1057a..7df1057a 100644 --- a/test/cmdlineTests/too_long_line.sol +++ b/test/cmdlineTests/too_long_line/input.sol diff --git a/test/cmdlineTests/too_long_line_both_sides_short.sol.err b/test/cmdlineTests/too_long_line_both_sides_short/err index 9a5ebfba..2868fcb1 100644 --- a/test/cmdlineTests/too_long_line_both_sides_short.sol.err +++ b/test/cmdlineTests/too_long_line_both_sides_short/err @@ -1,6 +1,6 @@ -too_long_line_both_sides_short.sol:1:1: Warning: Source file does not specify required compiler version! +too_long_line_both_sides_short/input.sol:1:1: Warning: Source file does not specify required compiler version! contract C { ^ (Relevant source part starts here and spans across multiple lines). -too_long_line_both_sides_short.sol:2:15: Error: Identifier not found or not unique. +too_long_line_both_sides_short/input.sol:2:15: Error: Identifier not found or not unique. function f(announcementTypeXXXXXXXXXXXXXXXXXXX ... XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Type, ^-------------------------------------------------------------------------^ diff --git a/test/cmdlineTests/too_long_line_both_sides_short.sol.exit b/test/cmdlineTests/too_long_line_both_sides_short/exit index d00491fd..d00491fd 100644 --- a/test/cmdlineTests/too_long_line_both_sides_short.sol.exit +++ b/test/cmdlineTests/too_long_line_both_sides_short/exit diff --git a/test/cmdlineTests/too_long_line_both_sides_short.sol b/test/cmdlineTests/too_long_line_both_sides_short/input.sol index 062f0292..062f0292 100644 --- a/test/cmdlineTests/too_long_line_both_sides_short.sol +++ b/test/cmdlineTests/too_long_line_both_sides_short/input.sol diff --git a/test/cmdlineTests/too_long_line_edge_in.sol.err b/test/cmdlineTests/too_long_line_edge_in/err index ad3b7805..626451e1 100644 --- a/test/cmdlineTests/too_long_line_edge_in.sol.err +++ b/test/cmdlineTests/too_long_line_edge_in/err @@ -1,6 +1,6 @@ -too_long_line_edge_in.sol:1:1: Warning: Source file does not specify required compiler version! +too_long_line_edge_in/input.sol:1:1: Warning: Source file does not specify required compiler version! contract C { ^ (Relevant source part starts here and spans across multiple lines). -too_long_line_edge_in.sol:2:36: Error: Identifier not found or not unique. +too_long_line_edge_in/input.sol:2:36: Error: Identifier not found or not unique. function ffffffffffffffffffffff(announcementTypeTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT Ty, string A) onlyOwner external { ^----------------------------------------------------------------------------------------------^ diff --git a/test/cmdlineTests/too_long_line_edge_in.sol.exit b/test/cmdlineTests/too_long_line_edge_in/exit index d00491fd..d00491fd 100644 --- a/test/cmdlineTests/too_long_line_edge_in.sol.exit +++ b/test/cmdlineTests/too_long_line_edge_in/exit diff --git a/test/cmdlineTests/too_long_line_edge_in.sol b/test/cmdlineTests/too_long_line_edge_in/input.sol index 6f181c83..6f181c83 100644 --- a/test/cmdlineTests/too_long_line_edge_in.sol +++ b/test/cmdlineTests/too_long_line_edge_in/input.sol diff --git a/test/cmdlineTests/too_long_line_edge_out.sol.err b/test/cmdlineTests/too_long_line_edge_out/err index d8495c11..7a4f935a 100644 --- a/test/cmdlineTests/too_long_line_edge_out.sol.err +++ b/test/cmdlineTests/too_long_line_edge_out/err @@ -1,6 +1,6 @@ -too_long_line_edge_out.sol:1:1: Warning: Source file does not specify required compiler version! +too_long_line_edge_out/input.sol:1:1: Warning: Source file does not specify required compiler version! contract C { ^ (Relevant source part starts here and spans across multiple lines). -too_long_line_edge_out.sol:2:37: Error: Identifier not found or not unique. +too_long_line_edge_out/input.sol:2:37: Error: Identifier not found or not unique. ... function fffffffffffffffffffffff(announcementTypeTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT Typ, string A) onlyOwner external ... ^----------------------------------------------------------------------------------------------^ diff --git a/test/cmdlineTests/too_long_line_edge_out.sol.exit b/test/cmdlineTests/too_long_line_edge_out/exit index d00491fd..d00491fd 100644 --- a/test/cmdlineTests/too_long_line_edge_out.sol.exit +++ b/test/cmdlineTests/too_long_line_edge_out/exit diff --git a/test/cmdlineTests/too_long_line_edge_out.sol b/test/cmdlineTests/too_long_line_edge_out/input.sol index 29d3cee6..29d3cee6 100644 --- a/test/cmdlineTests/too_long_line_edge_out.sol +++ b/test/cmdlineTests/too_long_line_edge_out/input.sol diff --git a/test/cmdlineTests/too_long_line_left_short.sol.err b/test/cmdlineTests/too_long_line_left_short.sol.err deleted file mode 100644 index 00b6be5c..00000000 --- a/test/cmdlineTests/too_long_line_left_short.sol.err +++ /dev/null @@ -1,6 +0,0 @@ -too_long_line_left_short.sol:1:1: Warning: Source file does not specify required compiler version! -contract C { -^ (Relevant source part starts here and spans across multiple lines). -too_long_line_left_short.sol:2:15: Error: Identifier not found or not unique. - function f(announcementType Type, string Announcement, string ... - ^--------------^ diff --git a/test/cmdlineTests/too_long_line_left_short/err b/test/cmdlineTests/too_long_line_left_short/err new file mode 100644 index 00000000..4aa830b6 --- /dev/null +++ b/test/cmdlineTests/too_long_line_left_short/err @@ -0,0 +1,6 @@ +too_long_line_left_short/input.sol:1:1: Warning: Source file does not specify required compiler version! +contract C { +^ (Relevant source part starts here and spans across multiple lines). +too_long_line_left_short/input.sol:2:15: Error: Identifier not found or not unique. + function f(announcementType Type, string Announcement, string ... + ^--------------^ diff --git a/test/cmdlineTests/too_long_line_left_short.sol.exit b/test/cmdlineTests/too_long_line_left_short/exit index d00491fd..d00491fd 100644 --- a/test/cmdlineTests/too_long_line_left_short.sol.exit +++ b/test/cmdlineTests/too_long_line_left_short/exit diff --git a/test/cmdlineTests/too_long_line_left_short.sol b/test/cmdlineTests/too_long_line_left_short/input.sol index 2accfcce..2accfcce 100644 --- a/test/cmdlineTests/too_long_line_left_short.sol +++ b/test/cmdlineTests/too_long_line_left_short/input.sol diff --git a/test/cmdlineTests/too_long_line_multiline.sol b/test/cmdlineTests/too_long_line_multiline.sol new file mode 100644 index 00000000..6609e125 --- /dev/null +++ b/test/cmdlineTests/too_long_line_multiline.sol @@ -0,0 +1,13 @@ +contract C { + function f() returns (byte _b, bytes2 _b2, bytes3 _b3, bytes memory _blit, bytes5 _b5, bytes6 _b6, string memory _str, bytes7 _b7, bytes22 _b22, bytes32 _b32) { + _b = 0x12; + _b2 = 0x1223; + _b5 = hex"043245"; + _b6 = hex"2345532532"; + _b7 = hex"03252353253253"; + _b22 = hex"325235235325325325235325"; + _b32 = hex"032523532532523532523532523532"; + _blit = hex"123498"; + _str = "heidy"; + } +} diff --git a/test/cmdlineTests/too_long_line_multiline.sol.err b/test/cmdlineTests/too_long_line_multiline.sol.err new file mode 100644 index 00000000..d7412ffe --- /dev/null +++ b/test/cmdlineTests/too_long_line_multiline.sol.err @@ -0,0 +1,6 @@ +too_long_line_multiline.sol:2:5: Error: No visibility specified. Did you intend to add "public"? + function f() returns (byte _b, byte ... _b7, bytes22 _b22, bytes32 _b32) { + ^ (Relevant source part starts here and spans across multiple lines). +too_long_line_multiline.sol:1:1: Warning: Source file does not specify required compiler version! +contract C { +^ (Relevant source part starts here and spans across multiple lines). diff --git a/test/cmdlineTests/too_long_line_right_short.sol.exit b/test/cmdlineTests/too_long_line_multiline.sol.exit index d00491fd..d00491fd 100644 --- a/test/cmdlineTests/too_long_line_right_short.sol.exit +++ b/test/cmdlineTests/too_long_line_multiline.sol.exit diff --git a/test/cmdlineTests/too_long_line_right_short.sol.err b/test/cmdlineTests/too_long_line_right_short/err index 88072d95..ed992565 100644 --- a/test/cmdlineTests/too_long_line_right_short.sol.err +++ b/test/cmdlineTests/too_long_line_right_short/err @@ -1,6 +1,6 @@ -too_long_line_right_short.sol:1:1: Warning: Source file does not specify required compiler version! +too_long_line_right_short/input.sol:1:1: Warning: Source file does not specify required compiler version! contract C { ^ (Relevant source part starts here and spans across multiple lines). -too_long_line_right_short.sol:2:164: Error: Identifier not found or not unique. +too_long_line_right_short/input.sol:2:164: Error: Identifier not found or not unique. ... ffffffffffffffffffffffffffffffffff(announcementType Type, ^--------------^ diff --git a/test/cmdlineTests/too_long_line_right_short/exit b/test/cmdlineTests/too_long_line_right_short/exit new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/test/cmdlineTests/too_long_line_right_short/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/too_long_line_right_short.sol b/test/cmdlineTests/too_long_line_right_short/input.sol index 936b3961..936b3961 100644 --- a/test/cmdlineTests/too_long_line_right_short.sol +++ b/test/cmdlineTests/too_long_line_right_short/input.sol diff --git a/test/externalTests.sh b/test/externalTests.sh index 16f9e55a..126b13b5 100755 --- a/test/externalTests.sh +++ b/test/externalTests.sh @@ -55,13 +55,13 @@ function test_truffle cd "$DIR" echo "Current commit hash: `git rev-parse HEAD`" npm install - # Replace solc package by master + # Replace solc package by v0.5.0 for d in node_modules node_modules/truffle/node_modules do ( cd $d rm -rf solc - git clone --depth 1 https://github.com/ethereum/solc-js.git solc + git clone --depth 1 -b v0.5.0 https://github.com/ethereum/solc-js.git solc cp "$SOLJSON" solc/ ) done diff --git a/test/libsolidity/ASTJSON/address_payable.json b/test/libsolidity/ASTJSON/address_payable.json index 0f30e8e8..c8b71628 100644 --- a/test/libsolidity/ASTJSON/address_payable.json +++ b/test/libsolidity/ASTJSON/address_payable.json @@ -277,7 +277,7 @@ "name" : "this", "nodeType" : "Identifier", "overloadedDeclarations" : [], - "referencedDeclaration" : 65, + "referencedDeclaration" : 66, "src" : "217:4:1", "typeDescriptions" : { diff --git a/test/libsolidity/ASTJSON/address_payable_legacy.json b/test/libsolidity/ASTJSON/address_payable_legacy.json index dd8a5582..8abebce0 100644 --- a/test/libsolidity/ASTJSON/address_payable_legacy.json +++ b/test/libsolidity/ASTJSON/address_payable_legacy.json @@ -424,7 +424,7 @@ [ null ], - "referencedDeclaration" : 65, + "referencedDeclaration" : 66, "type" : "contract C", "value" : "this" }, diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index baa9bff1..b5a1797b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -46,6 +46,7 @@ namespace dev { namespace solidity { +class Contract; namespace test { @@ -84,7 +85,7 @@ eth::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { Compiler compiler(dev::test::Options::get().evmVersion()); - compiler.compileContract(*contract, map<ContractDefinition const*, Assembly const*>{}, bytes()); + compiler.compileContract(*contract, map<ContractDefinition const*, shared_ptr<Compiler const>>{}, bytes()); return compiler.runtimeAssemblyItems(); } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index b6986041..92e4dcf6 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -316,7 +316,7 @@ BOOST_AUTO_TEST_CASE(switch_no_cases) BOOST_AUTO_TEST_CASE(switch_duplicate_case) { - CHECK_PARSE_ERROR("{ switch 42 case 1 {} case 1 {} default {} }", DeclarationError, "Duplicate case defined"); + CHECK_PARSE_ERROR("{ switch 42 case 1 {} case 1 {} default {} }", DeclarationError, "Duplicate case defined."); } BOOST_AUTO_TEST_CASE(switch_invalid_expression) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8d219d16..38be5ae7 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -14234,6 +14234,113 @@ BOOST_AUTO_TEST_CASE(base_access_to_function_type_variables) ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); } +BOOST_AUTO_TEST_CASE(code_access) +{ + char const* sourceCode = R"( + contract C { + function lengths() public pure returns (bool) { + uint crLen = type(D).creationCode.length; + uint runLen = type(D).runtimeCode.length; + require(runLen < crLen); + require(crLen >= 0x20); + require(runLen >= 0x20); + return true; + } + function creation() public pure returns (bytes memory) { + return type(D).creationCode; + } + function runtime() public pure returns (bytes memory) { + return type(D).runtimeCode; + } + function runtimeAllocCheck() public pure returns (bytes memory) { + uint[] memory a = new uint[](2); + bytes memory c = type(D).runtimeCode; + uint[] memory b = new uint[](2); + a[0] = 0x1111; + a[1] = 0x2222; + b[0] = 0x3333; + b[1] = 0x4444; + return c; + } + } + contract D { + function f() public pure returns (uint) { return 7; } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("lengths()"), encodeArgs(true)); + bytes codeCreation = callContractFunction("creation()"); + bytes codeRuntime1 = callContractFunction("runtime()"); + bytes codeRuntime2 = callContractFunction("runtimeAllocCheck()"); + ABI_CHECK(codeRuntime1, codeRuntime2); +} + +BOOST_AUTO_TEST_CASE(code_access_create) +{ + char const* sourceCode = R"( + contract C { + function test() public returns (uint) { + bytes memory c = type(D).creationCode; + D d; + assembly { + d := create(0, add(c, 0x20), mload(c)) + } + return d.f(); + } + } + contract D { + uint x; + constructor() public { x = 7; } + function f() public view returns (uint) { return x; } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs(7)); +} + +BOOST_AUTO_TEST_CASE(code_access_content) +{ + char const* sourceCode = R"( + contract C { + function testRuntime() public returns (bool) { + D d = new D(); + bytes32 runtimeHash = keccak256(type(D).runtimeCode); + bytes32 otherHash; + uint size; + assembly { + size := extcodesize(d) + extcodecopy(d, mload(0x40), 0, size) + otherHash := keccak256(mload(0x40), size) + } + require(size == type(D).runtimeCode.length); + require(runtimeHash == otherHash); + return true; + } + function testCreation() public returns (bool) { + D d = new D(); + bytes32 creationHash = keccak256(type(D).creationCode); + require(creationHash == d.x()); + return true; + } + } + contract D { + bytes32 public x; + constructor() public { + bytes32 codeHash; + assembly { + let size := codesize() + codecopy(mload(0x40), 0, size) + codeHash := keccak256(mload(0x40), size) + } + x = codeHash; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("testRuntime()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("testCreation()"), encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 774f67fe..0470cf4c 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -198,7 +198,7 @@ BOOST_AUTO_TEST_CASE(enum_external_type) } } -BOOST_AUTO_TEST_CASE(external_structs) +BOOST_AUTO_TEST_CASE(external_struct_signatures) { char const* text = R"( pragma experimental ABIEncoderV2; @@ -213,7 +213,10 @@ BOOST_AUTO_TEST_CASE(external_structs) function i(Nested[] calldata) external {} } )"; - SourceUnit const* sourceUnit = parseAndAnalyse(text); + // Ignore analysis errors. This test only checks that correct signatures + // are generated for external structs, but they are not yet supported + // in code generation and therefore cause an error in the TypeChecker. + SourceUnit const* sourceUnit = parseAnalyseAndReturnError(text, false, true, true).first; for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { @@ -226,7 +229,7 @@ BOOST_AUTO_TEST_CASE(external_structs) } } -BOOST_AUTO_TEST_CASE(external_structs_in_libraries) +BOOST_AUTO_TEST_CASE(external_struct_signatures_in_libraries) { char const* text = R"( pragma experimental ABIEncoderV2; @@ -241,7 +244,10 @@ BOOST_AUTO_TEST_CASE(external_structs_in_libraries) function i(Nested[] calldata) external {} } )"; - SourceUnit const* sourceUnit = parseAndAnalyse(text); + // Ignore analysis errors. This test only checks that correct signatures + // are generated for external structs, but calldata structs are not yet supported + // in code generation and therefore cause an error in the TypeChecker. + SourceUnit const* sourceUnit = parseAnalyseAndReturnError(text, false, true, true).first; for (ASTPointer<ASTNode> const& node: sourceUnit->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index a33c6134..413a8c9c 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -539,7 +539,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved) "supports", "switch", "try", - "type", "typedef", "typeof", "unchecked" diff --git a/test/libsolidity/smtCheckerTests/complex/warn_on_typecast.sol b/test/libsolidity/smtCheckerTests/complex/warn_on_typecast.sol index be785414..8ecfe41e 100644 --- a/test/libsolidity/smtCheckerTests/complex/warn_on_typecast.sol +++ b/test/libsolidity/smtCheckerTests/complex/warn_on_typecast.sol @@ -5,4 +5,4 @@ contract C { } } // ---- -// Warning: (106-114): Assertion checker does not yet implement this expression. +// Warning: (106-114): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/functions/function_call_does_not_clear_local_vars.sol b/test/libsolidity/smtCheckerTests/functions/function_call_does_not_clear_local_vars.sol index b4260224..0ceb3b46 100644 --- a/test/libsolidity/smtCheckerTests/functions/function_call_does_not_clear_local_vars.sol +++ b/test/libsolidity/smtCheckerTests/functions/function_call_does_not_clear_local_vars.sol @@ -9,5 +9,4 @@ contract C { } } // ---- -// Warning: (99-107): Assertion checker does not yet implement this type of function call. // Warning: (141-144): Assertion checker does not support recursive function calls. diff --git a/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol b/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol new file mode 100644 index 00000000..16482e7a --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_1.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract D +{ + function g(uint x) public; +} + +contract C +{ + uint x; + function f(uint y, D d) public { + require(x == y); + assert(x == y); + d.g(y); + // Storage knowledge is cleared after an external call. + assert(x == y); + } +} +// ---- +// Warning: (119-122): Assertion checker does not yet support the type of this variable. +// Warning: (240-254): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol b/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol new file mode 100644 index 00000000..1e704c9d --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_2.sol @@ -0,0 +1,21 @@ +pragma experimental SMTChecker; + +contract D +{ + function g(uint x) public; +} + +contract C +{ + mapping (uint => uint) map; + function f(uint y, D d) public { + require(map[0] == map[1]); + assert(map[0] == map[1]); + d.g(y); + // Storage knowledge is cleared after an external call. + assert(map[0] == map[1]); + } +} +// ---- +// Warning: (139-142): Assertion checker does not yet support the type of this variable. +// Warning: (280-304): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol b/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol new file mode 100644 index 00000000..dd36ec73 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/functions_external_3.sol @@ -0,0 +1,22 @@ +pragma experimental SMTChecker; + +contract D +{ + function g(uint x) public; +} + +contract C +{ + mapping (uint => uint) storageMap; + function f(uint y, D d) public { + mapping (uint => uint) storage map = storageMap; + require(map[0] == map[1]); + assert(map[0] == map[1]); + d.g(y); + // Storage knowledge is cleared after an external call. + assert(map[0] == map[1]); + } +} +// ---- +// Warning: (146-149): Assertion checker does not yet support the type of this variable. +// Warning: (338-362): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol b/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol index ad45d076..f122f4f2 100644 --- a/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol +++ b/test/libsolidity/smtCheckerTests/special/msg_sender_2.sol @@ -10,5 +10,4 @@ contract C } } // ---- -// Warning: (98-108): Assertion checker does not yet implement this expression. -// Warning: (98-108): Internal error: Expression undefined for SMT solver. +// Warning: (98-108): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol b/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol new file mode 100644 index 00000000..885118a5 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_address_1.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(address a) public pure { + require(a != address(0)); + assert(a != address(0)); + } +} +// ---- +// Warning: (98-108): Type conversion is not yet fully supported and might yield false positives. +// Warning: (125-135): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_different_size_1.sol b/test/libsolidity/smtCheckerTests/typecast/cast_different_size_1.sol new file mode 100644 index 00000000..baacaef1 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_different_size_1.sol @@ -0,0 +1,26 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + bytes2 a = 0x1234; + uint32 b = uint16(a); // b will be 0x00001234 + assert(b == 0x1234); + uint32 c = uint32(bytes4(a)); // c will be 0x12340000 + // This fails because right padding is not supported. + assert(c == 0x12340000); + uint8 d = uint8(uint16(a)); // d will be 0x34 + // False positive since truncating is not supported yet. + assert(d == 0x34); + uint8 e = uint8(bytes1(a)); // e will be 0x12 + // False positive since truncating is not supported yet. + assert(e == 0x12); + } +} +// ---- +// Warning: (186-195): Type conversion is not yet fully supported and might yield false positives. +// Warning: (280-303): Assertion violation happens here +// Warning: (317-333): Type conversion is not yet fully supported and might yield false positives. +// Warning: (414-431): Assertion violation happens here +// Warning: (451-460): Type conversion is not yet fully supported and might yield false positives. +// Warning: (542-559): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_larger_1.sol b/test/libsolidity/smtCheckerTests/typecast/cast_larger_1.sol new file mode 100644 index 00000000..4b0f42e7 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_larger_1.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint8 x) public pure { + uint16 y = uint16(x); + // True because of x's type + assert(y < 300); + } +} +// ---- +// Warning: (94-103): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_larger_2.sol b/test/libsolidity/smtCheckerTests/typecast/cast_larger_2.sol new file mode 100644 index 00000000..1f981c8c --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_larger_2.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint16 a = 0x1234; + uint32 b = uint32(a); // b will be 0x00001234 now + // This is correct (left padding). + assert(a == b); + } +} +// ---- +// Warning: (108-117): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_larger_2_fail.sol b/test/libsolidity/smtCheckerTests/typecast/cast_larger_2_fail.sol new file mode 100644 index 00000000..7f707381 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_larger_2_fail.sol @@ -0,0 +1,13 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint16 a = 0x1234; + uint32 b = uint32(a); // b will be 0x00001234 now + assert(a != b); + } +} +// ---- +// Warning: (108-117): Type conversion is not yet fully supported and might yield false positives. +// Warning: (149-163): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_larger_3.sol b/test/libsolidity/smtCheckerTests/typecast/cast_larger_3.sol new file mode 100644 index 00000000..cc51639e --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_larger_3.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + bytes2 a = 0x1234; + bytes4 b = bytes4(a); // b will be 0x12340000 + // False positive since right padding is not supported yet. + assert(b == 0x12340000); + // This should fail (right padding). + assert(a == b); + } +} +// ---- +// Warning: (108-117): Type conversion is not yet fully supported and might yield false positives. +// Warning: (207-230): Assertion violation happens here +// Warning: (273-287): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_smaller_1.sol b/test/libsolidity/smtCheckerTests/typecast/cast_smaller_1.sol new file mode 100644 index 00000000..3e964dfd --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_smaller_1.sol @@ -0,0 +1,12 @@ +pragma experimental SMTChecker; + +contract C +{ + function f(uint16 x) public pure { + uint8 y = uint8(x); + // True because of y's type + assert(y < 300); + } +} +// ---- +// Warning: (94-102): Type conversion is not yet fully supported and might yield false positives. diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_smaller_2.sol b/test/libsolidity/smtCheckerTests/typecast/cast_smaller_2.sol new file mode 100644 index 00000000..25270108 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_smaller_2.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + uint32 a = 0x12345678; + uint16 b = uint16(a); // b will be 0x5678 now + // False positive since truncation is not supported yet. + assert(b == 0x5678); + } +} +// ---- +// Warning: (112-121): Type conversion is not yet fully supported and might yield false positives. +// Warning: (208-227): Assertion violation happens here diff --git a/test/libsolidity/smtCheckerTests/typecast/cast_smaller_3.sol b/test/libsolidity/smtCheckerTests/typecast/cast_smaller_3.sol new file mode 100644 index 00000000..1c9ea545 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/typecast/cast_smaller_3.sol @@ -0,0 +1,14 @@ +pragma experimental SMTChecker; + +contract C +{ + function f() public pure { + bytes2 a = 0x1234; + bytes1 b = bytes1(a); // b will be 0x12 + // False positive since truncation is not supported yet. + assert(b == 0x12); + } +} +// ---- +// Warning: (108-117): Type conversion is not yet fully supported and might yield false positives. +// Warning: (198-215): Assertion violation happens here diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol index b868d61d..c7e3eacd 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol @@ -46,7 +46,11 @@ contract C { } // ---- // TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment. +// Warning: (146-151): Unreachable code. +// Warning: (169-174): Unreachable code. // TypeError: (223-234): This variable is of storage pointer type and can be returned without prior assignment. +// Warning: (316-321): Unreachable code. // TypeError: (440-451): This variable is of storage pointer type and can be returned without prior assignment. // TypeError: (654-665): This variable is of storage pointer type and can be returned without prior assignment. // TypeError: (871-882): This variable is of storage pointer type and can be returned without prior assignment. +// Warning: (933-938): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol index 55c5edd3..5a113668 100644 --- a/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol +++ b/test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol @@ -29,3 +29,4 @@ contract C { } } // ---- +// Warning: (567-572): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol index 96767402..da7f1a90 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol @@ -5,4 +5,6 @@ contract C { revert(); b; } -}
\ No newline at end of file +} +// ---- +// Warning: (125-126): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol index b941ad34..9ebd0321 100644 --- a/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol +++ b/test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol @@ -8,3 +8,4 @@ contract C { } } // ---- +// Warning: (112-135): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/comment_fine.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/comment_fine.sol new file mode 100644 index 00000000..17119ca6 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/comment_fine.sol @@ -0,0 +1,6 @@ +contract C { + function f() public pure { + return; + // unreachable comment + } +} diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/constant_condition.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/constant_condition.sol new file mode 100644 index 00000000..e00398bc --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/constant_condition.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + if (false) { + return; // unreachable, but not yet detected + } + return; + } +} diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/do_while_continue.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/do_while_continue.sol new file mode 100644 index 00000000..363c53e1 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/do_while_continue.sol @@ -0,0 +1,12 @@ +contract C { + function f() public pure { + do { + uint a = 42; a; + continue; + return; // this is unreachable + } while(false); + return; // this is still reachable + } +} +// ---- +// Warning: (119-126): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/double_return.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/double_return.sol new file mode 100644 index 00000000..9b755347 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/double_return.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure returns (uint) { + return 0; + return 0; + } +} +// ---- +// Warning: (85-93): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/double_revert.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/double_revert.sol new file mode 100644 index 00000000..a6457e4f --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/double_revert.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + revert(); + revert(); + } +} +// ---- +// Warning: (70-78): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/for_break.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/for_break.sol new file mode 100644 index 00000000..496addb2 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/for_break.sol @@ -0,0 +1,12 @@ +contract C { + function f() public pure { + for (uint a = 0; a < 1; a++) { + break; + uint b = 42; b; + } + return; + } +} +// ---- +// Warning: (76-79): Unreachable code. +// Warning: (114-128): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/if_both_return.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/if_both_return.sol new file mode 100644 index 00000000..3513b17d --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/if_both_return.sol @@ -0,0 +1,12 @@ +contract C { + function f(bool c) public pure { + if (c) { + return; + } else { + return; + } + return; // unreachable + } +} +// ---- +// Warning: (142-149): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/revert.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/revert.sol new file mode 100644 index 00000000..9bb6a41c --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/revert.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + revert(); + uint a = 0; a; + } +} +// ---- +// Warning: (70-83): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/revert_empty.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/revert_empty.sol new file mode 100644 index 00000000..4c80c5ca --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/revert_empty.sol @@ -0,0 +1,8 @@ +contract C { + function f() public pure { + revert(); + for(int i = 0; i < 3; i++) { f(); } + } +} +// ---- +// Warning: (70-105): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/while_break.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/while_break.sol new file mode 100644 index 00000000..2d1ddd4f --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/while_break.sol @@ -0,0 +1,12 @@ +contract C { + function f() public pure { + uint a = 0; + while (a < 100) { + a++; + break; + a--; + } + } +} +// ---- +// Warning: (138-141): Unreachable code. diff --git a/test/libsolidity/syntaxTests/controlFlow/unreachableCode/while_continue.sol b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/while_continue.sol new file mode 100644 index 00000000..55f98f67 --- /dev/null +++ b/test/libsolidity/syntaxTests/controlFlow/unreachableCode/while_continue.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + while(true) { + continue; + return; + } + return; // this is unreachable as well, but currently undetected (needs to consider constant condition "true") + } +} +// ---- +// Warning: (100-107): Unreachable code. diff --git a/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol b/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol index 42aebf30..b81e3859 100644 --- a/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol +++ b/test/libsolidity/syntaxTests/inheritance/override/calldata_memory_struct.sol @@ -15,3 +15,7 @@ contract B is A { } // ---- // Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (102-112): Calldata structs are not yet supported. +// TypeError: (146-156): Calldata structs are not yet supported. +// TypeError: (198-208): Calldata structs are not yet supported. +// TypeError: (250-260): Calldata structs are not yet supported. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol new file mode 100644 index 00000000..e90443e1 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol @@ -0,0 +1,12 @@ +contract Test { + function creationOther() public pure returns (bytes memory) { + return type(Other).creationCode; + } + function runtimeOther() public pure returns (bytes memory) { + return type(Other).runtimeCode; + } +} +contract Other { + function f(uint) public pure returns (uint) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol new file mode 100644 index 00000000..bae137c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol @@ -0,0 +1,10 @@ +contract Test { + function creationOther() public pure returns (bytes memory) { + return type(Other).creationCode; + } +} +contract Other { + function f(uint) public returns (uint); +} +// ---- +// TypeError: (97-121): Member "creationCode" not found or not visible after argument-dependent lookup in type(contract Other). diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol new file mode 100644 index 00000000..186d2714 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol @@ -0,0 +1,10 @@ +contract Test { + function runtime() public pure returns (bytes memory) { + return type(Other).runtimeCode; + } +} +contract Other { + function f(uint) public returns (uint); +} +// ---- +// TypeError: (91-114): Member "runtimeCode" not found or not visible after argument-dependent lookup in type(contract Other). diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol new file mode 100644 index 00000000..33dbfd7c --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol @@ -0,0 +1,22 @@ +contract Base { + function f() public pure returns (uint) {} +} +contract Test is Base { + function creation() public pure returns (bytes memory) { + return type(Test).creationCode; + } + function runtime() public pure returns (bytes memory) { + return type(Test).runtimeCode; + } + function creationBase() public pure returns (bytes memory) { + return type(Base).creationCode; + } + function runtimeBase() public pure returns (bytes memory) { + return type(Base).runtimeCode; + } +} +// ---- +// TypeError: (165-188): Circular reference for contract code access. +// TypeError: (271-293): Circular reference for contract code access. +// TypeError: (381-404): Circular reference for contract code access. +// TypeError: (491-513): Circular reference for contract code access. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol new file mode 100644 index 00000000..d5723df6 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol @@ -0,0 +1,12 @@ +contract A { + function f() public pure { + type(B).runtimeCode; + } +} +contract B { + function f() public pure { + type(A).runtimeCode; + } +} +// ---- +// TypeError: (133-152): Circular reference for contract code access. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol new file mode 100644 index 00000000..cda5d5c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol @@ -0,0 +1,7 @@ +contract Test { + bytes constant c = type(B).creationCode; + bytes constant r = type(B).runtimeCode; + +} +contract B { function f() public pure {} } +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol new file mode 100644 index 00000000..f746dc35 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol @@ -0,0 +1,12 @@ +contract Test { + function creationOther() public pure returns (bytes memory) { + return type(Library).creationCode; + } + function runtime() public pure returns (bytes memory) { + return type(Library).runtimeCode; + } +} +contract Library { + function f(uint) public pure returns (uint) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol b/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol new file mode 100644 index 00000000..022e4d6f --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol @@ -0,0 +1,10 @@ +contract Test { + function f() public pure { + type(C).creationCode = new bytes(6); + type(C).runtimeCode = new bytes(6); + } +} +contract C {} +// ---- +// TypeError: (55-75): Expression has to be an lvalue. +// TypeError: (100-119): Expression has to be an lvalue. diff --git a/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol b/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol new file mode 100644 index 00000000..542aaf53 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol @@ -0,0 +1,7 @@ +contract Test { + function creation() public pure returns (bytes memory) { + type(); + } +} +// ---- +// TypeError: (85-91): This function takes one argument, but 0 were provided. diff --git a/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol b/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol new file mode 100644 index 00000000..ec8d9784 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol @@ -0,0 +1,17 @@ +contract Test { + function f() public pure returns (uint) { + return type(C).runtimeCode.length + + type(D).runtimeCode.length + + type(C).creationCode.length + + type(D).creationCode.length; + } +} +contract C { + constructor() public { assembly {} } +} +contract D is C { + constructor() public {} +} +// ---- +// Warning: (77-96): The constructor of the contract (or its base) uses inline assembly. Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode. +// Warning: (118-137): The constructor of the contract (or its base) uses inline assembly. Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode. diff --git a/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol b/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol new file mode 100644 index 00000000..61c2b779 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol @@ -0,0 +1,7 @@ +contract Test { + function creation() public pure returns (bytes memory) { + type(1, 2); + } +} +// ---- +// TypeError: (85-95): This function takes one argument, but 2 were provided. diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol new file mode 100644 index 00000000..144ca1c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol @@ -0,0 +1,3 @@ +contract type { } +// ---- +// ParserError: (9-13): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol new file mode 100644 index 00000000..b7881f15 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol @@ -0,0 +1,6 @@ +contract Test { + function type() public pure { + } +} +// ---- +// ParserError: (29-33): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol new file mode 100644 index 00000000..001ba840 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol @@ -0,0 +1,6 @@ +contract Test { + function f(uint type) public pure { + } +} +// ---- +// ParserError: (36-40): Expected ',' but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol new file mode 100644 index 00000000..fa827a33 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol @@ -0,0 +1,5 @@ +contract Test { + uint type; +} +// ---- +// ParserError: (25-29): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol new file mode 100644 index 00000000..fa57698d --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol @@ -0,0 +1,7 @@ +contract Test { + function f() public pure { + uint type; + } +} +// ---- +// ParserError: (60-64): Expected ';' but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol b/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol new file mode 100644 index 00000000..036c36e8 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol @@ -0,0 +1,6 @@ +contract Test { + function f() public pure returns (bytes memory) { + type(Test); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol b/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol new file mode 100644 index 00000000..0ce06786 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol @@ -0,0 +1,8 @@ +contract Test { + function f() public pure { + type(type(type(Test))); + } +} +// ---- +// TypeError: (65-75): Invalid type for argument in function call. Contract type required, but type(contract Test) provided. +// TypeError: (60-76): Invalid type for argument in function call. Contract type required, but tuple() provided. diff --git a/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol b/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol new file mode 100644 index 00000000..5c27d42f --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol @@ -0,0 +1,9 @@ +contract Test { + struct S { uint x; } + function f() public pure { + // Unsupported for now, but might be supported in the future + type(S); + } +} +// ---- +// TypeError: (154-155): Invalid type for argument in function call. Contract type required, but type(struct Test.S) provided. diff --git a/test/libsolidity/syntaxTests/parsing/for_loop_simple_initexpr.sol b/test/libsolidity/syntaxTests/parsing/for_loop_simple_initexpr.sol index fce669dd..9fafd706 100644 --- a/test/libsolidity/syntaxTests/parsing/for_loop_simple_initexpr.sol +++ b/test/libsolidity/syntaxTests/parsing/for_loop_simple_initexpr.sol @@ -7,6 +7,8 @@ contract test { } } // ---- +// Warning: (103-106): Unreachable code. +// Warning: (144-152): Unreachable code. // Warning: (33-42): Unused function parameter. Remove or comment out the variable name to silence this warning. // Warning: (122-131): Unused local variable. // Warning: (20-169): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/for_loop_simple_noexpr.sol b/test/libsolidity/syntaxTests/parsing/for_loop_simple_noexpr.sol index 4adf0948..c36f9c58 100644 --- a/test/libsolidity/syntaxTests/parsing/for_loop_simple_noexpr.sol +++ b/test/libsolidity/syntaxTests/parsing/for_loop_simple_noexpr.sol @@ -7,6 +7,7 @@ contract test { } } // ---- +// Warning: (144-152): Unreachable code. // Warning: (37-46): Unused function parameter. Remove or comment out the variable name to silence this warning. // Warning: (122-131): Unused local variable. // Warning: (24-177): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/for_loop_vardef_initexpr.sol b/test/libsolidity/syntaxTests/parsing/for_loop_vardef_initexpr.sol index c22ae42f..c81f611b 100644 --- a/test/libsolidity/syntaxTests/parsing/for_loop_vardef_initexpr.sol +++ b/test/libsolidity/syntaxTests/parsing/for_loop_vardef_initexpr.sol @@ -6,6 +6,8 @@ contract test { } } // ---- +// Warning: (89-92): Unreachable code. +// Warning: (130-138): Unreachable code. // Warning: (33-42): Unused function parameter. Remove or comment out the variable name to silence this warning. // Warning: (108-117): Unused local variable. // Warning: (20-155): Function state mutability can be restricted to pure diff --git a/test/libsolidity/syntaxTests/parsing/while_loop.sol b/test/libsolidity/syntaxTests/parsing/while_loop.sol index dbb00a69..cdac929f 100644 --- a/test/libsolidity/syntaxTests/parsing/while_loop.sol +++ b/test/libsolidity/syntaxTests/parsing/while_loop.sol @@ -5,3 +5,4 @@ contract test { } } // ---- +// Warning: (105-113): Unreachable code. diff --git a/test/libsolidity/syntaxTests/structs/array_calldata.sol b/test/libsolidity/syntaxTests/structs/array_calldata.sol new file mode 100644 index 00000000..3aac5606 --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/array_calldata.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; +contract Test { + struct S { int a; } + function f(S[] calldata) external { } + function f(S[][] calldata) external { } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (89-101): Calldata structs are not yet supported. +// TypeError: (131-145): Calldata structs are not yet supported. diff --git a/test/libsolidity/syntaxTests/structs/calldata.sol b/test/libsolidity/syntaxTests/structs/calldata.sol new file mode 100644 index 00000000..dadf6e4f --- /dev/null +++ b/test/libsolidity/syntaxTests/structs/calldata.sol @@ -0,0 +1,8 @@ +pragma experimental ABIEncoderV2; +contract Test { + struct S { int a; } + function f(S calldata) external { } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (89-99): Calldata structs are not yet supported. diff --git a/test/libsolidity/syntaxTests/types/encoding_fractional.sol b/test/libsolidity/syntaxTests/types/encoding_fractional.sol new file mode 100644 index 00000000..16c76d9a --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_fractional.sol @@ -0,0 +1,7 @@ +contract C { + function f1() public pure returns (bytes memory) { + return abi.encode(0.1, 1); + } +} +// ---- +// TypeError: (92-95): Fractional numbers cannot yet be encoded. diff --git a/test/libsolidity/syntaxTests/types/encoding_fractional_abiencoderv2.sol b/test/libsolidity/syntaxTests/types/encoding_fractional_abiencoderv2.sol new file mode 100644 index 00000000..80efa9c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_fractional_abiencoderv2.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; +contract C { + function f1() public pure returns (bytes memory) { + return abi.encode(0.1, 1); + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (126-129): Fractional numbers cannot yet be encoded. diff --git a/test/libsolidity/syntaxTests/types/encoding_packed_fractional.sol b/test/libsolidity/syntaxTests/types/encoding_packed_fractional.sol new file mode 100644 index 00000000..71080cb0 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_packed_fractional.sol @@ -0,0 +1,8 @@ +contract C { + function f1() public pure returns (bytes memory) { + return abi.encodePacked(0.1, 1); + } +} +// ---- +// TypeError: (98-101): Fractional numbers cannot yet be encoded. +// TypeError: (103-104): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. diff --git a/test/libsolidity/syntaxTests/types/encoding_packed_fractional_abiencoderv2.sol b/test/libsolidity/syntaxTests/types/encoding_packed_fractional_abiencoderv2.sol new file mode 100644 index 00000000..e82c3c9a --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_packed_fractional_abiencoderv2.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; +contract C { + function f1() public pure returns (bytes memory) { + return abi.encodePacked(0.1, 1); + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (132-135): Fractional numbers cannot yet be encoded. +// TypeError: (137-138): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. diff --git a/test/libyul/Common.cpp b/test/libyul/Common.cpp index a337fa8d..ea4d0208 100644 --- a/test/libyul/Common.cpp +++ b/test/libyul/Common.cpp @@ -41,6 +41,14 @@ using namespace langutil; using namespace yul; using namespace dev::solidity; +namespace +{ +shared_ptr<Dialect> defaultDialect(bool _yul) +{ + return _yul ? yul::Dialect::yul() : yul::EVMDialect::strictAssemblyForEVM(); +} +} + void yul::test::printErrors(ErrorList const& _errors) { SourceReferenceFormatter formatter(cout); @@ -55,7 +63,7 @@ void yul::test::printErrors(ErrorList const& _errors) pair<shared_ptr<Block>, shared_ptr<yul::AsmAnalysisInfo>> yul::test::parse(string const& _source, bool _yul) { - shared_ptr<Dialect> dialect = _yul ? yul::Dialect::yul() : yul::EVMDialect::strictAssemblyForEVM(); + shared_ptr<Dialect> dialect = defaultDialect(_yul); ErrorList errors; ErrorReporter errorReporter(errors); auto scanner = make_shared<Scanner>(CharStream(_source, "")); @@ -87,7 +95,7 @@ pair<shared_ptr<Block>, shared_ptr<yul::AsmAnalysisInfo>> yul::test::parse(strin yul::Block yul::test::disambiguate(string const& _source, bool _yul) { auto result = parse(_source, _yul); - return boost::get<Block>(Disambiguator(*result.second, {})(*result.first)); + return boost::get<Block>(Disambiguator(*defaultDialect(_yul), *result.second, {})(*result.first)); } string yul::test::format(string const& _source, bool _yul) diff --git a/test/libyul/Metrics.cpp b/test/libyul/Metrics.cpp new file mode 100644 index 00000000..185a3755 --- /dev/null +++ b/test/libyul/Metrics.cpp @@ -0,0 +1,116 @@ +/* + 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/>. +*/ +/** + * Unit tests for the code metrics. + */ + +#include <test/Options.h> + +#include <test/libyul/Common.h> + +#include <libyul/optimiser/Metrics.h> +#include <libyul/AsmData.h> + + +using namespace std; +using namespace langutil; + +namespace yul +{ +namespace test +{ + +namespace +{ + +size_t codeSize(string const& _source) +{ + shared_ptr<Block> ast = parse(_source, false).first; + BOOST_REQUIRE(ast); + return CodeSize::codeSize(*ast); +} + +} + +BOOST_AUTO_TEST_SUITE(YulCodeSize) + +BOOST_AUTO_TEST_CASE(empty_code) +{ + BOOST_CHECK_EQUAL(codeSize("{}"), 0); +} + +BOOST_AUTO_TEST_CASE(nested_blocks) +{ + BOOST_CHECK_EQUAL(codeSize("{ {} {} {{ }} }"), 0); +} + +BOOST_AUTO_TEST_CASE(instruction) +{ + BOOST_CHECK_EQUAL(codeSize("{ pop(calldatasize()) }"), 2); +} + +BOOST_AUTO_TEST_CASE(variables_are_free) +{ + BOOST_CHECK_EQUAL(codeSize("{ let x let y let a, b, c }"), 0); +} + +BOOST_AUTO_TEST_CASE(constants_cost_one) +{ + BOOST_CHECK_EQUAL(codeSize("{ let x := 3 }"), 1); +} + +BOOST_AUTO_TEST_CASE(functions_are_skipped) +{ + BOOST_CHECK_EQUAL(codeSize("{ function f(x) -> r { r := mload(x) } }"), 0); +} + +BOOST_AUTO_TEST_CASE(function_with_arguments) +{ + BOOST_CHECK_EQUAL(codeSize("{ function f(x) { sstore(x, 2) } f(2) }"), 2); +} + +BOOST_AUTO_TEST_CASE(function_with_variables_as_arguments) +{ + BOOST_CHECK_EQUAL(codeSize("{ function f(x) { sstore(x, 2) } let y f(y) }"), 1); +} + +BOOST_AUTO_TEST_CASE(function_with_variables_and_constants_as_arguments) +{ + BOOST_CHECK_EQUAL(codeSize( + "{ function f(x, r) -> z { sstore(x, r) z := r } let y let t := f(y, 2) }" + ), 2); +} + +BOOST_AUTO_TEST_CASE(assignment) +{ + BOOST_CHECK_EQUAL(codeSize("{ let a a := 3 }"), 1); +} + +BOOST_AUTO_TEST_CASE(assignments_between_vars_are_free) +{ + BOOST_CHECK_EQUAL(codeSize("{ let a let b := a a := b }"), 0); +} + +BOOST_AUTO_TEST_CASE(assignment_complex) +{ + BOOST_CHECK_EQUAL(codeSize("{ let a let x := mload(a) a := sload(x) }"), 2); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/libyul/ObjectParser.cpp b/test/libyul/ObjectParser.cpp index bb88e4da..13a95788 100644 --- a/test/libyul/ObjectParser.cpp +++ b/test/libyul/ObjectParser.cpp @@ -250,6 +250,36 @@ BOOST_AUTO_TEST_CASE(to_string) BOOST_CHECK_EQUAL(asmStack.print(), expectation); } +BOOST_AUTO_TEST_CASE(arg_to_dataoffset_must_be_literal) +{ + string code = R"( + object "outer" { + code { let x := "outer" let y := dataoffset(x) } + } + )"; + CHECK_ERROR(code, TypeError, "Function expects direct literals as arguments."); +} + +BOOST_AUTO_TEST_CASE(arg_to_datasize_must_be_literal) +{ + string code = R"( + object "outer" { + code { let x := "outer" let y := datasize(x) } + } + )"; + CHECK_ERROR(code, TypeError, "Function expects direct literals as arguments."); +} + +BOOST_AUTO_TEST_CASE(args_to_datacopy_are_arbitrary) +{ + string code = R"( + object "outer" { + code { let x := 0 let y := 2 let s := 3 datacopy(x, y, s) } + } + )"; + BOOST_CHECK(successParse(code)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index df7e32a1..84f5c14b 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -304,6 +304,19 @@ BOOST_AUTO_TEST_CASE(if_statement_invalid) BOOST_CHECK(successParse("{ if 42:u256 { } }")); } +BOOST_AUTO_TEST_CASE(switch_case_types) +{ + CHECK_ERROR("{ switch 0:u256 case 0:u256 {} case 1:u32 {} }", TypeError, "Switch cases have non-matching types."); + // The following should be an error in the future, but this is not yet detected. + BOOST_CHECK(successParse("{ switch 0:u256 case 0:u32 {} case 1:u32 {} }")); +} + +BOOST_AUTO_TEST_CASE(switch_duplicate_case) +{ + CHECK_ERROR("{ switch 0:u256 case 0:u256 {} case 0x0:u256 {} }", DeclarationError, "Duplicate case defined."); + BOOST_CHECK(successParse("{ switch 0:u256 case 42:u256 {} case 0x42:u256 {} }")); +} + BOOST_AUTO_TEST_CASE(builtins_parser) { struct SimpleDialect: public Dialect @@ -331,7 +344,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis) { return _name == "builtin"_yulstring ? &f : nullptr; } - BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), false}; + BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), false, false}; }; shared_ptr<Dialect> dialect = make_shared<SimpleDialect>(); diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 9643a1e9..306721a0 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -26,6 +26,7 @@ #include <libyul/optimiser/Disambiguator.h> #include <libyul/optimiser/CommonSubexpressionEliminator.h> #include <libyul/optimiser/NameCollector.h> +#include <libyul/optimiser/EquivalentFunctionCombiner.h> #include <libyul/optimiser/ExpressionSplitter.h> #include <libyul/optimiser/FunctionGrouper.h> #include <libyul/optimiser/FunctionHoister.h> @@ -37,6 +38,7 @@ #include <libyul/optimiser/ExpressionSimplifier.h> #include <libyul/optimiser/UnusedPruner.h> #include <libyul/optimiser/ExpressionJoiner.h> +#include <libyul/optimiser/SSAReverser.h> #include <libyul/optimiser/SSATransform.h> #include <libyul/optimiser/RedundantAssignEliminator.h> #include <libyul/optimiser/StructuralSimplifier.h> @@ -117,12 +119,12 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con else if (m_optimizerStep == "commonSubexpressionEliminator") { disambiguate(); - (CommonSubexpressionEliminator{})(*m_ast); + (CommonSubexpressionEliminator{*m_dialect})(*m_ast); } else if (m_optimizerStep == "expressionSplitter") { - NameDispenser nameDispenser(*m_ast); - ExpressionSplitter{nameDispenser}(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); } else if (m_optimizerStep == "expressionJoiner") { @@ -132,8 +134,8 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con else if (m_optimizerStep == "splitJoin") { disambiguate(); - NameDispenser nameDispenser(*m_ast); - ExpressionSplitter{nameDispenser}(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); ExpressionJoiner::run(*m_ast); ExpressionJoiner::run(*m_ast); } @@ -150,15 +152,15 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con else if (m_optimizerStep == "expressionInliner") { disambiguate(); - ExpressionInliner(*m_ast).run(); + ExpressionInliner(*m_dialect, *m_ast).run(); } else if (m_optimizerStep == "fullInliner") { disambiguate(); (FunctionHoister{})(*m_ast); (FunctionGrouper{})(*m_ast); - NameDispenser nameDispenser(*m_ast); - ExpressionSplitter{nameDispenser}(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); FullInliner(*m_ast, nameDispenser).run(); ExpressionJoiner::run(*m_ast); } @@ -171,54 +173,76 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con else if (m_optimizerStep == "rematerialiser") { disambiguate(); - (Rematerialiser{})(*m_ast); + Rematerialiser::run(*m_dialect, *m_ast); } else if (m_optimizerStep == "expressionSimplifier") { disambiguate(); - ExpressionSimplifier::run(*m_ast); + ExpressionSimplifier::run(*m_dialect, *m_ast); } else if (m_optimizerStep == "fullSimplify") { disambiguate(); - NameDispenser nameDispenser(*m_ast); - ExpressionSplitter{nameDispenser}(*m_ast); - CommonSubexpressionEliminator{}(*m_ast); - ExpressionSimplifier::run(*m_ast); - UnusedPruner::runUntilStabilised(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; + ExpressionSplitter{*m_dialect, nameDispenser}(*m_ast); + CommonSubexpressionEliminator{*m_dialect}(*m_ast); + ExpressionSimplifier::run(*m_dialect, *m_ast); + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); ExpressionJoiner::run(*m_ast); ExpressionJoiner::run(*m_ast); } else if (m_optimizerStep == "unusedPruner") { disambiguate(); - UnusedPruner::runUntilStabilised(*m_ast); + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); } else if (m_optimizerStep == "ssaTransform") { disambiguate(); - NameDispenser nameDispenser(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; SSATransform::run(*m_ast, nameDispenser); } else if (m_optimizerStep == "redundantAssignEliminator") { disambiguate(); - RedundantAssignEliminator::run(*m_ast); + RedundantAssignEliminator::run(*m_dialect, *m_ast); } else if (m_optimizerStep == "ssaPlusCleanup") { disambiguate(); - NameDispenser nameDispenser(*m_ast); + NameDispenser nameDispenser{*m_dialect, *m_ast}; SSATransform::run(*m_ast, nameDispenser); - RedundantAssignEliminator::run(*m_ast); + RedundantAssignEliminator::run(*m_dialect, *m_ast); } else if (m_optimizerStep == "structuralSimplifier") { disambiguate(); - StructuralSimplifier{}(*m_ast); + StructuralSimplifier{*m_dialect}(*m_ast); + } + else if (m_optimizerStep == "equivalentFunctionCombiner") + { + disambiguate(); + EquivalentFunctionCombiner::run(*m_ast); + } + else if (m_optimizerStep == "ssaReverser") + { + disambiguate(); + SSAReverser::run(*m_ast); + } + else if (m_optimizerStep == "ssaAndBack") + { + disambiguate(); + // apply SSA + NameDispenser nameDispenser{*m_dialect, *m_ast}; + SSATransform::run(*m_ast, nameDispenser); + RedundantAssignEliminator::run(*m_dialect, *m_ast); + // reverse SSA + SSAReverser::run(*m_ast); + CommonSubexpressionEliminator{*m_dialect}(*m_ast); + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); } else if (m_optimizerStep == "fullSuite") - OptimiserSuite::run(*m_ast, *m_analysisInfo); + OptimiserSuite::run(*m_dialect, *m_ast, *m_analysisInfo); else { FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Invalid optimizer step: " << m_optimizerStep << endl; @@ -260,11 +284,11 @@ void YulOptimizerTest::printIndented(ostream& _stream, string const& _output, st bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted) { - shared_ptr<yul::Dialect> dialect = m_yul ? yul::Dialect::yul() : yul::EVMDialect::strictAssemblyForEVM(); + m_dialect = m_yul ? yul::Dialect::yul() : yul::EVMDialect::strictAssemblyForEVMObjects(); ErrorList errors; ErrorReporter errorReporter(errors); shared_ptr<Scanner> scanner = make_shared<Scanner>(CharStream(m_source, "")); - m_ast = yul::Parser(errorReporter, dialect).parse(scanner, false); + m_ast = yul::Parser(errorReporter, m_dialect).parse(scanner, false); if (!m_ast || !errorReporter.errors().empty()) { FormattedScope(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl; @@ -277,7 +301,7 @@ bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool c errorReporter, dev::test::Options::get().evmVersion(), boost::none, - dialect + m_dialect ); if (!analyzer.analyze(*m_ast) || !errorReporter.errors().empty()) { @@ -290,7 +314,7 @@ bool YulOptimizerTest::parse(ostream& _stream, string const& _linePrefix, bool c void YulOptimizerTest::disambiguate() { - *m_ast = boost::get<Block>(Disambiguator(*m_analysisInfo)(*m_ast)); + *m_ast = boost::get<Block>(Disambiguator(*m_dialect, *m_analysisInfo)(*m_ast)); m_analysisInfo.reset(); } diff --git a/test/libyul/YulOptimizerTest.h b/test/libyul/YulOptimizerTest.h index 5648e995..5009b82c 100644 --- a/test/libyul/YulOptimizerTest.h +++ b/test/libyul/YulOptimizerTest.h @@ -30,6 +30,7 @@ namespace yul { struct AsmAnalysisInfo; struct Block; +struct Dialect; } namespace yul @@ -64,6 +65,7 @@ private: std::string m_optimizerStep; std::string m_expectation; + std::shared_ptr<Dialect> m_dialect; std::shared_ptr<Block> m_ast; std::shared_ptr<AsmAnalysisInfo> m_analysisInfo; std::string m_obtainedResult; diff --git a/test/libyul/objectCompiler/nested_optimizer.yul b/test/libyul/objectCompiler/nested_optimizer.yul index 7739ce61..2775c346 100644 --- a/test/libyul/objectCompiler/nested_optimizer.yul +++ b/test/libyul/objectCompiler/nested_optimizer.yul @@ -19,31 +19,21 @@ object "a" { // Assembly: // /* "source":60:61 */ // 0x00 -// /* "source":137:138 */ -// dup1 -// /* "source":60:61 */ -// dup2 +// 0x00 // /* "source":47:62 */ // calldataload // /* "source":119:139 */ // sstore -// /* "source":32:143 */ -// pop // stop // // sub_0: assembly { // /* "source":200:201 */ // 0x00 -// /* "source":283:284 */ -// dup1 -// /* "source":200:201 */ -// dup2 +// 0x00 // /* "source":187:202 */ // calldataload // /* "source":265:285 */ // sstore -// /* "source":170:291 */ -// pop // } -// Bytecode: 60008081355550fe -// Opcodes: PUSH1 0x0 DUP1 DUP2 CALLDATALOAD SSTORE POP INVALID +// Bytecode: 600060003555fe +// Opcodes: PUSH1 0x0 PUSH1 0x0 CALLDATALOAD SSTORE INVALID diff --git a/test/libyul/objectCompiler/simple_optimizer.yul b/test/libyul/objectCompiler/simple_optimizer.yul index 43b33553..c757dee7 100644 --- a/test/libyul/objectCompiler/simple_optimizer.yul +++ b/test/libyul/objectCompiler/simple_optimizer.yul @@ -9,15 +9,10 @@ // Assembly: // /* "source":38:39 */ // 0x00 -// /* "source":109:110 */ -// dup1 -// /* "source":38:39 */ -// dup2 +// 0x00 // /* "source":25:40 */ // calldataload // /* "source":91:111 */ // sstore -// /* "source":12:113 */ -// pop -// Bytecode: 60008081355550 -// Opcodes: PUSH1 0x0 DUP1 DUP2 CALLDATALOAD SSTORE POP +// Bytecode: 600060003555 +// Opcodes: PUSH1 0x0 PUSH1 0x0 CALLDATALOAD SSTORE diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul new file mode 100644 index 00000000..5cfa3e6e --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/object_access.yul @@ -0,0 +1,23 @@ +{ + // Arguments to ``datasize`` and ``dataoffset`` need to be + // literals. We cannot simplify their arguments, but we can + // simplify them as a full expression. + // ``datacopy`` does not have this restriction. + let r := "abc" + let a := datasize("abc") + let x := dataoffset("abc") + // should be replaced by a + let y := datasize("abc") + datacopy("abc", x, y) + mstore(a, x) +} +// ---- +// commonSubexpressionEliminator +// { +// let r := "abc" +// let a := datasize("abc") +// let x := dataoffset("abc") +// let y := a +// datacopy(r, x, a) +// mstore(a, x) +// } diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul new file mode 100644 index 00000000..380f9f03 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/multiple_complex.yul @@ -0,0 +1,114 @@ +{ + pop(f(1,2,3)) + pop(g(4,5,6)) + pop(h(7,8,9)) + function f(f1, f2, f3) -> rf + { + switch f1 + case 0 { + if f2 + { + rf := f3 + } + if not(f2) + { + rf := f1 + } + } + default { + rf := 3 + } + } + function g(g1, g2, g3) -> rg + { + switch g1 + case 0 { + if g2 + { + rg := g3 + } + if not(g2) + { + rg := g1 + } + } + default { + rg := 3 + } + } + function h(h1, h2, h3) -> rh + { + switch h1 + case 1 { + if h2 + { + rh := h3 + } + if not(h2) + { + rh := h1 + } + } + default { + rh := 3 + } + } +} +// ---- +// equivalentFunctionCombiner +// { +// pop(f(1, 2, 3)) +// pop(f(4, 5, 6)) +// pop(h(7, 8, 9)) +// function f(f1, f2, f3) -> rf +// { +// switch f1 +// case 0 { +// if f2 +// { +// rf := f3 +// } +// if not(f2) +// { +// rf := f1 +// } +// } +// default { +// rf := 3 +// } +// } +// function g(g1, g2, g3) -> rg +// { +// switch g1 +// case 0 { +// if g2 +// { +// rg := g3 +// } +// if not(g2) +// { +// rg := g1 +// } +// } +// default { +// rg := 3 +// } +// } +// function h(h1, h2, h3) -> rh +// { +// switch h1 +// case 1 { +// if h2 +// { +// rh := h3 +// } +// if not(h2) +// { +// rh := h1 +// } +// } +// default { +// rh := 3 +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul new file mode 100644 index 00000000..2d5b3ef8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple.yul @@ -0,0 +1,20 @@ +{ + f() + g() + function f() { mstore(1, mload(0)) } + function g() { mstore(1, mload(0)) } +} +// ---- +// equivalentFunctionCombiner +// { +// f() +// f() +// function f() +// { +// mstore(1, mload(0)) +// } +// function g() +// { +// mstore(1, mload(0)) +// } +// } diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul new file mode 100644 index 00000000..d38a3d2e --- /dev/null +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/simple_different_vars.yul @@ -0,0 +1,22 @@ +{ + pop(f()) + pop(g()) + function f() -> b { let a := mload(0) b := a } + function g() -> a { let b := mload(0) a := b } +} +// ---- +// equivalentFunctionCombiner +// { +// pop(f()) +// pop(f()) +// function f() -> b +// { +// let a := mload(0) +// b := a +// } +// function g() -> a_1 +// { +// let b_2 := mload(0) +// a_1 := b_2 +// } +// } diff --git a/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul new file mode 100644 index 00000000..4f3cad36 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equivalentFunctionCombiner/switch_case_order.yul @@ -0,0 +1,32 @@ +{ + f(0) + g(1) + function f(x) { switch x case 0 { mstore(0, 42) } case 1 { mstore(1, 42) } } + function g(x) { switch x case 1 { mstore(1, 42) } case 0 { mstore(0, 42) } } +} +// ---- +// equivalentFunctionCombiner +// { +// f(0) +// f(1) +// function f(x) +// { +// switch x +// case 0 { +// mstore(0, 42) +// } +// case 1 { +// mstore(1, 42) +// } +// } +// function g(x_1) +// { +// switch x_1 +// case 1 { +// mstore(1, 42) +// } +// case 0 { +// mstore(0, 42) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul b/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul new file mode 100644 index 00000000..2689ab6f --- /dev/null +++ b/test/libyul/yulOptimizerTests/expressionSplitter/object_access.yul @@ -0,0 +1,21 @@ +{ + // We should never split arguments to ``dataoffset`` + // or ``datasize`` because they need to be literals + let x := dataoffset("abc") + let y := datasize("abc") + // datacopy is fine, though + datacopy(mload(0), mload(1), mload(2)) +} +// ---- +// expressionSplitter +// { +// let x := dataoffset("abc") +// let y := datasize("abc") +// let _1 := 2 +// let _2 := mload(_1) +// let _3 := 1 +// let _4 := mload(_3) +// let _5 := 0 +// let _6 := mload(_5) +// datacopy(_6, _4, _2) +// } diff --git a/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul b/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul index c00b1163..3a7ee2f3 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/large_function_multi_use.yul @@ -21,22 +21,28 @@ // { // let a_1 := mload(2) // let a2 := 2 -// let r := f(a_1) -// let f_a := a2 +// let f_a := a_1 // let f_b := 0 // let f_x := mload(f_a) // f_b := sload(f_x) // let f_y := add(f_a, f_x) // sstore(f_y, 10) -// let t := f_b -// let a3 -// let f_a_3 := a3 +// let r := f_b +// let f_a_3 := a2 // let f_b_4 := 0 // let f_x_5 := mload(f_a_3) // f_b_4 := sload(f_x_5) // let f_y_6 := add(f_a_3, f_x_5) // sstore(f_y_6, 10) -// let s := f_b_4 +// let t := f_b_4 +// let a3 +// let f_a_8 := a3 +// let f_b_9 := 0 +// let f_x_10 := mload(f_a_8) +// f_b_9 := sload(f_x_10) +// let f_y_11 := add(f_a_8, f_x_10) +// sstore(f_y_11, 10) +// let s := f_b_9 // } // function f(a) -> b // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul b/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul index 6e4acb97..dbbc5422 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/multi_fun_callback.yul @@ -26,13 +26,11 @@ // fullInliner // { // { -// { -// let f_x := 100 -// mstore(0, f_x) -// mstore(7, h()) -// g(10) -// mstore(1, f_x) -// } +// let f_x := 100 +// mstore(0, f_x) +// mstore(7, h()) +// g(10) +// mstore(1, f_x) // } // function f(x) // { diff --git a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul index f59e2c11..58922954 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_function.yul @@ -39,6 +39,24 @@ // let f_a_35 := f_b_33 // let f_b_36 := 0 // f_b_36 := sload(mload(f_a_35)) -// x_1 := f(f(f(f(f(f(f(f(f(f(f(f(f_b_36)))))))))))) +// let f_a_38 := f_b_36 +// let f_b_39 := 0 +// f_b_39 := sload(mload(f_a_38)) +// let f_a_41 := f_b_39 +// let f_b_42 := 0 +// f_b_42 := sload(mload(f_a_41)) +// let f_a_44 := f_b_42 +// let f_b_45 := 0 +// f_b_45 := sload(mload(f_a_44)) +// let f_a_47 := f_b_45 +// let f_b_48 := 0 +// f_b_48 := sload(mload(f_a_47)) +// let f_a_50 := f_b_48 +// let f_b_51 := 0 +// f_b_51 := sload(mload(f_a_50)) +// let f_a_53 := f_b_51 +// let f_b_54 := 0 +// f_b_54 := sload(mload(f_a_53)) +// x_1 := f(f(f(f(f(f(f_b_54)))))) // } // } diff --git a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul index f20b7221..3dc27b2d 100644 --- a/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul +++ b/test/libyul/yulOptimizerTests/fullInliner/no_inline_into_big_global_context.yul @@ -32,7 +32,25 @@ // let f_a_35 := f_b_33 // let f_b_36 := 0 // f_b_36 := sload(mload(f_a_35)) -// let x_1 := f(f(f(f(f(f(f(f(f(f(f(f(f_b_36)))))))))))) +// let f_a_38 := f_b_36 +// let f_b_39 := 0 +// f_b_39 := sload(mload(f_a_38)) +// let f_a_41 := f_b_39 +// let f_b_42 := 0 +// f_b_42 := sload(mload(f_a_41)) +// let f_a_44 := f_b_42 +// let f_b_45 := 0 +// f_b_45 := sload(mload(f_a_44)) +// let f_a_47 := f_b_45 +// let f_b_48 := 0 +// f_b_48 := sload(mload(f_a_47)) +// let f_a_50 := f_b_48 +// let f_b_51 := 0 +// f_b_51 := sload(mload(f_a_50)) +// let f_a_53 := f_b_51 +// let f_b_54 := 0 +// f_b_54 := sload(mload(f_a_53)) +// let x_1 := f(f(f(f(f(f(f_b_54)))))) // } // function f(a) -> b // { diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul new file mode 100644 index 00000000..41a52c67 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul @@ -0,0 +1,1144 @@ +{ + // This ignores many of the encoding / decoding functions. Over time, + // we should add them all here. + + let a, b := abi_decode_tuple_t_contract$_Module_$1038t_contract$_Module_$1038(mload(0), mload(1)) + sstore(0, a) + let x0, x1, x2, x3, x4 := abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(mload(7), mload(8)) + sstore(x1, x0) + sstore(x3, x2) + sstore(1, x4) + let r := abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_( + mload(30), + mload(31), + mload(32), + mload(33), + mload(34), + mload(35), + mload(36), + mload(37), + mload(38), + mload(39), + mload(40), + mload(41) + ) + + function abi_decode_t_address(offset, end) -> value + { + value := cleanup_revert_t_address(calldataload(offset)) + } + function abi_decode_t_address_payable(offset_1, end_2) -> value_3 + { + value_3 := cleanup_revert_t_address_payable(calldataload(offset_1)) + } + function abi_decode_t_array$_t_address_$dyn_calldata_ptr(offset_4, end_5) -> arrayPos, length + { + if iszero(slt(add(offset_4, 0x1f), end_5)) + { + revert(0, 0) + } + length := calldataload(offset_4) + if gt(length, 0xffffffffffffffff) + { + revert(0, 0) + } + arrayPos := add(offset_4, 0x20) + if gt(add(arrayPos, mul(length, 0x20)), end_5) + { + revert(0, 0) + } + } + function abi_decode_t_bool_fromMemory(offset_6, end_7) -> value_8 + { + value_8 := cleanup_revert_t_bool(mload(offset_6)) + } + function abi_decode_t_bytes32(offset_9, end_10) -> value_11 + { + value_11 := cleanup_revert_t_bytes32(calldataload(offset_9)) + } + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + length_15 := calldataload(offset_12) + if gt(length_15, 0xffffffffffffffff) + { + revert(0, 0) + } + arrayPos_14 := add(offset_12, 0x20) + if gt(add(arrayPos_14, mul(length_15, 0x1)), end_13) + { + revert(0, 0) + } + } + function abi_decode_t_bytes_memory_ptr(offset_16, end_17) -> array + { + if iszero(slt(add(offset_16, 0x1f), end_17)) + { + revert(0, 0) + } + let length_18 := calldataload(offset_16) + array := allocateMemory(array_allocation_size_t_bytes_memory_ptr(length_18)) + mstore(array, length_18) + let src := add(offset_16, 0x20) + let dst := add(array, 0x20) + if gt(add(src, length_18), end_17) + { + revert(0, 0) + } + copy_calldata_to_memory(src, dst, length_18) + } + function abi_decode_t_contract$_Module_$1038(offset_19, end_20) -> value_21 + { + value_21 := cleanup_revert_t_contract$_Module_$1038(calldataload(offset_19)) + } + function abi_decode_t_enum$_Operation_$1949(offset_22, end_23) -> value_24 + { + value_24 := cleanup_revert_t_enum$_Operation_$1949(calldataload(offset_22)) + } + function abi_decode_t_uint256(offset_25, end_26) -> value_27 + { + value_27 := cleanup_revert_t_uint256(calldataload(offset_25)) + } + function abi_decode_tuple_t_address(headStart, dataEnd) -> value0 + { + if slt(sub(dataEnd, headStart), 32) + { + revert(0, 0) + } + { + let offset_28 := 0 + value0 := abi_decode_t_address(add(headStart, offset_28), dataEnd) + } + } + function abi_decode_tuple_t_addresst_addresst_address(headStart_29, dataEnd_30) -> value0_31, value1, value2 + { + if slt(sub(dataEnd_30, headStart_29), 96) + { + revert(0, 0) + } + { + let offset_32 := 0 + value0_31 := abi_decode_t_address(add(headStart_29, offset_32), dataEnd_30) + } + { + let offset_33 := 32 + value1 := abi_decode_t_address(add(headStart_29, offset_33), dataEnd_30) + } + { + let offset_34 := 64 + value2 := abi_decode_t_address(add(headStart_29, offset_34), dataEnd_30) + } + } + function abi_decode_tuple_t_addresst_addresst_uint256(headStart_35, dataEnd_36) -> value0_37, value1_38, value2_39 + { + if slt(sub(dataEnd_36, headStart_35), 96) + { + revert(0, 0) + } + { + let offset_40 := 0 + value0_37 := abi_decode_t_address(add(headStart_35, offset_40), dataEnd_36) + } + { + let offset_41 := 32 + value1_38 := abi_decode_t_address(add(headStart_35, offset_41), dataEnd_36) + } + { + let offset_42 := 64 + value2_39 := abi_decode_t_uint256(add(headStart_35, offset_42), dataEnd_36) + } + } + function abi_decode_tuple_t_addresst_bytes32(headStart_43, dataEnd_44) -> value0_45, value1_46 + { + if slt(sub(dataEnd_44, headStart_43), 64) + { + revert(0, 0) + } + { + let offset_47 := 0 + value0_45 := abi_decode_t_address(add(headStart_43, offset_47), dataEnd_44) + } + { + let offset_48 := 32 + value1_46 := abi_decode_t_bytes32(add(headStart_43, offset_48), dataEnd_44) + } + } + function abi_decode_tuple_t_addresst_uint256(headStart_49, dataEnd_50) -> value0_51, value1_52 + { + if slt(sub(dataEnd_50, headStart_49), 64) + { + revert(0, 0) + } + { + let offset_53 := 0 + value0_51 := abi_decode_t_address(add(headStart_49, offset_53), dataEnd_50) + } + { + let offset_54 := 32 + value1_52 := abi_decode_t_uint256(add(headStart_49, offset_54), dataEnd_50) + } + } + function abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(headStart_55, dataEnd_56) -> value0_57, value1_58, value2_59, value3, value4 + { + if slt(sub(dataEnd_56, headStart_55), 128) + { + revert(0, 0) + } + { + let offset_60 := 0 + value0_57 := abi_decode_t_address(add(headStart_55, offset_60), dataEnd_56) + } + { + let offset_61 := 32 + value1_58 := abi_decode_t_uint256(add(headStart_55, offset_61), dataEnd_56) + } + { + let offset_62 := calldataload(add(headStart_55, 64)) + if gt(offset_62, 0xffffffffffffffff) + { + revert(0, 0) + } + value2_59, value3 := abi_decode_t_bytes_calldata_ptr(add(headStart_55, offset_62), dataEnd_56) + } + { + let offset_63 := 96 + value4 := abi_decode_t_enum$_Operation_$1949(add(headStart_55, offset_63), dataEnd_56) + } + } + function abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949t_uint256t_uint256t_uint256t_addresst_address_payablet_bytes_calldata_ptr(headStart_64, dataEnd_65) -> value0_66, value1_67, value2_68, value3_69, value4_70, value5, value6, value7, value8, value9, value10, value11 + { + if slt(sub(dataEnd_65, headStart_64), 320) + { + revert(0, 0) + } + { + let offset_71 := 0 + value0_66 := abi_decode_t_address(add(headStart_64, offset_71), dataEnd_65) + } + { + let offset_72 := 32 + value1_67 := abi_decode_t_uint256(add(headStart_64, offset_72), dataEnd_65) + } + { + let offset_73 := calldataload(add(headStart_64, 64)) + if gt(offset_73, 0xffffffffffffffff) + { + revert(0, 0) + } + value2_68, value3_69 := abi_decode_t_bytes_calldata_ptr(add(headStart_64, offset_73), dataEnd_65) + } + { + let offset_74 := 96 + value4_70 := abi_decode_t_enum$_Operation_$1949(add(headStart_64, offset_74), dataEnd_65) + } + { + let offset_75 := 128 + value5 := abi_decode_t_uint256(add(headStart_64, offset_75), dataEnd_65) + } + { + let offset_76 := 160 + value6 := abi_decode_t_uint256(add(headStart_64, offset_76), dataEnd_65) + } + { + let offset_77 := 192 + value7 := abi_decode_t_uint256(add(headStart_64, offset_77), dataEnd_65) + } + { + let offset_78 := 224 + value8 := abi_decode_t_address(add(headStart_64, offset_78), dataEnd_65) + } + { + let offset_79 := 256 + value9 := abi_decode_t_address_payable(add(headStart_64, offset_79), dataEnd_65) + } + { + let offset_80 := calldataload(add(headStart_64, 288)) + if gt(offset_80, 0xffffffffffffffff) + { + revert(0, 0) + } + value10, value11 := abi_decode_t_bytes_calldata_ptr(add(headStart_64, offset_80), dataEnd_65) + } + } + function abi_decode_tuple_t_addresst_uint256t_bytes_memory_ptrt_enum$_Operation_$1949(headStart_81, dataEnd_82) -> value0_83, value1_84, value2_85, value3_86 + { + if slt(sub(dataEnd_82, headStart_81), 128) + { + revert(0, 0) + } + { + let offset_87 := 0 + value0_83 := abi_decode_t_address(add(headStart_81, offset_87), dataEnd_82) + } + { + let offset_88 := 32 + value1_84 := abi_decode_t_uint256(add(headStart_81, offset_88), dataEnd_82) + } + { + let offset_89 := calldataload(add(headStart_81, 64)) + if gt(offset_89, 0xffffffffffffffff) + { + revert(0, 0) + } + value2_85 := abi_decode_t_bytes_memory_ptr(add(headStart_81, offset_89), dataEnd_82) + } + { + let offset_90 := 96 + value3_86 := abi_decode_t_enum$_Operation_$1949(add(headStart_81, offset_90), dataEnd_82) + } + } + function abi_decode_tuple_t_addresst_uint256t_bytes_memory_ptrt_enum$_Operation_$1949t_uint256t_uint256t_uint256t_addresst_addresst_uint256(headStart_91, dataEnd_92) -> value0_93, value1_94, value2_95, value3_96, value4_97, value5_98, value6_99, value7_100, value8_101, value9_102 + { + if slt(sub(dataEnd_92, headStart_91), 320) + { + revert(0, 0) + } + { + let offset_103 := 0 + value0_93 := abi_decode_t_address(add(headStart_91, offset_103), dataEnd_92) + } + { + let offset_104 := 32 + value1_94 := abi_decode_t_uint256(add(headStart_91, offset_104), dataEnd_92) + } + { + let offset_105 := calldataload(add(headStart_91, 64)) + if gt(offset_105, 0xffffffffffffffff) + { + revert(0, 0) + } + value2_95 := abi_decode_t_bytes_memory_ptr(add(headStart_91, offset_105), dataEnd_92) + } + { + let offset_106 := 96 + value3_96 := abi_decode_t_enum$_Operation_$1949(add(headStart_91, offset_106), dataEnd_92) + } + { + let offset_107 := 128 + value4_97 := abi_decode_t_uint256(add(headStart_91, offset_107), dataEnd_92) + } + { + let offset_108 := 160 + value5_98 := abi_decode_t_uint256(add(headStart_91, offset_108), dataEnd_92) + } + { + let offset_109 := 192 + value6_99 := abi_decode_t_uint256(add(headStart_91, offset_109), dataEnd_92) + } + { + let offset_110 := 224 + value7_100 := abi_decode_t_address(add(headStart_91, offset_110), dataEnd_92) + } + { + let offset_111 := 256 + value8_101 := abi_decode_t_address(add(headStart_91, offset_111), dataEnd_92) + } + { + let offset_112 := 288 + value9_102 := abi_decode_t_uint256(add(headStart_91, offset_112), dataEnd_92) + } + } + function abi_decode_tuple_t_array$_t_address_$dyn_calldata_ptrt_uint256t_addresst_bytes_calldata_ptr(headStart_113, dataEnd_114) -> value0_115, value1_116, value2_117, value3_118, value4_119, value5_120 + { + if slt(sub(dataEnd_114, headStart_113), 128) + { + revert(0, 0) + } + { + let offset_121 := calldataload(add(headStart_113, 0)) + if gt(offset_121, 0xffffffffffffffff) + { + revert(0, 0) + } + value0_115, value1_116 := abi_decode_t_array$_t_address_$dyn_calldata_ptr(add(headStart_113, offset_121), dataEnd_114) + } + { + let offset_122 := 32 + value2_117 := abi_decode_t_uint256(add(headStart_113, offset_122), dataEnd_114) + } + { + let offset_123 := 64 + value3_118 := abi_decode_t_address(add(headStart_113, offset_123), dataEnd_114) + } + { + let offset_124 := calldataload(add(headStart_113, 96)) + if gt(offset_124, 0xffffffffffffffff) + { + revert(0, 0) + } + value4_119, value5_120 := abi_decode_t_bytes_calldata_ptr(add(headStart_113, offset_124), dataEnd_114) + } + } + function abi_decode_tuple_t_bool_fromMemory(headStart_125, dataEnd_126) -> value0_127 + { + if slt(sub(dataEnd_126, headStart_125), 32) + { + revert(0, 0) + } + { + let offset_128 := 0 + value0_127 := abi_decode_t_bool_fromMemory(add(headStart_125, offset_128), dataEnd_126) + } + } + function abi_decode_tuple_t_bytes32(headStart_129, dataEnd_130) -> value0_131 + { + if slt(sub(dataEnd_130, headStart_129), 32) + { + revert(0, 0) + } + { + let offset_132 := 0 + value0_131 := abi_decode_t_bytes32(add(headStart_129, offset_132), dataEnd_130) + } + } + function abi_decode_tuple_t_bytes_calldata_ptr(headStart_133, dataEnd_134) -> value0_135, value1_136 + { + if slt(sub(dataEnd_134, headStart_133), 32) + { + revert(0, 0) + } + { + let offset_137 := calldataload(add(headStart_133, 0)) + if gt(offset_137, 0xffffffffffffffff) + { + revert(0, 0) + } + value0_135, value1_136 := abi_decode_t_bytes_calldata_ptr(add(headStart_133, offset_137), dataEnd_134) + } + } + function abi_decode_tuple_t_bytes_calldata_ptrt_bytes_calldata_ptr(headStart_138, dataEnd_139) -> value0_140, value1_141, value2_142, value3_143 + { + if slt(sub(dataEnd_139, headStart_138), 64) + { + revert(0, 0) + } + { + let offset_144 := calldataload(add(headStart_138, 0)) + if gt(offset_144, 0xffffffffffffffff) + { + revert(0, 0) + } + value0_140, value1_141 := abi_decode_t_bytes_calldata_ptr(add(headStart_138, offset_144), dataEnd_139) + } + { + let offset_145 := calldataload(add(headStart_138, 32)) + if gt(offset_145, 0xffffffffffffffff) + { + revert(0, 0) + } + value2_142, value3_143 := abi_decode_t_bytes_calldata_ptr(add(headStart_138, offset_145), dataEnd_139) + } + } + function abi_decode_tuple_t_bytes_memory_ptr(headStart_146, dataEnd_147) -> value0_148 + { + if slt(sub(dataEnd_147, headStart_146), 32) + { + revert(0, 0) + } + { + let offset_149 := calldataload(add(headStart_146, 0)) + if gt(offset_149, 0xffffffffffffffff) + { + revert(0, 0) + } + value0_148 := abi_decode_t_bytes_memory_ptr(add(headStart_146, offset_149), dataEnd_147) + } + } + function abi_decode_tuple_t_contract$_Module_$1038(headStart_150, dataEnd_151) -> value0_152 + { + if slt(sub(dataEnd_151, headStart_150), 32) + { + revert(0, 0) + } + { + let offset_153 := 0 + value0_152 := abi_decode_t_contract$_Module_$1038(add(headStart_150, offset_153), dataEnd_151) + } + } + function abi_decode_tuple_t_contract$_Module_$1038t_contract$_Module_$1038(headStart_154, dataEnd_155) -> value0_156, value1_157 + { + if slt(sub(dataEnd_155, headStart_154), 64) + { + revert(0, 0) + } + { + let offset_158 := 0 + value0_156 := abi_decode_t_contract$_Module_$1038(add(headStart_154, offset_158), dataEnd_155) + } + { + let offset_159 := 32 + value1_157 := abi_decode_t_contract$_Module_$1038(add(headStart_154, offset_159), dataEnd_155) + } + } + function abi_decode_tuple_t_uint256(headStart_160, dataEnd_161) -> value0_162 + { + if slt(sub(dataEnd_161, headStart_160), 32) + { + revert(0, 0) + } + { + let offset_163 := 0 + value0_162 := abi_decode_t_uint256(add(headStart_160, offset_163), dataEnd_161) + } + } + function abi_encode_t_address_to_t_address(value_164, pos) + { + mstore(pos, cleanup_assert_t_address(value_164)) + } + function abi_encode_t_array$_t_address_$dyn_memory_ptr_to_t_array$_t_address_$dyn_memory_ptr(value_165, pos_166) -> end_167 + { + let length_168 := array_length_t_array$_t_address_$dyn_memory_ptr(value_165) + mstore(pos_166, length_168) + pos_166 := add(pos_166, 0x20) + let srcPtr := array_dataslot_t_array$_t_address_$dyn_memory_ptr(value_165) + for { + let i := 0 + } + lt(i, length_168) + { + i := add(i, 1) + } + { + abi_encode_t_address_to_t_address(mload(srcPtr), pos_166) + srcPtr := array_nextElement_t_array$_t_address_$dyn_memory_ptr(srcPtr) + pos_166 := add(pos_166, 0x20) + } + end_167 := pos_166 + } + function abi_encode_t_bool_to_t_bool(value_169, pos_170) + { + mstore(pos_170, cleanup_assert_t_bool(value_169)) + } + function abi_encode_t_bytes32_to_t_bytes32(value_171, pos_172) + { + mstore(pos_172, cleanup_assert_t_bytes32(value_171)) + } + function abi_encode_t_bytes_memory_ptr_to_t_bytes_memory_ptr(value_173, pos_174) -> end_175 + { + let length_176 := array_length_t_bytes_memory_ptr(value_173) + mstore(pos_174, length_176) + copy_memory_to_memory(add(value_173, 0x20), add(pos_174, 0x20), length_176) + end_175 := add(add(pos_174, 0x20), round_up_to_mul_of_32(length_176)) + } + function abi_encode_t_contract$_GnosisSafe_$710_to_t_address_payable(value_177, pos_178) + { + mstore(pos_178, convert_t_contract$_GnosisSafe_$710_to_t_address_payable(value_177)) + } + function abi_encode_t_contract$_Module_$1038_to_t_address(value_179, pos_180) + { + mstore(pos_180, convert_t_contract$_Module_$1038_to_t_address(value_179)) + } + function abi_encode_t_enum$_Operation_$1949_to_t_uint8(value_181, pos_182) + { + mstore(pos_182, convert_t_enum$_Operation_$1949_to_t_uint8(value_181)) + } + function abi_encode_t_string_memory_ptr_to_t_string_memory_ptr(value_183, pos_184) -> end_185 + { + let length_186 := array_length_t_string_memory_ptr(value_183) + mstore(pos_184, length_186) + copy_memory_to_memory(add(value_183, 0x20), add(pos_184, 0x20), length_186) + end_185 := add(add(pos_184, 0x20), round_up_to_mul_of_32(length_186)) + } + function abi_encode_t_string_memory_to_t_string_memory_ptr(value_187, pos_188) -> end_189 + { + let length_190 := array_length_t_string_memory(value_187) + mstore(pos_188, length_190) + copy_memory_to_memory(add(value_187, 0x20), add(pos_188, 0x20), length_190) + end_189 := add(add(pos_188, 0x20), round_up_to_mul_of_32(length_190)) + } + function abi_encode_t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87_to_t_string_memory_ptr(pos_191) -> end_192 + { + mstore(pos_191, 36) + mstore(add(pos_191, 32), 0x496e76616c6964206d617374657220636f707920616464726573732070726f76) + mstore(add(pos_191, 64), 0x6964656400000000000000000000000000000000000000000000000000000000) + end_192 := add(pos_191, 96) + } + function abi_encode_t_stringliteral_1e0428ffa69bff65645154a36d5017c238f946ddaf89430d30eec813f30bdd77_to_t_string_memory_ptr(pos_193) -> end_194 + { + mstore(pos_193, 37) + mstore(add(pos_193, 32), 0x4d6f64756c6573206861766520616c7265616479206265656e20696e69746961) + mstore(add(pos_193, 64), 0x6c697a6564000000000000000000000000000000000000000000000000000000) + end_194 := add(pos_193, 96) + } + function abi_encode_t_stringliteral_21a1cd38818adb750881fbf07c26ce7223dde608fdd9dadd31a0d41afeca2094_to_t_string_memory_ptr(pos_195) -> end_196 + { + mstore(pos_195, 30) + mstore(add(pos_195, 32), 0x496e76616c6964206f776e657220616464726573732070726f76696465640000) + end_196 := add(pos_195, 64) + } + function abi_encode_t_stringliteral_5caa315f9c5cf61be71c182eef2dc9ef7b6ce6b42c320d36694e1d23e09c287e_to_t_string_memory_ptr(pos_197) -> end_198 + { + mstore(pos_197, 40) + mstore(add(pos_197, 32), 0x496e76616c696420707265764d6f64756c652c206d6f64756c65207061697220) + mstore(add(pos_197, 64), 0x70726f7669646564000000000000000000000000000000000000000000000000) + end_198 := add(pos_197, 96) + } + function abi_encode_t_stringliteral_60f21058f4a7689ef29853b3c9c17c9bf69856a949794649bb68878f00552475_to_t_string_memory_ptr(pos_199) -> end_200 + { + mstore(pos_199, 30) + mstore(add(pos_199, 32), 0x4f6e6c79206f776e6572732063616e20617070726f7665206120686173680000) + end_200 := add(pos_199, 64) + } + function abi_encode_t_stringliteral_63d26a9feb8568677e5c255c04e4da88e86a25137d5152a9a089790b7e710e86_to_t_string_memory_ptr(pos_201) -> end_202 + { + mstore(pos_201, 35) + mstore(add(pos_201, 32), 0x5468726573686f6c642063616e6e6f7420657863656564206f776e657220636f) + mstore(add(pos_201, 64), 0x756e740000000000000000000000000000000000000000000000000000000000) + end_202 := add(pos_201, 96) + } + function abi_encode_t_stringliteral_7913a3f9168bf3e458e3f42eb08db5c4b33f44228d345660887090b75e24c6aa_to_t_string_memory_ptr(pos_203) -> end_204 + { + mstore(pos_203, 31) + mstore(add(pos_203, 32), 0x436f756c64206e6f742066696e69736820696e697469616c697a6174696f6e00) + end_204 := add(pos_203, 64) + } + function abi_encode_t_stringliteral_839b4c4db845de24ec74ef067d85431087d6987a4c904418ee4f6ec699c02482_to_t_string_memory_ptr(pos_205) -> end_206 + { + mstore(pos_205, 53) + mstore(add(pos_205, 32), 0x4e6577206f776e657220636f756e74206e6565647320746f206265206c617267) + mstore(add(pos_205, 64), 0x6572207468616e206e6577207468726573686f6c640000000000000000000000) + end_206 := add(pos_205, 96) + } + function abi_encode_t_stringliteral_8560a13547eca5648355c8db1a9f8653b6f657d31d476c36bca25e47b45b08f4_to_t_string_memory_ptr(pos_207) -> end_208 + { + mstore(pos_207, 34) + mstore(add(pos_207, 32), 0x436f756c64206e6f74207061792067617320636f737473207769746820746f6b) + mstore(add(pos_207, 64), 0x656e000000000000000000000000000000000000000000000000000000000000) + end_208 := add(pos_207, 96) + } + function abi_encode_t_stringliteral_85bcea44c930431ef19052d068cc504a81260341ae6c5ee84bb5a38ec55acf05_to_t_string_memory_ptr(pos_209) -> end_210 + { + mstore(pos_209, 27) + mstore(add(pos_209, 32), 0x496e76616c6964207369676e6174757265732070726f76696465640000000000) + end_210 := add(pos_209, 64) + } + function abi_encode_t_stringliteral_8c2199b479423c52a835dfe8b0f2e9eb4c1ec1069ba198ccc38077a4a88a5c00_to_t_string_memory_ptr(pos_211) -> end_212 + { + mstore(pos_211, 31) + mstore(add(pos_211, 32), 0x496e76616c6964206d6f64756c6520616464726573732070726f766964656400) + end_212 := add(pos_211, 64) + } + function abi_encode_t_stringliteral_960698caed81fce71c9b7d572ab2e035b6014a5b812b51df8462ea9817fc4ebc_to_t_string_memory_ptr(pos_213) -> end_214 + { + mstore(pos_213, 38) + mstore(add(pos_213, 32), 0x496e76616c696420707265764f776e65722c206f776e65722070616972207072) + mstore(add(pos_213, 64), 0x6f76696465640000000000000000000000000000000000000000000000000000) + end_214 := add(pos_213, 96) + } + function abi_encode_t_stringliteral_9a45ae898fbe2bd07a0b33b5a6c421f76198e9deb66843b8d827b0b9e4a16f86_to_t_string_memory_ptr(pos_215) -> end_216 + { + mstore(pos_215, 30) + mstore(add(pos_215, 32), 0x4f776e657273206861766520616c7265616479206265656e2073657475700000) + end_216 := add(pos_215, 64) + } + function abi_encode_t_stringliteral_9d461d71e19b25cd406798d062d7e61f961ad52541d3077a543e857810427d47_to_t_string_memory_ptr(pos_217) -> end_218 + { + mstore(pos_217, 27) + mstore(add(pos_217, 32), 0x4164647265737320697320616c726561647920616e206f776e65720000000000) + end_218 := add(pos_217, 64) + } + function abi_encode_t_stringliteral_a2e1f2db9cd32eaa6a2caa3d6caa726a30dc0417d866440bfe13d6a6d030e5e2_to_t_string_memory_ptr(pos_219) -> end_220 + { + mstore(pos_219, 29) + mstore(add(pos_219, 32), 0x446f6d61696e20536570617261746f7220616c72656164792073657421000000) + end_220 := add(pos_219, 64) + } + function abi_encode_t_stringliteral_a803fa289679098e38a7f1f6fe43056918c5ab5af07441cb8db77b949c165ca1_to_t_string_memory_ptr(pos_221) -> end_222 + { + mstore(pos_221, 32) + mstore(add(pos_221, 32), 0x4475706c6963617465206f776e657220616464726573732070726f7669646564) + end_222 := add(pos_221, 64) + } + function abi_encode_t_stringliteral_ae2b4ea52eaf6de3fb2d8a64b7555be2dfd285b837a62821bf24e7dc6f329450_to_t_string_memory_ptr(pos_223) -> end_224 + { + mstore(pos_223, 29) + mstore(add(pos_223, 32), 0x4d6f64756c652068617320616c7265616479206265656e206164646564000000) + end_224 := add(pos_223, 64) + } + function abi_encode_t_stringliteral_b995394ed6031392a784e6dd5e04285cca83077a8dc3873d2fb7fcb090297ab4_to_t_string_memory_ptr(pos_225) -> end_226 + { + mstore(pos_225, 36) + mstore(add(pos_225, 32), 0x5468726573686f6c64206e6565647320746f2062652067726561746572207468) + mstore(add(pos_225, 64), 0x616e203000000000000000000000000000000000000000000000000000000000) + end_226 := add(pos_225, 96) + } + function abi_encode_t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733_to_t_string_memory_ptr(pos_227) -> end_228 + { + mstore(pos_227, 44) + mstore(add(pos_227, 32), 0x4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d2074) + mstore(add(pos_227, 64), 0x68697320636f6e74726163740000000000000000000000000000000000000000) + end_228 := add(pos_227, 96) + } + function abi_encode_t_stringliteral_cd36462b17a97c5a3df33333c859d5933a4fb7f5e1a0750f5def8eb51f3272e4_to_t_string_memory_ptr(pos_229) -> end_230 + { + mstore(pos_229, 48) + mstore(add(pos_229, 32), 0x4d6574686f642063616e206f6e6c792062652063616c6c65642066726f6d2061) + mstore(add(pos_229, 64), 0x6e20656e61626c6564206d6f64756c6500000000000000000000000000000000) + end_230 := add(pos_229, 96) + } + function abi_encode_t_stringliteral_e2a11e15f7be1214c1340779ad55027af8aa33aee6cb521776a28a0a44aea377_to_t_string_memory_ptr(pos_231) -> end_232 + { + mstore(pos_231, 34) + mstore(add(pos_231, 32), 0x436f756c64206e6f74207061792067617320636f737473207769746820657468) + mstore(add(pos_231, 64), 0x6572000000000000000000000000000000000000000000000000000000000000) + end_232 := add(pos_231, 96) + } + function abi_encode_t_stringliteral_e7ccb05a0f2c66d12451cdfc6bbab488c38ab704d0f6af9ad18763542e9e5f18_to_t_string_memory_ptr(pos_233) -> end_234 + { + mstore(pos_233, 42) + mstore(add(pos_233, 32), 0x4e6f7420656e6f7567682067617320746f206578656375746520736166652074) + mstore(add(pos_233, 64), 0x72616e73616374696f6e00000000000000000000000000000000000000000000) + end_234 := add(pos_233, 96) + } + function abi_encode_t_uint256_to_t_uint256(value_235, pos_236) + { + mstore(pos_236, cleanup_assert_t_uint256(value_235)) + } + function abi_encode_tuple_t_address__to_t_address_(headStart_237, value0_238) -> tail + { + tail := add(headStart_237, 32) + abi_encode_t_address_to_t_address(value0_238, add(headStart_237, 0)) + } + function abi_encode_tuple_t_address_t_uint256__to_t_address_t_uint256_(headStart_239, value1_240, value0_241) -> tail_242 + { + tail_242 := add(headStart_239, 64) + abi_encode_t_address_to_t_address(value0_241, add(headStart_239, 0)) + abi_encode_t_uint256_to_t_uint256(value1_240, add(headStart_239, 32)) + } + function abi_encode_tuple_t_array$_t_address_$dyn_memory_ptr__to_t_array$_t_address_$dyn_memory_ptr_(headStart_243, value0_244) -> tail_245 + { + tail_245 := add(headStart_243, 32) + mstore(add(headStart_243, 0), sub(tail_245, headStart_243)) + tail_245 := abi_encode_t_array$_t_address_$dyn_memory_ptr_to_t_array$_t_address_$dyn_memory_ptr(value0_244, tail_245) + } + function abi_encode_tuple_t_bool__to_t_bool_(headStart_246, value0_247) -> tail_248 + { + tail_248 := add(headStart_246, 32) + abi_encode_t_bool_to_t_bool(value0_247, add(headStart_246, 0)) + } + function abi_encode_tuple_t_bytes32__to_t_bytes32_(headStart_249, value0_250) -> tail_251 + { + tail_251 := add(headStart_249, 32) + abi_encode_t_bytes32_to_t_bytes32(value0_250, add(headStart_249, 0)) + } + function abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_(headStart_252, value10_253, value9_254, value8_255, value7_256, value6_257, value5_258, value4_259, value3_260, value2_261, value1_262, value0_263) -> tail_264 + { + tail_264 := add(headStart_252, 352) + abi_encode_t_bytes32_to_t_bytes32(value0_263, add(headStart_252, 0)) + abi_encode_t_address_to_t_address(value1_262, add(headStart_252, 32)) + abi_encode_t_uint256_to_t_uint256(value2_261, add(headStart_252, 64)) + abi_encode_t_bytes32_to_t_bytes32(value3_260, add(headStart_252, 96)) + abi_encode_t_enum$_Operation_$1949_to_t_uint8(value4_259, add(headStart_252, 128)) + abi_encode_t_uint256_to_t_uint256(value5_258, add(headStart_252, 160)) + abi_encode_t_uint256_to_t_uint256(value6_257, add(headStart_252, 192)) + abi_encode_t_uint256_to_t_uint256(value7_256, add(headStart_252, 224)) + abi_encode_t_address_to_t_address(value8_255, add(headStart_252, 256)) + abi_encode_t_address_to_t_address(value9_254, add(headStart_252, 288)) + abi_encode_t_uint256_to_t_uint256(value10_253, add(headStart_252, 320)) + } + function abi_encode_tuple_t_bytes32_t_bytes32__to_t_bytes32_t_bytes32_(headStart_265, value1_266, value0_267) -> tail_268 + { + tail_268 := add(headStart_265, 64) + abi_encode_t_bytes32_to_t_bytes32(value0_267, add(headStart_265, 0)) + abi_encode_t_bytes32_to_t_bytes32(value1_266, add(headStart_265, 32)) + } + function abi_encode_tuple_t_bytes32_t_contract$_GnosisSafe_$710__to_t_bytes32_t_address_payable_(headStart_269, value1_270, value0_271) -> tail_272 + { + tail_272 := add(headStart_269, 64) + abi_encode_t_bytes32_to_t_bytes32(value0_271, add(headStart_269, 0)) + abi_encode_t_contract$_GnosisSafe_$710_to_t_address_payable(value1_270, add(headStart_269, 32)) + } + function abi_encode_tuple_t_bytes_memory_ptr__to_t_bytes_memory_ptr_(headStart_273, value0_274) -> tail_275 + { + tail_275 := add(headStart_273, 32) + mstore(add(headStart_273, 0), sub(tail_275, headStart_273)) + tail_275 := abi_encode_t_bytes_memory_ptr_to_t_bytes_memory_ptr(value0_274, tail_275) + } + function abi_encode_tuple_t_bytes_memory_ptr_t_bytes_memory_ptr__to_t_bytes_memory_ptr_t_bytes_memory_ptr_(headStart_276, value1_277, value0_278) -> tail_279 + { + tail_279 := add(headStart_276, 64) + mstore(add(headStart_276, 0), sub(tail_279, headStart_276)) + tail_279 := abi_encode_t_bytes_memory_ptr_to_t_bytes_memory_ptr(value0_278, tail_279) + mstore(add(headStart_276, 32), sub(tail_279, headStart_276)) + tail_279 := abi_encode_t_bytes_memory_ptr_to_t_bytes_memory_ptr(value1_277, tail_279) + } + function abi_encode_tuple_t_contract$_Module_$1038__to_t_address_(headStart_280, value0_281) -> tail_282 + { + tail_282 := add(headStart_280, 32) + abi_encode_t_contract$_Module_$1038_to_t_address(value0_281, add(headStart_280, 0)) + } + function abi_encode_tuple_t_string_memory__to_t_string_memory_ptr_(headStart_283, value0_284) -> tail_285 + { + tail_285 := add(headStart_283, 32) + mstore(add(headStart_283, 0), sub(tail_285, headStart_283)) + tail_285 := abi_encode_t_string_memory_to_t_string_memory_ptr(value0_284, tail_285) + } + function abi_encode_tuple_t_string_memory_ptr__to_t_string_memory_ptr_(headStart_286, value0_287) -> tail_288 + { + tail_288 := add(headStart_286, 32) + mstore(add(headStart_286, 0), sub(tail_288, headStart_286)) + tail_288 := abi_encode_t_string_memory_ptr_to_t_string_memory_ptr(value0_287, tail_288) + } + function abi_encode_tuple_t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87__to_t_string_memory_ptr_(headStart_289) -> tail_290 + { + tail_290 := add(headStart_289, 32) + mstore(add(headStart_289, 0), sub(tail_290, headStart_289)) + tail_290 := abi_encode_t_stringliteral_108d84599042957b954e89d43b52f80be89321dfc114a37800028eba58dafc87_to_t_string_memory_ptr(tail_290) + } + function abi_encode_tuple_t_stringliteral_1e0428ffa69bff65645154a36d5017c238f946ddaf89430d30eec813f30bdd77__to_t_string_memory_ptr_(headStart_291) -> tail_292 + { + tail_292 := add(headStart_291, 32) + mstore(add(headStart_291, 0), sub(tail_292, headStart_291)) + tail_292 := abi_encode_t_stringliteral_1e0428ffa69bff65645154a36d5017c238f946ddaf89430d30eec813f30bdd77_to_t_string_memory_ptr(tail_292) + } + function abi_encode_tuple_t_stringliteral_21a1cd38818adb750881fbf07c26ce7223dde608fdd9dadd31a0d41afeca2094__to_t_string_memory_ptr_(headStart_293) -> tail_294 + { + tail_294 := add(headStart_293, 32) + mstore(add(headStart_293, 0), sub(tail_294, headStart_293)) + tail_294 := abi_encode_t_stringliteral_21a1cd38818adb750881fbf07c26ce7223dde608fdd9dadd31a0d41afeca2094_to_t_string_memory_ptr(tail_294) + } + function abi_encode_tuple_t_stringliteral_5caa315f9c5cf61be71c182eef2dc9ef7b6ce6b42c320d36694e1d23e09c287e__to_t_string_memory_ptr_(headStart_295) -> tail_296 + { + tail_296 := add(headStart_295, 32) + mstore(add(headStart_295, 0), sub(tail_296, headStart_295)) + tail_296 := abi_encode_t_stringliteral_5caa315f9c5cf61be71c182eef2dc9ef7b6ce6b42c320d36694e1d23e09c287e_to_t_string_memory_ptr(tail_296) + } + function abi_encode_tuple_t_stringliteral_60f21058f4a7689ef29853b3c9c17c9bf69856a949794649bb68878f00552475__to_t_string_memory_ptr_(headStart_297) -> tail_298 + { + tail_298 := add(headStart_297, 32) + mstore(add(headStart_297, 0), sub(tail_298, headStart_297)) + tail_298 := abi_encode_t_stringliteral_60f21058f4a7689ef29853b3c9c17c9bf69856a949794649bb68878f00552475_to_t_string_memory_ptr(tail_298) + } + function abi_encode_tuple_t_stringliteral_63d26a9feb8568677e5c255c04e4da88e86a25137d5152a9a089790b7e710e86__to_t_string_memory_ptr_(headStart_299) -> tail_300 + { + tail_300 := add(headStart_299, 32) + mstore(add(headStart_299, 0), sub(tail_300, headStart_299)) + tail_300 := abi_encode_t_stringliteral_63d26a9feb8568677e5c255c04e4da88e86a25137d5152a9a089790b7e710e86_to_t_string_memory_ptr(tail_300) + } + function abi_encode_tuple_t_stringliteral_7913a3f9168bf3e458e3f42eb08db5c4b33f44228d345660887090b75e24c6aa__to_t_string_memory_ptr_(headStart_301) -> tail_302 + { + tail_302 := add(headStart_301, 32) + mstore(add(headStart_301, 0), sub(tail_302, headStart_301)) + tail_302 := abi_encode_t_stringliteral_7913a3f9168bf3e458e3f42eb08db5c4b33f44228d345660887090b75e24c6aa_to_t_string_memory_ptr(tail_302) + } + function abi_encode_tuple_t_stringliteral_839b4c4db845de24ec74ef067d85431087d6987a4c904418ee4f6ec699c02482__to_t_string_memory_ptr_(headStart_303) -> tail_304 + { + tail_304 := add(headStart_303, 32) + mstore(add(headStart_303, 0), sub(tail_304, headStart_303)) + tail_304 := abi_encode_t_stringliteral_839b4c4db845de24ec74ef067d85431087d6987a4c904418ee4f6ec699c02482_to_t_string_memory_ptr(tail_304) + } + function abi_encode_tuple_t_stringliteral_8560a13547eca5648355c8db1a9f8653b6f657d31d476c36bca25e47b45b08f4__to_t_string_memory_ptr_(headStart_305) -> tail_306 + { + tail_306 := add(headStart_305, 32) + mstore(add(headStart_305, 0), sub(tail_306, headStart_305)) + tail_306 := abi_encode_t_stringliteral_8560a13547eca5648355c8db1a9f8653b6f657d31d476c36bca25e47b45b08f4_to_t_string_memory_ptr(tail_306) + } + function abi_encode_tuple_t_stringliteral_85bcea44c930431ef19052d068cc504a81260341ae6c5ee84bb5a38ec55acf05__to_t_string_memory_ptr_(headStart_307) -> tail_308 + { + tail_308 := add(headStart_307, 32) + mstore(add(headStart_307, 0), sub(tail_308, headStart_307)) + tail_308 := abi_encode_t_stringliteral_85bcea44c930431ef19052d068cc504a81260341ae6c5ee84bb5a38ec55acf05_to_t_string_memory_ptr(tail_308) + } + function abi_encode_tuple_t_stringliteral_8c2199b479423c52a835dfe8b0f2e9eb4c1ec1069ba198ccc38077a4a88a5c00__to_t_string_memory_ptr_(headStart_309) -> tail_310 + { + tail_310 := add(headStart_309, 32) + mstore(add(headStart_309, 0), sub(tail_310, headStart_309)) + tail_310 := abi_encode_t_stringliteral_8c2199b479423c52a835dfe8b0f2e9eb4c1ec1069ba198ccc38077a4a88a5c00_to_t_string_memory_ptr(tail_310) + } + function abi_encode_tuple_t_stringliteral_960698caed81fce71c9b7d572ab2e035b6014a5b812b51df8462ea9817fc4ebc__to_t_string_memory_ptr_(headStart_311) -> tail_312 + { + tail_312 := add(headStart_311, 32) + mstore(add(headStart_311, 0), sub(tail_312, headStart_311)) + tail_312 := abi_encode_t_stringliteral_960698caed81fce71c9b7d572ab2e035b6014a5b812b51df8462ea9817fc4ebc_to_t_string_memory_ptr(tail_312) + } + function abi_encode_tuple_t_stringliteral_9a45ae898fbe2bd07a0b33b5a6c421f76198e9deb66843b8d827b0b9e4a16f86__to_t_string_memory_ptr_(headStart_313) -> tail_314 + { + tail_314 := add(headStart_313, 32) + mstore(add(headStart_313, 0), sub(tail_314, headStart_313)) + tail_314 := abi_encode_t_stringliteral_9a45ae898fbe2bd07a0b33b5a6c421f76198e9deb66843b8d827b0b9e4a16f86_to_t_string_memory_ptr(tail_314) + } + function abi_encode_tuple_t_stringliteral_9d461d71e19b25cd406798d062d7e61f961ad52541d3077a543e857810427d47__to_t_string_memory_ptr_(headStart_315) -> tail_316 + { + tail_316 := add(headStart_315, 32) + mstore(add(headStart_315, 0), sub(tail_316, headStart_315)) + tail_316 := abi_encode_t_stringliteral_9d461d71e19b25cd406798d062d7e61f961ad52541d3077a543e857810427d47_to_t_string_memory_ptr(tail_316) + } + function abi_encode_tuple_t_stringliteral_a2e1f2db9cd32eaa6a2caa3d6caa726a30dc0417d866440bfe13d6a6d030e5e2__to_t_string_memory_ptr_(headStart_317) -> tail_318 + { + tail_318 := add(headStart_317, 32) + mstore(add(headStart_317, 0), sub(tail_318, headStart_317)) + tail_318 := abi_encode_t_stringliteral_a2e1f2db9cd32eaa6a2caa3d6caa726a30dc0417d866440bfe13d6a6d030e5e2_to_t_string_memory_ptr(tail_318) + } + function abi_encode_tuple_t_stringliteral_a803fa289679098e38a7f1f6fe43056918c5ab5af07441cb8db77b949c165ca1__to_t_string_memory_ptr_(headStart_319) -> tail_320 + { + tail_320 := add(headStart_319, 32) + mstore(add(headStart_319, 0), sub(tail_320, headStart_319)) + tail_320 := abi_encode_t_stringliteral_a803fa289679098e38a7f1f6fe43056918c5ab5af07441cb8db77b949c165ca1_to_t_string_memory_ptr(tail_320) + } + function abi_encode_tuple_t_stringliteral_ae2b4ea52eaf6de3fb2d8a64b7555be2dfd285b837a62821bf24e7dc6f329450__to_t_string_memory_ptr_(headStart_321) -> tail_322 + { + tail_322 := add(headStart_321, 32) + mstore(add(headStart_321, 0), sub(tail_322, headStart_321)) + tail_322 := abi_encode_t_stringliteral_ae2b4ea52eaf6de3fb2d8a64b7555be2dfd285b837a62821bf24e7dc6f329450_to_t_string_memory_ptr(tail_322) + } + function abi_encode_tuple_t_stringliteral_b995394ed6031392a784e6dd5e04285cca83077a8dc3873d2fb7fcb090297ab4__to_t_string_memory_ptr_(headStart_323) -> tail_324 + { + tail_324 := add(headStart_323, 32) + mstore(add(headStart_323, 0), sub(tail_324, headStart_323)) + tail_324 := abi_encode_t_stringliteral_b995394ed6031392a784e6dd5e04285cca83077a8dc3873d2fb7fcb090297ab4_to_t_string_memory_ptr(tail_324) + } + function abi_encode_tuple_t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733__to_t_string_memory_ptr_(headStart_325) -> tail_326 + { + tail_326 := add(headStart_325, 32) + mstore(add(headStart_325, 0), sub(tail_326, headStart_325)) + tail_326 := abi_encode_t_stringliteral_c4780ef0a1d41d59bac8c510cf9ada421bccf2b90f75a8e4ba2e8c09e8d72733_to_t_string_memory_ptr(tail_326) + } + function abi_encode_tuple_t_stringliteral_cd36462b17a97c5a3df33333c859d5933a4fb7f5e1a0750f5def8eb51f3272e4__to_t_string_memory_ptr_(headStart_327) -> tail_328 + { + tail_328 := add(headStart_327, 32) + mstore(add(headStart_327, 0), sub(tail_328, headStart_327)) + tail_328 := abi_encode_t_stringliteral_cd36462b17a97c5a3df33333c859d5933a4fb7f5e1a0750f5def8eb51f3272e4_to_t_string_memory_ptr(tail_328) + } + function abi_encode_tuple_t_stringliteral_e2a11e15f7be1214c1340779ad55027af8aa33aee6cb521776a28a0a44aea377__to_t_string_memory_ptr_(headStart_329) -> tail_330 + { + tail_330 := add(headStart_329, 32) + mstore(add(headStart_329, 0), sub(tail_330, headStart_329)) + tail_330 := abi_encode_t_stringliteral_e2a11e15f7be1214c1340779ad55027af8aa33aee6cb521776a28a0a44aea377_to_t_string_memory_ptr(tail_330) + } + function abi_encode_tuple_t_stringliteral_e7ccb05a0f2c66d12451cdfc6bbab488c38ab704d0f6af9ad18763542e9e5f18__to_t_string_memory_ptr_(headStart_331) -> tail_332 + { + tail_332 := add(headStart_331, 32) + mstore(add(headStart_331, 0), sub(tail_332, headStart_331)) + tail_332 := abi_encode_t_stringliteral_e7ccb05a0f2c66d12451cdfc6bbab488c38ab704d0f6af9ad18763542e9e5f18_to_t_string_memory_ptr(tail_332) + } + function abi_encode_tuple_t_uint256__to_t_uint256_(headStart_333, value0_334) -> tail_335 + { + tail_335 := add(headStart_333, 32) + abi_encode_t_uint256_to_t_uint256(value0_334, add(headStart_333, 0)) + } + function allocateMemory(size) -> memPtr + { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) + { + revert(0, 0) + } + mstore(64, newFreePtr) + } + function array_allocation_size_t_bytes_memory_ptr(length_336) -> size_337 + { + if gt(length_336, 0xffffffffffffffff) + { + revert(0, 0) + } + size_337 := and(add(length_336, 0x1f), not(0x1f)) + size_337 := add(size_337, 0x20) + } + function array_dataslot_t_array$_t_address_$dyn_memory_ptr(memPtr_338) -> dataPtr + { + dataPtr := add(memPtr_338, 0x20) + } + function array_length_t_array$_t_address_$dyn_memory_ptr(value_339) -> length_340 + { + length_340 := mload(value_339) + } + function array_length_t_bytes_memory_ptr(value_341) -> length_342 + { + length_342 := mload(value_341) + } + function array_length_t_string_memory(value_343) -> length_344 + { + length_344 := mload(value_343) + } + function array_length_t_string_memory_ptr(value_345) -> length_346 + { + length_346 := mload(value_345) + } + function array_nextElement_t_array$_t_address_$dyn_memory_ptr(memPtr_347) -> nextPtr + { + nextPtr := add(memPtr_347, 0x20) + } + function cleanup_assert_t_address(value_348) -> cleaned + { + cleaned := cleanup_assert_t_uint160(value_348) + } + function cleanup_assert_t_bool(value_349) -> cleaned_350 + { + cleaned_350 := iszero(iszero(value_349)) + } + function cleanup_assert_t_bytes32(value_351) -> cleaned_352 + { + cleaned_352 := value_351 + } + function cleanup_assert_t_enum$_Operation_$1949(value_353) -> cleaned_354 + { + if iszero(lt(value_353, 3)) + { + invalid() + } + cleaned_354 := value_353 + } + function cleanup_assert_t_uint160(value_355) -> cleaned_356 + { + cleaned_356 := and(value_355, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) + } + function cleanup_assert_t_uint256(value_357) -> cleaned_358 + { + cleaned_358 := value_357 + } + function cleanup_revert_t_address(value_359) -> cleaned_360 + { + cleaned_360 := cleanup_assert_t_uint160(value_359) + } + function cleanup_revert_t_address_payable(value_361) -> cleaned_362 + { + cleaned_362 := cleanup_assert_t_uint160(value_361) + } + function cleanup_revert_t_bool(value_363) -> cleaned_364 + { + cleaned_364 := iszero(iszero(value_363)) + } + function cleanup_revert_t_bytes32(value_365) -> cleaned_366 + { + cleaned_366 := value_365 + } + function cleanup_revert_t_contract$_Module_$1038(value_367) -> cleaned_368 + { + cleaned_368 := cleanup_assert_t_address(value_367) + } + function cleanup_revert_t_enum$_Operation_$1949(value_369) -> cleaned_370 + { + if iszero(lt(value_369, 3)) + { + revert(0, 0) + } + cleaned_370 := value_369 + } + function cleanup_revert_t_uint256(value_371) -> cleaned_372 + { + cleaned_372 := value_371 + } + function convert_t_contract$_GnosisSafe_$710_to_t_address_payable(value_373) -> converted + { + converted := convert_t_contract$_GnosisSafe_$710_to_t_uint160(value_373) + } + function convert_t_contract$_GnosisSafe_$710_to_t_uint160(value_374) -> converted_375 + { + converted_375 := cleanup_assert_t_uint160(value_374) + } + function convert_t_contract$_Module_$1038_to_t_address(value_376) -> converted_377 + { + converted_377 := convert_t_contract$_Module_$1038_to_t_uint160(value_376) + } + function convert_t_contract$_Module_$1038_to_t_uint160(value_378) -> converted_379 + { + converted_379 := cleanup_assert_t_uint160(value_378) + } + function convert_t_enum$_Operation_$1949_to_t_uint8(value_380) -> converted_381 + { + converted_381 := cleanup_assert_t_enum$_Operation_$1949(value_380) + } + function copy_calldata_to_memory(src_382, dst_383, length_384) + { + calldatacopy(dst_383, src_382, length_384) + mstore(add(dst_383, length_384), 0) + } + function copy_memory_to_memory(src_385, dst_386, length_387) + { + let i_388 := 0 + for { + } + lt(i_388, length_387) + { + i_388 := add(i_388, 32) + } + { + mstore(add(dst_386, i_388), mload(add(src_385, i_388))) + } + if gt(i_388, length_387) + { + mstore(add(dst_386, length_387), 0) + } + } + function round_up_to_mul_of_32(value_389) -> result + { + result := and(add(value_389, 31), not(31)) + } +} +// ---- +// fullSuite +// { +// let _2 := mload(1) +// let _153 := mload(0) +// if slt(sub(_2, _153), 64) +// { +// revert(0, 0) +// } +// sstore(0, and(calldataload(_153), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// let x0, x1, x2, x3, x4 := abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(mload(7), mload(8)) +// sstore(x1, x0) +// sstore(x3, x2) +// sstore(1, x4) +// pop(abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_(mload(30), mload(31), mload(32), mload(33), mload(34), mload(35), mload(36), mload(37), mload(38), mload(39), mload(40), mload(41))) +// function abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(headStart_55, dataEnd_56) -> value0_57, value1_58, value2_59, value3, value4 +// { +// if slt(sub(dataEnd_56, headStart_55), 128) +// { +// revert(value4, value4) +// } +// value0_57 := and(calldataload(add(headStart_55, value4)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) +// value1_58 := calldataload(add(headStart_55, 32)) +// let offset_62 := calldataload(add(headStart_55, 64)) +// let _201 := 0xffffffffffffffff +// if gt(offset_62, _201) +// { +// revert(value4, value4) +// } +// let _203 := add(headStart_55, offset_62) +// if iszero(slt(add(_203, 0x1f), dataEnd_56)) +// { +// revert(value4, value4) +// } +// let abi_decode_length_15_246 := calldataload(_203) +// if gt(abi_decode_length_15_246, _201) +// { +// revert(value4, value4) +// } +// if gt(add(add(_203, abi_decode_length_15_246), 32), dataEnd_56) +// { +// revert(value4, value4) +// } +// value2_59 := add(_203, 32) +// value3 := abi_decode_length_15_246 +// let _206 := calldataload(add(headStart_55, 96)) +// if iszero(lt(_206, 3)) +// { +// revert(value4, value4) +// } +// value4 := _206 +// } +// function abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_(headStart_252, value10_253, value9_254, value8_255, value7_256, value6_257, value5_258, value4_259, value3_260, value2_261, value1_262, value0_263) -> tail_264 +// { +// tail_264 := add(headStart_252, 352) +// mstore(headStart_252, value0_263) +// let _413 := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// mstore(add(headStart_252, 32), and(value1_262, _413)) +// mstore(add(headStart_252, 64), value2_261) +// mstore(add(headStart_252, 96), value3_260) +// if iszero(lt(value4_259, 3)) +// { +// invalid() +// } +// mstore(add(headStart_252, 128), value4_259) +// mstore(add(headStart_252, 160), value5_258) +// mstore(add(headStart_252, 192), value6_257) +// mstore(add(headStart_252, 224), value7_256) +// mstore(add(headStart_252, 256), and(value8_255, _413)) +// mstore(add(headStart_252, 288), and(value9_254, _413)) +// mstore(add(headStart_252, 320), value10_253) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index efb846f2..d38025ae 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -458,47 +458,62 @@ // ---- // fullSuite // { +// let _1 := 0x20 +// let _2 := 0 +// let _218 := mload(_2) +// let abi_encode_pos := _1 +// let abi_encode_length_68 := mload(_218) +// mstore(_1, abi_encode_length_68) +// abi_encode_pos := 64 +// let abi_encode_srcPtr := add(_218, _1) +// let abi_encode_i_69 := _2 +// for { +// } +// lt(abi_encode_i_69, abi_encode_length_68) +// { +// abi_encode_i_69 := add(abi_encode_i_69, 1) +// } // { -// let _1 := 0x20 -// let _2 := 0 -// let _485 := mload(_2) -// let abi_encode_pos := _1 -// let abi_encode_length_68 := mload(_485) -// mstore(_1, abi_encode_length_68) -// abi_encode_pos := 64 -// let abi_encode_srcPtr := add(_485, _1) -// let abi_encode_i_69 := _2 +// let _580 := mload(abi_encode_srcPtr) +// let abi_encode_pos_71_672 := abi_encode_pos +// let abi_encode_srcPtr_73_674 := _580 +// let abi_encode_i_74_675 := _2 // for { // } -// lt(abi_encode_i_69, abi_encode_length_68) +// lt(abi_encode_i_74_675, 0x3) // { -// abi_encode_i_69 := add(abi_encode_i_69, 1) +// abi_encode_i_74_675 := add(abi_encode_i_74_675, 1) // } // { -// let _863 := mload(abi_encode_srcPtr) -// let abi_encode_pos_71_971 := abi_encode_pos -// let abi_encode_length_72_972 := 0x3 -// let abi_encode_srcPtr_73_973 := _863 -// let abi_encode_i_74_974 := _2 -// for { -// } -// lt(abi_encode_i_74_974, abi_encode_length_72_972) -// { -// abi_encode_i_74_974 := add(abi_encode_i_74_974, 1) -// } -// { -// mstore(abi_encode_pos_71_971, and(mload(abi_encode_srcPtr_73_973), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) -// abi_encode_srcPtr_73_973 := add(abi_encode_srcPtr_73_973, _1) -// abi_encode_pos_71_971 := add(abi_encode_pos_71_971, _1) -// } -// abi_encode_srcPtr := add(abi_encode_srcPtr, _1) -// abi_encode_pos := add(abi_encode_pos, 0x60) +// mstore(abi_encode_pos_71_672, and(mload(abi_encode_srcPtr_73_674), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// abi_encode_srcPtr_73_674 := add(abi_encode_srcPtr_73_674, _1) +// abi_encode_pos_71_672 := add(abi_encode_pos_71_672, _1) // } -// let a, b, c, d := abi_decode_tuple_t_uint256t_uint256t_array$_t_uint256_$dyn_memory_ptrt_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(mload(_1), mload(0x40)) -// sstore(a, b) -// sstore(c, d) -// sstore(_2, abi_encode_pos) +// abi_encode_srcPtr := add(abi_encode_srcPtr, _1) +// abi_encode_pos := add(abi_encode_pos, 0x60) +// } +// let _220 := mload(64) +// let _221 := mload(_1) +// if slt(sub(_220, _221), 128) +// { +// revert(_2, _2) // } +// let abi_decode_offset_64 := calldataload(add(_221, 64)) +// let abi_decode__74 := 0xffffffffffffffff +// if gt(abi_decode_offset_64, abi_decode__74) +// { +// revert(_2, _2) +// } +// let abi_decode_value2_317 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_221, abi_decode_offset_64), _220) +// let abi_decode_offset_65 := calldataload(add(_221, 96)) +// if gt(abi_decode_offset_65, abi_decode__74) +// { +// revert(_2, _2) +// } +// let abi_decode_value3_318 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_221, abi_decode_offset_65), _220) +// sstore(calldataload(_221), calldataload(add(_221, _1))) +// sstore(abi_decode_value2_317, abi_decode_value3_318) +// sstore(_2, abi_encode_pos) // function abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(offset_3, end_4) -> array_5 // { // if iszero(slt(add(offset_3, 0x1f), end_4)) @@ -506,19 +521,15 @@ // revert(array_5, array_5) // } // let length_6 := calldataload(offset_3) -// let array_5_254 := allocateMemory(array_allocation_size_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(length_6)) -// array_5 := array_5_254 -// let dst_7 := array_5_254 -// mstore(array_5_254, length_6) -// let _36 := 0x20 -// let offset_3_256 := add(offset_3, _36) -// dst_7 := add(array_5_254, _36) -// let src_8 := offset_3_256 -// let _38 := 0x40 -// if gt(add(add(offset_3, mul(length_6, _38)), _36), end_4) +// array_5 := allocateMemory(array_allocation_size_t_array$_t_address_$dyn_memory(length_6)) +// let dst_7 := array_5 +// mstore(array_5, length_6) +// let _16 := 0x20 +// dst_7 := add(array_5, _16) +// let src_8 := add(offset_3, _16) +// if gt(add(add(offset_3, mul(length_6, 0x40)), _16), end_4) // { -// let _42 := 0 -// revert(_42, _42) +// revert(0, 0) // } // let i_9 := 0 // for { @@ -528,40 +539,33 @@ // i_9 := add(i_9, 1) // } // { -// mstore(dst_7, abi_decode_t_array$_t_uint256_$2_memory(src_8, end_4)) -// dst_7 := add(dst_7, _36) -// src_8 := add(src_8, _38) -// } -// } -// function abi_decode_t_array$_t_uint256_$2_memory(offset_11, end_12) -> array_13 -// { -// if iszero(slt(add(offset_11, 0x1f), end_12)) -// { -// revert(array_13, array_13) -// } -// let length_14 := 0x2 -// let array_allo__559 := 0x20 -// let array_allo_size_95_605 := 64 -// let array_13_263 := allocateMemory(array_allo_size_95_605) -// array_13 := array_13_263 -// let dst_15 := array_13_263 -// let src_16 := offset_11 -// if gt(add(offset_11, array_allo_size_95_605), end_12) -// { -// let _59 := 0 -// revert(_59, _59) -// } -// let i_17 := 0 -// for { -// } -// lt(i_17, length_14) -// { -// i_17 := add(i_17, 1) -// } -// { -// mstore(dst_15, calldataload(src_16)) -// dst_15 := add(dst_15, array_allo__559) -// src_16 := add(src_16, array_allo__559) +// if iszero(slt(add(src_8, 0x1f), end_4)) +// { +// revert(0, 0) +// } +// let abi_decode_dst_15 := allocateMemory(array_allocation_size_t_array$_t_uint256_$2_memory(0x2)) +// let abi_decode_dst_15_1155 := abi_decode_dst_15 +// let abi_decode_src_16 := src_8 +// let abi_decode__239 := add(src_8, 0x40) +// if gt(abi_decode__239, end_4) +// { +// revert(0, 0) +// } +// let abi_decode_i_17 := 0 +// for { +// } +// lt(abi_decode_i_17, 0x2) +// { +// abi_decode_i_17 := add(abi_decode_i_17, 1) +// } +// { +// mstore(abi_decode_dst_15, calldataload(abi_decode_src_16)) +// abi_decode_dst_15 := add(abi_decode_dst_15, _16) +// abi_decode_src_16 := add(abi_decode_src_16, _16) +// } +// mstore(dst_7, abi_decode_dst_15_1155) +// dst_7 := add(dst_7, _16) +// src_8 := abi_decode__239 // } // } // function abi_decode_t_array$_t_uint256_$dyn_memory_ptr(offset_27, end_28) -> array_29 @@ -571,18 +575,15 @@ // revert(array_29, array_29) // } // let length_30 := calldataload(offset_27) -// let array_29_279 := allocateMemory(array_allocation_size_t_array$_t_uint256_$dyn_memory_ptr(length_30)) -// array_29 := array_29_279 -// let dst_31 := array_29_279 -// mstore(array_29_279, length_30) -// let _91 := 0x20 -// let offset_27_281 := add(offset_27, _91) -// dst_31 := add(array_29_279, _91) -// let src_32 := offset_27_281 -// if gt(add(add(offset_27, mul(length_30, _91)), _91), end_28) +// array_29 := allocateMemory(array_allocation_size_t_array$_t_address_$dyn_memory(length_30)) +// let dst_31 := array_29 +// mstore(array_29, length_30) +// let _52 := 0x20 +// dst_31 := add(array_29, _52) +// let src_32 := add(offset_27, _52) +// if gt(add(add(offset_27, mul(length_30, _52)), _52), end_28) // { -// let _97 := 0 -// revert(_97, _97) +// revert(0, 0) // } // let i_33 := 0 // for { @@ -593,68 +594,34 @@ // } // { // mstore(dst_31, calldataload(src_32)) -// dst_31 := add(dst_31, _91) -// src_32 := add(src_32, _91) -// } -// } -// function abi_decode_tuple_t_uint256t_uint256t_array$_t_uint256_$dyn_memory_ptrt_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(headStart_58, dataEnd_59) -> value0_60, value1_61, value2, value3 -// { -// if slt(sub(dataEnd_59, headStart_58), 128) -// { -// revert(value2, value2) -// } -// { -// value0_60 := calldataload(add(headStart_58, value2)) -// } -// { -// value1_61 := calldataload(add(headStart_58, 32)) -// } -// { -// let offset_64 := calldataload(add(headStart_58, 64)) -// if gt(offset_64, 0xffffffffffffffff) -// { -// revert(value2, value2) -// } -// value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(headStart_58, offset_64), dataEnd_59) -// } -// { -// let offset_65 := calldataload(add(headStart_58, 96)) -// if gt(offset_65, 0xffffffffffffffff) -// { -// revert(value3, value3) -// } -// value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(headStart_58, offset_65), dataEnd_59) +// dst_31 := add(dst_31, _52) +// src_32 := add(src_32, _52) // } // } // function allocateMemory(size) -> memPtr // { -// let _199 := 64 -// let memPtr_315 := mload(_199) -// memPtr := memPtr_315 -// let newFreePtr := add(memPtr_315, size) -// if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr_315)) +// memPtr := mload(64) +// let newFreePtr := add(memPtr, size) +// if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) // { -// let _204 := 0 -// revert(_204, _204) +// revert(0, 0) // } -// mstore(_199, newFreePtr) +// mstore(64, newFreePtr) // } -// function array_allocation_size_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(length_92) -> size_93 +// function array_allocation_size_t_array$_t_address_$dyn_memory(length_90) -> size_91 // { -// if gt(length_92, 0xffffffffffffffff) +// if gt(length_90, 0xffffffffffffffff) // { -// revert(size_93, size_93) +// revert(0, 0) // } -// let _217 := 0x20 -// size_93 := add(mul(length_92, _217), _217) +// size_91 := add(mul(length_90, 0x20), 0x20) // } -// function array_allocation_size_t_array$_t_uint256_$dyn_memory_ptr(length_98) -> size_99 +// function array_allocation_size_t_array$_t_uint256_$2_memory(length_94) -> size_95 // { -// if gt(length_98, 0xffffffffffffffff) +// if gt(length_94, 0xffffffffffffffff) // { -// revert(size_99, size_99) +// revert(0, 0) // } -// let _234 := 0x20 -// size_99 := add(mul(length_98, _234), _234) +// size_95 := mul(length_94, 0x20) // } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul new file mode 100644 index 00000000..868c6b01 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -0,0 +1,410 @@ +/** + * @title Library to validate AZTEC zero-knowledge proofs + * @author Zachary Williamson, AZTEC + * @dev Don't include this as an internal library. This contract uses a static memory table to cache elliptic curve primitives and hashes. + * Calling this internally from another function will lead to memory mutation and undefined behaviour. + * The intended use case is to call this externally via `staticcall`. External calls to OptimizedAZTEC can be treated as pure functions as this contract contains no storage and makes no external calls (other than to precompiles) + * Copyright Spilbury Holdings Ltd 2018. All rights reserved. + * We will be releasing AZTEC as an open-source protocol that provides efficient transaction privacy for Ethereum. + * This will include our bespoke AZTEC decentralized exchange, allowing for cross-asset transfers with full transaction privacy + * and interopability with public decentralized exchanges. + * Stay tuned for updates! + * + * Permission to use as test case in the Solidity compiler granted by the author: + * https://github.com/ethereum/solidity/pull/5713#issuecomment-449042830 +**/ +{ + validateJoinSplit() + // should not get here + mstore(0x00, 404) + revert(0x00, 0x20) + + + function validateJoinSplit() { + mstore(0x80, 7673901602397024137095011250362199966051872585513276903826533215767972925880) // h_x + mstore(0xa0, 8489654445897228341090914135473290831551238522473825886865492707826370766375) // h_y + let notes := add(0x04, calldataload(0x04)) + let m := calldataload(0x24) + let n := calldataload(notes) + let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + let challenge := mod(calldataload(0x44), gen_order) + + // validate m <= n + if gt(m, n) { mstore(0x00, 404) revert(0x00, 0x20) } + + // recover k_{public} and calculate k_{public} + let kn := calldataload(sub(calldatasize(), 0xc0)) + + // add kn and m to final hash table + mstore(0x2a0, caller()) + mstore(0x2c0, kn) + mstore(0x2e0, m) + kn := mulmod(sub(gen_order, kn), challenge, gen_order) // we actually want c*k_{public} + hashCommitments(notes, n) + let b := add(0x300, mul(n, 0x80)) + + // Iterate over every note and calculate the blinding factor B_i = \gamma_i^{kBar}h^{aBar}\sigma_i^{-c}. + // We use the AZTEC protocol pairing optimization to reduce the number of pairing comparisons to 1, which adds some minor alterations + for { let i := 0 } lt(i, n) { i := add(i, 0x01) } { + + // Get the calldata index of this note + let noteIndex := add(add(notes, 0x20), mul(i, 0xc0)) + + + let k + let a := calldataload(add(noteIndex, 0x20)) + let c := challenge + + switch eq(add(i, 0x01), n) + case 1 { + k := kn + + // if all notes are input notes, invert k + if eq(m, n) { + k := sub(gen_order, k) + } + } + case 0 { k := calldataload(noteIndex) } + + // Check this commitment is well formed... + validateCommitment(noteIndex, k, a) + + // If i > m then this is an output note. + // Set k = kx_j, a = ax_j, c = cx_j, where j = i - (m+1) + switch gt(add(i, 0x01), m) + case 1 { + + // before we update k, update kn = \sum_{i=0}^{m-1}k_i - \sum_{i=m}^{n-1}k_i + kn := addmod(kn, sub(gen_order, k), gen_order) + let x := mod(mload(0x00), gen_order) + k := mulmod(k, x, gen_order) + a := mulmod(a, x, gen_order) + c := mulmod(challenge, x, gen_order) + + // calculate x_{j+1} + mstore(0x00, keccak256(0x00, 0x20)) + } + case 0 { + + // nothing to do here except update kn = \sum_{i=0}^{m-1}k_i - \sum_{i=m}^{n-1}k_i + kn := addmod(kn, k, gen_order) + } + + calldatacopy(0xe0, add(noteIndex, 0x80), 0x40) + calldatacopy(0x20, add(noteIndex, 0x40), 0x40) + mstore(0x120, sub(gen_order, c)) + mstore(0x60, k) + mstore(0xc0, a) + + // Using call instead of staticcall here to make it work on all targets. + let result := call(gas(), 7, 0, 0xe0, 0x60, 0x1a0, 0x40) + result := and(result, call(gas(), 7, 0, 0x20, 0x60, 0x120, 0x40)) + result := and(result, call(gas(), 7, 0, 0x80, 0x60, 0x160, 0x40)) + + result := and(result, call(gas(), 6, 0, 0x120, 0x80, 0x160, 0x40)) + + result := and(result, call(gas(), 6, 0, 0x160, 0x80, b, 0x40)) + + if eq(i, m) { + mstore(0x260, mload(0x20)) + mstore(0x280, mload(0x40)) + mstore(0x1e0, mload(0xe0)) + mstore(0x200, sub(0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47, mload(0x100))) + } + + if gt(i, m) { + mstore(0x60, c) + result := and(result, call(gas(), 7, 0, 0x20, 0x60, 0x220, 0x40)) + + result := and(result, call(gas(), 6, 0, 0x220, 0x80, 0x260, 0x40)) + result := and(result, call(gas(), 6, 0, 0x1a0, 0x80, 0x1e0, 0x40)) + } + + if iszero(result) { mstore(0x00, 400) revert(0x00, 0x20) } + b := add(b, 0x40) // increase B pointer by 2 words + } + + if lt(m, n) { + validatePairing(0x64) + } + + let expected := mod(keccak256(0x2a0, sub(b, 0x2a0)), gen_order) + if iszero(eq(expected, challenge)) { + + // No! Bad! No soup for you! + mstore(0x00, 404) + revert(0x00, 0x20) + } + + // Great! All done. This is a valid proof so return ```true``` + mstore(0x00, 0x01) + return(0x00, 0x20) + } + + function validatePairing(t2) { + let field_order := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + let t2_x_1 := calldataload(t2) + let t2_x_2 := calldataload(add(t2, 0x20)) + let t2_y_1 := calldataload(add(t2, 0x40)) + let t2_y_2 := calldataload(add(t2, 0x60)) + + // check provided setup pubkey is not zero or g2 + if or(or(or(or(or(or(or( + iszero(t2_x_1), + iszero(t2_x_2)), + iszero(t2_y_1)), + iszero(t2_y_2)), + eq(t2_x_1, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed)), + eq(t2_x_2, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2)), + eq(t2_y_1, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa)), + eq(t2_y_2, 0x90689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b)) + { + mstore(0x00, 400) + revert(0x00, 0x20) + } + + mstore(0x20, mload(0x1e0)) // sigma accumulator x + mstore(0x40, mload(0x200)) // sigma accumulator y + mstore(0x80, 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed) + mstore(0x60, 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2) + mstore(0xc0, 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa) + mstore(0xa0, 0x90689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b) + mstore(0xe0, mload(0x260)) // gamma accumulator x + mstore(0x100, mload(0x280)) // gamma accumulator y + mstore(0x140, t2_x_1) + mstore(0x120, t2_x_2) + mstore(0x180, t2_y_1) + mstore(0x160, t2_y_2) + + let success := call(gas(), 8, 0, 0x20, 0x180, 0x20, 0x20) + + if or(iszero(success), iszero(mload(0x20))) { + mstore(0x00, 400) + revert(0x00, 0x20) + } + } + + function validateCommitment(note, k, a) { + let gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 + let field_order := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + let gammaX := calldataload(add(note, 0x40)) + let gammaY := calldataload(add(note, 0x60)) + let sigmaX := calldataload(add(note, 0x80)) + let sigmaY := calldataload(add(note, 0xa0)) + if iszero( + and( + and( + and( + eq(mod(a, gen_order), a), // a is modulo generator order? + gt(a, 1) // can't be 0 or 1 either! + ), + and( + eq(mod(k, gen_order), k), // k is modulo generator order? + gt(k, 1) // and not 0 or 1 + ) + ), + and( + eq( // y^2 ?= x^3 + 3 + addmod(mulmod(mulmod(sigmaX, sigmaX, field_order), sigmaX, field_order), 3, field_order), + mulmod(sigmaY, sigmaY, field_order) + ), + eq( // y^2 ?= x^3 + 3 + addmod(mulmod(mulmod(gammaX, gammaX, field_order), gammaX, field_order), 3, field_order), + mulmod(gammaY, gammaY, field_order) + ) + ) + ) + ) { + mstore(0x00, 400) + revert(0x00, 0x20) + } + } + + function hashCommitments(notes, n) { + for { let i := 0 } lt(i, n) { i := add(i, 0x01) } { + let index := add(add(notes, mul(i, 0xc0)), 0x60) + calldatacopy(add(0x300, mul(i, 0x80)), index, 0x80) + } + mstore(0x00, keccak256(0x300, mul(n, 0x80))) + } +} +// ---- +// fullSuite +// { +// let validateJo__6 := 0x80 +// mstore(validateJo__6, 7673901602397024137095011250362199966051872585513276903826533215767972925880) +// mstore(0xa0, 8489654445897228341090914135473290831551238522473825886865492707826370766375) +// let validateJo__10 := calldataload(0x04) +// let validateJo_notes := add(0x04, validateJo__10) +// let validateJo_m := calldataload(0x24) +// let validateJo_n := calldataload(validateJo_notes) +// let validateJo_gen_order := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +// let validateJo_challenge := mod(calldataload(0x44), validateJo_gen_order) +// if gt(validateJo_m, validateJo_n) +// { +// mstore(0x00, 404) +// revert(0x00, 0x20) +// } +// let validateJo_kn := calldataload(add(calldatasize(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40)) +// let validateJo__24 := 0x2a0 +// mstore(validateJo__24, caller()) +// mstore(0x2c0, validateJo_kn) +// mstore(0x2e0, validateJo_m) +// validateJo_kn := mulmod(sub(validateJo_gen_order, validateJo_kn), validateJo_challenge, validateJo_gen_order) +// hashCommitments(validateJo_notes, validateJo_n) +// let validateJo_b := add(0x300, mul(validateJo_n, validateJo__6)) +// let validateJo_i := 0 +// let validateJo_i_1306 := validateJo_i +// for { +// } +// lt(validateJo_i, validateJo_n) +// { +// validateJo_i := add(validateJo_i, 0x01) +// } +// { +// let validateJo__34 := 0x20 +// let validateJo__351 := add(validateJo__10, mul(validateJo_i, 0xc0)) +// let validateJo_noteIndex := add(validateJo__351, 0x24) +// let validateJo_k := validateJo_i_1306 +// let validateJo_a := calldataload(add(validateJo__351, 0x44)) +// let validateJo_c := validateJo_challenge +// let validateJo__39 := add(validateJo_i, 0x01) +// switch eq(validateJo__39, validateJo_n) +// case 1 { +// validateJo_k := validateJo_kn +// if eq(validateJo_m, validateJo_n) +// { +// validateJo_k := sub(validateJo_gen_order, validateJo_kn) +// } +// } +// case 0 { +// validateJo_k := calldataload(validateJo_noteIndex) +// } +// validateCommitment(validateJo_noteIndex, validateJo_k, validateJo_a) +// switch gt(validateJo__39, validateJo_m) +// case 1 { +// validateJo_kn := addmod(validateJo_kn, sub(validateJo_gen_order, validateJo_k), validateJo_gen_order) +// let validateJo_x := mod(mload(validateJo_i_1306), validateJo_gen_order) +// validateJo_k := mulmod(validateJo_k, validateJo_x, validateJo_gen_order) +// validateJo_a := mulmod(validateJo_a, validateJo_x, validateJo_gen_order) +// validateJo_c := mulmod(validateJo_challenge, validateJo_x, validateJo_gen_order) +// mstore(validateJo_i_1306, keccak256(validateJo_i_1306, validateJo__34)) +// } +// case 0 { +// validateJo_kn := addmod(validateJo_kn, validateJo_k, validateJo_gen_order) +// } +// let validateJo__52 := 0x40 +// calldatacopy(0xe0, add(validateJo__351, 164), validateJo__52) +// calldatacopy(validateJo__34, add(validateJo__351, 100), validateJo__52) +// let validateJo__61 := 0x120 +// mstore(validateJo__61, sub(validateJo_gen_order, validateJo_c)) +// let validateJo__62 := 0x60 +// mstore(validateJo__62, validateJo_k) +// mstore(0xc0, validateJo_a) +// let validateJo__65 := 0x1a0 +// let validateJo_result := call(gas(), 7, validateJo_i_1306, 0xe0, validateJo__62, validateJo__65, validateJo__52) +// let validateJo_result_303 := and(validateJo_result, call(gas(), 7, validateJo_i_1306, validateJo__34, validateJo__62, validateJo__61, validateJo__52)) +// let validateJo__80 := 0x160 +// let validateJo_result_304 := and(validateJo_result_303, call(gas(), 7, validateJo_i_1306, validateJo__6, validateJo__62, validateJo__80, validateJo__52)) +// let validateJo_result_305 := and(validateJo_result_304, call(gas(), 6, validateJo_i_1306, validateJo__61, validateJo__6, validateJo__80, validateJo__52)) +// validateJo_result := and(validateJo_result_305, call(gas(), 6, validateJo_i_1306, validateJo__80, validateJo__6, validateJo_b, validateJo__52)) +// if eq(validateJo_i, validateJo_m) +// { +// mstore(0x260, mload(validateJo__34)) +// mstore(0x280, mload(validateJo__52)) +// mstore(0x1e0, mload(0xe0)) +// mstore(0x200, sub(0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47, mload(0x100))) +// } +// if gt(validateJo_i, validateJo_m) +// { +// mstore(validateJo__62, validateJo_c) +// let validateJo__120 := 0x220 +// let validateJo_result_307 := and(validateJo_result, call(gas(), 7, validateJo_i_1306, validateJo__34, validateJo__62, validateJo__120, validateJo__52)) +// let validateJo_result_308 := and(validateJo_result_307, call(gas(), 6, validateJo_i_1306, validateJo__120, validateJo__6, 0x260, validateJo__52)) +// validateJo_result := and(validateJo_result_308, call(gas(), 6, validateJo_i_1306, validateJo__65, validateJo__6, 0x1e0, validateJo__52)) +// } +// if iszero(validateJo_result) +// { +// mstore(validateJo_i_1306, 400) +// revert(validateJo_i_1306, validateJo__34) +// } +// validateJo_b := add(validateJo_b, validateJo__52) +// } +// if lt(validateJo_m, validateJo_n) +// { +// validatePairing(0x64) +// } +// if iszero(eq(mod(keccak256(validateJo__24, add(validateJo_b, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60)), validateJo_gen_order), validateJo_challenge)) +// { +// mstore(validateJo_i_1306, 404) +// revert(validateJo_i_1306, 0x20) +// } +// mstore(validateJo_i_1306, 0x01) +// return(validateJo_i_1306, 0x20) +// mstore(validateJo_i_1306, 404) +// revert(validateJo_i_1306, 0x20) +// function validatePairing(t2) +// { +// let t2_x_1 := calldataload(t2) +// let _165 := 0x20 +// let t2_x_2 := calldataload(add(t2, _165)) +// let t2_y_1 := calldataload(add(t2, 0x40)) +// let t2_y_2 := calldataload(add(t2, 0x60)) +// let _171 := 0x90689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b +// let _173 := 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa +// let _175 := 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2 +// let _177 := 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed +// if or(or(or(or(or(or(or(iszero(t2_x_1), iszero(t2_x_2)), iszero(t2_y_1)), iszero(t2_y_2)), eq(t2_x_1, _177)), eq(t2_x_2, _175)), eq(t2_y_1, _173)), eq(t2_y_2, _171)) +// { +// mstore(0x00, 400) +// revert(0x00, _165) +// } +// mstore(_165, mload(0x1e0)) +// mstore(0x40, mload(0x200)) +// mstore(0x80, _177) +// mstore(0x60, _175) +// mstore(0xc0, _173) +// mstore(0xa0, _171) +// mstore(0xe0, mload(0x260)) +// mstore(0x100, mload(0x280)) +// mstore(0x140, t2_x_1) +// mstore(0x120, t2_x_2) +// let _216 := 0x180 +// mstore(_216, t2_y_1) +// mstore(0x160, t2_y_2) +// let success := call(gas(), 8, 0, _165, _216, _165, _165) +// if or(iszero(success), iszero(mload(_165))) +// { +// mstore(0, 400) +// revert(0, _165) +// } +// } +// function validateCommitment(note, k_1, a_2) +// { +// let gen_order_3 := 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +// let field_order_4 := 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 +// let gammaX := calldataload(add(note, 0x40)) +// let gammaY := calldataload(add(note, 0x60)) +// let sigmaX := calldataload(add(note, 0x80)) +// let sigmaY := calldataload(add(note, 0xa0)) +// if iszero(and(and(and(eq(mod(a_2, gen_order_3), a_2), gt(a_2, 1)), and(eq(mod(k_1, gen_order_3), k_1), gt(k_1, 1))), and(eq(addmod(mulmod(mulmod(sigmaX, sigmaX, field_order_4), sigmaX, field_order_4), 3, field_order_4), mulmod(sigmaY, sigmaY, field_order_4)), eq(addmod(mulmod(mulmod(gammaX, gammaX, field_order_4), gammaX, field_order_4), 3, field_order_4), mulmod(gammaY, gammaY, field_order_4))))) +// { +// mstore(0x00, 400) +// revert(0x00, 0x20) +// } +// } +// function hashCommitments(notes_5, n_6) +// { +// let i_7 := 0 +// for { +// } +// lt(i_7, n_6) +// { +// i_7 := add(i_7, 0x01) +// } +// { +// calldatacopy(add(0x300, mul(i_7, 0x80)), add(add(notes_5, mul(i_7, 0xc0)), 0x60), 0x80) +// } +// mstore(0, keccak256(0x300, mul(n_6, 0x80))) +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/medium.yul b/test/libyul/yulOptimizerTests/fullSuite/medium.yul index fbe243d4..1d07cd03 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/medium.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/medium.yul @@ -19,13 +19,10 @@ // ---- // fullSuite // { -// { -// let _1 := 0x20 -// let allocate__19 := 0x40 -// mstore(allocate__19, add(mload(allocate__19), _1)) -// let allocate_p_24_41 := mload(allocate__19) -// mstore(allocate__19, add(allocate_p_24_41, allocate__19)) -// mstore(add(allocate_p_24_41, 96), 2) -// mstore(allocate__19, _1) -// } +// let allocate__19 := 0x40 +// mstore(allocate__19, add(mload(allocate__19), 0x20)) +// let allocate_p_35_39 := mload(allocate__19) +// mstore(allocate__19, add(allocate_p_35_39, allocate__19)) +// mstore(add(allocate_p_35_39, 96), 2) +// mstore(allocate__19, 0x20) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul new file mode 100644 index 00000000..204b444e --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul @@ -0,0 +1,62 @@ +{ + // This is an abi decode function with the SSA transform applied once. + // This test is supposed to verify that the SSA transform is correctly reversed by the full suite. + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + let length_15_1 := calldataload(offset_12) + length_15 := length_15_1 + if gt(length_15_1, 0xffffffffffffffff) + { + revert(0, 0) + } + let arrayPos_14_2 := add(offset_12, 0x20) + arrayPos_14 := arrayPos_14_2 + if gt(add(arrayPos_14_2, mul(length_15_1, 0x1)), end_13) + { + revert(0, 0) + } + } + + // prevent inlining + let a,b := abi_decode_t_bytes_calldata_ptr(mload(0),mload(1)) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + mstore(a,b) +} +// ---- +// fullSuite +// { +// let a, b := abi_decode_t_bytes_calldata_ptr(mload(0), mload(1)) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// mstore(a, b) +// function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 +// { +// if iszero(slt(add(offset_12, 0x1f), end_13)) +// { +// revert(0, 0) +// } +// length_15 := calldataload(offset_12) +// if gt(length_15, 0xffffffffffffffff) +// { +// revert(0, 0) +// } +// arrayPos_14 := add(offset_12, 0x20) +// if gt(add(add(offset_12, length_15), 0x20), end_13) +// { +// revert(0, 0) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul new file mode 100644 index 00000000..2e178f31 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul @@ -0,0 +1,23 @@ +{ + let a := mload(0) + let b := mload(1) + if mload(2) { + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + } + mstore(a, b) +} +// ---- +// fullSuite +// { +// let a := mload(0) +// let b := mload(1) +// if mload(2) +// { +// a := mload(mload(mload(b))) +// b := mload(a) +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul b/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul new file mode 100644 index 00000000..42e8a48e --- /dev/null +++ b/test/libyul/yulOptimizerTests/functionGrouper/already_grouped.yul @@ -0,0 +1,17 @@ +{ + { + let x := 2 + } + function f() -> y { y := 8 } +} +// ---- +// functionGrouper +// { +// { +// let x := 2 +// } +// function f() -> y +// { +// y := 8 +// } +// } diff --git a/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul b/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul new file mode 100644 index 00000000..0abb5d87 --- /dev/null +++ b/test/libyul/yulOptimizerTests/functionGrouper/grouped_but_not_ordered.yul @@ -0,0 +1,19 @@ +{ + function f() -> y { y := 8 } + { + let x := 2 + } +} +// ---- +// functionGrouper +// { +// { +// { +// let x := 2 +// } +// } +// function f() -> y +// { +// y := 8 +// } +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul index dbd1ee63..3160381f 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_for1.yul @@ -1,5 +1,5 @@ { - let a := 1 + let a := caller() for { pop(a) } a { pop(a) } { pop(a) } @@ -7,15 +7,15 @@ // ---- // rematerialiser // { -// let a := 1 +// let a := caller() // for { -// pop(1) +// pop(caller()) // } -// 1 +// caller() // { -// pop(1) +// pop(caller()) // } // { -// pop(1) +// pop(caller()) // } // } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul index 6a52e045..eb092e95 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_for2.yul @@ -1,7 +1,7 @@ { - let a := 1 + let a := caller() for { pop(a) } a { pop(a) } { - a := 7 + a := address() let c := a } let x := a @@ -9,17 +9,17 @@ // ---- // rematerialiser // { -// let a := 1 +// let a := caller() // for { -// pop(1) +// pop(caller()) // } // a // { -// pop(7) +// pop(address()) // } // { -// a := 7 -// let c := 7 +// a := address() +// let c := address() // } // let x := a // } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul index fc816419..e7c689ca 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init1.yul @@ -1,6 +1,6 @@ { let b := 0 - for { let a := 1 pop(a) } a { pop(a) } { + for { let a := caller() pop(a) } a { pop(a) } { b := 1 pop(a) } } @@ -9,15 +9,15 @@ // { // let b := 0 // for { -// let a := 1 -// pop(1) +// let a := caller() +// pop(caller()) // } -// 1 +// caller() // { -// pop(1) +// pop(caller()) // } // { // b := 1 -// pop(1) +// pop(caller()) // } // } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul index 3d916890..80ee9233 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_for_declared_in_init2.yul @@ -1,6 +1,6 @@ { let b := 0 - for { let a := 1 pop(a) } lt(a, 0) { pop(a) a := add(a, 3) } { + for { let a := caller() pop(a) } lt(a, 0) { pop(a) a := add(a, 3) } { b := 1 pop(a) } } @@ -9,8 +9,8 @@ // { // let b := 0 // for { -// let a := 1 -// pop(1) +// let a := caller() +// pop(caller()) // } // lt(a, 0) // { diff --git a/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul b/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul index c148c2f2..2aff06d4 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/branches_if.yul @@ -1,18 +1,18 @@ { - let a := 1 - let b := 2 + let a := caller() + let b := address() if b { pop(b) b := a } let c := b } // ---- // rematerialiser // { -// let a := 1 -// let b := 2 -// if 2 +// let a := caller() +// let b := address() +// if address() // { -// pop(2) -// b := 1 +// pop(address()) +// b := caller() // } // let c := b // } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul b/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul new file mode 100644 index 00000000..7e99e428 --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/cheap_caller.yul @@ -0,0 +1,16 @@ +{ + // The caller opcode is cheap, so inline it, + // no matter how often it is used + let a := caller() + mstore(a, a) + mstore(add(a, a), mload(a)) + sstore(a, sload(a)) +} +// ---- +// rematerialiser +// { +// let a := caller() +// mstore(caller(), caller()) +// mstore(add(caller(), caller()), mload(caller())) +// sstore(caller(), sload(caller())) +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/do_not_remat_large_amounts_of_code2.yul b/test/libyul/yulOptimizerTests/rematerialiser/do_not_remat_large_amounts_of_code2.yul deleted file mode 100644 index d95dc1fc..00000000 --- a/test/libyul/yulOptimizerTests/rematerialiser/do_not_remat_large_amounts_of_code2.yul +++ /dev/null @@ -1,10 +0,0 @@ -{ - let x := add(mul(calldataload(2), calldataload(4)), calldatasize()) - let b := x -} -// ---- -// rematerialiser -// { -// let x := add(mul(calldataload(2), calldataload(4)), calldatasize()) -// let b := add(mul(calldataload(2), calldataload(4)), calldatasize()) -// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/do_not_remat_large_amounts_of_code1.yul b/test/libyul/yulOptimizerTests/rematerialiser/do_remat_large_amounts_of_code_if_used_once.yul index 016fa0d7..e464d404 100644 --- a/test/libyul/yulOptimizerTests/rematerialiser/do_not_remat_large_amounts_of_code1.yul +++ b/test/libyul/yulOptimizerTests/rematerialiser/do_remat_large_amounts_of_code_if_used_once.yul @@ -6,5 +6,5 @@ // rematerialiser // { // let x := add(mul(calldataload(2), calldataload(4)), mul(2, calldatasize())) -// let b := x +// let b := add(mul(calldataload(2), calldataload(4)), mul(2, calldatasize())) // } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/expression.yul b/test/libyul/yulOptimizerTests/rematerialiser/expression.yul deleted file mode 100644 index a801677d..00000000 --- a/test/libyul/yulOptimizerTests/rematerialiser/expression.yul +++ /dev/null @@ -1,10 +0,0 @@ -{ - let a := add(mul(calldatasize(), 2), number()) - let b := add(a, a) -} -// ---- -// rematerialiser -// { -// let a := add(mul(calldatasize(), 2), number()) -// let b := add(add(mul(calldatasize(), 2), number()), add(mul(calldatasize(), 2), number())) -// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul b/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul new file mode 100644 index 00000000..9c7c66f1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/large_constant.yul @@ -0,0 +1,12 @@ +{ + // Constants cost depending on their magnitude. + // Do not rematerialize large constants. + let a := 0xffffffffffffffffffffff + mstore(a, a) +} +// ---- +// rematerialiser +// { +// let a := 0xffffffffffffffffffffff +// mstore(a, a) +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul b/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul new file mode 100644 index 00000000..b8a861aa --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/large_constant_used_once.yul @@ -0,0 +1,13 @@ +{ + // Constants cost depending on their magnitude. + // Do not rematerialize large constants, + // unless they are used exactly once. + let a := 0xffffffffffffffffffffff + mstore(0, a) +} +// ---- +// rematerialiser +// { +// let a := 0xffffffffffffffffffffff +// mstore(0, 0xffffffffffffffffffffff) +// } diff --git a/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul b/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul new file mode 100644 index 00000000..98cdbd09 --- /dev/null +++ b/test/libyul/yulOptimizerTests/rematerialiser/medium_sized_constant.yul @@ -0,0 +1,25 @@ +{ + // Constants cost depending on their magnitude. + // Rematerialize small constants only if they are + // not used too often. + // b is used 5 times + let b := 2 + mstore(b, b) + mstore(add(b, b), b) + // a is used 7 times + let a := 1 + mstore(a, a) + mstore(add(a, a), a) + mstore(a, mload(a)) +} +// ---- +// rematerialiser +// { +// let b := 2 +// mstore(2, 2) +// mstore(add(2, 2), 2) +// let a := 1 +// mstore(a, a) +// mstore(add(a, a), a) +// mstore(a, mload(a)) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul new file mode 100644 index 00000000..18498e61 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul @@ -0,0 +1,36 @@ +{ + for { + let a := mload(0) + let b := mload(1) + } + lt(mload(a),mload(b)) + { + a := mload(b) + } + { + b := mload(a) + a := mload(b) + a := mload(b) + a := mload(b) + b := mload(a) + } +} +// ---- +// ssaAndBack +// { +// for { +// let a := mload(0) +// let b := mload(1) +// } +// lt(mload(a), mload(b)) +// { +// a := mload(b) +// } +// { +// let b_3 := mload(a) +// pop(mload(b_3)) +// pop(mload(b_3)) +// let a_6 := mload(b_3) +// b := mload(a_6) +// } +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul new file mode 100644 index 00000000..23c433d1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul @@ -0,0 +1,18 @@ +{ + let a := mload(0) + a := mload(1) + a := mload(2) + a := mload(3) + a := mload(4) + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// pop(mload(0)) +// pop(mload(1)) +// pop(mload(2)) +// pop(mload(3)) +// let a_5 := mload(4) +// mstore(a_5, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul new file mode 100644 index 00000000..fd5981ef --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul @@ -0,0 +1,22 @@ +{ + let a := mload(0) + if mload(1) + { + a := mload(1) + a := mload(2) + a := mload(3) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// if mload(1) +// { +// pop(mload(1)) +// pop(mload(2)) +// a := mload(3) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul new file mode 100644 index 00000000..b0b3efb5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul @@ -0,0 +1,25 @@ +{ + let a := mload(0) + let b := mload(1) + if mload(2) { + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + } + mstore(a, b) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// let b := mload(1) +// if mload(2) +// { +// let a_3 := mload(b) +// let b_4 := mload(a_3) +// a := mload(b_4) +// b := mload(a) +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul new file mode 100644 index 00000000..50f56b87 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul @@ -0,0 +1,38 @@ +{ + let a := mload(0) + let b := mload(1) + switch mload(2) + case 0 { + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + } + default { + b := mload(a) + a := mload(b) + b := mload(a) + a := mload(b) + } + mstore(a, b) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// let b := mload(1) +// switch mload(2) +// case 0 { +// let a_3 := mload(b) +// let b_4 := mload(a_3) +// a := mload(b_4) +// b := mload(a) +// } +// default { +// let b_7 := mload(a) +// let a_8 := mload(b_7) +// b := mload(a_8) +// a := mload(b) +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul new file mode 100644 index 00000000..1efbbde7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul @@ -0,0 +1,32 @@ +{ + let a := mload(0) + switch mload(1) + case 0 { + a := mload(1) + a := mload(2) + a := mload(3) + } + default { + a := mload(4) + a := mload(5) + a := mload(6) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// switch mload(1) +// case 0 { +// pop(mload(1)) +// pop(mload(2)) +// a := mload(3) +// } +// default { +// pop(mload(4)) +// pop(mload(5)) +// a := mload(6) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul b/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul new file mode 100644 index 00000000..912940c5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul @@ -0,0 +1,12 @@ +{ + let a := mload(0) + a := mload(1) + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// pop(mload(0)) +// let a_2 := mload(1) +// mstore(a_2, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul new file mode 100644 index 00000000..5185245c --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul @@ -0,0 +1,18 @@ +{ + let a := mload(0) + if mload(1) + { + a := mload(1) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// if mload(1) +// { +// a := mload(1) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul new file mode 100644 index 00000000..e0e53b3f --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul @@ -0,0 +1,24 @@ +{ + let a := mload(0) + switch mload(1) + case 0 { + a := mload(1) + } + default { + a := mload(2) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// switch mload(1) +// case 0 { +// a := mload(1) +// } +// default { +// a := mload(2) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul b/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul new file mode 100644 index 00000000..d2ba6471 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul @@ -0,0 +1,45 @@ +{ + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + length_15 := calldataload(offset_12) + if gt(length_15, 0xffffffffffffffff) + { + revert(0, 0) + } + arrayPos_14 := add(offset_12, 0x20) + if gt(add(add(offset_12, length_15), 0x20), end_13) + { + revert(0, 0) + } + } + // prevent removal of the function + let a,b := abi_decode_t_bytes_calldata_ptr(mload(0),mload(1)) + mstore(a,b) +} +// ---- +// ssaAndBack +// { +// function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 +// { +// if iszero(slt(add(offset_12, 0x1f), end_13)) +// { +// revert(arrayPos_14, arrayPos_14) +// } +// length_15 := calldataload(offset_12) +// if gt(length_15, 0xffffffffffffffff) +// { +// revert(arrayPos_14, arrayPos_14) +// } +// arrayPos_14 := add(offset_12, 0x20) +// if gt(add(add(offset_12, length_15), 0x20), end_13) +// { +// revert(0, 0) +// } +// } +// let a, b := abi_decode_t_bytes_calldata_ptr(mload(0), mload(1)) +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul b/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul new file mode 100644 index 00000000..9f2a046e --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul @@ -0,0 +1,20 @@ +{ + let a := mload(0) + let b := mload(a) + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + mstore(a, b) +} +// ---- +// ssaAndBack +// { +// let a_1 := mload(0) +// let b_2 := mload(a_1) +// let a_3 := mload(b_2) +// let b_4 := mload(a_3) +// let a_5 := mload(b_4) +// let b_6 := mload(a_5) +// mstore(a_5, b_6) +// } diff --git a/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul b/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul new file mode 100644 index 00000000..923a42ba --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul @@ -0,0 +1,44 @@ +{ + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + let length_15_1 := calldataload(offset_12) + length_15 := length_15_1 + if gt(length_15_1, 0xffffffffffffffff) + { + revert(0, 0) + } + let arrayPos_14_2 := add(offset_12, 0x20) + arrayPos_14 := arrayPos_14_2 + if gt(add(arrayPos_14_2, mul(length_15_1, 0x1)), end_13) + { + revert(0, 0) + } + } +} +// ---- +// ssaReverser +// { +// function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 +// { +// if iszero(slt(add(offset_12, 0x1f), end_13)) +// { +// revert(0, 0) +// } +// length_15 := calldataload(offset_12) +// let length_15_1 := length_15 +// if gt(length_15_1, 0xffffffffffffffff) +// { +// revert(0, 0) +// } +// arrayPos_14 := add(offset_12, 0x20) +// let arrayPos_14_2 := arrayPos_14 +// if gt(add(arrayPos_14_2, mul(length_15_1, 0x1)), end_13) +// { +// revert(0, 0) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/ssaReverser/simple.yul b/test/libyul/yulOptimizerTests/ssaReverser/simple.yul new file mode 100644 index 00000000..eba1f5f1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaReverser/simple.yul @@ -0,0 +1,14 @@ +{ + let a := mload(1) + let a_1 := mload(0) + a := a_1 + mstore(a_1, 0) +} +// ---- +// ssaReverser +// { +// let a := mload(1) +// a := mload(0) +// let a_1 := a +// mstore(a_1, 0) +// } diff --git a/test/solcjsTests.sh b/test/solcjsTests.sh index b9224862..ab263178 100755 --- a/test/solcjsTests.sh +++ b/test/solcjsTests.sh @@ -39,8 +39,8 @@ VERSION="$2" DIR=$(mktemp -d) ( - echo "Preparing solc-js..." - git clone --depth 1 https://github.com/ethereum/solc-js "$DIR" + echo "Preparing solc-js (0.5.0)..." + git clone --depth 1 --branch v0.5.0 https://github.com/ethereum/solc-js "$DIR" cd "$DIR" # disable "prepublish" script which downloads the latest version # (we will replace it anyway and it is often incorrectly cached diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index fcbe308f..7203ef54 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -33,6 +33,7 @@ #include <libyul/optimiser/Disambiguator.h> #include <libyul/optimiser/CommonSubexpressionEliminator.h> #include <libyul/optimiser/NameCollector.h> +#include <libyul/optimiser/EquivalentFunctionCombiner.h> #include <libyul/optimiser/ExpressionSplitter.h> #include <libyul/optimiser/FunctionGrouper.h> #include <libyul/optimiser/FunctionHoister.h> @@ -45,6 +46,7 @@ #include <libyul/optimiser/UnusedPruner.h> #include <libyul/optimiser/ExpressionJoiner.h> #include <libyul/optimiser/RedundantAssignEliminator.h> +#include <libyul/optimiser/SSAReverser.h> #include <libyul/optimiser/SSATransform.h> #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/VarDeclInitializer.h> @@ -85,7 +87,7 @@ public: { ErrorReporter errorReporter(m_errors); shared_ptr<Scanner> scanner = make_shared<Scanner>(CharStream(_input, "")); - m_ast = yul::Parser(errorReporter, yul::EVMDialect::strictAssemblyForEVM()).parse(scanner, false); + m_ast = yul::Parser(errorReporter, m_dialect).parse(scanner, false); if (!m_ast || !errorReporter.errors().empty()) { cout << "Error parsing source." << endl; @@ -98,7 +100,7 @@ public: errorReporter, EVMVersion::byzantium(), langutil::Error::Type::SyntaxError, - EVMDialect::strictAssemblyForEVM() + m_dialect ); if (!analyzer.analyze(*m_ast) || !errorReporter.errors().empty()) { @@ -120,15 +122,15 @@ public: return; if (!disambiguated) { - *m_ast = boost::get<yul::Block>(Disambiguator(*m_analysisInfo)(*m_ast)); + *m_ast = boost::get<yul::Block>(Disambiguator(*m_dialect, *m_analysisInfo)(*m_ast)); m_analysisInfo.reset(); - m_nameDispenser = make_shared<NameDispenser>(*m_ast); + m_nameDispenser = make_shared<NameDispenser>(*m_dialect, *m_ast); disambiguated = true; } cout << "(q)quit/(f)flatten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; cout << " (e)xpr inline/(i)nline/(s)implify/(u)nusedprune/ss(a) transform/" << endl; cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-pre-rewriter/" << endl; - cout << " s(t)ructural simplifier? " << endl; + cout << " s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser? " << endl; cout.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; @@ -143,13 +145,13 @@ public: ForLoopInitRewriter{}(*m_ast); break; case 'c': - (CommonSubexpressionEliminator{})(*m_ast); + (CommonSubexpressionEliminator{*m_dialect})(*m_ast); break; case 'd': (VarDeclInitializer{})(*m_ast); break; case 'x': - ExpressionSplitter{*m_nameDispenser}(*m_ast); + ExpressionSplitter{*m_dialect, *m_nameDispenser}(*m_ast); break; case 'j': ExpressionJoiner::run(*m_ast); @@ -161,28 +163,34 @@ public: (FunctionHoister{})(*m_ast); break; case 'e': - ExpressionInliner{*m_ast}.run(); + ExpressionInliner{*m_dialect, *m_ast}.run(); break; case 'i': FullInliner(*m_ast, *m_nameDispenser).run(); break; case 's': - ExpressionSimplifier::run(*m_ast); + ExpressionSimplifier::run(*m_dialect, *m_ast); break; case 't': - (StructuralSimplifier{})(*m_ast); + (StructuralSimplifier{*m_dialect})(*m_ast); break; case 'u': - UnusedPruner::runUntilStabilised(*m_ast); + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); break; case 'a': SSATransform::run(*m_ast, *m_nameDispenser); break; case 'r': - RedundantAssignEliminator::run(*m_ast); + RedundantAssignEliminator::run(*m_dialect, *m_ast); break; case 'm': - Rematerialiser{}(*m_ast); + Rematerialiser::run(*m_dialect, *m_ast); + break; + case 'v': + EquivalentFunctionCombiner::run(*m_ast); + break; + case 'V': + SSAReverser::run(*m_ast); break; default: cout << "Unknown option." << endl; @@ -194,6 +202,7 @@ public: private: ErrorList m_errors; shared_ptr<yul::Block> m_ast; + shared_ptr<Dialect> m_dialect{EVMDialect::strictAssemblyForEVMObjects()}; shared_ptr<AsmAnalysisInfo> m_analysisInfo; shared_ptr<NameDispenser> m_nameDispenser; }; |