aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml2
-rw-r--r--CMakeLists.txt5
-rw-r--r--Changelog.md17
-rw-r--r--docs/abi-spec.rst45
-rw-r--r--docs/assembly.rst59
-rw-r--r--docs/bugs_by_version.json4
-rw-r--r--docs/contracts.rst143
-rw-r--r--docs/control-structures.rst22
-rw-r--r--docs/grammar.txt10
-rw-r--r--docs/index.rst17
-rw-r--r--docs/julia.rst564
-rw-r--r--docs/metadata.rst2
-rw-r--r--docs/security-considerations.rst73
-rw-r--r--docs/solidity-by-example.rst7
-rw-r--r--docs/structure-of-a-contract.rst86
-rw-r--r--docs/style-guide.rst6
-rw-r--r--docs/types.rst2
-rw-r--r--docs/units-and-global-variables.rst5
-rw-r--r--docs/using-the-compiler.rst23
-rw-r--r--docs/utils/SolidityLexer.py10
-rw-r--r--libdevcore/CommonData.cpp27
-rw-r--r--libdevcore/CommonData.h21
-rw-r--r--libdevcore/Exceptions.h1
-rw-r--r--libevmasm/JumpdestRemover.cpp4
-rw-r--r--libjulia/ASTDataForward.h52
-rw-r--r--libjulia/backends/evm/EVMAssembly.cpp2
-rw-r--r--libjulia/backends/evm/EVMCodeTransform.cpp41
-rw-r--r--libjulia/backends/evm/EVMCodeTransform.h42
-rw-r--r--libjulia/optimiser/ASTCopier.cpp175
-rw-r--r--libjulia/optimiser/ASTCopier.h94
-rw-r--r--libjulia/optimiser/ASTWalker.cpp149
-rw-r--r--libjulia/optimiser/ASTWalker.h104
-rw-r--r--libjulia/optimiser/Disambiguator.cpp85
-rw-r--r--libjulia/optimiser/Disambiguator.h68
-rw-r--r--libjulia/optimiser/NameCollector.cpp44
-rw-r--r--libjulia/optimiser/NameCollector.h52
-rw-r--r--libjulia/optimiser/README.md61
-rw-r--r--libjulia/optimiser/Substitution.cpp39
-rw-r--r--libjulia/optimiser/Substitution.h51
-rw-r--r--libsolc/CMakeLists.txt9
-rw-r--r--libsolc/libsolc.cpp (renamed from solc/jsonCompiler.cpp)7
-rw-r--r--libsolc/libsolc.h (renamed from solc/jsonCompiler.h)0
-rw-r--r--libsolidity/analysis/ConstantEvaluator.cpp22
-rw-r--r--libsolidity/analysis/ConstantEvaluator.h8
-rw-r--r--libsolidity/analysis/PostTypeChecker.h2
-rw-r--r--libsolidity/analysis/StaticAnalyzer.h2
-rw-r--r--libsolidity/analysis/SyntaxChecker.cpp2
-rw-r--r--libsolidity/analysis/SyntaxChecker.h2
-rw-r--r--libsolidity/analysis/TypeChecker.cpp21
-rw-r--r--libsolidity/analysis/TypeChecker.h2
-rw-r--r--libsolidity/analysis/ViewPureChecker.cpp19
-rw-r--r--libsolidity/ast/AST.cpp11
-rw-r--r--libsolidity/ast/AST.h2
-rw-r--r--libsolidity/ast/ASTPrinter.cpp12
-rw-r--r--libsolidity/ast/ASTPrinter.h2
-rw-r--r--libsolidity/ast/Types.cpp2
-rw-r--r--libsolidity/ast/Types.h4
-rw-r--r--libsolidity/codegen/ABIFunctions.cpp459
-rw-r--r--libsolidity/codegen/ABIFunctions.h49
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp19
-rw-r--r--libsolidity/codegen/CompilerUtils.h7
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp9
-rw-r--r--libsolidity/formal/SMTChecker.cpp510
-rw-r--r--libsolidity/formal/SMTChecker.h59
-rw-r--r--libsolidity/formal/SMTLib2Interface.cpp11
-rw-r--r--libsolidity/formal/SMTLib2Interface.h8
-rw-r--r--libsolidity/formal/SolverInterface.h75
-rw-r--r--libsolidity/formal/VariableUsage.cpp80
-rw-r--r--libsolidity/formal/VariableUsage.h50
-rw-r--r--libsolidity/formal/Z3Interface.cpp16
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.cpp34
-rw-r--r--libsolidity/inlineasm/AsmAnalysis.h1
-rw-r--r--libsolidity/inlineasm/AsmData.h6
-rw-r--r--libsolidity/inlineasm/AsmDataForward.h6
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp23
-rw-r--r--libsolidity/inlineasm/AsmPrinter.cpp13
-rw-r--r--libsolidity/inlineasm/AsmPrinter.h1
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.cpp11
-rw-r--r--libsolidity/inlineasm/AsmScopeFiller.h1
-rw-r--r--libsolidity/interface/StandardCompiler.cpp117
-rwxr-xr-xscripts/build.sh7
-rwxr-xr-xscripts/bytecodecompare/storebytecode.sh2
-rwxr-xr-xscripts/docker_deploy_manual.sh49
-rwxr-xr-xscripts/install_deps.sh7
-rwxr-xr-xscripts/test_emscripten.sh2
-rwxr-xr-xscripts/travis-emscripten/build_emscripten.sh4
-rw-r--r--solc/CMakeLists.txt8
-rw-r--r--test/CMakeLists.txt4
-rw-r--r--test/ExecutionFramework.h16
-rw-r--r--test/boostTest.cpp1
-rwxr-xr-xtest/externalTests.sh50
-rw-r--r--test/fuzzer.cpp2
-rw-r--r--test/libdevcore/Checksum.cpp34
-rw-r--r--test/libjulia/Common.cpp86
-rw-r--r--test/libjulia/Common.h55
-rw-r--r--test/libjulia/Disambiguator.cpp105
-rw-r--r--test/libjulia/Parser.cpp15
-rw-r--r--test/libsolidity/ABIDecoderTests.cpp794
-rw-r--r--test/libsolidity/ABIEncoderTests.cpp50
-rw-r--r--test/libsolidity/ABITestsCommon.h43
-rw-r--r--test/libsolidity/InlineAssembly.cpp38
-rw-r--r--test/libsolidity/JSONCompiler.cpp2
-rw-r--r--test/libsolidity/SMTChecker.cpp448
-rw-r--r--test/libsolidity/SolidityABIJSON.cpp3
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp36
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp206
-rw-r--r--test/libsolidity/StandardCompiler.cpp8
108 files changed, 5390 insertions, 594 deletions
diff --git a/.gitignore b/.gitignore
index 114420c9..14c227d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ prerelease.txt
build/
docs/_build
docs/utils/__pycache__
+docs/utils/*.pyc
# vim stuff
*.swp
diff --git a/.travis.yml b/.travis.yml
index a2301c42..708d3620 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -184,10 +184,8 @@ before_script:
script:
- test $SOLC_EMSCRIPTEN != On || (scripts/test_emscripten.sh)
- - test $? == 0 || SOLC_STOREBYTECODE=Off
- test $SOLC_DOCS != On || (scripts/docs.sh)
- test $SOLC_TESTS != On || (cd $TRAVIS_BUILD_DIR && scripts/tests.sh)
- - test $? == 0 || SOLC_STOREBYTECODE=Off
- test $SOLC_STOREBYTECODE != On || (cd $TRAVIS_BUILD_DIR && scripts/bytecodecompare/storebytecode.sh)
deploy:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 24bea3b3..8993f372 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
-set(PROJECT_VERSION "0.4.19")
+set(PROJECT_VERSION "0.4.20")
project(solidity VERSION ${PROJECT_VERSION})
option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF)
@@ -43,9 +43,10 @@ configure_project(TESTS)
add_subdirectory(libdevcore)
add_subdirectory(libevmasm)
add_subdirectory(libsolidity)
-add_subdirectory(solc)
+add_subdirectory(libsolc)
if (NOT EMSCRIPTEN)
+ add_subdirectory(solc)
add_subdirectory(liblll)
add_subdirectory(lllc)
endif()
diff --git a/Changelog.md b/Changelog.md
index 85a9fc7f..4cd83eeb 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,7 +1,22 @@
-### 0.4.19 (unreleased)
+### 0.4.20 (unreleased)
Features:
+ * Inline Assembly: Issue warning for using jump labels (already existed for jump instructions).
+
+Bugfixes:
+ * Type Checker: Suggest the experimental ABI encoder if using ``struct``s as function parameters
+ (instead of an internal compiler error).
+
+### 0.4.19 (2017-11-30)
+
+Features:
+ * Code Generator: New ABI decoder which supports structs and arbitrarily nested
+ arrays and checks input size (activate using ``pragma experimental ABIEncoderV2;``).
+ * General: Allow constant variables to be used as array length.
+ * Inline Assembly: ``if`` statement.
+ * Standard JSON: Support the ``outputSelection`` field for selective compilation of target artifacts.
* Syntax Checker: Turn the usage of ``callcode`` into an error as experimental 0.5.0 feature.
+ * Type Checker: Improve address checksum warning.
* Type Checker: More detailed errors for invalid array lengths (such as division by zero).
Bugfixes:
diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst
index 77d15026..e968fb06 100644
--- a/docs/abi-spec.rst
+++ b/docs/abi-spec.rst
@@ -330,15 +330,15 @@ For example,
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.0;
- contract Test {
- function Test(){ b = 0x12345678901234567890123456789012; }
- event Event(uint indexed a, bytes32 b);
- event Event2(uint indexed a, bytes32 b);
- function foo(uint a) { Event(a, b); }
- bytes32 b;
- }
+ contract Test {
+ function Test(){ b = 0x12345678901234567890123456789012; }
+ event Event(uint indexed a, bytes32 b);
+ event Event2(uint indexed a, bytes32 b);
+ function foo(uint a) { Event(a, b); }
+ bytes32 b;
+ }
would result in the JSON:
@@ -377,11 +377,15 @@ As an example, the code
::
- contract Test {
- struct S { uint a; uint[] b; T[] c; }
- struct T { uint x; uint y; }
- function f(S s, T t, uint a) { }
- }
+ pragma solidity ^0.4.19;
+ pragma experimental ABIEncoderV2;
+
+ contract Test {
+ struct S { uint a; uint[] b; T[] c; }
+ struct T { uint x; uint y; }
+ function f(S s, T t, uint a) { }
+ function g() returns (S s, T t, uint a) {}
+ }
would result in the JSON:
@@ -451,13 +455,18 @@ Non-standard Packed Mode
Solidity supports a non-standard packed mode where:
- no :ref:`function selector <abi_function_selector>` is encoded,
-- short types are not zero padded and
+- 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 ``uint1, bytes1, uint8, string`` with values ``1, 0x42, 0x2424, "Hello, world!"`` results in ::
+As an example encoding ``int1, bytes1, uint16, string`` with values ``-1, 0x42, 0x2424, "Hello, world!"`` results in ::
- 0x0142242448656c6c6f2c20776f726c6421
- ^^ uint1(1)
+ 0xff42242448656c6c6f2c20776f726c6421
+ ^^ int1(-1)
^^ bytes1(0x42)
- ^^^^ uint8(0x2424)
+ ^^^^ uint16(0x2424)
^^^^^^^^^^^^^^^^^^^^^^^^^^ string("Hello, world!") without a 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.
diff --git a/docs/assembly.rst b/docs/assembly.rst
index f5abcdc8..a4fa88c6 100644
--- a/docs/assembly.rst
+++ b/docs/assembly.rst
@@ -9,11 +9,6 @@ This assembly language can also be used as "inline assembly" inside Solidity
source code. We start with describing how to use inline assembly and how it
differs from standalone assembly and then specify assembly itself.
-.. note::
- TODO: Write about how scoping rules of inline assembly are a bit different
- and the complications that arise when for example using internal functions
- of libraries. Furthermore, write about the symbols defined by the compiler.
-
.. _inline-assembly:
Inline Assembly
@@ -31,6 +26,7 @@ arising when writing manual assembly by the following features:
* access to external variables: ``function f(uint x) { assembly { x := sub(x, 1) } }``
* labels: ``let x := 10 repeat: x := sub(x, 1) jumpi(repeat, eq(x, 0))``
* loops: ``for { let i := 0 } lt(i, x) { i := add(i, 1) } { y := mul(2, y) }``
+* if statements: ``if slt(x, 0) { x := sub(0, x) }``
* switch statements: ``switch x case 0 { y := mul(x, 2) } default { y := 0 }``
* function calls: ``function f(x) -> y { switch x case 0 { y := 1 } default { y := mul(x, f(sub(x, 1))) } }``
@@ -41,6 +37,11 @@ We now want to describe the inline assembly language in detail.
at a low level. This discards several important safety
features of Solidity.
+.. note::
+ TODO: Write about how scoping rules of inline assembly are a bit different
+ and the complications that arise when for example using internal functions
+ of libraries. Furthermore, write about the symbols defined by the compiler.
+
Example
-------
@@ -400,7 +401,7 @@ Labels
Another problem in EVM assembly is that ``jump`` and ``jumpi`` use absolute addresses
which can change easily. Solidity inline assembly provides labels to make the use of
jumps easier. Note that labels are a low-level feature and it is possible to write
-efficient assembly without labels, just using assembly functions, loops and switch instructions
+efficient assembly without labels, just using assembly functions, loops, if and switch instructions
(see below). The following code computes an element in the Fibonacci series.
.. code::
@@ -446,31 +447,6 @@ will have a wrong impression about the stack height at label ``two``:
three:
}
-This problem can be fixed by manually adjusting the stack height for the
-assembler - you can provide a stack height delta that is added
-to the stack height just prior to the label.
-Note that you will not have to care about these things if you just use
-loops and assembly-level functions.
-
-As an example how this can be done in extreme cases, please see the following.
-
-.. code::
-
- {
- let x := 8
- jump(two)
- 0 // This code is unreachable but will adjust the stack height correctly
- one:
- x := 9 // Now x can be accessed properly.
- jump(three)
- pop // Similar negative correction.
- two:
- 7 // push something onto the stack
- jump(one)
- three:
- pop // We have to pop the manually pushed value here again.
- }
-
Declaring Assembly-Local Variables
----------------------------------
@@ -523,6 +499,21 @@ is performed by replacing the variable's value on the stack by the new value.
=: v // instruction style assignment, puts the result of sload(10) into v
}
+If
+--
+
+The if statement can be used for conditionally executing code.
+There is no "else" part, consider using "switch" (see below) if
+you need multiple alternatives.
+
+.. code::
+
+ {
+ if eq(value, 0) { revert(0, 0) }
+ }
+
+The curly braces for the body are required.
+
Switch
------
@@ -622,7 +613,7 @@ Things to Avoid
---------------
Inline assembly might have a quite high-level look, but it actually is extremely
-low-level. Function calls, loops and switches are converted by simple
+low-level. Function calls, loops, ifs and switches are converted by simple
rewriting rules and after that, the only thing the assembler does for you is re-arranging
functional-style opcodes, managing jump labels, counting stack height for
variable access and removing stack slots for assembly-local variables when the end
@@ -669,7 +660,7 @@ for the Solidity compiler. In this form, it tries to achieve several goals:
3. Control flow should be easy to detect to help in formal verification and optimization.
In order to achieve the first and last goal, assembly provides high-level constructs
-like ``for`` loops, ``switch`` statements and function calls. It should be possible
+like ``for`` loops, ``if`` and ``switch`` statements and function calls. It should be possible
to write assembly programs that do not make use of explicit ``SWAP``, ``DUP``,
``JUMP`` and ``JUMPI`` statements, because the first two obfuscate the data flow
and the last two obfuscate control flow. Furthermore, functional statements of
@@ -875,6 +866,7 @@ Grammar::
FunctionalAssemblyAssignment |
AssemblyAssignment |
LabelDefinition |
+ AssemblyIf |
AssemblySwitch |
AssemblyFunctionDefinition |
AssemblyFor |
@@ -891,6 +883,7 @@ Grammar::
IdentifierList = Identifier ( ',' Identifier)*
AssemblyAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
+ AssemblyIf = 'if' FunctionalAssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' FunctionalAssemblyExpression AssemblyCase*
( 'default' AssemblyBlock )?
AssemblyCase = 'case' FunctionalAssemblyExpression AssemblyBlock
diff --git a/docs/bugs_by_version.json b/docs/bugs_by_version.json
index cca45428..3a8ff9a1 100644
--- a/docs/bugs_by_version.json
+++ b/docs/bugs_by_version.json
@@ -397,6 +397,10 @@
"bugs": [],
"released": "2017-10-18"
},
+ "0.4.19": {
+ "bugs": [],
+ "released": "2017-11-30"
+ },
"0.4.2": {
"bugs": [
"ZeroFunctionSelector",
diff --git a/docs/contracts.rst b/docs/contracts.rst
index cdc92315..ca4e79c0 100644
--- a/docs/contracts.rst
+++ b/docs/contracts.rst
@@ -198,7 +198,6 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
function compute(uint a, uint b) internal returns (uint) { return a+b; }
}
-
contract D {
function readData() {
C c = new C();
@@ -209,11 +208,10 @@ In the following example, ``D``, can call ``c.getData()`` to retrieve the value
}
}
-
contract E is C {
function g() {
C c = new C();
- uint val = compute(3, 5); // acces to internal member (from derivated to parent contract)
+ uint val = compute(3, 5); // access to internal member (from derived to parent contract)
}
}
@@ -238,7 +236,6 @@ be done at declaration.
uint public data = 42;
}
-
contract Caller {
C c = new C();
function f() {
@@ -321,7 +318,6 @@ inheritable properties of contracts and may be overridden by derived contracts.
}
}
-
contract mortal is owned {
// This contract inherits the "onlyOwner"-modifier from
// "owned" and applies it to the "close"-function, which
@@ -332,7 +328,6 @@ inheritable properties of contracts and may be overridden by derived contracts.
}
}
-
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) {
@@ -342,7 +337,6 @@ inheritable properties of contracts and may be overridden by derived contracts.
}
}
-
contract Register is priced, owned {
mapping (address => bool) registeredAddresses;
uint price;
@@ -432,12 +426,20 @@ value types and strings.
bytes32 constant myHash = keccak256("abc");
}
+.. index:: ! functions
+
+.. _functions:
+
+*********
+Functions
+*********
+
+.. index:: ! view function, function;view
.. _view-functions:
-**************
View Functions
-**************
+==============
Functions can be declared ``view`` in which case they promise not to modify the state.
@@ -471,11 +473,12 @@ The following statements are considered modifying the state:
.. warning::
The compiler does not enforce yet that a ``view`` method is not modifying state.
+.. 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.
@@ -504,9 +507,8 @@ In addition to the list of state modifying statements explained above, the follo
.. _fallback-function:
-*****************
Fallback Function
-*****************
+=================
A contract can have exactly one unnamed function. This function cannot have
arguments and cannot return anything.
@@ -570,7 +572,6 @@ Please ensure you test your fallback function thoroughly to ensure the execution
function() payable { }
}
-
contract Caller {
function callTest(Test test) {
test.call(0xabcdef01); // hash does not exist
@@ -584,6 +585,85 @@ Please ensure you test your fallback function thoroughly to ensure the execution
}
}
+.. index:: ! overload
+
+.. _overload-function:
+
+Function Overloading
+====================
+
+A Contract can have multiple functions of the same name but with different arguments.
+This also applies to inherited functions. The following example shows overloading of the
+``f`` function in the scope of contract ``A``.
+
+::
+
+ pragma solidity ^0.4.16;
+
+ contract A {
+ function f(uint _in) public pure returns (uint out) {
+ out = 1;
+ }
+
+ function f(uint _in, bytes32 _key) public pure returns (uint out) {
+ out = 2;
+ }
+ }
+
+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.
+
+::
+
+ // This will not compile
+ pragma solidity ^0.4.16;
+
+ 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;
+
+ 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 ``250`` 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:
@@ -687,12 +767,19 @@ as topics. The event call above can be performed in the same way as
::
- log3(
- msg.value,
- 0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
- msg.sender,
- _id
- );
+ pragma solidity ^0.4.10;
+
+ contract C {
+ function f() {
+ bytes32 _id = 0x420042;
+ log3(
+ bytes32(msg.value),
+ bytes32(0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20),
+ bytes32(msg.sender),
+ _id
+ );
+ }
+ }
where the long hexadecimal number is equal to
``keccak256("Deposit(address,hash256,uint256)")``, the signature of the event.
@@ -734,7 +821,6 @@ Details are given in the following example.
address 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
@@ -745,7 +831,6 @@ Details are given in the following example.
}
}
-
// 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
@@ -754,13 +839,11 @@ Details are given in the following example.
function lookup(uint id) returns (address adr);
}
-
contract NameReg {
function register(bytes32 name);
function unregister();
}
-
// 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++).
@@ -786,7 +869,6 @@ Details are given in the following example.
}
}
-
// 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)).
@@ -821,12 +903,10 @@ seen in the following example::
function kill() { /* do cleanup 1 */ mortal.kill(); }
}
-
contract Base2 is mortal {
function kill() { /* do cleanup 2 */ mortal.kill(); }
}
-
contract Final is Base1, Base2 {
}
@@ -848,7 +928,6 @@ derived override, but this function will bypass
}
}
-
contract Base1 is mortal {
function kill() { /* do cleanup 1 */ super.kill(); }
}
@@ -858,12 +937,11 @@ derived override, but this function will bypass
function kill() { /* do cleanup 2 */ super.kill(); }
}
-
contract Final is Base2, Base1 {
}
If ``Base1`` calls a function of ``super``, it does not simply
-call this function on one of its base contracts. Rather, it
+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 ``Base2.kill()`` (note that
the final inheritance sequence is -- starting with the most
@@ -888,7 +966,6 @@ the base constructors. This can be done in two ways::
function Base(uint _x) { x = _x; }
}
-
contract Derived is Base(7) {
function Derived(uint _y) Base(_y * _y) {
}
@@ -1081,7 +1158,6 @@ more advanced example to implement a set).
}
}
-
contract C {
Set.Data knownValues;
@@ -1157,7 +1233,6 @@ custom types without the overhead of external function calls:
}
}
-
contract C {
using BigInt for BigInt.bigint;
@@ -1250,7 +1325,6 @@ Let us rewrite the set example from the
}
}
-
contract C {
using Set for Set.Data; // this is the crucial change
Set.Data knownValues;
@@ -1276,7 +1350,6 @@ It is also possible to extend elementary types in that way::
}
}
-
contract C {
using Search for uint[];
uint[] data;
diff --git a/docs/control-structures.rst b/docs/control-structures.rst
index 0497365b..bcb597cf 100644
--- a/docs/control-structures.rst
+++ b/docs/control-structures.rst
@@ -194,7 +194,7 @@ Omitted Function Parameter Names
--------------------------------
The names of unused parameters (especially return parameters) can be omitted.
-Those names will still be present on the stack, but they are inaccessible.
+Those parameters will still be present on the stack, but they are inaccessible.
::
@@ -363,15 +363,19 @@ As a result, the following code is illegal and cause the compiler to throw an er
In addition to this, if a variable is declared, it will be initialized at the beginning of the function to its default value.
As a result, the following code is legal, despite being poorly written::
- function foo() returns (uint) {
- // baz is implicitly initialized as 0
- uint bar = 5;
- if (true) {
- bar += baz;
- } else {
- uint baz = 10;// never executes
+ pragma solidity ^0.4.0;
+
+ contract C {
+ function foo() returns (uint) {
+ // baz is implicitly initialized as 0
+ uint bar = 5;
+ if (true) {
+ bar += baz;
+ } else {
+ uint baz = 10;// never executes
+ }
+ return bar;// returns 5
}
- return bar;// returns 5
}
.. index:: ! exception, ! throw, ! assert, ! require, ! revert
diff --git a/docs/grammar.txt b/docs/grammar.txt
index 72364b7c..ce3fd3ad 100644
--- a/docs/grammar.txt
+++ b/docs/grammar.txt
@@ -127,10 +127,10 @@ StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
-DecimalNumber = [0-9]+
+DecimalNumber = [0-9]+ ( '.' [0-9]* )? ( [eE] [0-9]+ )?
-TupleExpression = '(' ( Expression ( ',' Expression )* )? ')'
- | '[' ( Expression ( ',' Expression )* )? ']'
+TupleExpression = '(' ( Expression? ( ',' Expression? )* )? ')'
+ | '[' ( Expression ( ',' Expression )* )? ']'
ElementaryTypeNameExpression = ElementaryTypeName
@@ -143,9 +143,9 @@ Uint = 'uint' | 'uint8' | 'uint16' | 'uint24' | 'uint32' | 'uint40' | 'uint48' |
Byte = 'byte' | 'bytes' | 'bytes1' | 'bytes2' | 'bytes3' | 'bytes4' | 'bytes5' | 'bytes6' | 'bytes7' | 'bytes8' | 'bytes9' | 'bytes10' | 'bytes11' | 'bytes12' | 'bytes13' | 'bytes14' | 'bytes15' | 'bytes16' | 'bytes17' | 'bytes18' | 'bytes19' | 'bytes20' | 'bytes21' | 'bytes22' | 'bytes23' | 'bytes24' | 'bytes25' | 'bytes26' | 'bytes27' | 'bytes28' | 'bytes29' | 'bytes30' | 'bytes31' | 'bytes32'
-Fixed = 'fixed' | ( 'fixed' DecimalNumber 'x' DecimalNumber )
+Fixed = 'fixed' | ( 'fixed' [0-9]+ 'x' [0-9]+ )
-Ufixed = 'ufixed' | ( 'ufixed' DecimalNumber 'x' DecimalNumber )
+Ufixed = 'ufixed' | ( 'ufixed' [0-9]+ 'x' [0-9]+ )
InlineAssemblyBlock = '{' AssemblyItem* '}'
diff --git a/docs/index.rst b/docs/index.rst
index 351f8ad7..3c617d36 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,8 +6,9 @@ Solidity
:alt: Solidity logo
:align: center
-Solidity is a contract-oriented, high-level language whose syntax is similar to that of JavaScript
-and it is designed to target the Ethereum Virtual Machine (EVM).
+Solidity is a contract-oriented, high-level language for implementing smart contracts.
+It was influenced by C++, Python and JavaScript
+and is designed to target the Ethereum Virtual Machine (EVM).
Solidity is statically typed, supports inheritance, libraries and complex
user-defined types among other features.
@@ -20,6 +21,15 @@ crowdfunding, blind auctions, multi-signature wallets and more.
`Remix <https://remix.ethereum.org/>`_
(it can take a while to load, please be patient).
+Translations
+------------
+
+This documentation is translated into several languages by community volunteers, but the English version stands as a reference.
+
+* `Spanish <https://solidity-es.readthedocs.io>`_
+* `Russian <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_ (rather outdated)
+
+
Useful links
------------
@@ -131,8 +141,6 @@ If you still have questions, you can try searching or asking on the
site, or come to our `gitter channel <https://gitter.im/ethereum/solidity/>`_.
Ideas for improving Solidity or this documentation are always welcome!
-See also `Russian version (русский перевод) <https://github.com/ethereum/wiki/wiki/%5BRussian%5D-%D0%A0%D1%83%D0%BA%D0%BE%D0%B2%D0%BE%D0%B4%D1%81%D1%82%D0%B2%D0%BE-%D0%BF%D0%BE-Solidity>`_.
-
Contents
========
@@ -149,6 +157,7 @@ Contents
using-the-compiler.rst
metadata.rst
abi-spec.rst
+ julia.rst
style-guide.rst
common-patterns.rst
bugs.rst
diff --git a/docs/julia.rst b/docs/julia.rst
new file mode 100644
index 00000000..309e6b36
--- /dev/null
+++ b/docs/julia.rst
@@ -0,0 +1,564 @@
+#################################################
+Joyfully Universal Language for (Inline) Assembly
+#################################################
+
+.. _julia:
+
+.. index:: ! assembly, ! asm, ! evmasm, ! julia
+
+JULIA is an intermediate language that can compile to various different backends
+(EVM 1.0, EVM 1.5 and eWASM are planned).
+Because of that, it is designed to be a usable common denominator of all three
+platforms.
+It can already be used for "inline assembly" inside Solidity and
+future versions of the Solidity compiler will even use JULIA as intermediate
+language. It should also be easy to build high-level optimizer stages for JULIA.
+
+The core components of JULIA are functions, blocks, variables, literals,
+for-loops, if-statements, switch-statements, expressions and assignments to variables.
+
+JULIA is typed, both variables and literals must specify the type with postfix
+notation. The supported types are ``bool``, ``u8``, ``s8``, ``u32``, ``s32``,
+``u64``, ``s64``, ``u128``, ``s128``, ``u256`` and ``s256``.
+
+JULIA in itself does not even provide operators. If the EVM is targeted,
+opcodes will be available as built-in functions, but they can be reimplemented
+if the backend changes. For a list of mandatory built-in functions, see the section below.
+
+The following example program assumes that the EVM opcodes ``mul``, ``div``
+and ``mod`` are available either natively or as functions and computes exponentiation.
+
+.. code::
+
+ {
+ function power(base:u256, exponent:u256) -> result:u256
+ {
+ switch exponent
+ case 0:u256 { result := 1:u256 }
+ case 1:u256 { result := base }
+ default:
+ {
+ result := power(mul(base, base), div(exponent, 2:u256))
+ switch mod(exponent, 2:u256)
+ case 1:u256 { result := mul(base, result) }
+ }
+ }
+ }
+
+It is also possible to implement the same function using a for-loop
+instead of with recursion. Here, we need the EVM opcodes ``lt`` (less-than)
+and ``add`` to be available.
+
+.. code::
+
+ {
+ function power(base:u256, exponent:u256) -> result:u256
+ {
+ result := 1:u256
+ for { let i := 0:u256 } lt(i, exponent) { i := add(i, 1:u256) }
+ {
+ result := mul(result, base)
+ }
+ }
+ }
+
+Specification of JULIA
+======================
+
+JULIA code is described in this chapter. JULIA code is usually placed into a JULIA object, which is described in the following chapter.
+
+Grammar::
+
+ Block = '{' Statement* '}'
+ Statement =
+ Block |
+ FunctionDefinition |
+ VariableDeclaration |
+ Assignment |
+ Expression |
+ Switch |
+ ForLoop |
+ BreakContinue
+ FunctionDefinition =
+ 'function' Identifier '(' TypedIdentifierList? ')'
+ ( '->' TypedIdentifierList )? Block
+ VariableDeclaration =
+ 'let' TypedIdentifierList ( ':=' Expression )?
+ Assignment =
+ IdentifierList ':=' Expression
+ Expression =
+ FunctionCall | Identifier | Literal
+ If =
+ 'if' Expression Block
+ Switch =
+ 'switch' Expression Case* ( 'default' Block )?
+ Case =
+ 'case' Literal Block
+ ForLoop =
+ 'for' Block Expression Block Block
+ BreakContinue =
+ 'break' | 'continue'
+ FunctionCall =
+ Identifier '(' ( Expression ( ',' Expression )* )? ')'
+ Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
+ IdentifierList = Identifier ( ',' Identifier)*
+ TypeName = Identifier | BuiltinTypeName
+ BuiltinTypeName = 'bool' | [us] ( '8' | '32' | '64' | '128' | '256' )
+ TypedIdentifierList = Identifier ':' TypeName ( ',' Identifier ':' TypeName )*
+ Literal =
+ (NumberLiteral | StringLiteral | HexLiteral | TrueLiteral | FalseLiteral) ':' TypeName
+ NumberLiteral = HexNumber | DecimalNumber
+ HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
+ StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
+ TrueLiteral = 'true'
+ FalseLiteral = 'false'
+ HexNumber = '0x' [0-9a-fA-F]+
+ DecimalNumber = [0-9]+
+
+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).
+
+Every expression evaluates to zero or more values. Identifiers and Literals
+evaluate to exactly
+one value and function calls evaluate to a number of values equal to the
+number of return values of the function called.
+
+In variable declarations and assignments, the right-hand-side expression
+(if present) has to evaluate to a number of values equal to the number of
+variables on the left-hand-side.
+This is the only situation where an expression evaluating
+to more than one value is allowed.
+
+Expressions that are also statements (i.e. at the block level) have to
+evaluate to zero values.
+
+In all other situations, expressions have to evaluate to exactly one value.
+
+The ``continue`` and ``break`` statements can only be used inside loop bodies
+and have to be in the same function as the loop (or both have to be at the
+top level).
+The condition part of the for-loop has to evaluate to exactly one value.
+
+Literals cannot be larger than the their type. The largest type defined is 256-bit wide.
+
+Scoping Rules
+-------------
+
+Scopes in JULIA are tied to Blocks (exceptions are functions and the for loop
+as explained below) and all declarations
+(``FunctionDefinition``, ``VariableDeclaration``)
+introduce new identifiers into these scopes.
+
+Identifiers are visible in
+the block they are defined in (including all sub-nodes and sub-blocks).
+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.
+The parameters and return parameters of functions are visible in the
+function body and their names cannot overlap.
+
+Variables can only be referenced after their declaration. In particular,
+variables cannot be referenced in the right hand side of their own variable
+declaration.
+Functions can be referenced already before their declaration (if they are visible).
+
+Shadowing is disallowed, i.e. you cannot declare an identifier at a point
+where another identifier with the same name is also visible, even if it is
+not accessible.
+
+Inside functions, it is not possible to access a variable that was declared
+outside of that function.
+
+Formal Specification
+--------------------
+
+We formally specify JULIA by providing an evaluation function E overloaded
+on the various nodes of the AST. Any functions can have side effects, so
+E takes two state objects and the AST node and returns two new
+state objects and a variable number of other values.
+The two state objects are the global state object
+(which in the context of the EVM is the memory, storage and state of the
+blockchain) and the local state object (the state of local variables, i.e. a
+segment of the stack in the EVM).
+If the AST node is a statement, E returns the two state objects and a "mode",
+which is used for the ``break`` and ``continue`` statements.
+If the AST node is an expression, E returns the two state objects and
+as many values as the expression evaluates to.
+
+
+The exact nature of the global state is unspecified for this high level
+description. The local state ``L`` is a mapping of identifiers ``i`` to values ``v``,
+denoted as ``L[i] = v``.
+
+For an identifier ``v``, let ``$v`` be the name of the identifier.
+
+We will use a destructuring notation for the AST nodes.
+
+.. code::
+
+ E(G, L, <{St1, ..., Stn}>: Block) =
+ let G1, L1, mode = E(G, L, St1, ..., Stn)
+ let L2 be a restriction of L1 to the identifiers of L
+ G1, L2, mode
+ E(G, L, St1, ..., Stn: Statement) =
+ if n is zero:
+ G, L, regular
+ else:
+ let G1, L1, mode = E(G, L, St1)
+ if mode is regular then
+ E(G1, L1, St2, ..., Stn)
+ otherwise
+ G1, L1, mode
+ E(G, L, FunctionDefinition) =
+ G, L, regular
+ E(G, L, <let var1, ..., varn := rhs>: VariableDeclaration) =
+ E(G, L, <var1, ..., varn := rhs>: Assignment)
+ E(G, L, <let var1, ..., varn>: VariableDeclaration) =
+ let L1 be a copy of L where L1[$vari] = 0 for i = 1, ..., n
+ G, L1, regular
+ E(G, L, <var1, ..., varn := rhs>: Assignment) =
+ let G1, L1, v1, ..., vn = E(G, L, rhs)
+ let L2 be a copy of L1 where L2[$vari] = vi for i = 1, ..., n
+ G, L2, regular
+ E(G, L, <for { i1, ..., in } condition post body>: ForLoop) =
+ if n >= 1:
+ let G1, L1, mode = E(G, L, i1, ..., in)
+ // mode has to be regular due to the syntactic restrictions
+ let G2, L2, mode = E(G1, L1, for {} condition post body)
+ // mode has to be regular due to the syntactic restrictions
+ let L3 be the restriction of L2 to only variables of L
+ G2, L3, regular
+ else:
+ let G1, L1, v = E(G, L, condition)
+ if v is false:
+ G1, L1, regular
+ else:
+ let G2, L2, mode = E(G1, L, body)
+ if mode is break:
+ G2, L2, regular
+ else:
+ G3, L3, mode = E(G2, L2, post)
+ E(G3, L3, for {} condition post body)
+ E(G, L, break: BreakContinue) =
+ G, L, break
+ E(G, L, continue: BreakContinue) =
+ G, L, continue
+ E(G, L, <if condition body>: If) =
+ let G0, L0, v = E(G, L, condition)
+ if v is true:
+ E(G0, L0, body)
+ else:
+ G0, L0, regular
+ E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn>: Switch) =
+ E(G, L, switch condition case l1:t1 st1 ... case ln:tn stn default {})
+ E(G, L, <switch condition case l1:t1 st1 ... case ln:tn stn default st'>: Switch) =
+ let G0, L0, v = E(G, L, condition)
+ // i = 1 .. n
+ // Evaluate literals, context doesn't matter
+ let _, _, v1 = E(G0, L0, l1)
+ ...
+ let _, _, vn = E(G0, L0, ln)
+ if there exists smallest i such that vi = v:
+ E(G0, L0, sti)
+ else:
+ E(G0, L0, st')
+
+ E(G, L, <name>: Identifier) =
+ G, L, L[$name]
+ E(G, L, <fname(arg1, ..., argn)>: FunctionCall) =
+ G1, L1, vn = E(G, L, argn)
+ ...
+ G(n-1), L(n-1), v2 = E(G(n-2), L(n-2), arg2)
+ Gn, Ln, v1 = E(G(n-1), L(n-1), arg1)
+ Let <function fname (param1, ..., paramn) -> ret1, ..., retm block>
+ be the function of name $fname visible at the point of the call.
+ Let L' be a new local state such that
+ L'[$parami] = vi and L'[$reti] = 0 for all i.
+ Let G'', L'', mode = E(Gn, L', block)
+ G'', Ln, L''[$ret1], ..., L''[$retm]
+ E(G, L, l: HexLiteral) = G, L, hexString(l),
+ where hexString decodes l from hex and left-aligns it into 32 bytes
+ E(G, L, l: StringLiteral) = G, L, utf8EncodeLeftAligned(l),
+ where utf8EncodeLeftAligned performs a utf8 encoding of l
+ and aligns it left into 32 bytes
+ E(G, L, n: HexNumber) = G, L, hex(n)
+ where hex is the hexadecimal decoding function
+ E(G, L, n: DecimalNumber) = G, L, dec(n),
+ where dec is the decimal decoding function
+
+Type Conversion Functions
+-------------------------
+
+JULIA has no support for implicit type conversion and therefore functions exists to provide explicit conversion.
+When converting a larger type to a shorter type a runtime exception can occur in case of an overflow.
+
+The following type conversion functions must be available:
+- ``u32tobool(x:u32) -> y:bool``
+- ``booltou32(x:bool) -> y:u32``
+- ``u32tou64(x:u32) -> y:u64``
+- ``u64tou32(x:u64) -> y:u32``
+- etc. (TBD)
+
+Low-level Functions
+-------------------
+
+The following functions must be available:
+
++---------------------------------------------------------------------------------------------------------------+
+| *Arithmetics* |
++---------------------------------------------------------------------------------------------------------------+
+| addu256(x:u256, y:u256) -> z:u256 | x + y |
++---------------------------------------------------------------------------------------------------------------+
+| subu256(x:u256, y:u256) -> z:u256 | x - y |
++---------------------------------------------------------------------------------------------------------------+
+| mulu256(x:u256, y:u256) -> z:u256 | x * y |
++---------------------------------------------------------------------------------------------------------------+
+| divu256(x:u256, y:u256) -> z:u256 | x / y |
++---------------------------------------------------------------------------------------------------------------+
+| divs256(x:s256, y:s256) -> z:s256 | x / y, for signed numbers in two's complement |
++---------------------------------------------------------------------------------------------------------------+
+| modu256(x:u256, y:u256) -> z:u256 | x % y |
++---------------------------------------------------------------------------------------------------------------+
+| mods256(x:s256, y:s256) -> z:s256 | x % y, for signed numbers in two's complement |
++---------------------------------------------------------------------------------------------------------------+
+| signextendu256(i:u256, x:u256) -> z:u256 | sign extend from (i*8+7)th bit counting from least significant |
++---------------------------------------------------------------------------------------------------------------+
+| expu256(x:u256, y:u256) -> z:u256 | x to the power of y |
++---------------------------------------------------------------------------------------------------------------+
+| addmodu256(x:u256, y:u256, m:u256) -> z:u256| (x + y) % m with arbitrary precision arithmetics |
++---------------------------------------------------------------------------------------------------------------+
+| mulmodu256(x:u256, y:u256, m:u256) -> z:u256| (x * y) % m with arbitrary precision arithmetics |
++---------------------------------------------------------------------------------------------------------------+
+| ltu256(x:u256, y:u256) -> z:bool | 1 if x < y, 0 otherwise |
++---------------------------------------------------------------------------------------------------------------+
+| gtu256(x:u256, y:u256) -> z:bool | 1 if x > y, 0 otherwise |
++---------------------------------------------------------------------------------------------------------------+
+| sltu256(x:s256, y:s256) -> z:bool | 1 if x < y, 0 otherwise, for signed numbers in two's complement |
++---------------------------------------------------------------------------------------------------------------+
+| sgtu256(x:s256, y:s256) -> z:bool | 1 if x > y, 0 otherwise, for signed numbers in two's complement |
++---------------------------------------------------------------------------------------------------------------+
+| equ256(x:u256, y:u256) -> z:bool | 1 if x == y, 0 otherwise |
++---------------------------------------------------------------------------------------------------------------+
+| notu256(x:u256) -> z:u256 | ~x, every bit of x is negated |
++---------------------------------------------------------------------------------------------------------------+
+| andu256(x:u256, y:u256) -> z:u256 | bitwise and of x and y |
++---------------------------------------------------------------------------------------------------------------+
+| oru256(x:u256, y:u256) -> z:u256 | bitwise or of x and y |
++---------------------------------------------------------------------------------------------------------------+
+| xoru256(x:u256, y:u256) -> z:u256 | bitwise xor of x and y |
++---------------------------------------------------------------------------------------------------------------+
+| shlu256(x:u256, y:u256) -> z:u256 | logical left shift of x by y |
++---------------------------------------------------------------------------------------------------------------+
+| shru256(x:u256, y:u256) -> z:u256 | logical right shift of x by y |
++---------------------------------------------------------------------------------------------------------------+
+| saru256(x:u256, y:u256) -> z:u256 | arithmetic right shift of x by y |
++---------------------------------------------------------------------------------------------------------------+
+| byte(n:u256, x:u256) -> v:u256 | nth byte of x, where the most significant byte is the 0th byte |
+| Cannot this be just replaced by and256(shr256(n, x), 0xff) and let it be optimised out by the EVM backend? |
++---------------------------------------------------------------------------------------------------------------+
+| *Memory and storage* |
++---------------------------------------------------------------------------------------------------------------+
+| mload(p:u256) -> v:u256 | mem[p..(p+32)) |
++---------------------------------------------------------------------------------------------------------------+
+| mstore(p:u256, v:u256) | mem[p..(p+32)) := v |
++---------------------------------------------------------------------------------------------------------------+
+| mstore8(p:u256, v:u256) | mem[p] := v & 0xff - only modifies a single byte |
++---------------------------------------------------------------------------------------------------------------+
+| sload(p:u256) -> v:u256 | storage[p] |
++---------------------------------------------------------------------------------------------------------------+
+| sstore(p:u256, v:u256) | storage[p] := v |
++---------------------------------------------------------------------------------------------------------------+
+| msize() -> size:u256 | size of memory, i.e. largest accessed memory index, albeit due |
+| | due to the memory extension function, which extends by words, |
+| | this will always be a multiple of 32 bytes |
++---------------------------------------------------------------------------------------------------------------+
+| *Execution control* |
++---------------------------------------------------------------------------------------------------------------+
+| create(v:u256, p:u256, s:u256) | create new contract with code mem[p..(p+s)) and send v wei |
+| | and return the new address |
++---------------------------------------------------------------------------------------------------------------+
+| call(g:u256, a:u256, v:u256, in:u256, | call contract at address a with input mem[in..(in+insize)) |
+| insize:u256, out:u256, | providing g gas and v wei and output area |
+| outsize:u256) | mem[out..(out+outsize)) returning 0 on error (eg. out of gas) |
+| -> r:u256 | and 1 on success |
++---------------------------------------------------------------------------------------------------------------+
+| callcode(g:u256, a:u256, v:u256, in:u256, | identical to ``call`` but only use the code from a |
+| insize:u256, out:u256, | and stay in the context of the |
+| outsize:u256) -> r:u256 | current contract otherwise |
++---------------------------------------------------------------------------------------------------------------+
+| delegatecall(g:u256, a:u256, in:u256, | identical to ``callcode``, |
+| insize:u256, out:u256, | but also keep ``caller`` |
+| outsize:u256) -> r:u256 | and ``callvalue`` |
++---------------------------------------------------------------------------------------------------------------+
+| stop() | stop execution, identical to return(0,0) |
+| Perhaps it would make sense retiring this as it equals to return(0,0). It can be an optimisation by the EVM |
+| backend. |
++---------------------------------------------------------------------------------------------------------------+
+| abort() | abort (equals to invalid instruction on EVM) |
++---------------------------------------------------------------------------------------------------------------+
+| return(p:u256, s:u256) | end execution, return data mem[p..(p+s)) |
++---------------------------------------------------------------------------------------------------------------+
+| revert(p:u256, s:u256) | end execution, revert state changes, return data mem[p..(p+s)) |
++---------------------------------------------------------------------------------------------------------------+
+| selfdestruct(a:u256) | end execution, destroy current contract and send funds to a |
++---------------------------------------------------------------------------------------------------------------+
+| log0(p:u256, s:u256) | log without topics and data mem[p..(p+s)) |
++---------------------------------------------------------------------------------------------------------------+
+| log1(p:u256, s:u256, t1:u256) | log with topic t1 and data mem[p..(p+s)) |
++---------------------------------------------------------------------------------------------------------------+
+| log2(p:u256, s:u256, t1:u256, t2:u256) | log with topics t1, t2 and data mem[p..(p+s)) |
++---------------------------------------------------------------------------------------------------------------+
+| log3(p:u256, s:u256, t1:u256, t2:u256, | log with topics t, t2, t3 and data mem[p..(p+s)) |
+| t3:u256) | |
++---------------------------------------------------------------------------------------------------------------+
+| log4(p:u256, s:u256, t1:u256, t2:u256, | log with topics t1, t2, t3, t4 and data mem[p..(p+s)) |
+| t3:u256, t4:u256) | |
++---------------------------------------------------------------------------------------------------------------+
+| *State queries* |
++---------------------------------------------------------------------------------------------------------------+
+| blockcoinbase() -> address:u256 | current mining beneficiary |
++---------------------------------------------------------------------------------------------------------------+
+| blockdifficulty() -> difficulty:u256 | difficulty of the current block |
++---------------------------------------------------------------------------------------------------------------+
+| blockgaslimit() -> limit:u256 | block gas limit of the current block |
++---------------------------------------------------------------------------------------------------------------+
+| blockhash(b:u256) -> hash:u256 | hash of block nr b - only for last 256 blocks excluding current |
++---------------------------------------------------------------------------------------------------------------+
+| blocknumber() -> block:u256 | current block number |
++---------------------------------------------------------------------------------------------------------------+
+| blocktimestamp() -> timestamp:u256 | timestamp of the current block in seconds since the epoch |
++---------------------------------------------------------------------------------------------------------------+
+| txorigin() -> address:u256 | transaction sender |
++---------------------------------------------------------------------------------------------------------------+
+| txgasprice() -> price:u256 | gas price of the transaction |
++---------------------------------------------------------------------------------------------------------------+
+| gasleft() -> gas:u256 | gas still available to execution |
++---------------------------------------------------------------------------------------------------------------+
+| balance(a:u256) -> v:u256 | wei balance at address a |
++---------------------------------------------------------------------------------------------------------------+
+| this() -> address:u256 | address of the current contract / execution context |
++---------------------------------------------------------------------------------------------------------------+
+| caller() -> address:u256 | call sender (excluding delegatecall) |
++---------------------------------------------------------------------------------------------------------------+
+| callvalue() -> v:u256 | wei sent together with the current call |
++---------------------------------------------------------------------------------------------------------------+
+| calldataload(p:u256) -> v:u256 | call data starting from position p (32 bytes) |
++---------------------------------------------------------------------------------------------------------------+
+| calldatasize() -> v:u256 | size of call data in bytes |
++---------------------------------------------------------------------------------------------------------------+
+| calldatacopy(t:u256, f:u256, s:u256) | copy s bytes from calldata at position f to mem at position t |
++---------------------------------------------------------------------------------------------------------------+
+| codesize() -> size:u256 | size of the code of the current contract / execution context |
++---------------------------------------------------------------------------------------------------------------+
+| codecopy(t:u256, f:u256, s:u256) | copy s bytes from code at position f to mem at position t |
++---------------------------------------------------------------------------------------------------------------+
+| extcodesize(a:u256) -> size:u256 | size of the code at address a |
++---------------------------------------------------------------------------------------------------------------+
+| extcodecopy(a:u256, t:u256, f:u256, s:u256) | like codecopy(t, f, s) but take code at address a |
++---------------------------------------------------------------------------------------------------------------+
+| *Others* |
++---------------------------------------------------------------------------------------------------------------+
+| discardu256(unused:u256) | discard value |
++---------------------------------------------------------------------------------------------------------------+
+| splitu256tou64(x:u256) -> (x1:u64, x2:u64, | split u256 to four u64's |
+| x3:u64, x4:u64) | |
++---------------------------------------------------------------------------------------------------------------+
+| combineu64tou256(x1:u64, x2:u64, x3:u64, | combine four u64's into a single u256 |
+| x4:u64) -> (x:u256) | |
++---------------------------------------------------------------------------------------------------------------+
+| sha3(p:u256, s:u256) -> v:u256 | keccak(mem[p...(p+s))) |
++---------------------------------------------------------------------------------------------------------------+
+
+Backends
+--------
+
+Backends or targets are the translators from JULIA to a specific bytecode. Each of the backends can expose functions
+prefixed with the name of the backend. We reserve ``evm_`` and ``ewasm_`` prefixes for the two proposed backends.
+
+Backend: EVM
+------------
+
+The EVM target will have all the underlying EVM opcodes exposed with the `evm_` prefix.
+
+Backend: "EVM 1.5"
+------------------
+
+TBD
+
+Backend: eWASM
+--------------
+
+TBD
+
+Specification of JULIA Object
+=============================
+
+Grammar::
+
+ TopLevelObject = 'object' '{' Code? ( Object | Data )* '}'
+ Object = 'object' StringLiteral '{' Code? ( Object | Data )* '}'
+ Code = 'code' Block
+ Data = 'data' StringLiteral HexLiteral
+ HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
+ StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
+
+Above, ``Block`` refers to ``Block`` in the JULIA code grammar explained in the previous chapter.
+
+An example JULIA Object is shown below:
+
+..code::
+
+ // Code consists of a single object. A single "code" node is the code of the object.
+ // Every (other) named object or data section is serialized and
+ // made accessible to the special built-in functions datacopy / dataoffset / datasize
+ object {
+ code {
+ let size = datasize("runtime")
+ let offset = allocate(size)
+ // This will turn into a memory->memory copy for eWASM and
+ // a codecopy for EVM
+ datacopy(dataoffset("runtime"), offset, size)
+ // this is a constructor and the runtime code is returned
+ return(offset, size)
+ }
+
+ data "Table2" hex"4123"
+
+ object "runtime" {
+ code {
+ // runtime code
+
+ let size = datasize("Contract2")
+ let offset = allocate(size)
+ // This will turn into a memory->memory copy for eWASM and
+ // a codecopy for EVM
+ datacopy(dataoffset("Contract2"), offset, size)
+ // constructor parameter is a single number 0x1234
+ mstore(add(offset, size), 0x1234)
+ create(offset, add(size, 32))
+ }
+
+ // Embedded object. Use case is that the outside is a factory contract,
+ // and Contract2 is the code to be created by the factory
+ object "Contract2" {
+ code {
+ // code here ...
+ }
+
+ object "runtime" {
+ code {
+ // code here ...
+ }
+ }
+
+ data "Table1" hex"4123"
+ }
+ }
+ }
diff --git a/docs/metadata.rst b/docs/metadata.rst
index dbde87e8..5e37219e 100644
--- a/docs/metadata.rst
+++ b/docs/metadata.rst
@@ -131,6 +131,8 @@ user interface for the contract.
Furthermore, Mist can use the userdoc to display a confirmation message to the user
whenever they interact with the contract.
+Additional information about Ethereum Natural Specification (NatSpec) can be found `here <https://github.com/ethereum/wiki/wiki/Ethereum-Natural-Specification-Format>`_.
+
Usage for Source Code Verification
==================================
diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst
index 6586cb5f..1e2138fa 100644
--- a/docs/security-considerations.rst
+++ b/docs/security-considerations.rst
@@ -55,42 +55,59 @@ complete contract):
::
- pragma solidity ^0.4.0;
-
- // THIS CONTRACT CONTAINS A BUG - DO NOT USE
- contract Fund {
- /// Mapping of ether shares of the contract.
- mapping(address => uint) shares;
- /// Withdraw your share.
- function withdraw() {
- if (msg.sender.send(shares[msg.sender]))
- shares[msg.sender] = 0;
- }
- }
+ pragma solidity ^0.4.0;
+
+ // THIS CONTRACT CONTAINS A BUG - DO NOT USE
+ contract Fund {
+ /// Mapping of ether shares of the contract.
+ mapping(address => uint) shares;
+ /// Withdraw your share.
+ function withdraw() {
+ if (msg.sender.send(shares[msg.sender]))
+ shares[msg.sender] = 0;
+ }
+ }
The problem is not too serious here because of the limited gas as part
-of ``send``, but it still exposes a weakness: Ether transfer always
-includes code execution, so the recipient could be a contract that calls
+of ``send``, but it still exposes a weakness: Ether transfer can always
+include code execution, so the recipient could be a contract that calls
back into ``withdraw``. This would let it get multiple refunds and
-basically retrieve all the Ether in the contract.
+basically retrieve all the Ether in the contract. In particular, the
+following contract will allow an attacker to refund multiple times
+as it uses ``call`` which forwards all remaining gas by default:
+
+::
+
+ pragma solidity ^0.4.0;
+
+ // THIS CONTRACT CONTAINS A BUG - DO NOT USE
+ contract Fund {
+ /// Mapping of ether shares of the contract.
+ mapping(address => uint) shares;
+ /// Withdraw your share.
+ function withdraw() {
+ if (msg.sender.call.value(shares[msg.sender])())
+ shares[msg.sender] = 0;
+ }
+ }
To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern as
outlined further below:
::
- pragma solidity ^0.4.11;
+ pragma solidity ^0.4.11;
- contract Fund {
- /// Mapping of ether shares of the contract.
- mapping(address => uint) shares;
- /// Withdraw your share.
- function withdraw() {
- var share = shares[msg.sender];
- shares[msg.sender] = 0;
- msg.sender.transfer(share);
- }
- }
+ contract Fund {
+ /// Mapping of ether shares of the contract.
+ mapping(address => uint) shares;
+ /// Withdraw your share.
+ function withdraw() {
+ var share = shares[msg.sender];
+ shares[msg.sender] = 0;
+ msg.sender.transfer(share);
+ }
+ }
Note that re-entrancy is not only an effect of Ether transfer but of any
function call on another contract. Furthermore, you also have to take
@@ -179,7 +196,9 @@ Never use tx.origin for authorization. Let's say you have a wallet contract like
}
}
-Now someone tricks you into sending ether to the address of this attack wallet::
+Now someone tricks you into sending ether to the address of this attack wallet:
+
+::
pragma solidity ^0.4.11;
diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst
index 59ab7962..9489665e 100644
--- a/docs/solidity-by-example.rst
+++ b/docs/solidity-by-example.rst
@@ -492,7 +492,7 @@ high or low invalid bids.
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 (see the remark above about
+ // before `transfer` returns (see the remark above about
// conditions -> effects -> interaction).
pendingReturns[msg.sender] = 0;
@@ -508,12 +508,11 @@ high or low invalid bids.
require(!ended);
AuctionEnded(highestBidder, highestBid);
ended = true;
- // We send all the money we have, because some
- // of the refunds might have failed.
- beneficiary.transfer(this.balance);
+ beneficiary.transfer(highestBid);
}
}
+
.. index:: purchase, remote purchase, escrow
********************
diff --git a/docs/structure-of-a-contract.rst b/docs/structure-of-a-contract.rst
index 224eb368..0b554800 100644
--- a/docs/structure-of-a-contract.rst
+++ b/docs/structure-of-a-contract.rst
@@ -20,12 +20,12 @@ State variables are values which are permanently stored in contract storage.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.0;
- contract SimpleStorage {
- uint storedData; // State variable
- // ...
- }
+ contract SimpleStorage {
+ uint storedData; // State variable
+ // ...
+ }
See the :ref:`types` section for valid state variable types and
:ref:`visibility-and-getters` for possible choices for
@@ -40,13 +40,13 @@ Functions are the executable units of code within a contract.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.0;
- contract SimpleAuction {
- function bid() payable { // Function
- // ...
- }
- }
+ contract SimpleAuction {
+ function bid() payable { // Function
+ // ...
+ }
+ }
:ref:`function-calls` can happen internally or externally
and have different levels of visibility (:ref:`visibility-and-getters`)
@@ -62,20 +62,20 @@ Function modifiers can be used to amend the semantics of functions in a declarat
::
- pragma solidity ^0.4.11;
+ pragma solidity ^0.4.11;
- contract Purchase {
- address public seller;
+ contract Purchase {
+ address public seller;
- modifier onlySeller() { // Modifier
- require(msg.sender == seller);
- _;
- }
+ modifier onlySeller() { // Modifier
+ require(msg.sender == seller);
+ _;
+ }
- function abort() onlySeller { // Modifier usage
- // ...
- }
- }
+ function abort() onlySeller { // Modifier usage
+ // ...
+ }
+ }
.. _structure-events:
@@ -86,16 +86,16 @@ Events are convenience interfaces with the EVM logging facilities.
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.0;
- contract SimpleAuction {
- event HighestBidIncreased(address bidder, uint amount); // Event
+ contract SimpleAuction {
+ event HighestBidIncreased(address bidder, uint amount); // Event
- function bid() payable {
- // ...
- HighestBidIncreased(msg.sender, msg.value); // Triggering event
- }
- }
+ function bid() payable {
+ // ...
+ HighestBidIncreased(msg.sender, msg.value); // Triggering event
+ }
+ }
See :ref:`events` in contracts section for information on how events are declared
and can be used from within a dapp.
@@ -110,16 +110,16 @@ Structs are custom defined types that can group several variables (see
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.0;
- contract Ballot {
- struct Voter { // Struct
- uint weight;
- bool voted;
- address delegate;
- uint vote;
- }
- }
+ contract Ballot {
+ struct Voter { // Struct
+ uint weight;
+ bool voted;
+ address delegate;
+ uint vote;
+ }
+ }
.. _structure-enum-types:
@@ -131,8 +131,8 @@ Enums can be used to create custom types with a finite set of values (see
::
- pragma solidity ^0.4.0;
+ pragma solidity ^0.4.0;
- contract Purchase {
- enum State { Created, Locked, Inactive } // Enum
- }
+ contract Purchase {
+ enum State { Created, Locked, Inactive } // Enum
+ }
diff --git a/docs/style-guide.rst b/docs/style-guide.rst
index a438b3d0..5b6f42a2 100644
--- a/docs/style-guide.rst
+++ b/docs/style-guide.rst
@@ -739,6 +739,12 @@ Modifier Names
Use mixedCase. Examples: ``onlyBy``, ``onlyAfter``, ``onlyDuringThePreSale``.
+Enums
+=====
+
+Enums, in the style of simple type declarations, should be named using the CapWords style. Examples: ``TokenGroup``, ``Frame``, ``HashStyle``, ``CharacterLocation``.
+
+
Avoiding Naming Collisions
==========================
diff --git a/docs/types.rst b/docs/types.rst
index 0be8255e..c716b95e 100644
--- a/docs/types.rst
+++ b/docs/types.rst
@@ -990,6 +990,6 @@ parameters or return parameters.
.. warning::
The type is only deduced from the first assignment, so
the loop in the following snippet is infinite, as ``i`` will have the type
- ``uint8`` and any value of this type is smaller than ``2000``.
+ ``uint8`` and the highest value of this type is smaller than ``2000``.
``for (var i = 0; i < 2000; i++) { ... }``
diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst
index 7af97376..8261bdde 100644
--- a/docs/units-and-global-variables.rst
+++ b/docs/units-and-global-variables.rst
@@ -85,11 +85,6 @@ Block and Transaction Properties
consecutive blocks in the canonical chain.
.. note::
- If you want to implement access restrictions in library functions using
- ``msg.sender``, you have to manually supply the value of
- ``msg.sender`` as an argument.
-
-.. note::
The block hashes are not available for all blocks for scalability reasons.
You can only access the hashes of the most recent 256 blocks, all other
values will be zero.
diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst
index 7f82df70..f1f13b82 100644
--- a/docs/using-the-compiler.rst
+++ b/docs/using-the-compiler.rst
@@ -138,7 +138,7 @@ Input Description
// ewasm.wasm - eWASM binary format (not supported atm)
//
// Note that using a using `evm`, `evm.bytecode`, `ewasm`, etc. will select every
- // target part of that output.
+ // target part of that output. Additionally, `*` can be used as a wildcard to request everything.
//
outputSelection: {
// Enable the metadata and bytecode outputs of every single contract.
@@ -177,7 +177,8 @@ Output Description
start: 0,
end: 100
],
- // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc
+ // Mandatory: Error type, such as "TypeError", "InternalCompilerError", "Exception", etc.
+ // See below for complete list of types.
type: "TypeError",
// Mandatory: Component where the error originated, such as "general", "ewasm", etc.
component: "general",
@@ -273,3 +274,21 @@ Output Description
}
}
}
+
+
+Error types
+~~~~~~~~~~~
+
+1. ``JSONError``: JSON input doesn't conform to the required format, e.g. input is not a JSON object, the language is not supported, etc.
+2. ``IOError``: IO and import processing errors, such as unresolvable URL or hash mismatch in supplied sources.
+3. ``ParserError``: Source code doesn't conform to the language rules.
+4. ``DocstringParsingError``: The NatSpec tags in the comment block cannot be parsed.
+5. ``SyntaxError``: Syntactical error, such as ``continue`` is used outside of a ``for`` loop.
+6. ``DeclarationError``: Invalid, unresolvable or clashing identifier names. e.g. ``Identifier not found``
+7. ``TypeError``: Error within the type system, such as invalid type conversions, invalid assignments, etc.
+8. ``UnimplementedFeatureError``: Feature is not supported by the compiler, but is expected to be supported in future versions.
+9. ``InternalCompilerError``: Internal bug triggered in the compiler - this should be reported as an issue.
+10. ``Exception``: Unknown failure during compilation - this should be reported as an issue.
+11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue.
+12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue.
+13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible.
diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py
index a828146f..50f51cf4 100644
--- a/docs/utils/SolidityLexer.py
+++ b/docs/utils/SolidityLexer.py
@@ -56,7 +56,7 @@ class SolidityLexer(RegexLexer):
(r'[})\].]', Punctuation),
(r'(anonymous|as|assembly|break|constant|continue|do|delete|else|external|for|hex|if|'
r'indexed|internal|import|is|mapping|memory|new|payable|public|pragma|'
- r'private|return|returns|storage|super|this|throw|using|while)\b', Keyword, 'slashstartsregex'),
+ r'private|pure|return|returns|storage|super|this|throw|using|view|while)\b', Keyword, 'slashstartsregex'),
(r'(var|function|event|modifier|struct|enum|contract|library|interface)\b', Keyword.Declaration, 'slashstartsregex'),
(r'(bytes|string|address|uint|int|bool|byte|' +
'|'.join(
@@ -67,15 +67,15 @@ class SolidityLexer(RegexLexer):
['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)]
) + r')\b', Keyword.Type, 'slashstartsregex'),
(r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'),
- (r'(abstract|after|case|catch|default|final|in|inline|interface|let|match|'
- r'null|of|pure|relocatable|static|switch|try|type|typeof|view)\b', Keyword.Reserved),
+ (r'(abstract|after|case|catch|default|final|in|inline|let|match|'
+ r'null|of|relocatable|static|switch|try|type|typeof)\b', Keyword.Reserved),
(r'(true|false)\b', Keyword.Constant),
(r'(block|msg|tx|now|suicide|selfdestruct|addmod|mulmod|sha3|keccak256|log[0-4]|'
r'sha256|ecrecover|ripemd160|assert|revert|require)', Name.Builtin),
(r'[$a-zA-Z_][a-zA-Z0-9_]*', Name.Other),
- (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?', Number.Float),
+ (r'[0-9][0-9]*\.[0-9]+([eE][0-9]+)?', Number.Float),
(r'0x[0-9a-fA-F]+', Number.Hex),
- (r'[0-9]+', Number.Integer),
+ (r'[0-9]+([eE][0-9]+)?', Number.Integer),
(r'"(\\\\|\\"|[^"])*"', String.Double),
(r"'(\\\\|\\'|[^'])*'", String.Single),
]
diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp
index db11e61c..445d11cd 100644
--- a/libdevcore/CommonData.cpp
+++ b/libdevcore/CommonData.cpp
@@ -21,6 +21,7 @@
#include <libdevcore/CommonData.h>
#include <libdevcore/Exceptions.h>
+#include <libdevcore/Assertions.h>
#include <libdevcore/SHA3.h>
#include <boost/algorithm/string.hpp>
@@ -86,20 +87,26 @@ bool dev::passesAddressChecksum(string const& _str, bool _strict)
))
return true;
+ return _str == dev::getChecksummedAddress(_str);
+}
+
+string dev::getChecksummedAddress(string const& _addr)
+{
+ string s = _addr.substr(0, 2) == "0x" ? _addr.substr(2) : _addr;
+ assertThrow(s.length() == 40, InvalidAddress, "");
+ assertThrow(s.find_first_not_of("0123456789abcdefABCDEF") == string::npos, InvalidAddress, "");
+
h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
+
+ string ret = "0x";
for (size_t i = 0; i < 40; ++i)
{
char addressCharacter = s[i];
- bool lowerCase;
- if ('a' <= addressCharacter && addressCharacter <= 'f')
- lowerCase = true;
- else if ('A' <= addressCharacter && addressCharacter <= 'F')
- lowerCase = false;
- else
- continue;
unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf;
- if ((nibble >= 8) == lowerCase)
- return false;
+ if (nibble >= 8)
+ ret += toupper(addressCharacter);
+ else
+ ret += tolower(addressCharacter);
}
- return true;
+ return ret;
}
diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h
index 765707f8..b85abe95 100644
--- a/libdevcore/CommonData.h
+++ b/libdevcore/CommonData.h
@@ -183,6 +183,12 @@ template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U con
_a.push_back(i);
return _a;
}
+/// Concatenate the contents of a container onto a vector, move variant.
+template <class T, class U> std::vector<T>& operator+=(std::vector<T>& _a, U&& _b)
+{
+ std::move(_b.begin(), _b.end(), std::back_inserter(_a));
+ return _a;
+}
/// Concatenate the contents of a container onto a set
template <class T, class U> std::set<T>& operator+=(std::set<T>& _a, U const& _b)
{
@@ -197,6 +203,17 @@ inline std::vector<T> operator+(std::vector<T> const& _a, std::vector<T> const&
ret += _b;
return ret;
}
+/// Concatenate two vectors of elements, moving them.
+template <class T>
+inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
+{
+ std::vector<T> ret(std::move(_a));
+ if (&_a == &_b)
+ ret += ret;
+ else
+ ret += std::move(_b);
+ return ret;
+}
template <class T, class V>
bool contains(T const& _t, V const& _v)
@@ -209,4 +226,8 @@ bool contains(T const& _t, V const& _v)
/// are considered valid.
bool passesAddressChecksum(std::string const& _str, bool _strict);
+/// @returns the checksummed version of an address
+/// @param hex strings that look like an address
+std::string getChecksummedAddress(std::string const& _addr);
+
}
diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h
index a3e638bf..cfe72fbf 100644
--- a/libdevcore/Exceptions.h
+++ b/libdevcore/Exceptions.h
@@ -44,6 +44,7 @@ private:
#define DEV_SIMPLE_EXCEPTION(X) struct X: virtual Exception { const char* what() const noexcept override { return #X; } }
+DEV_SIMPLE_EXCEPTION(InvalidAddress);
DEV_SIMPLE_EXCEPTION(BadHexCharacter);
DEV_SIMPLE_EXCEPTION(FileError);
diff --git a/libevmasm/JumpdestRemover.cpp b/libevmasm/JumpdestRemover.cpp
index b6016798..60493a99 100644
--- a/libevmasm/JumpdestRemover.cpp
+++ b/libevmasm/JumpdestRemover.cpp
@@ -21,8 +21,6 @@
#include "JumpdestRemover.h"
-#include <libsolidity/interface/Exceptions.h>
-
#include <libevmasm/AssemblyItem.h>
using namespace std;
@@ -45,7 +43,7 @@ bool JumpdestRemover::optimise(set<size_t> const& _tagsReferencedFromOutside)
if (_item.type() != Tag)
return false;
auto asmIdAndTag = _item.splitForeignPushTag();
- solAssert(asmIdAndTag.first == size_t(-1), "Sub-assembly tag used as label.");
+ assertThrow(asmIdAndTag.first == size_t(-1), OptimizerException, "Sub-assembly tag used as label.");
size_t tag = asmIdAndTag.second;
return !references.count(tag);
}
diff --git a/libjulia/ASTDataForward.h b/libjulia/ASTDataForward.h
new file mode 100644
index 00000000..3806e321
--- /dev/null
+++ b/libjulia/ASTDataForward.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @date 2017
+ * Pull in some identifiers from the solidity::assembly namespace.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+namespace dev
+{
+namespace julia
+{
+
+using Instruction = solidity::assembly::Instruction;
+using Literal = solidity::assembly::Literal;
+using Label = solidity::assembly::Label;
+using StackAssignment = solidity::assembly::StackAssignment;
+using Identifier = solidity::assembly::Identifier;
+using Assignment = solidity::assembly::Assignment;
+using VariableDeclaration = solidity::assembly::VariableDeclaration;
+using FunctionalInstruction = solidity::assembly::FunctionalInstruction;
+using FunctionDefinition = solidity::assembly::FunctionDefinition;
+using FunctionCall = solidity::assembly::FunctionCall;
+using If = solidity::assembly::If;
+using Case = solidity::assembly::Case;
+using Switch = solidity::assembly::Switch;
+using ForLoop = solidity::assembly::ForLoop;
+using Block = solidity::assembly::Block;
+
+using TypedName = solidity::assembly::TypedName;
+
+using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
+
+}
+}
diff --git a/libjulia/backends/evm/EVMAssembly.cpp b/libjulia/backends/evm/EVMAssembly.cpp
index 1d499b20..07ad05c9 100644
--- a/libjulia/backends/evm/EVMAssembly.cpp
+++ b/libjulia/backends/evm/EVMAssembly.cpp
@@ -26,7 +26,7 @@
using namespace std;
using namespace dev;
-using namespace julia;
+using namespace dev::julia;
namespace
{
diff --git a/libjulia/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp
index 66f593e8..f92939be 100644
--- a/libjulia/backends/evm/EVMCodeTransform.cpp
+++ b/libjulia/backends/evm/EVMCodeTransform.cpp
@@ -31,7 +31,8 @@ using namespace std;
using namespace dev;
using namespace dev::julia;
using namespace dev::solidity;
-using namespace dev::solidity::assembly;
+
+using Scope = dev::solidity::assembly::Scope;
void CodeTransform::operator()(VariableDeclaration const& _varDecl)
{
@@ -124,11 +125,11 @@ void CodeTransform::operator()(FunctionCall const& _call)
void CodeTransform::operator()(FunctionalInstruction const& _instruction)
{
if (m_evm15 && (
- _instruction.instruction.instruction == solidity::Instruction::JUMP ||
- _instruction.instruction.instruction == solidity::Instruction::JUMPI
+ _instruction.instruction == solidity::Instruction::JUMP ||
+ _instruction.instruction == solidity::Instruction::JUMPI
))
{
- bool const isJumpI = _instruction.instruction.instruction == solidity::Instruction::JUMPI;
+ bool const isJumpI = _instruction.instruction == solidity::Instruction::JUMPI;
if (isJumpI)
{
solAssert(_instruction.arguments.size() == 2, "");
@@ -149,7 +150,8 @@ void CodeTransform::operator()(FunctionalInstruction const& _instruction)
{
for (auto const& arg: _instruction.arguments | boost::adaptors::reversed)
visitExpression(arg);
- (*this)(_instruction.instruction);
+ m_assembly.setSourceLocation(_instruction.location);
+ m_assembly.appendInstruction(_instruction.instruction);
}
checkStackHeight(&_instruction);
}
@@ -217,6 +219,19 @@ void CodeTransform::operator()(assembly::Instruction const& _instruction)
checkStackHeight(&_instruction);
}
+void CodeTransform::operator()(If const& _if)
+{
+ visitExpression(*_if.condition);
+ m_assembly.setSourceLocation(_if.location);
+ m_assembly.appendInstruction(solidity::Instruction::ISZERO);
+ AbstractAssembly::LabelID end = m_assembly.newLabelId();
+ m_assembly.appendJumpToIf(end);
+ (*this)(_if.body);
+ m_assembly.setSourceLocation(_if.location);
+ m_assembly.appendLabel(end);
+ checkStackHeight(&_if);
+}
+
void CodeTransform::operator()(Switch const& _switch)
{
//@TODO use JUMPV in EVM1.5?
@@ -276,7 +291,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
solAssert(m_info.scopes.at(&_function.body), "");
Scope* varScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get();
solAssert(varScope, "");
- for (auto const& v: _function.arguments | boost::adaptors::reversed)
+ for (auto const& v: _function.parameters | boost::adaptors::reversed)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
m_context->variableStackHeights[&var] = height++;
@@ -289,7 +304,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
if (m_evm15)
{
m_assembly.appendJumpTo(afterFunction, -stackHeightBefore);
- m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.arguments.size());
+ m_assembly.appendBeginsub(functionEntryID(_function.name, function), _function.parameters.size());
}
else
{
@@ -298,7 +313,7 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
}
m_stackAdjustment += localStackAdjustment;
- for (auto const& v: _function.returns)
+ for (auto const& v: _function.returnVariables)
{
auto& var = boost::get<Scope::Variable>(varScope->identifiers.at(v.name));
m_context->variableStackHeights[&var] = height++;
@@ -328,9 +343,9 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
// modified parallel to the actual stack.
vector<int> stackLayout;
if (!m_evm15)
- stackLayout.push_back(_function.returns.size()); // Move return label to the top
- stackLayout += vector<int>(_function.arguments.size(), -1); // discard all arguments
- for (size_t i = 0; i < _function.returns.size(); ++i)
+ stackLayout.push_back(_function.returnVariables.size()); // Move return label to the top
+ stackLayout += vector<int>(_function.parameters.size(), -1); // discard all arguments
+ for (size_t i = 0; i < _function.returnVariables.size(); ++i)
stackLayout.push_back(i); // Move return values down, but keep order.
solAssert(stackLayout.size() <= 17, "Stack too deep");
@@ -350,9 +365,9 @@ void CodeTransform::operator()(FunctionDefinition const& _function)
}
if (m_evm15)
- m_assembly.appendReturnsub(_function.returns.size(), stackHeightBefore);
+ m_assembly.appendReturnsub(_function.returnVariables.size(), stackHeightBefore);
else
- m_assembly.appendJump(stackHeightBefore - _function.returns.size());
+ m_assembly.appendJump(stackHeightBefore - _function.returnVariables.size());
m_stackAdjustment -= localStackAdjustment;
m_assembly.appendLabel(afterFunction);
checkStackHeight(&_function);
diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h
index 951c8a50..577cc8ba 100644
--- a/libjulia/backends/evm/EVMCodeTransform.h
+++ b/libjulia/backends/evm/EVMCodeTransform.h
@@ -20,8 +20,9 @@
#include <libjulia/backends/evm/EVMAssembly.h>
+#include <libjulia/ASTDataForward.h>
+
#include <libsolidity/inlineasm/AsmScope.h>
-#include <libsolidity/inlineasm/AsmDataForward.h>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
@@ -95,37 +96,38 @@ protected:
{}
public:
- void operator()(solidity::assembly::Instruction const& _instruction);
- void operator()(solidity::assembly::Literal const& _literal);
- void operator()(solidity::assembly::Identifier const& _identifier);
- void operator()(solidity::assembly::FunctionalInstruction const& _instr);
- void operator()(solidity::assembly::FunctionCall const&);
- void operator()(solidity::assembly::Label const& _label);
- void operator()(solidity::assembly::StackAssignment const& _assignment);
- void operator()(solidity::assembly::Assignment const& _assignment);
- void operator()(solidity::assembly::VariableDeclaration const& _varDecl);
- void operator()(solidity::assembly::Switch const& _switch);
- void operator()(solidity::assembly::FunctionDefinition const&);
- void operator()(solidity::assembly::ForLoop const&);
- void operator()(solidity::assembly::Block const& _block);
+ void operator()(Instruction const& _instruction);
+ void operator()(Literal const& _literal);
+ void operator()(Identifier const& _identifier);
+ void operator()(FunctionalInstruction const& _instr);
+ void operator()(FunctionCall const&);
+ void operator()(Label const& _label);
+ void operator()(StackAssignment const& _assignment);
+ void operator()(Assignment const& _assignment);
+ void operator()(VariableDeclaration const& _varDecl);
+ void operator()(If const& _if);
+ void operator()(Switch const& _switch);
+ void operator()(FunctionDefinition const&);
+ void operator()(ForLoop const&);
+ void operator()(Block const& _block);
private:
- AbstractAssembly::LabelID labelFromIdentifier(solidity::assembly::Identifier const& _identifier);
+ AbstractAssembly::LabelID labelFromIdentifier(Identifier const& _identifier);
/// @returns the label ID corresponding to the given label, allocating a new one if
/// necessary.
AbstractAssembly::LabelID labelID(solidity::assembly::Scope::Label const& _label);
AbstractAssembly::LabelID functionEntryID(std::string const& _name, solidity::assembly::Scope::Function const& _function);
/// Generates code for an expression that is supposed to return a single value.
- void visitExpression(solidity::assembly::Statement const& _expression);
+ void visitExpression(Statement const& _expression);
- void visitStatements(std::vector<solidity::assembly::Statement> const& _statements);
+ void visitStatements(std::vector<Statement> const& _statements);
/// Pops all variables declared in the block and checks that the stack height is equal
/// to @a _blackStartStackHeight.
- void finalizeBlock(solidity::assembly::Block const& _block, int _blockStartStackHeight);
+ void finalizeBlock(Block const& _block, int _blockStartStackHeight);
- void generateMultiAssignment(std::vector<solidity::assembly::Identifier> const& _variableNames);
- void generateAssignment(solidity::assembly::Identifier const& _variableName);
+ void generateMultiAssignment(std::vector<Identifier> const& _variableNames);
+ void generateAssignment(Identifier const& _variableName);
/// Determines the stack height difference to the given variables. Throws
/// if it is not yet in scope or the height difference is too large. Returns
diff --git a/libjulia/optimiser/ASTCopier.cpp b/libjulia/optimiser/ASTCopier.cpp
new file mode 100644
index 00000000..a461f434
--- /dev/null
+++ b/libjulia/optimiser/ASTCopier.cpp
@@ -0,0 +1,175 @@
+/*
+ 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/>.
+*/
+/**
+ * Creates an independent copy of an AST, renaming identifiers to be unique.
+ */
+
+#include <libjulia/optimiser/ASTCopier.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <libdevcore/Common.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::julia;
+
+
+Statement ASTCopier::operator()(Instruction const&)
+{
+ solAssert(false, "Invalid operation.");
+ return {};
+}
+
+Statement ASTCopier::operator()(VariableDeclaration const& _varDecl)
+{
+ return VariableDeclaration{
+ _varDecl.location,
+ translateVector(_varDecl.variables),
+ translate(_varDecl.value)
+ };
+}
+
+Statement ASTCopier::operator()(Assignment const& _assignment)
+{
+ return Assignment{
+ _assignment.location,
+ translateVector(_assignment.variableNames),
+ translate(_assignment.value)
+ };
+}
+
+Statement ASTCopier::operator()(StackAssignment const&)
+{
+ solAssert(false, "Invalid operation.");
+ return {};
+}
+
+Statement ASTCopier::operator()(Label const&)
+{
+ solAssert(false, "Invalid operation.");
+ return {};
+}
+
+Statement ASTCopier::operator()(FunctionCall const& _call)
+{
+ return FunctionCall{
+ _call.location,
+ translate(_call.functionName),
+ translateVector(_call.arguments)
+ };
+}
+
+Statement ASTCopier::operator()(FunctionalInstruction const& _instruction)
+{
+ return FunctionalInstruction{
+ _instruction.location,
+ _instruction.instruction,
+ translateVector(_instruction.arguments)
+ };
+}
+
+Statement ASTCopier::operator()(Identifier const& _identifier)
+{
+ return Identifier{_identifier.location, translateIdentifier(_identifier.name)};
+}
+
+Statement ASTCopier::operator()(Literal const& _literal)
+{
+ return translate(_literal);
+}
+
+Statement ASTCopier::operator()(If const& _if)
+{
+ return If{_if.location, translate(_if.condition), translate(_if.body)};
+}
+
+Statement ASTCopier::operator()(Switch const& _switch)
+{
+ return Switch{_switch.location, translate(_switch.expression), translateVector(_switch.cases)};
+}
+
+Statement ASTCopier::operator()(FunctionDefinition const& _function)
+{
+ string translatedName = translateIdentifier(_function.name);
+
+ enterFunction(_function);
+ ScopeGuard g([&]() { this->leaveFunction(_function); });
+
+ return FunctionDefinition{
+ _function.location,
+ move(translatedName),
+ translateVector(_function.parameters),
+ translateVector(_function.returnVariables),
+ translate(_function.body)
+ };
+}
+
+Statement ASTCopier::operator()(ForLoop const& _forLoop)
+{
+ enterScope(_forLoop.pre);
+ ScopeGuard g([&]() { this->leaveScope(_forLoop.pre); });
+
+ return ForLoop{
+ _forLoop.location,
+ translate(_forLoop.pre),
+ translate(_forLoop.condition),
+ translate(_forLoop.post),
+ translate(_forLoop.body)
+ };
+}
+
+Statement ASTCopier::operator ()(Block const& _block)
+{
+ return translate(_block);
+}
+
+Statement ASTCopier::translate(Statement const& _statement)
+{
+ return boost::apply_visitor(*this, _statement);
+}
+
+Block ASTCopier::translate(Block const& _block)
+{
+ enterScope(_block);
+ ScopeGuard g([&]() { this->leaveScope(_block); });
+
+ return Block{_block.location, translateVector(_block.statements)};
+}
+
+Case ASTCopier::translate(Case const& _case)
+{
+ return Case{_case.location, translate(_case.value), translate(_case.body)};
+}
+
+Identifier ASTCopier::translate(Identifier const& _identifier)
+{
+ return Identifier{_identifier.location, translateIdentifier(_identifier.name)};
+}
+
+Literal ASTCopier::translate(Literal const& _literal)
+{
+ return _literal;
+}
+
+TypedName ASTCopier::translate(TypedName const& _typedName)
+{
+ return TypedName{_typedName.location, translateIdentifier(_typedName.name), _typedName.type};
+}
+
diff --git a/libjulia/optimiser/ASTCopier.h b/libjulia/optimiser/ASTCopier.h
new file mode 100644
index 00000000..5dde2ce9
--- /dev/null
+++ b/libjulia/optimiser/ASTCopier.h
@@ -0,0 +1,94 @@
+/*
+ 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/>.
+*/
+/**
+ * Creates an independent copy of an AST, renaming identifiers to be unique.
+ */
+
+#pragma once
+
+#include <libjulia/ASTDataForward.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+#include <vector>
+#include <set>
+#include <memory>
+
+namespace dev
+{
+namespace julia
+{
+
+/**
+ * Creates a copy of a iulia AST potentially replacing identifier names.
+ * Base class to be extended.
+ */
+class ASTCopier: public boost::static_visitor<Statement>
+{
+public:
+ Statement operator()(Literal const& _literal);
+ Statement operator()(Instruction const& _instruction);
+ Statement operator()(Identifier const& _identifier);
+ Statement operator()(FunctionalInstruction const& _instr);
+ Statement operator()(FunctionCall const&);
+ Statement operator()(Label const& _label);
+ Statement operator()(StackAssignment const& _assignment);
+ Statement operator()(Assignment const& _assignment);
+ Statement operator()(VariableDeclaration const& _varDecl);
+ Statement operator()(If const& _if);
+ Statement operator()(Switch const& _switch);
+ Statement operator()(FunctionDefinition const&);
+ Statement operator()(ForLoop const&);
+ Statement operator()(Block const& _block);
+
+ virtual Statement translate(Statement const& _statement);
+
+protected:
+ template <typename T>
+ std::vector<T> translateVector(std::vector<T> const& _values);
+
+ template <typename T>
+ std::shared_ptr<T> translate(std::shared_ptr<T> const& _v)
+ {
+ return _v ? std::make_shared<T>(translate(*_v)) : nullptr;
+ }
+ Block translate(Block const& _block);
+ Case translate(Case const& _case);
+ Identifier translate(Identifier const& _identifier);
+ Literal translate(Literal const& _literal);
+ TypedName translate(TypedName const& _typedName);
+
+ virtual void enterScope(Block const&) { }
+ virtual void leaveScope(Block const&) { }
+ virtual void enterFunction(FunctionDefinition const&) { }
+ virtual void leaveFunction(FunctionDefinition const&) { }
+ virtual std::string translateIdentifier(std::string const& _name) { return _name; }
+};
+
+template <typename T>
+std::vector<T> ASTCopier::translateVector(std::vector<T> const& _values)
+{
+ std::vector<T> translated;
+ for (auto const& v: _values)
+ translated.emplace_back(translate(v));
+ return translated;
+}
+
+
+}
+}
diff --git a/libjulia/optimiser/ASTWalker.cpp b/libjulia/optimiser/ASTWalker.cpp
new file mode 100644
index 00000000..0caef04e
--- /dev/null
+++ b/libjulia/optimiser/ASTWalker.cpp
@@ -0,0 +1,149 @@
+/*
+ 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/>.
+*/
+/**
+ * Generic AST walker.
+ */
+
+#include <libjulia/optimiser/ASTWalker.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+using namespace std;
+using namespace dev;
+using namespace dev::julia;
+using namespace dev::solidity;
+
+
+void ASTWalker::operator()(FunctionalInstruction const& _instr)
+{
+ walkVector(_instr.arguments | boost::adaptors::reversed);
+}
+
+void ASTWalker::operator()(FunctionCall const& _funCall)
+{
+ walkVector(_funCall.arguments | boost::adaptors::reversed);
+}
+
+void ASTWalker::operator()(Assignment const& _assignment)
+{
+ for (auto const& name: _assignment.variableNames)
+ (*this)(name);
+ boost::apply_visitor(*this, *_assignment.value);
+}
+
+void ASTWalker::operator()(VariableDeclaration const& _varDecl)
+{
+ if (_varDecl.value)
+ boost::apply_visitor(*this, *_varDecl.value);
+}
+
+void ASTWalker::operator()(If const& _if)
+{
+ boost::apply_visitor(*this, *_if.condition);
+ (*this)(_if.body);
+}
+
+void ASTWalker::operator()(Switch const& _switch)
+{
+ boost::apply_visitor(*this, *_switch.expression);
+ for (auto const& _case: _switch.cases)
+ {
+ if (_case.value)
+ (*this)(*_case.value);
+ (*this)(_case.body);
+ }
+}
+
+void ASTWalker::operator()(FunctionDefinition const& _fun)
+{
+ (*this)(_fun.body);
+}
+
+void ASTWalker::operator()(ForLoop const& _for)
+{
+ (*this)(_for.pre);
+ boost::apply_visitor(*this, *_for.condition);
+ (*this)(_for.post);
+ (*this)(_for.body);
+}
+
+void ASTWalker::operator()(Block const& _block)
+{
+ walkVector(_block.statements);
+}
+
+void ASTModifier::operator()(FunctionalInstruction& _instr)
+{
+ walkVector(_instr.arguments | boost::adaptors::reversed);
+}
+
+void ASTModifier::operator()(FunctionCall& _funCall)
+{
+ walkVector(_funCall.arguments | boost::adaptors::reversed);
+}
+
+void ASTModifier::operator()(Assignment& _assignment)
+{
+ for (auto& name: _assignment.variableNames)
+ (*this)(name);
+ visit(*_assignment.value);
+}
+
+void ASTModifier::operator()(VariableDeclaration& _varDecl)
+{
+ if (_varDecl.value)
+ visit(*_varDecl.value);
+}
+
+void ASTModifier::operator()(If& _if)
+{
+ visit(*_if.condition);
+ (*this)(_if.body);
+}
+
+void ASTModifier::operator()(Switch& _switch)
+{
+ visit(*_switch.expression);
+ for (auto& _case: _switch.cases)
+ {
+ if (_case.value)
+ (*this)(*_case.value);
+ (*this)(_case.body);
+ }
+}
+
+void ASTModifier::operator()(FunctionDefinition& _fun)
+{
+ (*this)(_fun.body);
+}
+
+void ASTModifier::operator()(ForLoop& _for)
+{
+ (*this)(_for.pre);
+ visit(*_for.condition);
+ (*this)(_for.post);
+ (*this)(_for.body);
+}
+
+void ASTModifier::operator()(Block& _block)
+{
+ walkVector(_block.statements);
+}
diff --git a/libjulia/optimiser/ASTWalker.h b/libjulia/optimiser/ASTWalker.h
new file mode 100644
index 00000000..8bd867d5
--- /dev/null
+++ b/libjulia/optimiser/ASTWalker.h
@@ -0,0 +1,104 @@
+/*
+ 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/>.
+*/
+/**
+ * Generic AST walker.
+ */
+
+#pragma once
+
+#include <libjulia/ASTDataForward.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+#include <vector>
+#include <set>
+#include <map>
+
+namespace dev
+{
+namespace julia
+{
+
+/**
+ * Generic AST walker.
+ */
+class ASTWalker: public boost::static_visitor<>
+{
+public:
+ virtual void operator()(Literal const&) {}
+ virtual void operator()(Instruction const&) { solAssert(false, ""); }
+ virtual void operator()(Identifier const&) {}
+ virtual void operator()(FunctionalInstruction const& _instr);
+ virtual void operator()(FunctionCall const& _funCall);
+ virtual void operator()(Label const&) { solAssert(false, ""); }
+ virtual void operator()(StackAssignment const&) { solAssert(false, ""); }
+ virtual void operator()(Assignment const& _assignment);
+ virtual void operator()(VariableDeclaration const& _varDecl);
+ virtual void operator()(If const& _if);
+ virtual void operator()(Switch const& _switch);
+ virtual void operator()(FunctionDefinition const&);
+ virtual void operator()(ForLoop const&);
+ virtual void operator()(Block const& _block);
+
+protected:
+ template <class T>
+ void walkVector(T const& _statements)
+ {
+ for (auto const& st: _statements)
+ boost::apply_visitor(*this, st);
+ }
+};
+
+/**
+ * Generic AST modifier (i.e. non-const version of ASTWalker).
+ */
+class ASTModifier: public boost::static_visitor<>
+{
+public:
+ virtual void operator()(Literal&) {}
+ virtual void operator()(Instruction&) { solAssert(false, ""); }
+ virtual void operator()(Identifier&) {}
+ virtual void operator()(FunctionalInstruction& _instr);
+ virtual void operator()(FunctionCall& _funCall);
+ virtual void operator()(Label&) { solAssert(false, ""); }
+ virtual void operator()(StackAssignment&) { solAssert(false, ""); }
+ virtual void operator()(Assignment& _assignment);
+ virtual void operator()(VariableDeclaration& _varDecl);
+ virtual void operator()(If& _if);
+ virtual void operator()(Switch& _switch);
+ virtual void operator()(FunctionDefinition&);
+ virtual void operator()(ForLoop&);
+ virtual void operator()(Block& _block);
+
+protected:
+ template <class T>
+ void walkVector(T&& _statements)
+ {
+ for (auto& st: _statements)
+ visit(st);
+ }
+ virtual void visit(Statement& _st)
+ {
+ boost::apply_visitor(*this, _st);
+ }
+};
+
+}
+}
diff --git a/libjulia/optimiser/Disambiguator.cpp b/libjulia/optimiser/Disambiguator.cpp
new file mode 100644
index 00000000..df2984e5
--- /dev/null
+++ b/libjulia/optimiser/Disambiguator.cpp
@@ -0,0 +1,85 @@
+/*
+ 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 makes all identifiers unique.
+ */
+
+#include <libjulia/optimiser/Disambiguator.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <libsolidity/interface/Exceptions.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::julia;
+using namespace dev::solidity;
+
+using Scope = dev::solidity::assembly::Scope;
+
+string Disambiguator::translateIdentifier(string const& _originalName)
+{
+ solAssert(!m_scopes.empty() && m_scopes.back(), "");
+ Scope::Identifier const* id = m_scopes.back()->lookup(_originalName);
+ solAssert(id, "");
+ if (!m_translations.count(id))
+ {
+ string translated = _originalName;
+ size_t suffix = 0;
+ while (m_usedNames.count(translated))
+ {
+ suffix++;
+ translated = _originalName + "_" + std::to_string(suffix);
+ }
+ m_usedNames.insert(translated);
+ m_translations[id] = translated;
+ }
+ return m_translations.at(id);
+}
+
+void Disambiguator::enterScope(Block const& _block)
+{
+ enterScopeInternal(*m_info.scopes.at(&_block));
+}
+
+void Disambiguator::leaveScope(Block const& _block)
+{
+ leaveScopeInternal(*m_info.scopes.at(&_block));
+}
+
+void Disambiguator::enterFunction(FunctionDefinition const& _function)
+{
+ enterScopeInternal(*m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()));
+}
+
+void Disambiguator::leaveFunction(FunctionDefinition const& _function)
+{
+ leaveScopeInternal(*m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()));
+}
+
+void Disambiguator::enterScopeInternal(Scope& _scope)
+{
+ m_scopes.push_back(&_scope);
+}
+
+void Disambiguator::leaveScopeInternal(Scope& _scope)
+{
+ solAssert(!m_scopes.empty(), "");
+ solAssert(m_scopes.back() == &_scope, "");
+ m_scopes.pop_back();
+}
diff --git a/libjulia/optimiser/Disambiguator.h b/libjulia/optimiser/Disambiguator.h
new file mode 100644
index 00000000..cc9488d5
--- /dev/null
+++ b/libjulia/optimiser/Disambiguator.h
@@ -0,0 +1,68 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Optimiser component that makes all identifiers unique.
+ */
+
+#pragma once
+
+#include <libjulia/ASTDataForward.h>
+
+#include <libjulia/optimiser/ASTCopier.h>
+
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+#include <set>
+
+namespace dev
+{
+namespace julia
+{
+class EVMAssembly;
+
+/**
+ * Creates a copy of a iulia AST replacing all identifiers by unique names.
+ */
+class Disambiguator: public ASTCopier
+{
+public:
+ Disambiguator(solidity::assembly::AsmAnalysisInfo const& _analysisInfo):
+ m_info(_analysisInfo)
+ {}
+
+protected:
+ virtual void enterScope(Block const& _block) override;
+ virtual void leaveScope(Block const& _block) override;
+ virtual void enterFunction(FunctionDefinition const& _function) override;
+ virtual void leaveFunction(FunctionDefinition const& _function) override;
+ virtual std::string translateIdentifier(std::string const& _name) override;
+
+ void enterScopeInternal(solidity::assembly::Scope& _scope);
+ void leaveScopeInternal(solidity::assembly::Scope& _scope);
+
+ solidity::assembly::AsmAnalysisInfo const& m_info;
+
+ std::vector<solidity::assembly::Scope*> m_scopes;
+ std::map<void const*, std::string> m_translations;
+ std::set<std::string> m_usedNames;
+};
+
+}
+}
diff --git a/libjulia/optimiser/NameCollector.cpp b/libjulia/optimiser/NameCollector.cpp
new file mode 100644
index 00000000..7b4c4793
--- /dev/null
+++ b/libjulia/optimiser/NameCollector.cpp
@@ -0,0 +1,44 @@
+/*
+ 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/>.
+*/
+/**
+ * Specific AST walker that collects all defined names.
+ */
+
+#include <libjulia/optimiser/NameCollector.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::julia;
+
+void NameCollector::operator()(VariableDeclaration const& _varDecl)
+{
+ for (auto const& var: _varDecl.variables)
+ m_names.insert(var.name);
+}
+
+void NameCollector::operator ()(FunctionDefinition const& _funDef)
+{
+ m_names.insert(_funDef.name);
+ m_functions[_funDef.name] = &_funDef;
+ for (auto const arg: _funDef.parameters)
+ m_names.insert(arg.name);
+ for (auto const ret: _funDef.returnVariables)
+ m_names.insert(ret.name);
+ ASTWalker::operator ()(_funDef);
+}
diff --git a/libjulia/optimiser/NameCollector.h b/libjulia/optimiser/NameCollector.h
new file mode 100644
index 00000000..b7e38f46
--- /dev/null
+++ b/libjulia/optimiser/NameCollector.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Specific AST walker that collects all defined names.
+ */
+
+#pragma once
+
+#include <libjulia/optimiser/ASTWalker.h>
+
+#include <string>
+#include <map>
+#include <set>
+
+namespace dev
+{
+namespace julia
+{
+
+/**
+ * Specific AST walker that collects all defined names.
+ */
+class NameCollector: public ASTWalker
+{
+public:
+ using ASTWalker::operator ();
+ virtual void operator()(VariableDeclaration const& _varDecl) override;
+ virtual void operator()(FunctionDefinition const& _funDef) override;
+
+ std::set<std::string> const& names() const { return m_names; }
+ std::map<std::string, FunctionDefinition const*> const& functions() const { return m_functions; }
+private:
+ std::set<std::string> m_names;
+ std::map<std::string, FunctionDefinition const*> m_functions;
+};
+
+}
+}
diff --git a/libjulia/optimiser/README.md b/libjulia/optimiser/README.md
new file mode 100644
index 00000000..771cb707
--- /dev/null
+++ b/libjulia/optimiser/README.md
@@ -0,0 +1,61 @@
+## IULIA Optimiser
+
+The iulia optimiser consists of several stages and components that all transform
+the AST in a semantically equivalent way. The goal is to end up either with code
+that is shorter or at least only marginally longer but will allow further
+optimisation steps.
+
+The optimiser currently follows a purely greedy strategy and does not do any
+backtracking.
+
+## Disambiguator
+
+The disambiguator takes an AST and returns a fresh copy where all identifiers have
+names unique to the input AST. This is a prerequisite for all other optimiser stages.
+One of the benefits is that identifier lookup does not need to take scopes into account
+and we can basically ignore the result of the analysis phase.
+
+All subsequent stages have the property that all names stay unique. This means if
+a new identifier needs to be introduced, a new unique name is generated.
+
+## Function Hoister
+
+The function hoister moves all function definitions to the topmost block. This is
+a semantically equivalent transformation as long as it is performed after the
+disambiguation stage. The reason is that moving a definition upwards cannot decrease
+its visibility and it is impossible to reference variables defined in a different function.
+
+The benefit of this stage is that function definitions can be lookup up more easily.
+
+## Function Grouper
+
+The function grouper has to be applied after the disambiguator and the function hoister.
+Its effect is that all topmost elements that are not function definitions are moved
+into a single block which is the first satement of the root block.
+
+After this step, a program has the following normal form:
+
+ { I F... }
+
+Where I is a block that does not contain any function definitions (not even recursively)
+and F is a list of function definitions such that no function contains a function definition.
+
+## Functional Inliner
+
+The functional inliner depends on the disambiguator, the function hoister and function grouper.
+It performs function inlining such that the result of the inlining is an expression. This can
+only be done if the body of the function to be inlined has the form ``{ r := E }`` where ``r``
+is the single return value of the function, ``E`` is an expression and all arguments in the
+function call are so-called movable expressions. A movable expression is either a literal, a
+variable or a function call (or EVM opcode) which does not have side-effects and also does not
+depend on any side-effects.
+
+As an example, neither ``mload`` nor ``mstore`` would be allowed.
+
+## Full Function Inliner
+
+## Variable Eliminator
+
+## Unused Declaration Pruner
+
+## Function Unifier
diff --git a/libjulia/optimiser/Substitution.cpp b/libjulia/optimiser/Substitution.cpp
new file mode 100644
index 00000000..a49f1f7a
--- /dev/null
+++ b/libjulia/optimiser/Substitution.cpp
@@ -0,0 +1,39 @@
+/*
+ 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/>.
+*/
+/**
+ * Specific AST copier that replaces certain identifiers with expressions.
+ */
+
+#include <libjulia/optimiser/Substitution.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::julia;
+
+Statement Substitution::translate(Statement const& _statement)
+{
+ if (_statement.type() == typeid(Identifier))
+ {
+ string const& name = boost::get<Identifier>(_statement).name;
+ if (m_substitutions.count(name))
+ // No recursive substitution
+ return ASTCopier().translate(*m_substitutions.at(name));
+ }
+ return ASTCopier::translate(_statement);
+}
diff --git a/libjulia/optimiser/Substitution.h b/libjulia/optimiser/Substitution.h
new file mode 100644
index 00000000..10bdf32e
--- /dev/null
+++ b/libjulia/optimiser/Substitution.h
@@ -0,0 +1,51 @@
+/*
+ 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/>.
+*/
+/**
+ * Specific AST copier that replaces certain identifiers with expressions.
+ */
+
+#pragma once
+
+#include <libjulia/optimiser/ASTCopier.h>
+
+#include <string>
+#include <map>
+#include <set>
+
+namespace dev
+{
+namespace julia
+{
+
+/**
+ * Specific AST copier that replaces certain identifiers with expressions.
+ * Only works on ASTs that are expressions.
+ */
+class Substitution: public ASTCopier
+{
+public:
+ Substitution(std::map<std::string, Statement const*> const& _substitutions):
+ m_substitutions(_substitutions)
+ {}
+ virtual Statement translate(Statement const& _statement) override;
+
+private:
+ std::map<std::string, Statement const*> const& m_substitutions;
+};
+
+}
+}
diff --git a/libsolc/CMakeLists.txt b/libsolc/CMakeLists.txt
new file mode 100644
index 00000000..e67583dd
--- /dev/null
+++ b/libsolc/CMakeLists.txt
@@ -0,0 +1,9 @@
+if (EMSCRIPTEN)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_license\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20")
+ add_executable(soljson libsolc.cpp)
+ target_link_libraries(soljson PRIVATE solidity)
+else()
+ add_library(libsolc libsolc.cpp)
+ set_target_properties(libsolc PROPERTIES OUTPUT_NAME solc)
+ target_link_libraries(libsolc PRIVATE solidity)
+endif()
diff --git a/solc/jsonCompiler.cpp b/libsolc/libsolc.cpp
index 7e797a62..3a6e1521 100644
--- a/solc/jsonCompiler.cpp
+++ b/libsolc/libsolc.cpp
@@ -20,7 +20,7 @@
* JSON interface for the solidity compiler to be used from Javascript.
*/
-#include <solc/jsonCompiler.h>
+#include <libsolc/libsolc.h>
#include <libdevcore/Common.h>
#include <libdevcore/JSON.h>
#include <libsolidity/interface/StandardCompiler.h>
@@ -128,6 +128,11 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback
input["settings"]["optimizer"]["enabled"] = _optimize;
input["settings"]["optimizer"]["runs"] = 200;
+ // Enable all SourceUnit-level outputs.
+ input["settings"]["outputSelection"]["*"][""][0] = "*";
+ // Enable all Contract-level outputs.
+ input["settings"]["outputSelection"]["*"]["*"][0] = "*";
+
StandardCompiler compiler(wrapReadCallback(_readCallback));
Json::Value ret = compiler.compile(input);
diff --git a/solc/jsonCompiler.h b/libsolc/libsolc.h
index c392ce93..c392ce93 100644
--- a/solc/jsonCompiler.h
+++ b/libsolc/libsolc.h
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp
index bc3b7cf1..4d546e68 100644
--- a/libsolidity/analysis/ConstantEvaluator.cpp
+++ b/libsolidity/analysis/ConstantEvaluator.cpp
@@ -74,3 +74,25 @@ void ConstantEvaluator::endVisit(Literal const& _literal)
if (!_literal.annotation().type)
m_errorReporter.fatalTypeError(_literal.location(), "Invalid literal value.");
}
+
+void ConstantEvaluator::endVisit(Identifier const& _identifier)
+{
+ VariableDeclaration const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration);
+ if (!variableDeclaration)
+ return;
+ if (!variableDeclaration->isConstant())
+ m_errorReporter.fatalTypeError(_identifier.location(), "Identifier must be declared constant.");
+
+ ASTPointer<Expression> value = variableDeclaration->value();
+ if (!value)
+ m_errorReporter.fatalTypeError(_identifier.location(), "Constant identifier declaration must have a constant value.");
+
+ if (!value->annotation().type)
+ {
+ if (m_depth > 32)
+ m_errorReporter.fatalTypeError(_identifier.location(), "Cyclic constant definition (or maximum recursion depth exhausted).");
+ ConstantEvaluator e(*value, m_errorReporter, m_depth + 1);
+ }
+
+ _identifier.annotation().type = value->annotation().type;
+}
diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h
index 90bceb5d..6725d610 100644
--- a/libsolidity/analysis/ConstantEvaluator.h
+++ b/libsolidity/analysis/ConstantEvaluator.h
@@ -38,8 +38,9 @@ class TypeChecker;
class ConstantEvaluator: private ASTConstVisitor
{
public:
- ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter):
- m_errorReporter(_errorReporter)
+ ConstantEvaluator(Expression const& _expr, ErrorReporter& _errorReporter, size_t _newDepth = 0):
+ m_errorReporter(_errorReporter),
+ m_depth(_newDepth)
{
_expr.accept(*this);
}
@@ -48,8 +49,11 @@ private:
virtual void endVisit(BinaryOperation const& _operation);
virtual void endVisit(UnaryOperation const& _operation);
virtual void endVisit(Literal const& _literal);
+ virtual void endVisit(Identifier const& _identifier);
ErrorReporter& m_errorReporter;
+ /// Current recursion depth.
+ size_t m_depth;
};
}
diff --git a/libsolidity/analysis/PostTypeChecker.h b/libsolidity/analysis/PostTypeChecker.h
index 91d2b0b9..bafc1ae6 100644
--- a/libsolidity/analysis/PostTypeChecker.h
+++ b/libsolidity/analysis/PostTypeChecker.h
@@ -38,7 +38,7 @@ class ErrorReporter;
class PostTypeChecker: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ /// @param _errorReporter provides the error logging functionality.
PostTypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool check(ASTNode const& _astRoot);
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h
index 24ed119f..124c4e7c 100644
--- a/libsolidity/analysis/StaticAnalyzer.h
+++ b/libsolidity/analysis/StaticAnalyzer.h
@@ -43,7 +43,7 @@ namespace solidity
class StaticAnalyzer: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during static analysis.
+ /// @param _errorReporter provides the error logging functionality.
explicit StaticAnalyzer(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// Performs static analysis on the given source unit and all of its sub-nodes.
diff --git a/libsolidity/analysis/SyntaxChecker.cpp b/libsolidity/analysis/SyntaxChecker.cpp
index 0ca4b86c..b6cc04da 100644
--- a/libsolidity/analysis/SyntaxChecker.cpp
+++ b/libsolidity/analysis/SyntaxChecker.cpp
@@ -54,7 +54,7 @@ void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
string(".") +
to_string(recommendedVersion.minor()) +
string(".") +
- to_string(recommendedVersion.patch());
+ to_string(recommendedVersion.patch()) +
string(";\"");
m_errorReporter.warning(_sourceUnit.location(), errorString);
diff --git a/libsolidity/analysis/SyntaxChecker.h b/libsolidity/analysis/SyntaxChecker.h
index 7fffbec0..d5d72f14 100644
--- a/libsolidity/analysis/SyntaxChecker.h
+++ b/libsolidity/analysis/SyntaxChecker.h
@@ -38,7 +38,7 @@ namespace solidity
class SyntaxChecker: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ /// @param _errorReporter provides the error logging functionality.
SyntaxChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
bool checkSyntax(ASTNode const& _astRoot);
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 746e762e..a578ad0f 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -570,6 +570,17 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions.");
+ if (
+ _function.visibility() > FunctionDefinition::Visibility::Internal &&
+ type(*var)->category() == Type::Category::Struct &&
+ !type(*var)->dataStoredIn(DataLocation::Storage) &&
+ !_function.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2)
+ )
+ m_errorReporter.typeError(
+ var->location(),
+ "Structs are only supported in the new experimental ABI encoder. "
+ "Use \"pragma experimental ABIEncoderV2;\" to enable the feature."
+ );
var->accept(*this);
}
@@ -1060,7 +1071,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
_statement.initialValue()->location(),
"Invalid rational " +
valueComponentType->toString() +
- " (absolute value too large or divison by zero)."
+ " (absolute value too large or division by zero)."
);
else
solAssert(false, "");
@@ -1122,7 +1133,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement)
var.annotation().type->toString() +
". Try converting to type " +
valueComponentType->mobileType()->toString() +
- " or use an explicit conversion."
+ " or use an explicit conversion."
);
else
m_errorReporter.typeError(
@@ -1320,7 +1331,7 @@ bool TypeChecker::visit(TupleExpression const& _tuple)
_tuple.annotation().isPure = isPure;
if (_tuple.isInlineArray())
{
- if (!inlineArrayType)
+ if (!inlineArrayType)
m_errorReporter.fatalTypeError(_tuple.location(), "Unable to deduce common type for array elements.");
_tuple.annotation().type = make_shared<ArrayType>(DataLocation::Memory, inlineArrayType, types.size());
}
@@ -2000,7 +2011,9 @@ void TypeChecker::endVisit(Literal const& _literal)
m_errorReporter.warning(
_literal.location(),
"This looks like an address but has an invalid checksum. "
- "If this is not used as an address, please prepend '00'."
+ "If this is not used as an address, please prepend '00'. " +
+ (!_literal.getChecksummedAddress().empty() ? "Correct checksummed address: '" + _literal.getChecksummedAddress() + "'. " : "") +
+ "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals"
);
}
if (!_literal.annotation().type)
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index abe6dac1..344b019d 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -42,7 +42,7 @@ class ErrorReporter;
class TypeChecker: private ASTConstVisitor
{
public:
- /// @param _errors the reference to the list of errors and warnings to add them found during type checking.
+ /// @param _errorReporter provides the error logging functionality.
TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
/// Performs type checking on the given contract and all of its sub-nodes.
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
index 7f28c7d2..6788cb05 100644
--- a/libsolidity/analysis/ViewPureChecker.cpp
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -40,16 +40,13 @@ public:
void operator()(assembly::Label const&) { }
void operator()(assembly::Instruction const& _instruction)
{
- if (eth::SemanticInformation::invalidInViewFunctions(_instruction.instruction))
- m_reportMutability(StateMutability::NonPayable, _instruction.location);
- else if (eth::SemanticInformation::invalidInPureFunctions(_instruction.instruction))
- m_reportMutability(StateMutability::View, _instruction.location);
+ checkInstruction(_instruction.location, _instruction.instruction);
}
void operator()(assembly::Literal const&) {}
void operator()(assembly::Identifier const&) {}
void operator()(assembly::FunctionalInstruction const& _instr)
{
- (*this)(_instr.instruction);
+ checkInstruction(_instr.location, _instr.instruction);
for (auto const& arg: _instr.arguments)
boost::apply_visitor(*this, arg);
}
@@ -72,6 +69,11 @@ public:
for (auto const& arg: _funCall.arguments)
boost::apply_visitor(*this, arg);
}
+ void operator()(assembly::If const& _if)
+ {
+ boost::apply_visitor(*this, *_if.condition);
+ (*this)(_if.body);
+ }
void operator()(assembly::Switch const& _switch)
{
boost::apply_visitor(*this, *_switch.expression);
@@ -97,6 +99,13 @@ public:
private:
std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
+ void checkInstruction(SourceLocation _location, solidity::Instruction _instruction)
+ {
+ if (eth::SemanticInformation::invalidInViewFunctions(_instruction))
+ m_reportMutability(StateMutability::NonPayable, _location);
+ else if (eth::SemanticInformation::invalidInPureFunctions(_instruction))
+ m_reportMutability(StateMutability::View, _location);
+ }
};
}
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 1048b610..8da6964e 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -583,3 +583,14 @@ bool Literal::passesAddressChecksum() const
solAssert(isHexNumber(), "Expected hex number");
return dev::passesAddressChecksum(value(), true);
}
+
+std::string Literal::getChecksummedAddress() const
+{
+ solAssert(isHexNumber(), "Expected hex number");
+ /// Pad literal to be a proper hex address.
+ string address = value().substr(2);
+ if (address.length() > 40)
+ return string();
+ address.insert(address.begin(), 40 - address.size(), '0');
+ return dev::getChecksummedAddress(address);
+}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 733e7c78..feffde64 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -1613,6 +1613,8 @@ public:
bool looksLikeAddress() const;
/// @returns true if it passes the address checksum test.
bool passesAddressChecksum() const;
+ /// @returns the checksummed version of an address (or empty string if not valid)
+ std::string getChecksummedAddress() const;
private:
Token::Value m_token;
diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp
index 81e6cc44..23c3cbe1 100644
--- a/libsolidity/ast/ASTPrinter.cpp
+++ b/libsolidity/ast/ASTPrinter.cpp
@@ -78,6 +78,13 @@ bool ASTPrinter::visit(InheritanceSpecifier const& _node)
return goDeeper();
}
+bool ASTPrinter::visit(UsingForDirective const& _node)
+{
+ writeLine("UsingForDirective");
+ printSourcePart(_node);
+ return goDeeper();
+}
+
bool ASTPrinter::visit(StructDefinition const& _node)
{
writeLine("StructDefinition \"" + _node.name() + "\"");
@@ -385,6 +392,11 @@ void ASTPrinter::endVisit(InheritanceSpecifier const&)
m_indentation--;
}
+void ASTPrinter::endVisit(UsingForDirective const&)
+{
+ m_indentation--;
+}
+
void ASTPrinter::endVisit(StructDefinition const&)
{
m_indentation--;
diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h
index d6897dfd..01e4f7fc 100644
--- a/libsolidity/ast/ASTPrinter.h
+++ b/libsolidity/ast/ASTPrinter.h
@@ -51,6 +51,7 @@ public:
bool visit(ImportDirective const& _node) override;
bool visit(ContractDefinition const& _node) override;
bool visit(InheritanceSpecifier const& _node) override;
+ bool visit(UsingForDirective const& _node) override;
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
@@ -94,6 +95,7 @@ public:
void endVisit(ImportDirective const&) override;
void endVisit(ContractDefinition const&) override;
void endVisit(InheritanceSpecifier const&) override;
+ void endVisit(UsingForDirective const&) override;
void endVisit(StructDefinition const&) override;
void endVisit(EnumDefinition const&) override;
void endVisit(EnumValue const&) override;
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index ee5f462b..21daac2c 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -2574,7 +2574,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
"selector",
make_shared<FixedBytesType>(4)
));
- if (m_kind != Kind::BareDelegateCall && m_kind != Kind::DelegateCall)
+ if (m_kind != Kind::BareDelegateCall)
{
if (isPayable())
members.push_back(MemberList::Member(
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index ce29975e..635279ab 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -707,10 +707,6 @@ public:
/// Returns the function type of the constructor modified to return an object of the contract's type.
FunctionTypePointer const& newExpressionType() const;
- /// @returns the identifier of the function with the given name or Invalid256 if such a name does
- /// not exist.
- u256 functionIdentifier(std::string const& _functionName) const;
-
/// @returns a list of all state variables (including inherited) of the contract and their
/// offsets in storage.
std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> stateVariables() const;
diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp
index 080be359..6648be06 100644
--- a/libsolidity/codegen/ABIFunctions.cpp
+++ b/libsolidity/codegen/ABIFunctions.cpp
@@ -22,9 +22,13 @@
#include <libsolidity/codegen/ABIFunctions.h>
+#include <libsolidity/ast/AST.h>
+#include <libsolidity/codegen/CompilerUtils.h>
+
#include <libdevcore/Whiskers.h>
-#include <libsolidity/ast/AST.h>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
@@ -99,6 +103,79 @@ string ABIFunctions::tupleEncoder(
});
}
+string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
+{
+ string functionName = string("abi_decode_tuple_");
+ for (auto const& t: _types)
+ functionName += t->identifier();
+ if (_fromMemory)
+ functionName += "_fromMemory";
+
+ solAssert(!_types.empty(), "");
+
+ return createFunction(functionName, [&]() {
+ TypePointers decodingTypes;
+ for (auto const& t: _types)
+ decodingTypes.emplace_back(t->decodingType());
+
+ Whiskers templ(R"(
+ function <functionName>(headStart, dataEnd) -> <valueReturnParams> {
+ switch slt(sub(dataEnd, headStart), <minimumSize>) case 1 { revert(0, 0) }
+ <decodeElements>
+ }
+ )");
+ templ("functionName", functionName);
+ templ("minimumSize", to_string(headSize(decodingTypes)));
+
+ string decodeElements;
+ vector<string> valueReturnParams;
+ size_t headPos = 0;
+ size_t stackPos = 0;
+ for (size_t i = 0; i < _types.size(); ++i)
+ {
+ solAssert(_types[i], "");
+ solAssert(decodingTypes[i], "");
+ size_t sizeOnStack = _types[i]->sizeOnStack();
+ solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), "");
+ solAssert(sizeOnStack > 0, "");
+ vector<string> valueNamesLocal;
+ for (size_t j = 0; j < sizeOnStack; j++)
+ {
+ valueNamesLocal.push_back("value" + to_string(stackPos));
+ valueReturnParams.push_back("value" + to_string(stackPos));
+ stackPos++;
+ }
+ bool dynamic = decodingTypes[i]->isDynamicallyEncoded();
+ Whiskers elementTempl(
+ dynamic ?
+ R"(
+ {
+ let offset := <load>(add(headStart, <pos>))
+ switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) }
+ <values> := <abiDecode>(add(headStart, offset), dataEnd)
+ }
+ )" :
+ R"(
+ {
+ let offset := <pos>
+ <values> := <abiDecode>(add(headStart, offset), dataEnd)
+ }
+ )"
+ );
+ elementTempl("load", _fromMemory ? "mload" : "calldataload");
+ elementTempl("values", boost::algorithm::join(valueNamesLocal, ", "));
+ elementTempl("pos", to_string(headPos));
+ elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true));
+ decodeElements += elementTempl.render();
+ headPos += dynamic ? 0x20 : decodingTypes[i]->calldataEncodedSize();
+ }
+ templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", "));
+ templ("decodeElements", decodeElements);
+
+ return templ.render();
+ });
+}
+
string ABIFunctions::requestedFunctions()
{
string result;
@@ -141,10 +218,9 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
solUnimplemented("Fixed point types not implemented.");
break;
case Type::Category::Array:
- solAssert(false, "Array cleanup requested.");
- break;
case Type::Category::Struct:
- solAssert(false, "Struct cleanup requested.");
+ solAssert(_type.dataStoredIn(DataLocation::Storage), "Cleanup requested for non-storage reference type.");
+ templ("body", "cleaned := value");
break;
case Type::Category::FixedBytes:
{
@@ -168,7 +244,7 @@ string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
{
size_t members = dynamic_cast<EnumType const&>(_type).numberOfMembers();
solAssert(members > 0, "empty enum should have caused a parser error.");
- Whiskers w("switch lt(value, <members>) case 0 { <failure> } cleaned := value");
+ Whiskers w("if iszero(lt(value, <members>)) { <failure> } cleaned := value");
w("members", to_string(members));
if (_revertOnFailure)
w("failure", "revert(0, 0)");
@@ -367,6 +443,24 @@ string ABIFunctions::combineExternalFunctionIdFunction()
});
}
+string ABIFunctions::splitExternalFunctionIdFunction()
+{
+ string functionName = "split_external_function_id";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(combined) -> addr, selector {
+ combined := <shr64>(combined)
+ selector := and(combined, 0xffffffff)
+ addr := <shr32>(combined)
+ }
+ )")
+ ("functionName", functionName)
+ ("shr32", shiftRightFunction(32, false))
+ ("shr64", shiftRightFunction(64, false))
+ .render();
+ });
+}
+
string ABIFunctions::abiEncodingFunction(
Type const& _from,
Type const& _to,
@@ -483,7 +577,7 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
_to.identifier() +
(_encodeAsLibraryTypes ? "_library" : "");
return createFunction(functionName, [&]() {
- solUnimplementedAssert(fromArrayType.isByteArray(), "");
+ 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,
// because the encoding is position-independent, but we have to check that.
Whiskers templ(R"(
@@ -754,7 +848,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
_to.identifier() +
(_encodeAsLibraryTypes ? "_library" : "");
- solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "");
+ solUnimplementedAssert(!_from.dataStoredIn(DataLocation::CallData), "Encoding struct from calldata is not yet supported.");
solAssert(&_from.structDefinition() == &_to.structDefinition(), "");
return createFunction(functionName, [&]() {
@@ -963,6 +1057,307 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
});
}
+string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack)
+{
+ // The decoding function has to perform bounds checks unless it decodes a value type.
+ // Conversely, bounds checks have to be performed before the decoding function
+ // of a value type is called.
+
+ TypePointer decodingType = _type.decodingType();
+ solAssert(decodingType, "");
+
+ if (auto arrayType = dynamic_cast<ArrayType const*>(decodingType.get()))
+ {
+ if (arrayType->dataStoredIn(DataLocation::CallData))
+ {
+ solAssert(!_fromMemory, "");
+ return abiDecodingFunctionCalldataArray(*arrayType);
+ }
+ else if (arrayType->isByteArray())
+ return abiDecodingFunctionByteArray(*arrayType, _fromMemory);
+ else
+ return abiDecodingFunctionArray(*arrayType, _fromMemory);
+ }
+ else if (auto const* structType = dynamic_cast<StructType const*>(decodingType.get()))
+ return abiDecodingFunctionStruct(*structType, _fromMemory);
+ else if (auto const* functionType = dynamic_cast<FunctionType const*>(decodingType.get()))
+ return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack);
+ else
+ return abiDecodingFunctionValueType(_type, _fromMemory);
+}
+
+string ABIFunctions::abiDecodingFunctionValueType(const Type& _type, bool _fromMemory)
+{
+ TypePointer decodingType = _type.decodingType();
+ solAssert(decodingType, "");
+ solAssert(decodingType->sizeOnStack() == 1, "");
+ solAssert(decodingType->isValueType(), "");
+ solAssert(decodingType->calldataEncodedSize() == 32, "");
+ solAssert(!decodingType->isDynamicallyEncoded(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ function <functionName>(offset, end) -> value {
+ value := <cleanup>(<load>(offset))
+ }
+ )");
+ templ("functionName", functionName);
+ templ("load", _fromMemory ? "mload" : "calldataload");
+ // Cleanup itself should use the type and not decodingType, because e.g.
+ // the decoding type of an enum is a plain int.
+ templ("cleanup", cleanupFunction(_type, true));
+ return templ.render();
+ });
+
+}
+
+string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ solAssert(!_type.isByteArray(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ solAssert(!_type.dataStoredIn(DataLocation::Storage), "");
+
+ return createFunction(functionName, [&]() {
+ string load = _fromMemory ? "mload" : "calldataload";
+ bool dynamicBase = _type.baseType()->isDynamicallyEncoded();
+ Whiskers templ(
+ R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> array {
+ switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) }
+ let length := <retrieveLength>
+ array := <allocate>(<allocationSize>(length))
+ let dst := array
+ <storeLength> // might update offset and dst
+ let src := offset
+ <staticBoundsCheck>
+ for { let i := 0 } lt(i, length) { i := add(i, 1) }
+ {
+ let elementPos := <retrieveElementPos>
+ mstore(dst, <decodingFun>(elementPos, end))
+ dst := add(dst, 0x20)
+ src := add(src, <baseEncodedSize>)
+ }
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("readableTypeName", _type.toString(true));
+ templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
+ templ("allocate", allocationFunction());
+ templ("allocationSize", arrayAllocationSizeFunction(_type));
+ if (_type.isDynamicallySized())
+ templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
+ else
+ templ("storeLength", "");
+ if (dynamicBase)
+ {
+ templ("staticBoundsCheck", "");
+ templ("retrieveElementPos", "add(offset, " + load + "(src))");
+ templ("baseEncodedSize", "0x20");
+ }
+ else
+ {
+ string baseEncodedSize = toCompactHexWithPrefix(_type.baseType()->calldataEncodedSize());
+ templ("staticBoundsCheck", "switch gt(add(src, mul(length, " + baseEncodedSize + ")), end) case 1 { revert(0, 0) }");
+ templ("retrieveElementPos", "src");
+ templ("baseEncodedSize", baseEncodedSize);
+ }
+ templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type)
+{
+ // This does not work with arrays of complex types - the array access
+ // is not yet implemented in Solidity.
+ solAssert(_type.dataStoredIn(DataLocation::CallData), "");
+ if (!_type.isDynamicallySized())
+ solAssert(_type.length() < u256("0xffffffffffffffff"), "");
+ solAssert(!_type.baseType()->isDynamicallyEncoded(), "");
+ solAssert(_type.baseType()->calldataEncodedSize() < u256("0xffffffffffffffff"), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier();
+ return createFunction(functionName, [&]() {
+ string templ;
+ if (_type.isDynamicallySized())
+ templ = R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> arrayPos, length {
+ switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) }
+ length := calldataload(offset)
+ switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) }
+ arrayPos := add(offset, 0x20)
+ switch gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) case 1 { revert(0, 0) }
+ }
+ )";
+ else
+ templ = R"(
+ // <readableTypeName>
+ function <functionName>(offset, end) -> arrayPos {
+ arrayPos := offset
+ switch gt(add(arrayPos, mul(<length>, <baseEncodedSize>)), end) case 1 { revert(0, 0) }
+ }
+ )";
+ Whiskers w{templ};
+ w("functionName", functionName);
+ w("readableTypeName", _type.toString(true));
+ w("baseEncodedSize", toCompactHexWithPrefix(_type.isByteArray() ? 1 : _type.baseType()->calldataEncodedSize()));
+ w("length", _type.isDynamicallyEncoded() ? "length" : toCompactHexWithPrefix(_type.length()));
+ return w.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ solAssert(_type.isByteArray(), "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ return createFunction(functionName, [&]() {
+ Whiskers templ(
+ R"(
+ function <functionName>(offset, end) -> array {
+ switch slt(add(offset, 0x1f), end) case 0 { revert(0, 0) }
+ let length := <load>(offset)
+ array := <allocate>(<allocationSize>(length))
+ mstore(array, length)
+ let src := add(offset, 0x20)
+ let dst := add(array, 0x20)
+ switch gt(add(src, length), end) case 1 { revert(0, 0) }
+ <copyToMemFun>(src, dst, length)
+ }
+ )"
+ );
+ templ("functionName", functionName);
+ templ("load", _fromMemory ? "mload" : "calldataload");
+ templ("allocate", allocationFunction());
+ templ("allocationSize", arrayAllocationSizeFunction(_type));
+ templ("copyToMemFun", copyToMemoryFunction(!_fromMemory));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory)
+{
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "");
+
+ solUnimplementedAssert(!_type.dataStoredIn(DataLocation::CallData), "");
+
+ return createFunction(functionName, [&]() {
+ Whiskers templ(R"(
+ // <readableTypeName>
+ function <functionName>(headStart, end) -> value {
+ switch slt(sub(end, headStart), <minimumSize>) case 1 { revert(0, 0) }
+ value := <allocate>(<memorySize>)
+ <#members>
+ {
+ // <memberName>
+ <decode>
+ }
+ </members>
+ }
+ )");
+ templ("functionName", functionName);
+ templ("readableTypeName", _type.toString(true));
+ templ("allocate", allocationFunction());
+ solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
+ templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
+ size_t headPos = 0;
+ vector<map<string, string>> members;
+ for (auto const& member: _type.members(nullptr))
+ {
+ solAssert(member.type, "");
+ solAssert(member.type->canLiveOutsideStorage(), "");
+ auto decodingType = member.type->decodingType();
+ solAssert(decodingType, "");
+ bool dynamic = decodingType->isDynamicallyEncoded();
+ Whiskers memberTempl(
+ dynamic ?
+ R"(
+ let offset := <load>(add(headStart, <pos>))
+ switch gt(offset, 0xffffffffffffffff) case 1 { revert(0, 0) }
+ mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
+ )" :
+ R"(
+ let offset := <pos>
+ mstore(add(value, <memoryOffset>), <abiDecode>(add(headStart, offset), end))
+ )"
+ );
+ memberTempl("load", _fromMemory ? "mload" : "calldataload");
+ memberTempl("pos", to_string(headPos));
+ memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name)));
+ memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false));
+
+ members.push_back({});
+ members.back()["decode"] = memberTempl.render();
+ members.back()["memberName"] = member.name;
+ headPos += dynamic ? 0x20 : decodingType->calldataEncodedSize();
+ }
+ templ("members", members);
+ templ("minimumSize", toCompactHexWithPrefix(headPos));
+ return templ.render();
+ });
+}
+
+string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack)
+{
+ solAssert(_type.kind() == FunctionType::Kind::External, "");
+
+ string functionName =
+ "abi_decode_" +
+ _type.identifier() +
+ (_fromMemory ? "_fromMemory" : "") +
+ (_forUseOnStack ? "_onStack" : "");
+
+ return createFunction(functionName, [&]() {
+ if (_forUseOnStack)
+ {
+ return Whiskers(R"(
+ function <functionName>(offset, end) -> addr, function_selector {
+ addr, function_selector := <splitExtFun>(<load>(offset))
+ }
+ )")
+ ("functionName", functionName)
+ ("load", _fromMemory ? "mload" : "calldataload")
+ ("splitExtFun", splitExternalFunctionIdFunction())
+ .render();
+ }
+ else
+ {
+ return Whiskers(R"(
+ function <functionName>(offset, end) -> fun {
+ fun := <cleanExtFun>(<load>(offset))
+ }
+ )")
+ ("functionName", functionName)
+ ("load", _fromMemory ? "mload" : "calldataload")
+ ("cleanExtFun", cleanupCombinedExternalFunctionIdFunction())
+ .render();
+ }
+ });
+}
+
string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
{
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
@@ -988,8 +1383,8 @@ string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
{
mstore(add(dst, i), mload(add(src, i)))
}
- switch eq(i, length)
- case 0 {
+ if gt(i, length)
+ {
// clear end
mstore(add(dst, length), 0)
}
@@ -1098,6 +1493,33 @@ string ABIFunctions::arrayLengthFunction(ArrayType const& _type)
});
}
+string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
+{
+ solAssert(_type.dataStoredIn(DataLocation::Memory), "");
+ string functionName = "array_allocation_size_" + _type.identifier();
+ return createFunction(functionName, [&]() {
+ Whiskers w(R"(
+ function <functionName>(length) -> size {
+ // Make sure we can allocate memory without overflow
+ switch gt(length, 0xffffffffffffffff) case 1 { revert(0, 0) }
+ size := <allocationSize>
+ <addLengthSlot>
+ }
+ )");
+ w("functionName", functionName);
+ if (_type.isByteArray())
+ // Round up
+ w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
+ else
+ w("allocationSize", "mul(length, 0x20)");
+ if (_type.isDynamicallySized())
+ w("addLengthSlot", "size := add(size, 0x20)");
+ else
+ w("addLengthSlot", "");
+ return w.render();
+ });
+}
+
string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type)
{
string functionName = "array_dataslot_" + _type.identifier();
@@ -1189,6 +1611,25 @@ string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
});
}
+string ABIFunctions::allocationFunction()
+{
+ string functionName = "allocateMemory";
+ return createFunction(functionName, [&]() {
+ return Whiskers(R"(
+ function <functionName>(size) -> memPtr {
+ memPtr := mload(<freeMemoryPointer>)
+ let newFreePtr := add(memPtr, size)
+ // protect against overflow
+ switch or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) case 1 { revert(0, 0) }
+ mstore(<freeMemoryPointer>, newFreePtr)
+ }
+ )")
+ ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
+ ("functionName", functionName)
+ .render();
+ });
+}
+
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
{
if (!m_requestedFunctions.count(_name))
diff --git a/libsolidity/codegen/ABIFunctions.h b/libsolidity/codegen/ABIFunctions.h
index e61f68bc..2b582e84 100644
--- a/libsolidity/codegen/ABIFunctions.h
+++ b/libsolidity/codegen/ABIFunctions.h
@@ -66,6 +66,16 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// @returns name of an assembly function to ABI-decode values of @a _types
+ /// into memory. If @a _fromMemory is true, decodes from memory instead of
+ /// from calldata.
+ /// Can allocate memory.
+ /// Inputs: <source_offset> <source_end> (layout reversed on stack)
+ /// Outputs: <value0> <value1> ... <valuen>
+ /// The values represent stack slots. If a type occupies more or less than one
+ /// stack slot, it takes exactly that number of values.
+ std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false);
+
/// @returns concatenation of all generated functions.
std::string requestedFunctions();
@@ -87,6 +97,10 @@ private:
/// for use in the ABI.
std::string combineExternalFunctionIdFunction();
+ /// @returns a function that splits the address and selector from a single value
+ /// for use in the ABI.
+ std::string splitExternalFunctionIdFunction();
+
/// @returns the name of the ABI encoding function with the given type
/// and queues the generation of the function to the requested functions.
/// @param _fromStack if false, the input value was just loaded from storage
@@ -146,6 +160,31 @@ private:
bool _fromStack
);
+ /// @returns the name of the ABI decoding function for the given type
+ /// and queues the generation of the function to the requested functions.
+ /// The caller has to ensure that no out of bounds access (at least to the static
+ /// part) can happen inside this function.
+ /// @param _fromMemory if decoding from memory instead of from calldata
+ /// @param _forUseOnStack if the decoded value is stored on stack or in memory.
+ std::string abiDecodingFunction(
+ Type const& _Type,
+ bool _fromMemory,
+ bool _forUseOnStack
+ );
+
+ /// Part of @a abiDecodingFunction for value types.
+ std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for "regular" array types.
+ std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for calldata array types.
+ std::string abiDecodingFunctionCalldataArray(ArrayType const& _type);
+ /// Part of @a abiDecodingFunction for byte array types.
+ std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for struct types.
+ std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory);
+ /// Part of @a abiDecodingFunction for array types.
+ std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
+
/// @returns a function that copies raw bytes of dynamic length from calldata
/// or memory to memory.
/// Pads with zeros and might write more than exactly length.
@@ -158,6 +197,10 @@ private:
std::string roundUpFunction();
std::string arrayLengthFunction(ArrayType const& _type);
+ /// @returns the name of a function that computes the number of bytes required
+ /// to store an array in memory given its length (internally encoded, not ABI encoded).
+ /// The function reverts for too large lengthes.
+ std::string arrayAllocationSizeFunction(ArrayType const& _type);
/// @returns the name of a function that converts a storage slot number
/// or a memory pointer to the slot number / memory pointer for the data position of an array
/// which is stored in that slot / memory area.
@@ -166,6 +209,12 @@ private:
/// Only works for memory arrays and storage arrays that store one item per slot.
std::string nextArrayElementFunction(ArrayType const& _type);
+ /// @returns the name of a function that allocates memory.
+ /// Modifies the "free memory pointer"
+ /// Arguments: size
+ /// Return value: pointer
+ std::string allocationFunction();
+
/// Helper function that uses @a _creator to create a function and add it to
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
/// cases.
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index f9b181ae..533aca5c 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -121,7 +121,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
{
if (auto ref = dynamic_cast<ReferenceType const*>(&_type))
{
- solUnimplementedAssert(ref->location() == DataLocation::Memory, "");
+ solUnimplementedAssert(ref->location() == DataLocation::Memory, "Only in-memory reference type can be stored.");
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
}
else if (auto str = dynamic_cast<StringLiteralType const*>(&_type))
@@ -319,6 +319,23 @@ void CompilerUtils::abiEncodeV2(
m_context << ret.tag();
}
+void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory)
+{
+ // stack: <source_offset>
+ auto ret = m_context.pushNewTag();
+ m_context << Instruction::SWAP1;
+ if (_fromMemory)
+ // TODO pass correct size for the memory case
+ m_context << (u256(1) << 63);
+ else
+ m_context << Instruction::CALLDATASIZE;
+ m_context << Instruction::SWAP1;
+ string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory);
+ m_context.appendJumpTo(m_context.namedTag(decoderName));
+ m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3);
+ m_context << ret.tag();
+}
+
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{
auto repeat = m_context.newTag();
diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h
index ad3989ad..3cde281b 100644
--- a/libsolidity/codegen/CompilerUtils.h
+++ b/libsolidity/codegen/CompilerUtils.h
@@ -146,6 +146,13 @@ public:
bool _encodeAsLibraryTypes = false
);
+ /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true,
+ /// the data is taken from memory instead of from calldata.
+ /// Can allocate memory.
+ /// Stack pre: <source_offset>
+ /// Stack post: <value0> <value1> ... <valuen>
+ void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false);
+
/// Zero-initialises (the data part of) an already allocated memory array.
/// Length has to be nonzero!
/// Stack pre: <length> <memptr>
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 74565ae4..a81ba518 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -322,6 +322,15 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter
{
// We do not check the calldata size, everything is zero-padded
+ if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2))
+ {
+ // Use the new JULIA-based decoding function
+ auto stackHeightBefore = m_context.stackHeight();
+ CompilerUtils(m_context).abiDecodeV2(_typeParameters, _fromMemory);
+ solAssert(m_context.stackHeight() - stackHeightBefore == CompilerUtils(m_context).sizeOnStack(_typeParameters) - 1, "");
+ return;
+ }
+
//@todo this does not yet support nested dynamic arrays
// Retain the offset pointer as base_offset, the point from which the data offsets are computed.
diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp
index 2d2f05ec..a22e35d6 100644
--- a/libsolidity/formal/SMTChecker.cpp
+++ b/libsolidity/formal/SMTChecker.cpp
@@ -23,9 +23,12 @@
#include <libsolidity/formal/SMTLib2Interface.h>
#endif
+#include <libsolidity/formal/VariableUsage.h>
+
#include <libsolidity/interface/ErrorReporter.h>
#include <boost/range/adaptor/map.hpp>
+#include <boost/algorithm/string/replace.hpp>
using namespace std;
using namespace dev;
@@ -44,28 +47,15 @@ SMTChecker::SMTChecker(ErrorReporter& _errorReporter, ReadCallback::Callback con
void SMTChecker::analyze(SourceUnit const& _source)
{
+ m_variableUsage = make_shared<VariableUsage>(_source);
if (_source.annotation().experimentalFeatures.count(ExperimentalFeature::SMTChecker))
- {
- m_interface->reset();
- m_currentSequenceCounter.clear();
- m_nextFreeSequenceCounter.clear();
_source.accept(*this);
- }
}
void SMTChecker::endVisit(VariableDeclaration const& _varDecl)
{
- if (_varDecl.value())
- {
- m_errorReporter.warning(
- _varDecl.location(),
- "Assertion checker does not yet support this."
- );
- }
- else if (_varDecl.isLocalOrReturn())
- createVariable(_varDecl, true);
- else if (_varDecl.isCallableParameter())
- createVariable(_varDecl, false);
+ if (_varDecl.isLocalVariable() && _varDecl.type()->isValueType() &&_varDecl.value())
+ assignment(_varDecl, *_varDecl.value(), _varDecl.location());
}
bool SMTChecker::visit(FunctionDefinition const& _function)
@@ -75,20 +65,22 @@ bool SMTChecker::visit(FunctionDefinition const& _function)
_function.location(),
"Assertion checker does not yet support constructors and functions with modifiers."
);
- // TODO actually we probably also have to reset all local variables and similar things.
m_currentFunction = &_function;
- m_interface->push();
+ // We only handle local variables, so we clear at the beginning of the function.
+ // If we add storage variables, those should be cleared differently.
+ m_interface->reset();
+ m_currentSequenceCounter.clear();
+ m_nextFreeSequenceCounter.clear();
+ m_conditionalExecutionHappened = false;
+ initializeLocalVariables(_function);
return true;
}
void SMTChecker::endVisit(FunctionDefinition const&)
{
// TOOD we could check for "reachability", i.e. satisfiability here.
- // We only handle local variables, so we clear everything.
+ // We only handle local variables, so we clear at the beginning of the function.
// If we add storage variables, those should be cleared differently.
- m_currentSequenceCounter.clear();
- m_nextFreeSequenceCounter.clear();
- m_interface->pop();
m_currentFunction = nullptr;
}
@@ -96,57 +88,84 @@ bool SMTChecker::visit(IfStatement const& _node)
{
_node.condition().accept(*this);
- // TODO Check if condition is always true
-
- auto countersAtStart = m_currentSequenceCounter;
- m_interface->push();
- m_interface->addAssertion(expr(_node.condition()));
- _node.trueStatement().accept(*this);
- auto countersAtEndOfTrue = m_currentSequenceCounter;
- m_interface->pop();
+ checkBooleanNotConstant(_node.condition(), "Condition is always $VALUE.");
- decltype(m_currentSequenceCounter) countersAtEndOfFalse;
+ visitBranch(_node.trueStatement(), expr(_node.condition()));
+ vector<Declaration const*> touchedVariables = m_variableUsage->touchedVariables(_node.trueStatement());
if (_node.falseStatement())
{
- m_currentSequenceCounter = countersAtStart;
- m_interface->push();
- m_interface->addAssertion(!expr(_node.condition()));
- _node.falseStatement()->accept(*this);
- countersAtEndOfFalse = m_currentSequenceCounter;
- m_interface->pop();
+ visitBranch(*_node.falseStatement(), !expr(_node.condition()));
+ touchedVariables += m_variableUsage->touchedVariables(*_node.falseStatement());
}
- else
- countersAtEndOfFalse = countersAtStart;
- // Reset all values that have been touched.
+ resetVariables(touchedVariables);
- // TODO this should use a previously generated side-effect structure
+ return false;
+}
- solAssert(countersAtEndOfFalse.size() == countersAtEndOfTrue.size(), "");
- for (auto const& declCounter: countersAtEndOfTrue)
+bool SMTChecker::visit(WhileStatement const& _node)
+{
+ auto touchedVariables = m_variableUsage->touchedVariables(_node);
+ resetVariables(touchedVariables);
+ if (_node.isDoWhile())
{
- solAssert(countersAtEndOfFalse.count(declCounter.first), "");
- auto decl = declCounter.first;
- int trueCounter = countersAtEndOfTrue.at(decl);
- int falseCounter = countersAtEndOfFalse.at(decl);
- if (trueCounter == falseCounter)
- continue; // Was not modified
- newValue(*decl);
- setValue(*decl, 0);
+ visitBranch(_node.body());
+ // TODO the assertions generated in the body should still be active in the condition
+ _node.condition().accept(*this);
+ checkBooleanNotConstant(_node.condition(), "Do-while loop condition is always $VALUE.");
}
+ else
+ {
+ _node.condition().accept(*this);
+ checkBooleanNotConstant(_node.condition(), "While loop condition is always $VALUE.");
+
+ visitBranch(_node.body(), expr(_node.condition()));
+ }
+ resetVariables(touchedVariables);
+
return false;
}
-bool SMTChecker::visit(WhileStatement const& _node)
+bool SMTChecker::visit(ForStatement const& _node)
{
- _node.condition().accept(*this);
+ if (_node.initializationExpression())
+ _node.initializationExpression()->accept(*this);
+
+ // Do not reset the init expression part.
+ auto touchedVariables =
+ m_variableUsage->touchedVariables(_node.body());
+ if (_node.condition())
+ touchedVariables += m_variableUsage->touchedVariables(*_node.condition());
+ if (_node.loopExpression())
+ touchedVariables += m_variableUsage->touchedVariables(*_node.loopExpression());
+ // Remove duplicates
+ std::sort(touchedVariables.begin(), touchedVariables.end());
+ touchedVariables.erase(std::unique(touchedVariables.begin(), touchedVariables.end()), touchedVariables.end());
+
+ resetVariables(touchedVariables);
+
+ if (_node.condition())
+ {
+ _node.condition()->accept(*this);
+ checkBooleanNotConstant(*_node.condition(), "For loop condition is always $VALUE.");
+ }
+
+ VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
+ m_interface->push();
+ if (_node.condition())
+ m_interface->addAssertion(expr(*_node.condition()));
+ _node.body().accept(*this);
+ if (_node.loopExpression())
+ _node.loopExpression()->accept(*this);
- //m_interface->push();
- //m_interface->addAssertion(expr(_node.condition()));
- // TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions
+ m_interface->pop();
- // TODO combine similar to if
- return true;
+ m_conditionalExecutionHappened = true;
+ m_currentSequenceCounter = sequenceCountersStart;
+
+ resetVariables(touchedVariables);
+
+ return false;
}
void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
@@ -159,9 +178,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl)
else if (knownVariable(*_varDecl.declarations()[0]))
{
if (_varDecl.initialValue())
- // TODO more checks?
- // TODO add restrictions about type (might be assignment from smaller type)
- m_interface->addAssertion(newValue(*_varDecl.declarations()[0]) == expr(*_varDecl.initialValue()));
+ assignment(*_varDecl.declarations()[0], *_varDecl.initialValue(), _varDecl.location());
}
else
m_errorReporter.warning(
@@ -190,9 +207,10 @@ void SMTChecker::endVisit(Assignment const& _assignment)
{
Declaration const* decl = identifier->annotation().referencedDeclaration;
if (knownVariable(*decl))
- // TODO more checks?
- // TODO add restrictions about type (might be assignment from smaller type)
- m_interface->addAssertion(newValue(*decl) == expr(_assignment.rightHandSide()));
+ {
+ assignment(*decl, _assignment.rightHandSide(), _assignment.location());
+ defineExpr(_assignment, expr(_assignment.rightHandSide()));
+ }
else
m_errorReporter.warning(
_assignment.location(),
@@ -214,7 +232,81 @@ void SMTChecker::endVisit(TupleExpression const& _tuple)
"Assertion checker does not yet implement tules and inline arrays."
);
else
- m_interface->addAssertion(expr(_tuple) == expr(*_tuple.components()[0]));
+ defineExpr(_tuple, expr(*_tuple.components()[0]));
+}
+
+void SMTChecker::checkUnderOverflow(smt::Expression _value, IntegerType const& _type, SourceLocation const& _location)
+{
+ checkCondition(
+ _value < minValue(_type),
+ _location,
+ "Underflow (resulting value less than " + formatNumber(_type.minValue()) + ")",
+ "value",
+ &_value
+ );
+ checkCondition(
+ _value > maxValue(_type),
+ _location,
+ "Overflow (resulting value larger than " + formatNumber(_type.maxValue()) + ")",
+ "value",
+ &_value
+ );
+}
+
+void SMTChecker::endVisit(UnaryOperation const& _op)
+{
+ switch (_op.getOperator())
+ {
+ case Token::Not: // !
+ {
+ solAssert(_op.annotation().type->category() == Type::Category::Bool, "");
+ defineExpr(_op, !expr(_op.subExpression()));
+ break;
+ }
+ case Token::Inc: // ++ (pre- or postfix)
+ case Token::Dec: // -- (pre- or postfix)
+ {
+ solAssert(_op.annotation().type->category() == Type::Category::Integer, "");
+ solAssert(_op.subExpression().annotation().lValueRequested, "");
+ if (Identifier const* identifier = dynamic_cast<Identifier const*>(&_op.subExpression()))
+ {
+ Declaration const* decl = identifier->annotation().referencedDeclaration;
+ if (knownVariable(*decl))
+ {
+ auto innerValue = currentValue(*decl);
+ auto newValue = _op.getOperator() == Token::Inc ? innerValue + 1 : innerValue - 1;
+ assignment(*decl, newValue, _op.location());
+ defineExpr(_op, _op.isPrefixOperation() ? newValue : innerValue);
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement such assignments."
+ );
+ }
+ else
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement such increments / decrements."
+ );
+ break;
+ }
+ case Token::Add: // +
+ defineExpr(_op, expr(_op.subExpression()));
+ break;
+ case Token::Sub: // -
+ {
+ defineExpr(_op, 0 - expr(_op.subExpression()));
+ if (auto intType = dynamic_cast<IntegerType const*>(_op.annotation().type.get()))
+ checkUnderOverflow(expr(_op), *intType, _op.location());
+ break;
+ }
+ default:
+ m_errorReporter.warning(
+ _op.location(),
+ "Assertion checker does not yet implement this operator."
+ );
+ }
}
void SMTChecker::endVisit(BinaryOperation const& _op)
@@ -258,10 +350,8 @@ void SMTChecker::endVisit(FunctionCall const& _funCall)
{
solAssert(args.size() == 1, "");
solAssert(args[0]->annotation().type->category() == Type::Category::Bool, "");
+ checkBooleanNotConstant(*args[0], "Condition is always $VALUE.");
m_interface->addAssertion(expr(*args[0]));
- checkCondition(!(expr(*args[0])), _funCall.location(), "Unreachable code");
- // TODO is there something meaningful we can check here?
- // We can check whether the condition is always fulfilled or never fulfilled.
}
}
@@ -269,23 +359,17 @@ void SMTChecker::endVisit(Identifier const& _identifier)
{
Declaration const* decl = _identifier.annotation().referencedDeclaration;
solAssert(decl, "");
- if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
+ if (_identifier.annotation().lValueRequested)
{
- m_interface->addAssertion(expr(_identifier) == currentValue(*decl));
- return;
+ // Will be translated as part of the node that requested the lvalue.
}
+ else if (dynamic_cast<IntegerType const*>(_identifier.annotation().type.get()))
+ defineExpr(_identifier, currentValue(*decl));
else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get()))
{
if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require)
return;
- // TODO for others, clear our knowledge about storage and memory
}
- m_errorReporter.warning(
- _identifier.location(),
- "Assertion checker does not yet support the type of this expression (" +
- _identifier.annotation().type->toString() +
- ")."
- );
}
void SMTChecker::endVisit(Literal const& _literal)
@@ -296,12 +380,14 @@ void SMTChecker::endVisit(Literal const& _literal)
if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type))
solAssert(!rational->isFractional(), "");
- m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal)));
+ defineExpr(_literal, smt::Expression(type.literalValue(&_literal)));
}
+ else if (type.category() == Type::Category::Bool)
+ defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false));
else
m_errorReporter.warning(
_literal.location(),
- "Assertion checker does not yet support the type of this expression (" +
+ "Assertion checker does not yet support the type of this literal (" +
_literal.annotation().type->toString() +
")."
);
@@ -314,36 +400,30 @@ void SMTChecker::arithmeticOperation(BinaryOperation const& _op)
case Token::Add:
case Token::Sub:
case Token::Mul:
+ case Token::Div:
{
solAssert(_op.annotation().commonType, "");
solAssert(_op.annotation().commonType->category() == Type::Category::Integer, "");
+ auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
smt::Expression left(expr(_op.leftExpression()));
smt::Expression right(expr(_op.rightExpression()));
Token::Value op = _op.getOperator();
smt::Expression value(
op == Token::Add ? left + right :
op == Token::Sub ? left - right :
+ op == Token::Div ? division(left, right, intType) :
/*op == Token::Mul*/ left * right
);
- // Overflow check
- auto const& intType = dynamic_cast<IntegerType const&>(*_op.annotation().commonType);
- checkCondition(
- value < minValue(intType),
- _op.location(),
- "Underflow (resulting value less than " + formatNumber(intType.minValue()) + ")",
- "value",
- &value
- );
- checkCondition(
- value > maxValue(intType),
- _op.location(),
- "Overflow (resulting value larger than " + formatNumber(intType.maxValue()) + ")",
- "value",
- &value
- );
+ if (_op.getOperator() == Token::Div)
+ {
+ checkCondition(right == 0, _op.location(), "Division by zero", "value", &right);
+ m_interface->addAssertion(right != 0);
+ }
- m_interface->addAssertion(expr(_op) == value);
+ checkUnderOverflow(value, intType, _op.location());
+
+ defineExpr(_op, value);
break;
}
default:
@@ -371,7 +451,7 @@ void SMTChecker::compareOperation(BinaryOperation const& _op)
/*op == Token::GreaterThanOrEqual*/ (left >= right)
);
// TODO: check that other values for op are not possible.
- m_interface->addAssertion(expr(_op) == value);
+ defineExpr(_op, value);
}
else
m_errorReporter.warning(
@@ -386,16 +466,62 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op)
solAssert(_op.annotation().commonType, "");
if (_op.annotation().commonType->category() == Type::Category::Bool)
{
+ // @TODO check that both of them are not constant
if (_op.getOperator() == Token::And)
- m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) && expr(_op.rightExpression()));
+ defineExpr(_op, expr(_op.leftExpression()) && expr(_op.rightExpression()));
else
- m_interface->addAssertion(expr(_op) == expr(_op.leftExpression()) || expr(_op.rightExpression()));
+ defineExpr(_op, expr(_op.leftExpression()) || expr(_op.rightExpression()));
}
else
m_errorReporter.warning(
_op.location(),
"Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations"
- );
+ );
+}
+
+smt::Expression SMTChecker::division(smt::Expression _left, smt::Expression _right, IntegerType const& _type)
+{
+ // Signed division in SMTLIB2 rounds differently for negative division.
+ if (_type.isSigned())
+ return (smt::Expression::ite(
+ _left >= 0,
+ smt::Expression::ite(_right >= 0, _left / _right, 0 - (_left / (0 - _right))),
+ smt::Expression::ite(_right >= 0, 0 - ((0 - _left) / _right), (0 - _left) / (0 - _right))
+ ));
+ else
+ return _left / _right;
+}
+
+void SMTChecker::assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location)
+{
+ assignment(_variable, expr(_value), _location);
+}
+
+void SMTChecker::assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location)
+{
+ TypePointer type = _variable.type();
+ if (auto const* intType = dynamic_cast<IntegerType const*>(type.get()))
+ checkUnderOverflow(_value, *intType, _location);
+ m_interface->addAssertion(newValue(_variable) == _value);
+}
+
+void SMTChecker::visitBranch(Statement const& _statement, smt::Expression _condition)
+{
+ visitBranch(_statement, &_condition);
+}
+
+void SMTChecker::visitBranch(Statement const& _statement, smt::Expression const* _condition)
+{
+ VariableSequenceCounters sequenceCountersStart = m_currentSequenceCounter;
+
+ m_interface->push();
+ if (_condition)
+ m_interface->addAssertion(*_condition);
+ _statement.accept(*this);
+ m_interface->pop();
+
+ m_conditionalExecutionHappened = true;
+ m_currentSequenceCounter = sequenceCountersStart;
}
void SMTChecker::checkCondition(
@@ -433,19 +559,13 @@ void SMTChecker::checkCondition(
}
smt::CheckResult result;
vector<string> values;
- try
- {
- tie(result, values) = m_interface->check(expressionsToEvaluate);
- }
- catch (smt::SolverError const& _e)
- {
- string description("Error querying SMT solver");
- if (_e.comment())
- description += ": " + *_e.comment();
- m_errorReporter.warning(_location, description);
- return;
- }
+ tie(result, values) = checkSatisifableAndGenerateModel(expressionsToEvaluate);
+ string conditionalComment;
+ if (m_conditionalExecutionHappened)
+ conditionalComment =
+ "\nNote that some information is erased after conditional execution of parts of the code.\n"
+ "You can re-introduce information using require().";
switch (result)
{
case smt::CheckResult::SATISFIABLE:
@@ -457,27 +577,17 @@ void SMTChecker::checkCondition(
message << " for:\n";
solAssert(values.size() == expressionNames.size(), "");
for (size_t i = 0; i < values.size(); ++i)
- {
- string formattedValue = values.at(i);
- try
- {
- // Parse and re-format nicely
- formattedValue = formatNumber(bigint(formattedValue));
- }
- catch (...) { }
-
- message << " " << expressionNames.at(i) << " = " << formattedValue << "\n";
- }
+ message << " " << expressionNames.at(i) << " = " << values.at(i) << "\n";
}
else
message << ".";
- m_errorReporter.warning(_location, message.str());
+ m_errorReporter.warning(_location, message.str() + conditionalComment);
break;
}
case smt::CheckResult::UNSATISFIABLE:
break;
case smt::CheckResult::UNKNOWN:
- m_errorReporter.warning(_location, _description + " might happen here.");
+ m_errorReporter.warning(_location, _description + " might happen here." + conditionalComment);
break;
case smt::CheckResult::ERROR:
m_errorReporter.warning(_location, "Error trying to invoke SMT solver.");
@@ -488,7 +598,110 @@ void SMTChecker::checkCondition(
m_interface->pop();
}
-void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setToZero)
+void SMTChecker::checkBooleanNotConstant(Expression const& _condition, string const& _description)
+{
+ // Do not check for const-ness if this is a constant.
+ if (dynamic_cast<Literal const*>(&_condition))
+ return;
+
+ m_interface->push();
+ m_interface->addAssertion(expr(_condition));
+ auto positiveResult = checkSatisifable();
+ m_interface->pop();
+
+ m_interface->push();
+ m_interface->addAssertion(!expr(_condition));
+ auto negatedResult = checkSatisifable();
+ m_interface->pop();
+
+ if (positiveResult == smt::CheckResult::ERROR || negatedResult == smt::CheckResult::ERROR)
+ m_errorReporter.warning(_condition.location(), "Error trying to invoke SMT solver.");
+ else if (positiveResult == smt::CheckResult::SATISFIABLE && negatedResult == smt::CheckResult::SATISFIABLE)
+ {
+ // everything fine.
+ }
+ else if (positiveResult == smt::CheckResult::UNSATISFIABLE && negatedResult == smt::CheckResult::UNSATISFIABLE)
+ m_errorReporter.warning(_condition.location(), "Condition unreachable.");
+ else
+ {
+ string value;
+ if (positiveResult == smt::CheckResult::SATISFIABLE)
+ {
+ solAssert(negatedResult == smt::CheckResult::UNSATISFIABLE, "");
+ value = "true";
+ }
+ else
+ {
+ solAssert(positiveResult == smt::CheckResult::UNSATISFIABLE, "");
+ solAssert(negatedResult == smt::CheckResult::SATISFIABLE, "");
+ value = "false";
+ }
+ m_errorReporter.warning(_condition.location(), boost::algorithm::replace_all_copy(_description, "$VALUE", value));
+ }
+}
+
+pair<smt::CheckResult, vector<string>>
+SMTChecker::checkSatisifableAndGenerateModel(vector<smt::Expression> const& _expressionsToEvaluate)
+{
+ smt::CheckResult result;
+ vector<string> values;
+ try
+ {
+ tie(result, values) = m_interface->check(_expressionsToEvaluate);
+ }
+ catch (smt::SolverError const& _e)
+ {
+ string description("Error querying SMT solver");
+ if (_e.comment())
+ description += ": " + *_e.comment();
+ m_errorReporter.warning(description);
+ result = smt::CheckResult::ERROR;
+ }
+
+ for (string& value: values)
+ {
+ try
+ {
+ // Parse and re-format nicely
+ value = formatNumber(bigint(value));
+ }
+ catch (...) { }
+ }
+
+ return make_pair(result, values);
+}
+
+smt::CheckResult SMTChecker::checkSatisifable()
+{
+ return checkSatisifableAndGenerateModel({}).first;
+}
+
+void SMTChecker::initializeLocalVariables(FunctionDefinition const& _function)
+{
+ for (auto const& variable: _function.localVariables())
+ if (createVariable(*variable))
+ setZeroValue(*variable);
+
+ for (auto const& param: _function.parameters())
+ if (createVariable(*param))
+ setUnknownValue(*param);
+
+ if (_function.returnParameterList())
+ for (auto const& retParam: _function.returnParameters())
+ if (createVariable(*retParam))
+ setZeroValue(*retParam);
+}
+
+void SMTChecker::resetVariables(vector<Declaration const*> _variables)
+{
+ for (auto const* decl: _variables)
+ {
+ newValue(*decl);
+ setUnknownValue(*decl);
+ }
+}
+
+bool SMTChecker::createVariable(VariableDeclaration const& _varDecl)
{
if (dynamic_cast<IntegerType const*>(_varDecl.type().get()))
{
@@ -498,13 +711,16 @@ void SMTChecker::createVariable(VariableDeclaration const& _varDecl, bool _setTo
m_currentSequenceCounter[&_varDecl] = 0;
m_nextFreeSequenceCounter[&_varDecl] = 1;
m_variables.emplace(&_varDecl, m_interface->newFunction(uniqueSymbol(_varDecl), smt::Sort::Int, smt::Sort::Int));
- setValue(_varDecl, _setToZero);
+ return true;
}
else
+ {
m_errorReporter.warning(
_varDecl.location(),
"Assertion checker does not yet support the type of this variable."
);
+ return false;
+ }
}
string SMTChecker::uniqueSymbol(Declaration const& _decl)
@@ -535,23 +751,22 @@ smt::Expression SMTChecker::valueAtSequence(const Declaration& _decl, int _seque
smt::Expression SMTChecker::newValue(Declaration const& _decl)
{
- solAssert(m_currentSequenceCounter.count(&_decl), "");
solAssert(m_nextFreeSequenceCounter.count(&_decl), "");
m_currentSequenceCounter[&_decl] = m_nextFreeSequenceCounter[&_decl]++;
return currentValue(_decl);
}
-void SMTChecker::setValue(Declaration const& _decl, bool _setToZero)
+void SMTChecker::setZeroValue(Declaration const& _decl)
{
- auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type());
+ solAssert(_decl.type()->category() == Type::Category::Integer, "");
+ m_interface->addAssertion(currentValue(_decl) == 0);
+}
- if (_setToZero)
- m_interface->addAssertion(currentValue(_decl) == 0);
- else
- {
- m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
- m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
- }
+void SMTChecker::setUnknownValue(Declaration const& _decl)
+{
+ auto const& intType = dynamic_cast<IntegerType const&>(*_decl.type());
+ m_interface->addAssertion(currentValue(_decl) >= minValue(intType));
+ m_interface->addAssertion(currentValue(_decl) <= maxValue(intType));
}
smt::Expression SMTChecker::minValue(IntegerType const& _t)
@@ -568,6 +783,18 @@ smt::Expression SMTChecker::expr(Expression const& _e)
{
if (!m_expressions.count(&_e))
{
+ m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." );
+ createExpr(_e);
+ }
+ return m_expressions.at(&_e);
+}
+
+void SMTChecker::createExpr(Expression const& _e)
+{
+ if (m_expressions.count(&_e))
+ m_errorReporter.warning(_e.location(), "Internal error: Expression created twice in SMT solver." );
+ else
+ {
solAssert(_e.annotation().type, "");
switch (_e.annotation().type->category())
{
@@ -588,7 +815,12 @@ smt::Expression SMTChecker::expr(Expression const& _e)
solAssert(false, "Type not implemented.");
}
}
- return m_expressions.at(&_e);
+}
+
+void SMTChecker::defineExpr(Expression const& _e, smt::Expression _value)
+{
+ createExpr(_e);
+ m_interface->addAssertion(expr(_e) == _value);
}
smt::Expression SMTChecker::var(Declaration const& _decl)
diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h
index faaac639..e7481cca 100644
--- a/libsolidity/formal/SMTChecker.h
+++ b/libsolidity/formal/SMTChecker.h
@@ -17,8 +17,11 @@
#pragma once
-#include <libsolidity/ast/ASTVisitor.h>
+
#include <libsolidity/formal/SolverInterface.h>
+
+#include <libsolidity/ast/ASTVisitor.h>
+
#include <libsolidity/interface/ReadFile.h>
#include <map>
@@ -29,6 +32,7 @@ namespace dev
namespace solidity
{
+class VariableUsage;
class ErrorReporter;
class SMTChecker: private ASTConstVisitor
@@ -48,10 +52,12 @@ private:
virtual void endVisit(FunctionDefinition const& _node) override;
virtual bool visit(IfStatement const& _node) override;
virtual bool visit(WhileStatement const& _node) override;
+ virtual bool visit(ForStatement const& _node) override;
virtual void endVisit(VariableDeclarationStatement const& _node) override;
virtual void endVisit(ExpressionStatement const& _node) override;
virtual void endVisit(Assignment const& _node) override;
virtual void endVisit(TupleExpression const& _node) override;
+ virtual void endVisit(UnaryOperation const& _node) override;
virtual void endVisit(BinaryOperation const& _node) override;
virtual void endVisit(FunctionCall const& _node) override;
virtual void endVisit(Identifier const& _node) override;
@@ -61,6 +67,19 @@ private:
void compareOperation(BinaryOperation const& _op);
void booleanOperation(BinaryOperation const& _op);
+ /// Division expression in the given type. Requires special treatment because
+ /// of rounding for signed division.
+ smt::Expression division(smt::Expression _left, smt::Expression _right, IntegerType const& _type);
+
+ void assignment(Declaration const& _variable, Expression const& _value, SourceLocation const& _location);
+ void assignment(Declaration const& _variable, smt::Expression const& _value, SourceLocation const& _location);
+
+ // Visits the branch given by the statement, pushes and pops the SMT checker.
+ // @param _condition if present, asserts that this condition is true within the branch.
+ void visitBranch(Statement const& _statement, smt::Expression const* _condition = nullptr);
+ void visitBranch(Statement const& _statement, smt::Expression _condition);
+
+ /// Check that a condition can be satisfied.
void checkCondition(
smt::Expression _condition,
SourceLocation const& _location,
@@ -68,8 +87,27 @@ private:
std::string const& _additionalValueName = "",
smt::Expression* _additionalValue = nullptr
);
+ /// Checks that a boolean condition is not constant. Do not warn if the expression
+ /// is a literal constant.
+ /// @param _description the warning string, $VALUE will be replaced by the constant value.
+ void checkBooleanNotConstant(
+ Expression const& _condition,
+ std::string const& _description
+ );
+ /// Checks that the value is in the range given by the type.
+ void checkUnderOverflow(smt::Expression _value, IntegerType const& _Type, SourceLocation const& _location);
+
- void createVariable(VariableDeclaration const& _varDecl, bool _setToZero);
+ std::pair<smt::CheckResult, std::vector<std::string>>
+ checkSatisifableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate);
+
+ smt::CheckResult checkSatisifable();
+
+ void initializeLocalVariables(FunctionDefinition const& _function);
+ void resetVariables(std::vector<Declaration const*> _variables);
+ /// Tries to create an uninitialized variable and returns true on success.
+ /// This fails if the type is not supported.
+ bool createVariable(VariableDeclaration const& _varDecl);
static std::string uniqueSymbol(Declaration const& _decl);
static std::string uniqueSymbol(Expression const& _expr);
@@ -87,20 +125,29 @@ private:
/// sequence number to this value and returns the expression.
smt::Expression newValue(Declaration const& _decl);
- /// Sets the value of the declaration either to zero or to its intrinsic range.
- void setValue(Declaration const& _decl, bool _setToZero);
+ /// Sets the value of the declaration to zero.
+ void setZeroValue(Declaration const& _decl);
+ /// Resets the variable to an unknown value (in its range).
+ void setUnknownValue(Declaration const& decl);
static smt::Expression minValue(IntegerType const& _t);
static smt::Expression maxValue(IntegerType const& _t);
- /// Returns the expression corresponding to the AST node. Creates a new expression
- /// if it does not exist yet.
+ using VariableSequenceCounters = std::map<Declaration const*, int>;
+
+ /// Returns the expression corresponding to the AST node. Throws if the expression does not exist.
smt::Expression expr(Expression const& _e);
+ /// Creates the expression (value can be arbitrary)
+ void createExpr(Expression const& _e);
+ /// Creates the expression and sets its value.
+ void defineExpr(Expression const& _e, smt::Expression _value);
/// Returns the function declaration corresponding to the given variable.
/// The function takes one argument which is the "sequence number".
smt::Expression var(Declaration const& _decl);
std::shared_ptr<smt::SolverInterface> m_interface;
+ std::shared_ptr<VariableUsage> m_variableUsage;
+ bool m_conditionalExecutionHappened = false;
std::map<Declaration const*, int> m_currentSequenceCounter;
std::map<Declaration const*, int> m_nextFreeSequenceCounter;
std::map<Expression const*, smt::Expression> m_expressions;
diff --git a/libsolidity/formal/SMTLib2Interface.cpp b/libsolidity/formal/SMTLib2Interface.cpp
index c627057a..0e00665a 100644
--- a/libsolidity/formal/SMTLib2Interface.cpp
+++ b/libsolidity/formal/SMTLib2Interface.cpp
@@ -64,8 +64,6 @@ void SMTLib2Interface::pop()
Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codomain)
{
- solAssert(!m_variables.count(_name), "");
- m_variables[_name] = SMTVariableType::Function;
write(
"(declare-fun |" +
_name +
@@ -80,16 +78,12 @@ Expression SMTLib2Interface::newFunction(string _name, Sort _domain, Sort _codom
Expression SMTLib2Interface::newInteger(string _name)
{
- solAssert(!m_variables.count(_name), "");
- m_variables[_name] = SMTVariableType::Integer;
write("(declare-const |" + _name + "| Int)");
return SolverInterface::newInteger(move(_name));
}
Expression SMTLib2Interface::newBool(string _name)
{
- solAssert(!m_variables.count(_name), "");
- m_variables[_name] = SMTVariableType::Bool;
write("(declare-const |" + _name + "| Bool)");
return SolverInterface::newBool(std::move(_name));
}
@@ -151,9 +145,8 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _
for (size_t i = 0; i < _expressionsToEvaluate.size(); i++)
{
auto const& e = _expressionsToEvaluate.at(i);
- solAssert(m_variables.count(e.name), "");
- solAssert(m_variables[e.name] == SMTVariableType::Integer, "");
- command += "(declare-const |EVALEXPR_" + to_string(i) + "| Int)\n";
+ solAssert(e.sort == Sort::Int || e.sort == Sort::Bool, "Invalid sort for expression to evaluate.");
+ command += "(declare-const |EVALEXPR_" + to_string(i) + "| " + (e.sort == Sort::Int ? "Int" : "Bool") + "\n";
command += "(assert (= |EVALEXPR_" + to_string(i) + "| " + toSExpr(e) + "))\n";
}
command += "(check-sat)\n";
diff --git a/libsolidity/formal/SMTLib2Interface.h b/libsolidity/formal/SMTLib2Interface.h
index e827449f..63188acd 100644
--- a/libsolidity/formal/SMTLib2Interface.h
+++ b/libsolidity/formal/SMTLib2Interface.h
@@ -68,14 +68,6 @@ private:
ReadCallback::Callback m_queryCallback;
std::vector<std::string> m_accumulatedOutput;
-
- enum class SMTVariableType {
- Function,
- Integer,
- Bool
- };
-
- std::map<std::string,SMTVariableType> m_variables;
};
}
diff --git a/libsolidity/formal/SolverInterface.h b/libsolidity/formal/SolverInterface.h
index 70dc1585..74c993e8 100644
--- a/libsolidity/formal/SolverInterface.h
+++ b/libsolidity/formal/SolverInterface.h
@@ -44,7 +44,9 @@ enum class CheckResult
enum class Sort
{
- Int, Bool
+ Int,
+ Bool,
+ IntIntFun // Function of one Int returning a single Int
};
/// C++ representation of an SMTLIB2 expression.
@@ -52,9 +54,10 @@ class Expression
{
friend class SolverInterface;
public:
- Expression(size_t _number): name(std::to_string(_number)) {}
- Expression(u256 const& _number): name(_number.str()) {}
- Expression(bigint const& _number): name(_number.str()) {}
+ explicit Expression(bool _v): name(_v ? "true" : "false"), sort(Sort::Bool) {}
+ Expression(size_t _number): name(std::to_string(_number)), sort(Sort::Int) {}
+ Expression(u256 const& _number): name(_number.str()), sort(Sort::Int) {}
+ Expression(bigint const& _number): name(_number.str()), sort(Sort::Int) {}
Expression(Expression const&) = default;
Expression(Expression&&) = default;
@@ -63,26 +66,27 @@ public:
static Expression ite(Expression _condition, Expression _trueValue, Expression _falseValue)
{
+ solAssert(_trueValue.sort == _falseValue.sort, "");
return Expression("ite", std::vector<Expression>{
std::move(_condition), std::move(_trueValue), std::move(_falseValue)
- });
+ }, _trueValue.sort);
}
friend Expression operator!(Expression _a)
{
- return Expression("not", std::move(_a));
+ return Expression("not", std::move(_a), Sort::Bool);
}
friend Expression operator&&(Expression _a, Expression _b)
{
- return Expression("and", std::move(_a), std::move(_b));
+ return Expression("and", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator||(Expression _a, Expression _b)
{
- return Expression("or", std::move(_a), std::move(_b));
+ return Expression("or", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator==(Expression _a, Expression _b)
{
- return Expression("=", std::move(_a), std::move(_b));
+ return Expression("=", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator!=(Expression _a, Expression _b)
{
@@ -90,52 +94,60 @@ public:
}
friend Expression operator<(Expression _a, Expression _b)
{
- return Expression("<", std::move(_a), std::move(_b));
+ return Expression("<", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator<=(Expression _a, Expression _b)
{
- return Expression("<=", std::move(_a), std::move(_b));
+ return Expression("<=", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator>(Expression _a, Expression _b)
{
- return Expression(">", std::move(_a), std::move(_b));
+ return Expression(">", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator>=(Expression _a, Expression _b)
{
- return Expression(">=", std::move(_a), std::move(_b));
+ return Expression(">=", std::move(_a), std::move(_b), Sort::Bool);
}
friend Expression operator+(Expression _a, Expression _b)
{
- return Expression("+", std::move(_a), std::move(_b));
+ return Expression("+", std::move(_a), std::move(_b), Sort::Int);
}
friend Expression operator-(Expression _a, Expression _b)
{
- return Expression("-", std::move(_a), std::move(_b));
+ return Expression("-", std::move(_a), std::move(_b), Sort::Int);
}
friend Expression operator*(Expression _a, Expression _b)
{
- return Expression("*", std::move(_a), std::move(_b));
+ return Expression("*", std::move(_a), std::move(_b), Sort::Int);
+ }
+ friend Expression operator/(Expression _a, Expression _b)
+ {
+ return Expression("/", std::move(_a), std::move(_b), Sort::Int);
}
Expression operator()(Expression _a) const
{
- solAssert(arguments.empty(), "Attempted function application to non-function.");
- return Expression(name, _a);
+ solAssert(
+ sort == Sort::IntIntFun && arguments.empty(),
+ "Attempted function application to non-function."
+ );
+ return Expression(name, _a, Sort::Int);
}
std::string const name;
std::vector<Expression> const arguments;
+ Sort sort;
private:
/// Manual constructor, should only be used by SolverInterface and this class itself.
- Expression(std::string _name, std::vector<Expression> _arguments):
- name(std::move(_name)), arguments(std::move(_arguments)) {}
-
- explicit Expression(std::string _name):
- Expression(std::move(_name), std::vector<Expression>{}) {}
- Expression(std::string _name, Expression _arg):
- Expression(std::move(_name), std::vector<Expression>{std::move(_arg)}) {}
- Expression(std::string _name, Expression _arg1, Expression _arg2):
- Expression(std::move(_name), std::vector<Expression>{std::move(_arg1), std::move(_arg2)}) {}
+ Expression(std::string _name, std::vector<Expression> _arguments, Sort _sort):
+ name(std::move(_name)), arguments(std::move(_arguments)), sort(_sort) {}
+
+ explicit Expression(std::string _name, Sort _sort):
+ Expression(std::move(_name), std::vector<Expression>{}, _sort) {}
+ Expression(std::string _name, Expression _arg, Sort _sort):
+ Expression(std::move(_name), std::vector<Expression>{std::move(_arg)}, _sort) {}
+ Expression(std::string _name, Expression _arg1, Expression _arg2, Sort _sort):
+ Expression(std::move(_name), std::vector<Expression>{std::move(_arg1), std::move(_arg2)}, _sort) {}
};
DEV_SIMPLE_EXCEPTION(SolverError);
@@ -148,20 +160,21 @@ public:
virtual void push() = 0;
virtual void pop() = 0;
- virtual Expression newFunction(std::string _name, Sort /*_domain*/, Sort /*_codomain*/)
+ virtual Expression newFunction(std::string _name, Sort _domain, Sort _codomain)
{
+ solAssert(_domain == Sort::Int && _codomain == Sort::Int, "Function sort not supported.");
// Subclasses should do something here
- return Expression(std::move(_name), {});
+ return Expression(std::move(_name), {}, Sort::IntIntFun);
}
virtual Expression newInteger(std::string _name)
{
// Subclasses should do something here
- return Expression(std::move(_name), {});
+ return Expression(std::move(_name), {}, Sort::Int);
}
virtual Expression newBool(std::string _name)
{
// Subclasses should do something here
- return Expression(std::move(_name), {});
+ return Expression(std::move(_name), {}, Sort::Bool);
}
virtual void addAssertion(Expression const& _expr) = 0;
diff --git a/libsolidity/formal/VariableUsage.cpp b/libsolidity/formal/VariableUsage.cpp
new file mode 100644
index 00000000..4e96059d
--- /dev/null
+++ b/libsolidity/formal/VariableUsage.cpp
@@ -0,0 +1,80 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <libsolidity/formal/VariableUsage.h>
+
+#include <libsolidity/ast/ASTVisitor.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+
+VariableUsage::VariableUsage(ASTNode const& _node)
+{
+ auto nodeFun = [&](ASTNode const& n) -> bool
+ {
+ if (Identifier const* identifier = dynamic_cast<decltype(identifier)>(&n))
+ {
+ Declaration const* declaration = identifier->annotation().referencedDeclaration;
+ solAssert(declaration, "");
+ if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
+ if (
+ varDecl->isLocalVariable() &&
+ identifier->annotation().lValueRequested &&
+ varDecl->annotation().type->isValueType()
+ )
+ m_touchedVariable[&n] = varDecl;
+ }
+ return true;
+ };
+ auto edgeFun = [&](ASTNode const& _parent, ASTNode const& _child)
+ {
+ if (m_touchedVariable.count(&_child) || m_children.count(&_child))
+ m_children[&_parent].push_back(&_child);
+ };
+
+ ASTReduce reducer(nodeFun, edgeFun);
+ _node.accept(reducer);
+}
+
+vector<Declaration const*> VariableUsage::touchedVariables(ASTNode const& _node) const
+{
+ if (!m_children.count(&_node) && !m_touchedVariable.count(&_node))
+ return {};
+
+ set<Declaration const*> touched;
+ vector<ASTNode const*> toVisit;
+ toVisit.push_back(&_node);
+
+ while (!toVisit.empty())
+ {
+ ASTNode const* n = toVisit.back();
+ toVisit.pop_back();
+ if (m_children.count(n))
+ {
+ solAssert(!m_touchedVariable.count(n), "");
+ toVisit += m_children.at(n);
+ }
+ else
+ {
+ solAssert(m_touchedVariable.count(n), "");
+ touched.insert(m_touchedVariable.at(n));
+ }
+ }
+
+ return {touched.begin(), touched.end()};
+}
diff --git a/libsolidity/formal/VariableUsage.h b/libsolidity/formal/VariableUsage.h
new file mode 100644
index 00000000..62561cce
--- /dev/null
+++ b/libsolidity/formal/VariableUsage.h
@@ -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/>.
+*/
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <vector>
+
+namespace dev
+{
+namespace solidity
+{
+
+class ASTNode;
+class Declaration;
+
+/**
+ * This class collects information about which local variables of value type
+ * are modified in which parts of the AST.
+ */
+class VariableUsage
+{
+public:
+ explicit VariableUsage(ASTNode const& _node);
+
+ std::vector<Declaration const*> touchedVariables(ASTNode const& _node) const;
+
+private:
+ // Variable touched by a specific AST node.
+ std::map<ASTNode const*, Declaration const*> m_touchedVariable;
+ std::map<ASTNode const*, std::vector<ASTNode const*>> m_children;
+};
+
+}
+}
diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp
index 6111b2c8..769e6edb 100644
--- a/libsolidity/formal/Z3Interface.cpp
+++ b/libsolidity/formal/Z3Interface.cpp
@@ -91,7 +91,7 @@ pair<CheckResult, vector<string>> Z3Interface::check(vector<Expression> const& _
solAssert(false, "");
}
- if (result != CheckResult::UNSATISFIABLE)
+ if (result != CheckResult::UNSATISFIABLE && !_expressionsToEvaluate.empty())
{
z3::model m = m_solver.get_model();
for (Expression const& e: _expressionsToEvaluate)
@@ -127,7 +127,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
{">=", 2},
{"+", 2},
{"-", 2},
- {"*", 2}
+ {"*", 2},
+ {"/", 2}
};
string const& n = _expr.name;
if (m_functions.count(n))
@@ -139,8 +140,13 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
}
else if (arguments.empty())
{
- // We assume it is an integer...
- return m_context.int_val(n.c_str());
+ if (n == "true")
+ return m_context.bool_val(true);
+ else if (n == "false")
+ return m_context.bool_val(false);
+ else
+ // We assume it is an integer...
+ return m_context.int_val(n.c_str());
}
solAssert(arity.count(n) && arity.at(n) == arguments.size(), "");
@@ -168,6 +174,8 @@ z3::expr Z3Interface::toZ3Expr(Expression const& _expr)
return arguments[0] - arguments[1];
else if (n == "*")
return arguments[0] * arguments[1];
+ else if (n == "/")
+ return arguments[0] / arguments[1];
// Cannot reach here.
solAssert(false, "");
return arguments[0];
diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp
index e5bdc90f..049af65f 100644
--- a/libsolidity/inlineasm/AsmAnalysis.cpp
+++ b/libsolidity/inlineasm/AsmAnalysis.cpp
@@ -56,6 +56,7 @@ bool AsmAnalyzer::operator()(Label const& _label)
{
solAssert(!m_julia, "");
m_info.stackHeightInfo[&_label] = m_stackHeight;
+ warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location);
return true;
}
@@ -146,10 +147,11 @@ bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
if (!expectExpression(arg))
success = false;
// Parser already checks that the number of arguments is correct.
- solAssert(instructionInfo(_instr.instruction.instruction).args == int(_instr.arguments.size()), "");
- if (!(*this)(_instr.instruction))
- success = false;
+ auto const& info = instructionInfo(_instr.instruction);
+ solAssert(info.args == int(_instr.arguments.size()), "");
+ m_stackHeight += info.ret - info.args;
m_info.stackHeightInfo[&_instr] = m_stackHeight;
+ warnOnInstructions(_instr.instruction, _instr.location);
return success;
}
@@ -217,14 +219,14 @@ bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
solAssert(virtualBlock, "");
Scope& varScope = scope(virtualBlock);
- for (auto const& var: _funDef.arguments + _funDef.returns)
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
{
expectValidType(var.type, var.location);
m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name)));
}
int const stackHeight = m_stackHeight;
- m_stackHeight = _funDef.arguments.size() + _funDef.returns.size();
+ m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size();
bool success = (*this)(_funDef.body);
@@ -286,6 +288,22 @@ bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
return success;
}
+bool AsmAnalyzer::operator()(If const& _if)
+{
+ bool success = true;
+
+ if (!expectExpression(*_if.condition))
+ success = false;
+ m_stackHeight--;
+
+ if (!(*this)(_if.body))
+ success = false;
+
+ m_info.stackHeightInfo[&_if] = m_stackHeight;
+
+ return success;
+}
+
bool AsmAnalyzer::operator()(Switch const& _switch)
{
bool success = true;
@@ -506,11 +524,11 @@ void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocatio
"the Metropolis hard fork. Before that it acts as an invalid instruction."
);
- if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI)
+ if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST)
m_errorReporter.warning(
_location,
- "Jump instructions are low-level EVM features that can lead to "
+ "Jump instructions and labels are low-level EVM features that can lead to "
"incorrect stack access. Because of that they are discouraged. "
- "Please consider using \"switch\" or \"for\" statements instead."
+ "Please consider using \"switch\", \"if\" or \"for\" statements instead."
);
}
diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h
index 9b2a8f9c..e484b876 100644
--- a/libsolidity/inlineasm/AsmAnalysis.h
+++ b/libsolidity/inlineasm/AsmAnalysis.h
@@ -70,6 +70,7 @@ public:
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const& _functionCall);
+ bool operator()(assembly::If const& _if);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);
diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h
index b0dd85ca..11e56fae 100644
--- a/libsolidity/inlineasm/AsmData.h
+++ b/libsolidity/inlineasm/AsmData.h
@@ -60,14 +60,16 @@ struct StackAssignment { SourceLocation location; Identifier variableName; };
/// the same amount of items as the number of variables.
struct Assignment { SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Statement> value; };
/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
-struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector<Statement> arguments; };
+struct FunctionalInstruction { SourceLocation location; solidity::Instruction instruction; std::vector<Statement> arguments; };
struct FunctionCall { SourceLocation location; Identifier functionName; std::vector<Statement> arguments; };
/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted
struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr<Statement> value; };
/// Block that creates a scope (frees declared stack variables)
struct Block { SourceLocation location; std::vector<Statement> statements; };
/// Function definition ("function f(a, b) -> (d, e) { ... }")
-struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList arguments; TypedNameList returns; Block body; };
+struct FunctionDefinition { SourceLocation location; std::string name; TypedNameList parameters; TypedNameList returnVariables; Block body; };
+/// Conditional execution without "else" part.
+struct If { SourceLocation location; std::shared_ptr<Statement> condition; Block body; };
/// Switch case or default case
struct Case { SourceLocation location; std::shared_ptr<Literal> value; Block body; };
/// Switch statement
diff --git a/libsolidity/inlineasm/AsmDataForward.h b/libsolidity/inlineasm/AsmDataForward.h
index 4ead7ff5..1ab62cc0 100644
--- a/libsolidity/inlineasm/AsmDataForward.h
+++ b/libsolidity/inlineasm/AsmDataForward.h
@@ -41,11 +41,15 @@ struct VariableDeclaration;
struct FunctionalInstruction;
struct FunctionDefinition;
struct FunctionCall;
+struct If;
struct Switch;
+struct Case;
struct ForLoop;
struct Block;
-using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>;
+struct TypedName;
+
+using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
}
}
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 1f4df75b..4f8802a0 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -73,13 +73,23 @@ assembly::Statement Parser::parseStatement()
return parseFunctionDefinition();
case Token::LBrace:
return parseBlock();
+ case Token::If:
+ {
+ assembly::If _if = createWithLocation<assembly::If>();
+ m_scanner->next();
+ _if.condition = make_shared<Statement>(parseExpression());
+ if (_if.condition->type() == typeid(assembly::Instruction))
+ fatalParserError("Instructions are not supported as conditions for if - try to append \"()\".");
+ _if.body = parseBlock();
+ return _if;
+ }
case Token::Switch:
{
assembly::Switch _switch = createWithLocation<assembly::Switch>();
m_scanner->next();
_switch.expression = make_shared<Statement>(parseExpression());
if (_switch.expression->type() == typeid(assembly::Instruction))
- fatalParserError("Instructions are not supported as expressions for switch.");
+ fatalParserError("Instructions are not supported as expressions for switch - try to append \"()\".");
while (m_scanner->currentToken() == Token::Case)
_switch.cases.emplace_back(parseCase());
if (m_scanner->currentToken() == Token::Default)
@@ -409,7 +419,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::LParen);
while (currentToken() != Token::RParen)
{
- funDef.arguments.emplace_back(parseTypedName());
+ funDef.parameters.emplace_back(parseTypedName());
if (currentToken() == Token::RParen)
break;
expectToken(Token::Comma);
@@ -421,7 +431,7 @@ assembly::FunctionDefinition Parser::parseFunctionDefinition()
expectToken(Token::GreaterThan);
while (true)
{
- funDef.returns.emplace_back(parseTypedName());
+ funDef.returnVariables.emplace_back(parseTypedName());
if (currentToken() == Token::LBrace)
break;
expectToken(Token::Comma);
@@ -438,10 +448,11 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
if (_instruction.type() == typeid(Instruction))
{
solAssert(!m_julia, "Instructions are invalid in JULIA");
+ Instruction const& instruction = std::move(boost::get<Instruction>(_instruction));
FunctionalInstruction ret;
- ret.instruction = std::move(boost::get<Instruction>(_instruction));
- ret.location = ret.instruction.location;
- solidity::Instruction instr = ret.instruction.instruction;
+ ret.instruction = instruction.instruction;
+ ret.location = std::move(instruction.location);
+ solidity::Instruction instr = ret.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
if (solidity::isDupInstruction(instr))
fatalParserError("DUPi instructions not allowed for functional notation");
diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp
index a5272808..c72586cb 100644
--- a/libsolidity/inlineasm/AsmPrinter.cpp
+++ b/libsolidity/inlineasm/AsmPrinter.cpp
@@ -94,7 +94,7 @@ string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functional
{
solAssert(!m_julia, "");
return
- (*this)(_functionalInstruction.instruction) +
+ boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) +
"(" +
boost::algorithm::join(
_functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
@@ -144,17 +144,17 @@ string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefin
{
string out = "function " + _functionDefinition.name + "(";
out += boost::algorithm::join(
- _functionDefinition.arguments | boost::adaptors::transformed(
+ _functionDefinition.parameters | boost::adaptors::transformed(
[this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
),
", "
);
out += ")";
- if (!_functionDefinition.returns.empty())
+ if (!_functionDefinition.returnVariables.empty())
{
out += " -> ";
out += boost::algorithm::join(
- _functionDefinition.returns | boost::adaptors::transformed(
+ _functionDefinition.returnVariables | boost::adaptors::transformed(
[this](TypedName argument) { return argument.name + appendTypeName(argument.type); }
),
", "
@@ -174,6 +174,11 @@ string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
")";
}
+string AsmPrinter::operator()(If const& _if)
+{
+ return "if " + boost::apply_visitor(*this, *_if.condition) + "\n" + (*this)(_if.body);
+}
+
string AsmPrinter::operator()(Switch const& _switch)
{
string out = "switch " + boost::apply_visitor(*this, *_switch.expression);
diff --git a/libsolidity/inlineasm/AsmPrinter.h b/libsolidity/inlineasm/AsmPrinter.h
index 66520632..eadf81d9 100644
--- a/libsolidity/inlineasm/AsmPrinter.h
+++ b/libsolidity/inlineasm/AsmPrinter.h
@@ -48,6 +48,7 @@ public:
std::string operator()(assembly::VariableDeclaration const& _variableDeclaration);
std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
std::string operator()(assembly::FunctionCall const& _functionCall);
+ std::string operator()(assembly::If const& _if);
std::string operator()(assembly::Switch const& _switch);
std::string operator()(assembly::ForLoop const& _forLoop);
std::string operator()(assembly::Block const& _block);
diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp
index b70ae9ac..0984e7d2 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.cpp
+++ b/libsolidity/inlineasm/AsmScopeFiller.cpp
@@ -71,10 +71,10 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
{
bool success = true;
vector<Scope::JuliaType> arguments;
- for (auto const& _argument: _funDef.arguments)
+ for (auto const& _argument: _funDef.parameters)
arguments.push_back(_argument.type);
vector<Scope::JuliaType> returns;
- for (auto const& _return: _funDef.returns)
+ for (auto const& _return: _funDef.returnVariables)
returns.push_back(_return.type);
if (!m_currentScope->registerFunction(_funDef.name, arguments, returns))
{
@@ -91,7 +91,7 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
varScope.superScope = m_currentScope;
m_currentScope = &varScope;
varScope.functionScope = true;
- for (auto const& var: _funDef.arguments + _funDef.returns)
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
if (!registerVariable(var, _funDef.location, varScope))
success = false;
@@ -104,6 +104,11 @@ bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
return success;
}
+bool ScopeFiller::operator()(If const& _if)
+{
+ return (*this)(_if.body);
+}
+
bool ScopeFiller::operator()(Switch const& _switch)
{
bool success = true;
diff --git a/libsolidity/inlineasm/AsmScopeFiller.h b/libsolidity/inlineasm/AsmScopeFiller.h
index 80c03d2c..ed28abbf 100644
--- a/libsolidity/inlineasm/AsmScopeFiller.h
+++ b/libsolidity/inlineasm/AsmScopeFiller.h
@@ -59,6 +59,7 @@ public:
bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
bool operator()(assembly::FunctionDefinition const& _functionDefinition);
bool operator()(assembly::FunctionCall const&) { return true; }
+ bool operator()(assembly::If const& _if);
bool operator()(assembly::Switch const& _switch);
bool operator()(assembly::ForLoop const& _forLoop);
bool operator()(assembly::Block const& _block);
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index 430739ac..ad01821e 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -131,6 +131,61 @@ StringMap createSourceList(Json::Value const& _input)
return sources;
}
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _artifact)
+{
+ for (auto const& artifact: _outputSelection)
+ /// @TODO support sub-matching, e.g "evm" matches "evm.assembly"
+ if (artifact == "*" || artifact == _artifact)
+ return true;
+ return false;
+}
+
+///
+/// @a _outputSelection is a JSON object containining a two-level hashmap, where the first level is the filename,
+/// the second level is the contract name and the value is an array of artifact names to be requested for that contract.
+/// @a _file is the current file
+/// @a _contract is the current contract
+/// @a _artifact is the current artifact name
+///
+/// @returns true if the @a _outputSelection has a match for the requested target in the specific file / contract.
+///
+/// In @a _outputSelection the use of '*' as a wildcard is permitted.
+///
+/// @TODO optimise this. Perhaps flatten the structure upfront.
+///
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, string const& _artifact)
+{
+ if (!_outputSelection.isObject())
+ return false;
+
+ for (auto const& file: { _file, string("*") })
+ if (_outputSelection.isMember(file) && _outputSelection[file].isObject())
+ {
+ /// For SourceUnit-level targets (such as AST) only allow empty name, otherwise
+ /// for Contract-level targets try both contract name and wildcard
+ vector<string> contracts{ _contract };
+ if (!_contract.empty())
+ contracts.push_back("*");
+ for (auto const& contract: contracts)
+ if (
+ _outputSelection[file].isMember(contract) &&
+ _outputSelection[file][contract].isArray() &&
+ isArtifactRequested(_outputSelection[file][contract], _artifact)
+ )
+ return true;
+ }
+
+ return false;
+}
+
+bool isArtifactRequested(Json::Value const& _outputSelection, string const& _file, string const& _contract, vector<string> const& _artifacts)
+{
+ for (auto const& artifact: _artifacts)
+ if (isArtifactRequested(_outputSelection, _file, _contract, artifact))
+ return true;
+ return false;
+}
+
Json::Value formatLinkReferences(std::map<size_t, std::string> const& linkReferences)
{
Json::Value ret(Json::objectValue);
@@ -396,8 +451,10 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
{
Json::Value sourceResult = Json::objectValue;
sourceResult["id"] = sourceIndex++;
- sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
- sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
+ if (isArtifactRequested(outputSelection, sourceName, "", "ast"))
+ sourceResult["ast"] = ASTJsonConverter(false, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
+ if (isArtifactRequested(outputSelection, sourceName, "", "legacyAST"))
+ sourceResult["legacyAST"] = ASTJsonConverter(true, m_compilerStack.sourceIndices()).toJson(m_compilerStack.ast(sourceName));
output["sources"][sourceName] = sourceResult;
}
@@ -411,28 +468,48 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
// ABI, documentation and metadata
Json::Value contractData(Json::objectValue);
- contractData["abi"] = m_compilerStack.contractABI(contractName);
- contractData["metadata"] = m_compilerStack.metadata(contractName);
- contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
- contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "abi"))
+ contractData["abi"] = m_compilerStack.contractABI(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "metadata"))
+ contractData["metadata"] = m_compilerStack.metadata(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "userdoc"))
+ contractData["userdoc"] = m_compilerStack.natspecUser(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "devdoc"))
+ contractData["devdoc"] = m_compilerStack.natspecDev(contractName);
// EVM
Json::Value evmData(Json::objectValue);
// @TODO: add ir
- evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
- evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
- evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
- evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
-
- evmData["bytecode"] = collectEVMObject(
- m_compilerStack.object(contractName),
- m_compilerStack.sourceMapping(contractName)
- );
-
- evmData["deployedBytecode"] = collectEVMObject(
- m_compilerStack.runtimeObject(contractName),
- m_compilerStack.runtimeSourceMapping(contractName)
- );
+ if (isArtifactRequested(outputSelection, file, name, "evm.assembly"))
+ evmData["assembly"] = m_compilerStack.assemblyString(contractName, createSourceList(_input));
+ if (isArtifactRequested(outputSelection, file, name, "evm.legacyAssembly"))
+ evmData["legacyAssembly"] = m_compilerStack.assemblyJSON(contractName, createSourceList(_input));
+ if (isArtifactRequested(outputSelection, file, name, "evm.methodIdentifiers"))
+ evmData["methodIdentifiers"] = m_compilerStack.methodIdentifiers(contractName);
+ if (isArtifactRequested(outputSelection, file, name, "evm.gasEstimates"))
+ evmData["gasEstimates"] = m_compilerStack.gasEstimates(contractName);
+
+ if (isArtifactRequested(
+ outputSelection,
+ file,
+ name,
+ { "evm.bytecode", "evm.bytecode.object", "evm.bytecode.opcodes", "evm.bytecode.sourceMap", "evm.bytecode.linkReferences" }
+ ))
+ evmData["bytecode"] = collectEVMObject(
+ m_compilerStack.object(contractName),
+ m_compilerStack.sourceMapping(contractName)
+ );
+
+ if (isArtifactRequested(
+ outputSelection,
+ file,
+ name,
+ { "evm.deployedBytecode", "evm.deployedBytecode.object", "evm.deployedBytecode.opcodes", "evm.deployedBytecode.sourceMap", "evm.deployedBytecode.linkReferences" }
+ ))
+ evmData["deployedBytecode"] = collectEVMObject(
+ m_compilerStack.runtimeObject(contractName),
+ m_compilerStack.runtimeSourceMapping(contractName)
+ );
contractData["evm"] = evmData;
diff --git a/scripts/build.sh b/scripts/build.sh
index 3785e1c1..bddbb97a 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -7,6 +7,11 @@ else
fi
cd $(dirname "$0")/.. &&
+
+if [[ "$(git tag --points-at HEAD 2>/dev/null)" == v* ]]; then
+ touch prerelease.txt
+fi
+
mkdir -p build &&
cd build &&
cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" &&
@@ -20,4 +25,4 @@ fi
if [ -z $CI ]; then
echo "Installing solc and soltest"
install solc/solc /usr/local/bin && install test/soltest /usr/local/bin
-fi \ No newline at end of file
+fi
diff --git a/scripts/bytecodecompare/storebytecode.sh b/scripts/bytecodecompare/storebytecode.sh
index 8d4100bf..557e3275 100755
--- a/scripts/bytecodecompare/storebytecode.sh
+++ b/scripts/bytecodecompare/storebytecode.sh
@@ -40,7 +40,7 @@ TMPDIR=$(mktemp -d)
if [[ "$SOLC_EMSCRIPTEN" = "On" ]]
then
- cp "$REPO_ROOT/build/solc/soljson.js" .
+ cp "$REPO_ROOT/build/libsolc/soljson.js" .
npm install solc
cat > solc <<EOF
#!/usr/bin/env node
diff --git a/scripts/docker_deploy_manual.sh b/scripts/docker_deploy_manual.sh
new file mode 100755
index 00000000..c098f4ee
--- /dev/null
+++ b/scripts/docker_deploy_manual.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env sh
+
+set -e
+
+if [ -z "$1" ]
+then
+ echo "Usage: $0 <tag/branch>"
+ exit 1
+fi
+branch="$1"
+
+#docker login
+
+DIR=$(mktemp -d)
+(
+cd "$DIR"
+
+git clone --depth 2 https://github.com/ethereum/solidity.git -b "$branch"
+cd solidity
+commithash=$(git rev-parse --short=8 HEAD)
+echo -n "$commithash" > commit_hash.txt
+version=$($(dirname "$0")/get_version.sh)
+if [ "$branch" = "release" -o "$branch" = v"$version" ]
+then
+ echo -n > prerelease.txt
+else
+ date -u +"nightly.%Y.%-m.%-d" > prerelease.txt
+fi
+
+rm -rf .git
+docker build -t ethereum/solc:build -f scripts/Dockerfile .
+tmp_container=$(docker create ethereum/solc:build sh)
+if [ "$branch" = "develop" ]
+then
+ docker tag ethereum/solc:build ethereum/solc:nightly;
+ docker tag ethereum/solc:build ethereum/solc:nightly-"$version"-"$commithash"
+ docker push ethereum/solc:nightly-"$version"-"$commithash";
+ docker push ethereum/solc:nightly;
+elif [ "$branch" = v"$version" ]
+then
+ docker tag ethereum/solc:build ethereum/solc:stable;
+ docker tag ethereum/solc:build ethereum/solc:"$version";
+ docker push ethereum/solc:stable;
+ docker push ethereum/solc:"$version";
+else
+ echo "Not publishing docker image from branch or tag $branch"
+fi
+)
+rm -rf "$DIR"
diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh
index 49f864a0..15e864b5 100755
--- a/scripts/install_deps.sh
+++ b/scripts/install_deps.sh
@@ -294,12 +294,17 @@ case $(uname -s) in
echo "Installing solidity dependencies on Ubuntu Zesty (17.04)."
install_z3="libz3-dev"
;;
+ artful)
+ #artful
+ echo "Installing solidity dependencies on Ubuntu Artful (17.10)."
+ install_z3="libz3-dev"
+ ;;
*)
#other Ubuntu
echo "ERROR - Unknown or unsupported Ubuntu version (" $(lsb_release -cs) ")"
echo "ERROR - This might not work, but we are trying anyway."
echo "Please drop us a message at https://gitter.im/ethereum/solidity-dev."
- echo "We only support Trusty, Utopic, Vivid, Wily, Xenial and Yakkety."
+ echo "We only support Trusty, Utopic, Vivid, Wily, Xenial, Yakkety, Zesty and Artful."
install_z3="libz3-dev"
;;
esac
diff --git a/scripts/test_emscripten.sh b/scripts/test_emscripten.sh
index 4996e957..b659e5e5 100755
--- a/scripts/test_emscripten.sh
+++ b/scripts/test_emscripten.sh
@@ -29,7 +29,7 @@
set -e
REPO_ROOT=$(cd $(dirname "$0")/.. && pwd)
-SOLJSON="$REPO_ROOT/build/solc/soljson.js"
+SOLJSON="$REPO_ROOT/build/libsolc/soljson.js"
DIR=$(mktemp -d)
(
diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh
index bf460e8e..56826997 100755
--- a/scripts/travis-emscripten/build_emscripten.sh
+++ b/scripts/travis-emscripten/build_emscripten.sh
@@ -87,8 +87,8 @@ make -j 4
cd ..
mkdir -p upload
-cp build/solc/soljson.js upload/
-cp build/solc/soljson.js ./
+cp build/libsolc/soljson.js upload/
+cp build/libsolc/soljson.js ./
OUTPUT_SIZE=`ls -la soljson.js`
diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt
index 656b27c3..d9b12a06 100644
--- a/solc/CMakeLists.txt
+++ b/solc/CMakeLists.txt
@@ -10,14 +10,6 @@ target_link_libraries(solc PRIVATE solidity ${Boost_PROGRAM_OPTIONS_LIBRARIES})
include(GNUInstallDirs)
install(TARGETS solc DESTINATION "${CMAKE_INSTALL_BINDIR}")
-if (EMSCRIPTEN)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s EXPORTED_FUNCTIONS='[\"_compileJSON\",\"_license\",\"_version\",\"_compileJSONMulti\",\"_compileJSONCallback\",\"_compileStandard\"]' -s RESERVED_FUNCTION_POINTERS=20")
- add_executable(soljson jsonCompiler.cpp)
-else()
- add_library(soljson jsonCompiler.cpp)
-endif()
-target_link_libraries(soljson PRIVATE solidity)
-
if(SOLC_LINK_STATIC AND UNIX AND NOT APPLE)
# Produce solc as statically linked binary (includes C/C++ standard libraries)
# This is not supported on macOS, see
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 6a8a4399..f36ad4c5 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -3,7 +3,7 @@ list(REMOVE_ITEM sources "${CMAKE_CURRENT_SOURCE_DIR}/fuzzer.cpp")
file(GLOB_RECURSE headers "*.h")
add_executable(soltest ${sources} ${headers})
-target_link_libraries(soltest PRIVATE soljson solidity lll evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
+target_link_libraries(soltest PRIVATE libsolc solidity lll evmasm devcore ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
add_executable(solfuzzer fuzzer.cpp)
-target_link_libraries(solfuzzer soljson evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES})
+target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES})
diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h
index 2c61c0a6..8aa99473 100644
--- a/test/ExecutionFramework.h
+++ b/test/ExecutionFramework.h
@@ -84,14 +84,24 @@ public:
return callFallbackWithValue(0);
}
- template <class... Args>
- bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments)
+ bytes const& callContractFunctionWithValueNoEncoding(std::string _sig, u256 const& _value, bytes const& _arguments)
{
FixedHash<4> hash(dev::keccak256(_sig));
- sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value);
+ sendMessage(hash.asBytes() + _arguments, false, _value);
return m_output;
}
+ bytes const& callContractFunctionNoEncoding(std::string _sig, bytes const& _arguments)
+ {
+ return callContractFunctionWithValueNoEncoding(_sig, 0, _arguments);
+ }
+
+ template <class... Args>
+ bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments)
+ {
+ return callContractFunctionWithValueNoEncoding(_sig, _value, encodeArgs(_arguments...));
+ }
+
template <class... Args>
bytes const& callContractFunction(std::string _sig, Args const&... _arguments)
{
diff --git a/test/boostTest.cpp b/test/boostTest.cpp
index 7b452e06..a3cc51c5 100644
--- a/test/boostTest.cpp
+++ b/test/boostTest.cpp
@@ -57,6 +57,7 @@ test_suite* init_unit_test_suite( int /*argc*/, char* /*argv*/[] )
if (dev::test::Options::get().disableIPC)
{
for (auto suite: {
+ "ABIDecoderTest",
"ABIEncoderTest",
"SolidityAuctionRegistrar",
"SolidityFixedFeeRegistrar",
diff --git a/test/externalTests.sh b/test/externalTests.sh
index 6ff2ebc5..1cc0af19 100755
--- a/test/externalTests.sh
+++ b/test/externalTests.sh
@@ -42,7 +42,55 @@ DIR=$(mktemp -d)
git clone --depth 1 https://github.com/OpenZeppelin/zeppelin-solidity.git "$DIR"
cd "$DIR"
npm install
- cp "$SOLJSON" ./node_modules/solc/soljson.js
+ find . -name soljson.js -exec cp "$SOLJSON" {} \;
+
+ # This is a patch that lets truffle ignore the pre-release compiler warning
+ cat > truffle.patch <<EOF
+--- node_modules/truffle/build/cli.bundled.js 2017-11-27 16:56:47.114830112 +0100
++++ /tmp/patched 2017-11-27 16:52:31.887064115 +0100
+@@ -313846,9 +313846,12 @@
+ });
+
+ output = JSON.parse(output);
++ var errors = output.errors.filter(function(solidity_error) {
++ return solidity_error.formattedMessage.indexOf("pre-release compiler") < 0;
++ });
+
+- if (output.errors) {
+- throw new CompileError(output.errors[0].formattedMessage);
++ if (errors) {
++ throw new CompileError(errors[0].formattedMessage);
+ }
+
+ return {
+@@ -313901,9 +313904,13 @@
+ return {error: importErrorKey};
+ });
+
+- output = JSON.parse(output);
++ output = JSON.parse(output);
++
++ var errors = output.errors.filter(function(solidity_error) {
++ return solidity_error.formattedMessage.indexOf("pre-release compiler") < 0;
++ });
+
+- var nonImportErrors = output.errors.filter(function(solidity_error) {
++ var nonImportErrors = errors.filter(function(solidity_error) {
+ // If the import error key is not found, we must not have an import error.
+ // This means we have a *different* parsing error which we should show to the user.
+ // Note: solc can return multiple parsing errors at once.
+@@ -313917,7 +313924,7 @@
+
+ // Now, all errors must be import errors.
+ // Filter out our forced import, then get the import paths of the rest.
+- var imports = output.errors.filter(function(solidity_error) {
++ var imports = errors.filter(function(solidity_error) {
+ return solidity_error.message.indexOf(failingImportFileName) < 0;
+ }).map(function(solidity_error) {
+ var matches = solidity_error.formattedMessage.match(/import[^'"]+("|')([^'"]+)("|');/);
+EOF
+
+ patch node_modules/truffle/build/cli.bundled.js ./truffle.patch
npm run test
)
rm -rf "$DIR"
diff --git a/test/fuzzer.cpp b/test/fuzzer.cpp
index 53ba7201..578e63a4 100644
--- a/test/fuzzer.cpp
+++ b/test/fuzzer.cpp
@@ -20,7 +20,7 @@
#include <libevmasm/Assembly.h>
#include <libevmasm/ConstantOptimiser.h>
-#include <solc/jsonCompiler.h>
+#include <libsolc/libsolc.h>
#include <json/json.h>
diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp
index 17a17d22..4eedbd99 100644
--- a/test/libdevcore/Checksum.cpp
+++ b/test/libdevcore/Checksum.cpp
@@ -19,6 +19,8 @@
*/
#include <libdevcore/CommonData.h>
+#include <libdevcore/Exceptions.h>
+
#include "../TestHelper.h"
@@ -31,6 +33,38 @@ namespace test
BOOST_AUTO_TEST_SUITE(Checksum)
+BOOST_AUTO_TEST_CASE(calculate)
+{
+ BOOST_CHECK(!getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed").empty());
+ BOOST_CHECK(!getChecksummedAddress("0x0123456789abcdefABCDEF0123456789abcdefAB").empty());
+ // too short
+ BOOST_CHECK_THROW(getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beae"), InvalidAddress);
+ BOOST_CHECK_THROW(getChecksummedAddress("5aaeb6053f3e94c9b9a09f33669435e7ef1beae"), InvalidAddress);
+ // too long
+ BOOST_CHECK_THROW(getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed1"), InvalidAddress);
+ BOOST_CHECK_THROW(getChecksummedAddress("5aaeb6053f3e94c9b9a09f33669435e7ef1beaed1"), InvalidAddress);
+ // non-hex character
+ BOOST_CHECK_THROW(getChecksummedAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaeK"), InvalidAddress);
+
+ // the official test suite from EIP-55
+ vector<string> cases {
+ // all upper case
+ "0x52908400098527886E0F7030069857D2E4169EE7",
+ "0x8617E340B3D01FA5F11F306F4090FD50E238070D",
+ // all lower case
+ "0xde709f2102306220921060314715629080e2fb77",
+ "0x27b1fdb04752bbc536007a920d24acb045561c26",
+ // regular
+ "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
+ "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
+ "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
+ "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
+ };
+
+ for (size_t i = 0; i < cases.size(); i++)
+ BOOST_REQUIRE_MESSAGE(getChecksummedAddress(cases[i]) == cases[i], cases[i]);
+}
+
BOOST_AUTO_TEST_CASE(regular)
{
BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
diff --git a/test/libjulia/Common.cpp b/test/libjulia/Common.cpp
new file mode 100644
index 00000000..da1538f3
--- /dev/null
+++ b/test/libjulia/Common.cpp
@@ -0,0 +1,86 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Common functions the iulia tests.
+ */
+
+#include <test/libjulia/Common.h>
+
+#include <libjulia/optimiser/Disambiguator.h>
+
+#include <libsolidity/parsing/Scanner.h>
+
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmPrinter.h>
+
+#include <libsolidity/interface/SourceReferenceFormatter.h>
+#include <libsolidity/interface/ErrorReporter.h>
+
+#include <boost/test/unit_test.hpp>
+
+using namespace std;
+using namespace dev::julia;
+using namespace dev::solidity;
+
+void dev::julia::test::printErrors(ErrorList const& _errors, Scanner const& _scanner)
+{
+ for (auto const& error: _errors)
+ SourceReferenceFormatter::printExceptionInformation(
+ cout,
+ *error,
+ (error->type() == Error::Type::Warning) ? "Warning" : "Error",
+ [&](std::string const&) -> Scanner const& { return _scanner; }
+ );
+}
+
+
+pair<shared_ptr<Block>, shared_ptr<assembly::AsmAnalysisInfo>> dev::julia::test::parse(string const& _source, bool _julia)
+{
+ ErrorList errors;
+ ErrorReporter errorReporter(errors);
+ auto scanner = make_shared<Scanner>(CharStream(_source), "");
+ auto parserResult = assembly::Parser(errorReporter, _julia).parse(scanner);
+ if (parserResult)
+ {
+ BOOST_REQUIRE(errorReporter.errors().empty());
+ auto analysisInfo = make_shared<assembly::AsmAnalysisInfo>();
+ assembly::AsmAnalyzer analyzer(*analysisInfo, errorReporter, _julia);
+ if (analyzer.analyze(*parserResult))
+ {
+ BOOST_REQUIRE(errorReporter.errors().empty());
+ return make_pair(parserResult, analysisInfo);
+ }
+ }
+ printErrors(errors, *scanner);
+ BOOST_FAIL("Invalid source.");
+
+ // Unreachable.
+ return {};
+}
+
+assembly::Block dev::julia::test::disambiguate(string const& _source, bool _julia)
+{
+ auto result = parse(_source, _julia);
+ return boost::get<Block>(Disambiguator(*result.second)(*result.first));
+}
+
+string dev::julia::test::format(string const& _source, bool _julia)
+{
+ return assembly::AsmPrinter(_julia)(*parse(_source, _julia).first);
+}
diff --git a/test/libjulia/Common.h b/test/libjulia/Common.h
new file mode 100644
index 00000000..1371101c
--- /dev/null
+++ b/test/libjulia/Common.h
@@ -0,0 +1,55 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Common functions the iulia tests.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmData.h>
+
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+class Scanner;
+class Error;
+using ErrorList = std::vector<std::shared_ptr<Error const>>;
+namespace assembly
+{
+struct AsmAnalysisInfo;
+}
+}
+namespace julia
+{
+namespace test
+{
+
+void printErrors(solidity::ErrorList const& _errors, solidity::Scanner const& _scanner);
+std::pair<std::shared_ptr<solidity::assembly::Block>, std::shared_ptr<solidity::assembly::AsmAnalysisInfo>>
+parse(std::string const& _source, bool _julia = true);
+solidity::assembly::Block disambiguate(std::string const& _source, bool _julia = true);
+std::string format(std::string const& _source, bool _julia = true);
+
+}
+}
+}
diff --git a/test/libjulia/Disambiguator.cpp b/test/libjulia/Disambiguator.cpp
new file mode 100644
index 00000000..a6338449
--- /dev/null
+++ b/test/libjulia/Disambiguator.cpp
@@ -0,0 +1,105 @@
+/*
+ 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/>.
+*/
+/**
+ * @date 2017
+ * Unit tests for the iulia name disambiguator.
+ */
+
+#include <test/libjulia/Common.h>
+
+#include <libsolidity/inlineasm/AsmPrinter.h>
+
+#include <boost/test/unit_test.hpp>
+
+using namespace std;
+using namespace dev::julia::test;
+using namespace dev::solidity;
+
+#define CHECK(_original, _expectation)\
+do\
+{\
+ assembly::AsmPrinter p(true);\
+ string result = p(disambiguate(_original));\
+ BOOST_CHECK_EQUAL(result, format(_expectation));\
+ BOOST_CHECK_EQUAL(result, p(disambiguate(result)));\
+}\
+while(false)
+
+BOOST_AUTO_TEST_SUITE(IuliaDisambiguator)
+
+BOOST_AUTO_TEST_CASE(smoke_test)
+{
+ CHECK("{ }", "{ }");
+}
+
+BOOST_AUTO_TEST_CASE(variables)
+{
+ CHECK(
+ "{ { let a:u256 } { let a:u256 } }",
+ "{ { let a:u256 } { let a_1:u256 } }"
+ );
+}
+
+BOOST_AUTO_TEST_CASE(variables_clash)
+{
+ CHECK(
+ "{ { let a:u256 let a_1:u256 } { let a:u256 } }",
+ "{ { let a:u256 let a_1:u256 } { let a_2:u256 } }"
+ );
+}
+
+BOOST_AUTO_TEST_CASE(variables_inside_functions)
+{
+ CHECK(
+ "{ { let c:u256 let b:u256 } function f(a:u256, c:u256) -> b:u256 { let x:u256 } { let a:u256 let x:u256 } }",
+ "{ { let c:u256 let b:u256 } function f(a:u256, c_1:u256) -> b_1:u256 { let x:u256 } { let a_1:u256 let x_1:u256 } }"
+ );
+}
+
+BOOST_AUTO_TEST_CASE(function_call)
+{
+ CHECK(
+ "{ { let a:u256, b:u256, c:u256, d:u256, f:u256 } { function f(a:u256) -> c:u256, d:u256 { let b:u256, c_1:u256 := f(a) } } }",
+ "{ { let a:u256, b:u256, c:u256, d:u256, f:u256 } { function f_1(a_1:u256) -> c_1:u256, d_1:u256 { let b_1:u256, c_1_1:u256 := f_1(a_1) } } }"
+ );
+}
+
+BOOST_AUTO_TEST_CASE(for_statement)
+{
+ CHECK(
+ "{ { let a:u256, b:u256 } { for { let a:u256 } a { a := a } { let b:u256 := a } } }",
+ "{ { let a:u256, b:u256 } { for { let a_1:u256 } a_1 { a_1 := a_1 } { let b_1:u256 := a_1 } } }"
+ );
+}
+
+BOOST_AUTO_TEST_CASE(switch_statement)
+{
+ CHECK(
+ "{ { let a:u256, b:u256, c:u256 } { let a:u256 switch a case 0:u256 { let b:u256 := a } default { let c:u256 := a } } }",
+ "{ { let a:u256, b:u256, c:u256 } { let a_1:u256 switch a_1 case 0:u256 { let b_1:u256 := a_1 } default { let c_1:u256 := a_1 } } }"
+ );
+}
+
+BOOST_AUTO_TEST_CASE(if_statement)
+{
+ CHECK(
+ "{ { let a:u256, b:u256, c:u256 } { let a:bool if a { let b:bool := a } } }",
+ "{ { let a:u256, b:u256, c:u256 } { let a_1:bool if a_1 { let b_1:bool := a_1 } } }"
+ );
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp
index f8c1aa4d..9aa325a4 100644
--- a/test/libjulia/Parser.cpp
+++ b/test/libjulia/Parser.cpp
@@ -269,6 +269,21 @@ BOOST_AUTO_TEST_CASE(multiple_assignment)
BOOST_CHECK(successParse(text));
}
+BOOST_AUTO_TEST_CASE(if_statement)
+{
+ BOOST_CHECK(successParse("{ if true:bool {} }"));
+ BOOST_CHECK(successParse("{ if false:bool { let x:u256 := 3:u256 } }"));
+ BOOST_CHECK(successParse("{ function f() -> x:bool {} if f() { let b:bool := f() } }"));
+}
+
+BOOST_AUTO_TEST_CASE(if_statement_invalid)
+{
+ CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected.");
+ CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace");
+ // TODO change this to an error once we check types.
+ BOOST_CHECK(successParse("{ if 42:u256 { } }"));
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/ABIDecoderTests.cpp b/test/libsolidity/ABIDecoderTests.cpp
new file mode 100644
index 00000000..15c04b37
--- /dev/null
+++ b/test/libsolidity/ABIDecoderTests.cpp
@@ -0,0 +1,794 @@
+/*
+ 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 Solidity's ABI decoder.
+ */
+
+#include <functional>
+#include <string>
+#include <tuple>
+#include <boost/test/unit_test.hpp>
+#include <libsolidity/interface/Exceptions.h>
+#include <test/libsolidity/SolidityExecutionFramework.h>
+
+#include <test/libsolidity/ABITestsCommon.h>
+
+using namespace std;
+using namespace std::placeholders;
+using namespace dev::test;
+
+namespace dev
+{
+namespace solidity
+{
+namespace test
+{
+
+BOOST_FIXTURE_TEST_SUITE(ABIDecoderTest, SolidityExecutionFramework)
+
+BOOST_AUTO_TEST_CASE(both_encoders_macro)
+{
+ // This tests that the "both decoders macro" at least runs twice and
+ // modifies the source.
+ string sourceCode;
+ int runs = 0;
+ BOTH_ENCODERS(runs++;)
+ BOOST_CHECK(sourceCode == NewEncoderPragma);
+ BOOST_CHECK_EQUAL(runs, 2);
+}
+
+BOOST_AUTO_TEST_CASE(value_types)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool e, C g) public returns (uint) {
+ if (a != 1) return 1;
+ if (b != 2) return 2;
+ if (c != 3) return 3;
+ if (d != 4) return 4;
+ if (x != "abc") return 5;
+ if (e != true) return 6;
+ if (g != this) return 7;
+ return 20;
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunction(
+ "f(uint256,uint16,uint24,int24,bytes3,bool,address)",
+ 1, 2, 3, 4, string("abc"), true, u160(m_contractAddress)
+ ), encodeArgs(u256(20)));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(enums)
+{
+ string sourceCode = R"(
+ contract C {
+ enum E { A, B }
+ function f(E e) public pure returns (uint x) {
+ assembly { x := e }
+ }
+ }
+ )";
+ bool newDecoder = false;
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunction("f(uint8)", 0), encodeArgs(u256(0)));
+ ABI_CHECK(callContractFunction("f(uint8)", 1), encodeArgs(u256(1)));
+ // The old decoder was not as strict about enums
+ ABI_CHECK(callContractFunction("f(uint8)", 2), (newDecoder ? encodeArgs() : encodeArgs(2)));
+ ABI_CHECK(callContractFunction("f(uint8)", u256(-1)), (newDecoder? encodeArgs() : encodeArgs(u256(0xff))));
+ newDecoder = true;
+ )
+}
+
+BOOST_AUTO_TEST_CASE(cleanup)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint16 a, int16 b, address c, bytes3 d, bool e)
+ public pure returns (uint v, uint w, uint x, uint y, uint z) {
+ assembly { v := a w := b x := c y := d z := e}
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ ABI_CHECK(
+ callContractFunction("f(uint16,int16,address,bytes3,bool)", 1, 2, 3, "a", true),
+ encodeArgs(u256(1), u256(2), u256(3), string("a"), true)
+ );
+ ABI_CHECK(
+ callContractFunction(
+ "f(uint16,int16,address,bytes3,bool)",
+ u256(0xffffff), u256(0x1ffff), u256(-1), string("abcd"), u256(4)
+ ),
+ encodeArgs(u256(0xffff), u256(-1), (u256(1) << 160) - 1, string("abc"), true)
+ );
+ )
+}
+
+BOOST_AUTO_TEST_CASE(fixed_arrays)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint16[3] a, uint16[2][3] b, uint i, uint j, uint k)
+ public pure returns (uint, uint) {
+ return (a[i], b[j][k]);
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ bytes args = encodeArgs(
+ 1, 2, 3,
+ 11, 12,
+ 21, 22,
+ 31, 32,
+ 1, 2, 1
+ );
+ ABI_CHECK(
+ callContractFunction("f(uint16[3],uint16[2][3],uint256,uint256,uint256)", args),
+ encodeArgs(u256(2), u256(32))
+ );
+ )
+}
+
+BOOST_AUTO_TEST_CASE(dynamic_arrays)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint a, uint16[] b, uint c)
+ public pure returns (uint, uint, uint) {
+ return (b.length, b[a], c);
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ bytes args = encodeArgs(
+ 6, 0x60, 9,
+ 7,
+ 11, 12, 13, 14, 15, 16, 17
+ );
+ ABI_CHECK(
+ callContractFunction("f(uint256,uint16[],uint256)", args),
+ encodeArgs(u256(7), u256(17), u256(9))
+ );
+ )
+}
+
+BOOST_AUTO_TEST_CASE(dynamic_nested_arrays)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint a, uint16[][] b, uint[2][][3] c, uint d)
+ public pure returns (uint, uint, uint, uint, uint, uint, uint) {
+ return (a, b.length, b[1].length, b[1][1], c[1].length, c[1][1][1], d);
+ }
+ function test() view returns (uint, uint, uint, uint, uint, uint, uint) {
+ uint16[][] memory b = new uint16[][](3);
+ b[0] = new uint16[](2);
+ b[0][0] = 0x55;
+ b[0][1] = 0x56;
+ b[1] = new uint16[](4);
+ b[1][0] = 0x65;
+ b[1][1] = 0x66;
+ b[1][2] = 0x67;
+ b[1][3] = 0x68;
+
+ uint[2][][3] memory c;
+ c[0] = new uint[2][](1);
+ c[0][0][1] = 0x75;
+ c[1] = new uint[2][](5);
+ c[1][1][1] = 0x85;
+
+ return this.f(0x12, b, c, 0x13);
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode);
+ bytes args = encodeArgs(
+ 0x12, 4 * 0x20, 17 * 0x20, 0x13,
+ // b
+ 3, 3 * 0x20, 6 * 0x20, 11 * 0x20,
+ 2, 85, 86,
+ 4, 101, 102, 103, 104,
+ 0,
+ // c
+ 3 * 0x20, 6 * 0x20, 17 * 0x20,
+ 1, 0, 117,
+ 5, 0, 0, 0, 133, 0, 0, 0, 0, 0, 0,
+ 0
+ );
+
+ bytes expectation = encodeArgs(0x12, 3, 4, 0x66, 5, 0x85, 0x13);
+ ABI_CHECK(callContractFunction("test()"), expectation);
+ ABI_CHECK(callContractFunction("f(uint256,uint16[][],uint256[2][][3],uint256)", args), expectation);
+ )
+}
+
+BOOST_AUTO_TEST_CASE(byte_arrays)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint a, bytes b, uint c)
+ public pure returns (uint, uint, byte, uint) {
+ return (a, b.length, b[3], c);
+ }
+
+ function f_external(uint a, bytes b, uint c)
+ external pure returns (uint, uint, byte, uint) {
+ return (a, b.length, b[3], c);
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ bytes args = encodeArgs(
+ 6, 0x60, 9,
+ 7, "abcdefg"
+ );
+ ABI_CHECK(
+ callContractFunction("f(uint256,bytes,uint256)", args),
+ encodeArgs(u256(6), u256(7), "d", 9)
+ );
+ ABI_CHECK(
+ callContractFunction("f_external(uint256,bytes,uint256)", args),
+ encodeArgs(u256(6), u256(7), "d", 9)
+ );
+ )
+}
+
+BOOST_AUTO_TEST_CASE(calldata_arrays_too_large)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint a, uint[] b, uint c) external pure returns (uint) {
+ return 7;
+ }
+ }
+ )";
+ bool newEncoder = false;
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ bytes args = encodeArgs(
+ 6, 0x60, 9,
+ (u256(1) << 255) + 2, 1, 2
+ );
+ ABI_CHECK(
+ callContractFunction("f(uint256,uint256[],uint256)", args),
+ newEncoder ? encodeArgs() : encodeArgs(7)
+ );
+ newEncoder = true;
+ )
+}
+
+BOOST_AUTO_TEST_CASE(decode_from_memory_simple)
+{
+ string sourceCode = R"(
+ contract C {
+ uint public _a;
+ uint[] public _b;
+ function C(uint a, uint[] b) {
+ _a = a;
+ _b = b;
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode, 0, "C", encodeArgs(
+ 7, 0x40,
+ // b
+ 3, 0x21, 0x22, 0x23
+ ));
+ ABI_CHECK(callContractFunction("_a()"), encodeArgs(7));
+ ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21));
+ ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22));
+ ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23));
+ ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs());
+ )
+}
+
+BOOST_AUTO_TEST_CASE(decode_function_type)
+{
+ string sourceCode = R"(
+ contract D {
+ function () external returns (uint) public _a;
+ function D(function () external returns (uint) a) {
+ _a = a;
+ }
+ }
+ contract C {
+ function f() returns (uint) {
+ return 3;
+ }
+ function g(function () external returns (uint) _f) returns (uint) {
+ return _f();
+ }
+ // uses "decode from memory"
+ function test1() returns (uint) {
+ D d = new D(this.f);
+ return d._a()();
+ }
+ // uses "decode from calldata"
+ function test2() returns (uint) {
+ return this.g(this.f);
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("test1()"), encodeArgs(3));
+ ABI_CHECK(callContractFunction("test2()"), encodeArgs(3));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(decode_function_type_array)
+{
+ string sourceCode = R"(
+ contract D {
+ function () external returns (uint)[] public _a;
+ function D(function () external returns (uint)[] a) {
+ _a = a;
+ }
+ }
+ contract E {
+ function () external returns (uint)[3] public _a;
+ function E(function () external returns (uint)[3] a) {
+ _a = a;
+ }
+ }
+ contract C {
+ function f1() public returns (uint) {
+ return 1;
+ }
+ function f2() public returns (uint) {
+ return 2;
+ }
+ function f3() public returns (uint) {
+ return 3;
+ }
+ function g(function () external returns (uint)[] _f, uint i) public returns (uint) {
+ return _f[i]();
+ }
+ function h(function () external returns (uint)[3] _f, uint i) public returns (uint) {
+ return _f[i]();
+ }
+ // uses "decode from memory"
+ function test1_dynamic() public returns (uint) {
+ var x = new function() external returns (uint)[](3);
+ x[0] = this.f1;
+ x[1] = this.f2;
+ x[2] = this.f3;
+ D d = new D(x);
+ return d._a(2)();
+ }
+ function test1_static() public returns (uint) {
+ E e = new E([this.f1, this.f2, this.f3]);
+ return e._a(2)();
+ }
+ // uses "decode from calldata"
+ function test2_dynamic() public returns (uint) {
+ var x = new function() external returns (uint)[](3);
+ x[0] = this.f1;
+ x[1] = this.f2;
+ x[2] = this.f3;
+ return this.g(x, 0);
+ }
+ function test2_static() public returns (uint) {
+ return this.h([this.f1, this.f2, this.f3], 0);
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("test1_static()"), encodeArgs(3));
+ ABI_CHECK(callContractFunction("test1_dynamic()"), encodeArgs(3));
+ ABI_CHECK(callContractFunction("test2_static()"), encodeArgs(1));
+ ABI_CHECK(callContractFunction("test2_dynamic()"), encodeArgs(1));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(decode_from_memory_complex)
+{
+ string sourceCode = R"(
+ contract C {
+ uint public _a;
+ uint[] public _b;
+ bytes[2] public _c;
+ function C(uint a, uint[] b, bytes[2] c) {
+ _a = a;
+ _b = b;
+ _c = c;
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C", encodeArgs(
+ 7, 0x60, 7 * 0x20,
+ // b
+ 3, 0x21, 0x22, 0x23,
+ // c
+ 0x40, 0x80,
+ 8, string("abcdefgh"),
+ 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ ));
+ ABI_CHECK(callContractFunction("_a()"), encodeArgs(7));
+ ABI_CHECK(callContractFunction("_b(uint256)", 0), encodeArgs(0x21));
+ ABI_CHECK(callContractFunction("_b(uint256)", 1), encodeArgs(0x22));
+ ABI_CHECK(callContractFunction("_b(uint256)", 2), encodeArgs(0x23));
+ ABI_CHECK(callContractFunction("_b(uint256)", 3), encodeArgs());
+ ABI_CHECK(callContractFunction("_c(uint256)", 0), encodeArgs(0x20, 8, string("abcdefgh")));
+ ABI_CHECK(callContractFunction("_c(uint256)", 1), encodeArgs(0x20, 52, string("ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ")));
+ ABI_CHECK(callContractFunction("_c(uint256)", 2), encodeArgs());
+ )
+}
+
+BOOST_AUTO_TEST_CASE(short_input_value_type)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint a, uint b) public pure returns (uint) { return a; }
+ }
+ )";
+ bool newDecoder = false;
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunction("f(uint256,uint256)", 1, 2), encodeArgs(1));
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(64, 0)), encodeArgs(0));
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256,uint256)", bytes(63, 0)), newDecoder ? encodeArgs() : encodeArgs(0));
+ newDecoder = true;
+ )
+}
+
+BOOST_AUTO_TEST_CASE(short_input_array)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(uint[] a) public pure returns (uint) { return 7; }
+ }
+ )";
+ bool newDecoder = false;
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 0)), encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1)), newDecoder ? encodeArgs() : encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(31, 0)), newDecoder ? encodeArgs() : encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 1) + bytes(32, 0)), encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("f(uint256[])", encodeArgs(0x20, 2, 5, 6)), encodeArgs(7));
+ newDecoder = true;
+ )
+}
+
+BOOST_AUTO_TEST_CASE(short_dynamic_input_array)
+{
+ string sourceCode = R"(
+ contract C {
+ function f(bytes[1] a) public pure returns (uint) { return 7; }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunctionNoEncoding("f(bytes[1])", encodeArgs(0x20)), encodeArgs());
+ )
+}
+
+BOOST_AUTO_TEST_CASE(short_input_bytes)
+{
+ string sourceCode = R"(
+ contract C {
+ function e(bytes a) public pure returns (uint) { return 7; }
+ function f(bytes[] a) public pure returns (uint) { return 7; }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(5, 0)), encodeArgs());
+ ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(6, 0)), encodeArgs());
+ ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(7, 0)), encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("e(bytes)", encodeArgs(0x20, 7) + bytes(8, 0)), encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(5, 0)), encodeArgs());
+ ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(6, 0)), encodeArgs());
+ ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(7, 0)), encodeArgs(7));
+ ABI_CHECK(callContractFunctionNoEncoding("f(bytes[])", encodeArgs(0x20, 1, 0x20, 7) + bytes(8, 0)), encodeArgs(7));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(cleanup_int_inside_arrays)
+{
+ string sourceCode = R"(
+ contract C {
+ enum E { A, B }
+ function f(uint16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } }
+ function g(int16[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } }
+ function h(E[] a) public pure returns (uint r) { assembly { r := mload(add(a, 0x20)) } }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode);
+ ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, 7), encodeArgs(7));
+ ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, 7), encodeArgs(7));
+ ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256("0xffff")));
+ ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0xffff")), encodeArgs(u256(-1)));
+ ABI_CHECK(callContractFunction("f(uint16[])", 0x20, 1, u256("0x1ffff")), encodeArgs(u256("0xffff")));
+ ABI_CHECK(callContractFunction("g(int16[])", 0x20, 1, u256("0x10fff")), encodeArgs(u256("0x0fff")));
+ ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 0), encodeArgs(u256(0)));
+ ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 1), encodeArgs(u256(1)));
+ ABI_CHECK(callContractFunction("h(uint8[])", 0x20, 1, 2), encodeArgs());
+ )
+}
+
+BOOST_AUTO_TEST_CASE(storage_ptr)
+{
+ string sourceCode = R"(
+ library L {
+ struct S { uint x; uint y; }
+ function f(uint[] storage r, S storage s) public returns (uint, uint, uint, uint) {
+ r[2] = 8;
+ s.x = 7;
+ return (r[0], r[1], s.x, s.y);
+ }
+ }
+ contract C {
+ uint8 x = 3;
+ L.S s;
+ uint[] r;
+ function f() public returns (uint, uint, uint, uint, uint, uint) {
+ r.length = 6;
+ r[0] = 1;
+ r[1] = 2;
+ r[2] = 3;
+ s.x = 11;
+ s.y = 12;
+ var (a, b, c, d) = L.f(r, s);
+ return (r[2], s.x, a, b, c, d);
+ }
+ }
+ )";
+ BOTH_ENCODERS(
+ compileAndRun(sourceCode, 0, "L");
+ compileAndRun(sourceCode, 0, "C", bytes(), map<string, Address>{{"L", m_contractAddress}});
+ ABI_CHECK(callContractFunction("f()"), encodeArgs(8, 7, 1, 2, 7, 12));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(struct_simple)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { uint a; uint8 b; uint8 c; bytes2 d; }
+ function f(S s) public pure returns (uint a, uint b, uint c, uint d) {
+ a = s.a;
+ b = s.b;
+ c = s.c;
+ d = uint(s.d);
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("f((uint256,uint8,uint8,bytes2))", 1, 2, 3, "ab"), encodeArgs(1, 2, 3, 'a' * 0x100 + 'b'));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(struct_cleanup)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { int16 a; uint8 b; bytes2 c; }
+ function f(S s) public pure returns (uint a, uint b, uint c) {
+ assembly {
+ a := mload(s)
+ b := mload(add(s, 0x20))
+ c := mload(add(s, 0x40))
+ }
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(
+ callContractFunction("f((int16,uint8,bytes2))", 0xff010, 0xff0002, "abcd"),
+ encodeArgs(u256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff010"), 2, "ab")
+ );
+ )
+}
+
+BOOST_AUTO_TEST_CASE(struct_short)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { int a; uint b; bytes16 c; }
+ function f(S s) public pure returns (S q) {
+ q = s;
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(
+ callContractFunction("f((int256,uint256,bytes16))", 0xff010, 0xff0002, "abcd"),
+ encodeArgs(0xff010, 0xff0002, "abcd")
+ );
+ ABI_CHECK(
+ callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(32, 0)),
+ encodeArgs(0xff010, 0xff0002, 0)
+ );
+ ABI_CHECK(
+ callContractFunctionNoEncoding("f((int256,uint256,bytes16))", encodeArgs(0xff010, 0xff0002) + bytes(31, 0)),
+ encodeArgs()
+ );
+ )
+}
+
+BOOST_AUTO_TEST_CASE(struct_function)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { function () external returns (uint) f; uint b; }
+ function f(S s) public returns (uint, uint) {
+ return (s.f(), s.b);
+ }
+ function test() public returns (uint, uint) {
+ return this.f(S(this.g, 3));
+ }
+ function g() public returns (uint) { return 7; }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("test()"), encodeArgs(7, 3));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(empty_struct)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { }
+ function f(uint a, S s, uint b) public pure returns (uint x, uint y) {
+ assembly { x := a y := b }
+ }
+ function g() public returns (uint, uint) {
+ return this.f(7, S(), 8);
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("f(uint256,(),uint256)", 7, 8), encodeArgs(7, 8));
+ ABI_CHECK(callContractFunction("g()"), encodeArgs(7, 8));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(mediocre_struct)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { C c; }
+ function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) {
+ r1 = a;
+ r2 = s1[0].c;
+ r3 = b;
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ string sig = "f(uint256,(address)[2],uint256)";
+ ABI_CHECK(callContractFunction(sig,
+ 7, u256(u160(m_contractAddress)), 0, 8
+ ), encodeArgs(7, u256(u160(m_contractAddress)), 8));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(mediocre2_struct)
+{
+ string sourceCode = R"(
+ contract C {
+ struct S { C c; uint[] x; }
+ function f(uint a, S[2] s1, uint b) public returns (uint r1, C r2, uint r3) {
+ r1 = a;
+ r2 = s1[0].c;
+ r3 = b;
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ string sig = "f(uint256,(address,uint256[])[2],uint256)";
+ ABI_CHECK(callContractFunction(sig,
+ 7, 0x60, 8,
+ 0x40, 7 * 0x20,
+ u256(u160(m_contractAddress)), 0x40,
+ 2, 0x11, 0x12,
+ 0x99, 0x40,
+ 4, 0x31, 0x32, 0x34, 0x35
+ ), encodeArgs(7, u256(u160(m_contractAddress)), 8));
+ )
+}
+
+BOOST_AUTO_TEST_CASE(complex_struct)
+{
+ string sourceCode = R"(
+ contract C {
+ enum E {A, B, C}
+ struct T { uint x; E e; uint8 y; }
+ struct S { C c; T[] t;}
+ function f(uint a, S[2] s1, S[] s2, uint b) public returns
+ (uint r1, C r2, uint r3, uint r4, C r5, uint r6, E r7, uint8 r8) {
+ r1 = a;
+ r2 = s1[0].c;
+ r3 = b;
+ r4 = s2.length;
+ r5 = s2[1].c;
+ r6 = s2[1].t.length;
+ r7 = s2[1].t[1].e;
+ r8 = s2[1].t[1].y;
+ }
+ }
+ )";
+ NEW_ENCODER(
+ compileAndRun(sourceCode, 0, "C");
+ string sig = "f(uint256,(address,(uint256,uint8,uint8)[])[2],(address,(uint256,uint8,uint8)[])[],uint256)";
+ bytes args = encodeArgs(
+ 7, 0x80, 0x1e0, 8,
+ // S[2] s1
+ 0x40,
+ 0x100,
+ // S s1[0]
+ u256(u160(m_contractAddress)),
+ 0x40,
+ // T s1[0].t
+ 1, // length
+ // s1[0].t[0]
+ 0x11, 1, 0x12,
+ // S s1[1]
+ 0, 0x40,
+ // T s1[1].t
+ 0,
+ // S[] s2 (0x1e0)
+ 2, // length
+ 0x40, 0xa0,
+ // S s2[0]
+ 0, 0x40, 0,
+ // S s2[1]
+ 0x1234, 0x40,
+ // s2[1].t
+ 3, // length
+ 0, 0, 0,
+ 0x21, 2, 0x22,
+ 0, 0, 0
+ );
+ ABI_CHECK(callContractFunction(sig, args), encodeArgs(7, u256(u160(m_contractAddress)), 8, 2, 0x1234, 3, 2, 0x22));
+ // invalid enum value
+ args.data()[0x20 * 28] = 3;
+ ABI_CHECK(callContractFunction(sig, args), encodeArgs());
+ )
+}
+
+
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
+} // end namespaces
diff --git a/test/libsolidity/ABIEncoderTests.cpp b/test/libsolidity/ABIEncoderTests.cpp
index af51edcc..49db9ce1 100644
--- a/test/libsolidity/ABIEncoderTests.cpp
+++ b/test/libsolidity/ABIEncoderTests.cpp
@@ -25,6 +25,8 @@
#include <libsolidity/interface/Exceptions.h>
#include <test/libsolidity/SolidityExecutionFramework.h>
+#include <test/libsolidity/ABITestsCommon.h>
+
using namespace std;
using namespace std::placeholders;
using namespace dev::test;
@@ -42,20 +44,6 @@ namespace test
BOOST_CHECK_EQUAL(toHex(m_logs[0].data), toHex(DATA)); \
} while (false)
-static string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n";
-
-#define NEW_ENCODER(CODE) \
-{ \
- sourceCode = NewEncoderPragma + sourceCode; \
- { CODE } \
-}
-
-#define BOTH_ENCODERS(CODE) \
-{ \
- { CODE } \
- NEW_ENCODER(CODE) \
-}
-
BOOST_FIXTURE_TEST_SUITE(ABIEncoderTest, SolidityExecutionFramework)
BOOST_AUTO_TEST_CASE(both_encoders_macro)
@@ -74,7 +62,7 @@ BOOST_AUTO_TEST_CASE(value_types)
string sourceCode = R"(
contract C {
event E(uint a, uint16 b, uint24 c, int24 d, bytes3 x, bool, C);
- function f() {
+ function f() public {
bytes6 x = hex"1bababababa2";
bool b;
assembly { b := 7 }
@@ -98,7 +86,7 @@ BOOST_AUTO_TEST_CASE(string_literal)
string sourceCode = R"(
contract C {
event E(string, bytes20, string);
- function f() {
+ function f() public {
E("abcdef", "abcde", "abcdefabcdefgehabcabcasdfjklabcdefabcedefghabcabcasdfjklabcdefabcdefghabcabcasdfjklabcdeefabcdefghabcabcasdefjklabcdefabcdefghabcabcasdfjkl");
}
}
@@ -120,7 +108,7 @@ BOOST_AUTO_TEST_CASE(enum_type_cleanup)
string sourceCode = R"(
contract C {
enum E { A, B }
- function f(uint x) returns (E en) {
+ function f(uint x) public returns (E en) {
assembly { en := x }
}
}
@@ -138,7 +126,7 @@ BOOST_AUTO_TEST_CASE(conversion)
string sourceCode = R"(
contract C {
event E(bytes4, bytes4, uint16, uint8, int16, int8);
- function f() {
+ function f() public {
bytes2 x; assembly { x := 0xf1f2f3f400000000000000000000000000000000000000000000000000000000 }
uint8 a;
uint16 b = 0x1ff;
@@ -164,7 +152,7 @@ BOOST_AUTO_TEST_CASE(memory_array_one_dim)
string sourceCode = R"(
contract C {
event E(uint a, int16[] b, uint c);
- function f() {
+ function f() public {
int16[] memory x = new int16[](3);
assembly {
for { let i := 0 } lt(i, 3) { i := add(i, 1) } {
@@ -191,7 +179,7 @@ BOOST_AUTO_TEST_CASE(memory_array_two_dim)
string sourceCode = R"(
contract C {
event E(uint a, int16[][2] b, uint c);
- function f() {
+ function f() public {
int16[][2] memory x;
x[0] = new int16[](3);
x[1] = new int16[](2);
@@ -216,7 +204,7 @@ BOOST_AUTO_TEST_CASE(memory_byte_array)
string sourceCode = R"(
contract C {
event E(uint a, bytes[] b, uint c);
- function f() {
+ function f() public {
bytes[] memory x = new bytes[](2);
x[0] = "abcabcdefghjklmnopqrsuvwabcdefgijklmnopqrstuwabcdefgijklmnoprstuvw";
x[1] = "abcdefghijklmnopqrtuvwabcfghijklmnopqstuvwabcdeghijklmopqrstuvw";
@@ -243,7 +231,7 @@ BOOST_AUTO_TEST_CASE(storage_byte_array)
bytes short;
bytes long;
event E(bytes s, bytes l);
- function f() {
+ function f() public {
short = "123456789012345678901234567890a";
long = "ffff123456789012345678901234567890afffffffff123456789012345678901234567890a";
E(short, long);
@@ -267,7 +255,7 @@ BOOST_AUTO_TEST_CASE(storage_array)
contract C {
address[3] addr;
event E(address[3] a);
- function f() {
+ function f() public {
assembly {
sstore(0, sub(0, 1))
sstore(1, sub(0, 2))
@@ -290,7 +278,7 @@ BOOST_AUTO_TEST_CASE(storage_array_dyn)
contract C {
address[] addr;
event E(address[] a);
- function f() {
+ function f() public {
addr.push(1);
addr.push(2);
addr.push(3);
@@ -311,7 +299,7 @@ BOOST_AUTO_TEST_CASE(storage_array_compact)
contract C {
int72[] x;
event E(int72[]);
- function f() {
+ function f() public {
x.push(-1);
x.push(2);
x.push(-3);
@@ -339,7 +327,7 @@ BOOST_AUTO_TEST_CASE(external_function)
contract C {
event E(function(uint) external returns (uint), function(uint) external returns (uint));
function(uint) external returns (uint) g;
- function f(uint) returns (uint) {
+ function f(uint) public returns (uint) {
g = this.f;
E(this.f, g);
}
@@ -347,7 +335,7 @@ BOOST_AUTO_TEST_CASE(external_function)
)";
BOTH_ENCODERS(
compileAndRun(sourceCode);
- callContractFunction("f(uint256)");
+ callContractFunction("f(uint256)", u256(0));
string functionIdF = asString(m_contractAddress.ref()) + asString(FixedHash<4>(dev::keccak256("f(uint256)")).ref());
REQUIRE_LOG_DATA(encodeArgs(functionIdF, functionIdF));
)
@@ -360,7 +348,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup)
event E(function(uint) external returns (uint), function(uint) external returns (uint));
// This test relies on the fact that g is stored in slot zero.
function(uint) external returns (uint) g;
- function f(uint) returns (uint) {
+ function f(uint) public returns (uint) {
function(uint) external returns (uint)[1] memory h;
assembly { sstore(0, sub(0, 1)) mstore(h, sub(0, 1)) }
E(h[0], g);
@@ -369,7 +357,7 @@ BOOST_AUTO_TEST_CASE(external_function_cleanup)
)";
BOTH_ENCODERS(
compileAndRun(sourceCode);
- callContractFunction("f(uint256)");
+ callContractFunction("f(uint256)", u256(0));
REQUIRE_LOG_DATA(encodeArgs(string(24, char(-1)), string(24, char(-1))));
)
}
@@ -404,7 +392,7 @@ BOOST_AUTO_TEST_CASE(function_name_collision)
// and by the ABI encoder
string sourceCode = R"(
contract C {
- function f(uint x) returns (uint) {
+ function f(uint x) public returns (uint) {
assembly {
function abi_encode_t_uint256_to_t_uint256() {
mstore(0, 7)
@@ -432,7 +420,7 @@ BOOST_AUTO_TEST_CASE(structs)
struct T { uint64[2] x; }
S s;
event e(uint16, S);
- function f() returns (uint, S) {
+ function f() public returns (uint, S) {
uint16 x = 7;
s.a = 8;
s.b = 9;
diff --git a/test/libsolidity/ABITestsCommon.h b/test/libsolidity/ABITestsCommon.h
new file mode 100644
index 00000000..2ef555f3
--- /dev/null
+++ b/test/libsolidity/ABITestsCommon.h
@@ -0,0 +1,43 @@
+/*
+ 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 <string>
+
+namespace dev
+{
+namespace solidity
+{
+namespace test
+{
+
+static std::string const NewEncoderPragma = "pragma experimental ABIEncoderV2;\n";
+
+#define NEW_ENCODER(CODE) \
+{ \
+ sourceCode = NewEncoderPragma + sourceCode; \
+ { CODE } \
+}
+
+#define BOTH_ENCODERS(CODE) \
+{ \
+ { CODE } \
+ NEW_ENCODER(CODE) \
+}
+
+}
+}
+} // end namespaces
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index da3522b4..82bafd49 100644
--- a/test/libsolidity/InlineAssembly.cpp
+++ b/test/libsolidity/InlineAssembly.cpp
@@ -251,6 +251,27 @@ BOOST_AUTO_TEST_CASE(variable_use_before_decl)
CHECK_PARSE_ERROR("{ let x := mul(2, x) }", DeclarationError, "Variable x used before it was declared.");
}
+BOOST_AUTO_TEST_CASE(if_statement)
+{
+ BOOST_CHECK(successParse("{ if 42 {} }"));
+ BOOST_CHECK(successParse("{ if 42 { let x := 3 } }"));
+ BOOST_CHECK(successParse("{ function f() -> x {} if f() { pop(f()) } }"));
+}
+
+BOOST_AUTO_TEST_CASE(if_statement_scope)
+{
+ BOOST_CHECK(successParse("{ let x := 2 if 42 { x := 3 } }"));
+ CHECK_PARSE_ERROR("{ if 32 { let x := 3 } x := 2 }", DeclarationError, "Variable not found or variable not lvalue.");
+}
+
+BOOST_AUTO_TEST_CASE(if_statement_invalid)
+{
+ CHECK_PARSE_ERROR("{ if calldatasize {}", ParserError, "Instructions are not supported as conditions for if");
+ BOOST_CHECK("{ if calldatasize() {}");
+ CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
+ CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace");
+}
+
BOOST_AUTO_TEST_CASE(switch_statement)
{
BOOST_CHECK(successParse("{ switch 42 default {} }"));
@@ -275,7 +296,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case)
BOOST_AUTO_TEST_CASE(switch_invalid_expression)
{
CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected.");
- CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch.");
+ CHECK_PARSE_ERROR("{ switch calldatasize default {} }", ParserError, "Instructions are not supported as expressions for switch");
CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
}
@@ -487,6 +508,11 @@ BOOST_AUTO_TEST_CASE(print_string_literal_unicode)
parsePrintCompare(parsed);
}
+BOOST_AUTO_TEST_CASE(print_if)
+{
+ parsePrintCompare("{\n if 2\n {\n pop(mload(0))\n }\n}");
+}
+
BOOST_AUTO_TEST_CASE(print_switch)
{
parsePrintCompare("{\n switch 42\n case 1 {\n }\n case 2 {\n }\n default {\n }\n}");
@@ -628,6 +654,11 @@ BOOST_AUTO_TEST_CASE(for_statement)
BOOST_CHECK(successAssemble("{ let x := calldatasize() for { let i := 0} lt(i, x) { i := add(i, 1) } { mstore(i, 2) } }"));
}
+BOOST_AUTO_TEST_CASE(if_statement)
+{
+ BOOST_CHECK(successAssemble("{ if 1 {} }"));
+ BOOST_CHECK(successAssemble("{ let x := 0 if eq(calldatasize(), 0) { x := 1 } mstore(0, x) }"));
+}
BOOST_AUTO_TEST_CASE(large_constant)
{
@@ -681,8 +712,9 @@ BOOST_AUTO_TEST_CASE(jump_warning)
{
CHECK_PARSE_WARNING("{ 1 jump }", Warning, "Jump instructions");
CHECK_PARSE_WARNING("{ 1 2 jumpi }", Warning, "Jump instructions");
- CHECK_PARSE_WARNING("{ a: jump(a) }", Warning, "Jump instructions");
- CHECK_PARSE_WARNING("{ a: jumpi(a, 2) }", Warning, "Jump instructions");
+ CHECK_PARSE_WARNING("{ jump(44) }", Warning, "Jump instructions");
+ CHECK_PARSE_WARNING("{ jumpi(44, 2) }", Warning, "Jump instructions");
+ CHECK_PARSE_WARNING("{ a: }", Warning, "Jump instructions");
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/libsolidity/JSONCompiler.cpp b/test/libsolidity/JSONCompiler.cpp
index 7dc4808b..0c904c77 100644
--- a/test/libsolidity/JSONCompiler.cpp
+++ b/test/libsolidity/JSONCompiler.cpp
@@ -23,7 +23,7 @@
#include <boost/test/unit_test.hpp>
#include <libdevcore/JSON.h>
#include <libsolidity/interface/Version.h>
-#include <solc/jsonCompiler.h>
+#include <libsolc/libsolc.h>
#include "../Metadata.h"
#include "../TestHelper.h"
diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp
index 8d712a80..3a65aa43 100644
--- a/test/libsolidity/SMTChecker.cpp
+++ b/test/libsolidity/SMTChecker.cpp
@@ -94,6 +94,7 @@ BOOST_AUTO_TEST_CASE(warn_on_typecast)
BOOST_AUTO_TEST_CASE(warn_on_struct)
{
string text = R"(
+ pragma experimental ABIEncoderV2;
contract C {
struct A { uint a; uint b; }
function f() public pure returns (A) {
@@ -105,6 +106,453 @@ BOOST_AUTO_TEST_CASE(warn_on_struct)
CHECK_WARNING_ALLOW_MULTI(text, "");
}
+BOOST_AUTO_TEST_CASE(simple_assert)
+{
+ string text = R"(
+ contract C {
+ function f(uint a) public pure { assert(a == 2); }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here for");
+}
+
+BOOST_AUTO_TEST_CASE(simple_assert_with_require)
+{
+ string text = R"(
+ contract C {
+ function f(uint a) public pure { require(a < 10); assert(a < 20); }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(assignment_in_declaration)
+{
+ string text = R"(
+ contract C {
+ function f() public pure { uint a = 2; assert(a == 2); }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(use_before_declaration)
+{
+ string text = R"(
+ contract C {
+ function f() public pure { a = 3; uint a = 2; assert(a == 2); }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f() public pure { assert(a == 0); uint a = 2; assert(a == 2); }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(function_call_does_not_clear_local_vars)
+{
+ string text = R"(
+ contract C {
+ function f() public {
+ uint a = 3;
+ this.f();
+ assert(a == 3);
+ f();
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(branches_clear_variables)
+{
+ // Only clears accessed variables
+ string text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ // It is just a plain clear and will not combine branches.
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ a = 3;
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+ // Clear also works on the else branch
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ } else {
+ a = 3;
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+ // Variable is not cleared, if it is only read.
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ assert(a == 3);
+ } else {
+ assert(a == 3);
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(branches_assert_condition)
+{
+ string text = R"(
+ contract C {
+ function f(uint x) public pure {
+ if (x > 10) {
+ assert(x > 9);
+ }
+ else
+ {
+ assert(x < 11);
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ if (x > 10) {
+ assert(x > 9);
+ }
+ else if (x > 2)
+ {
+ assert(x <= 10 && x > 2);
+ }
+ else
+ {
+ assert(0 <= x && x <= 2);
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(ways_to_clear_variables)
+{
+ string text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ a++;
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ ++a;
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ uint a = 3;
+ if (x > 10) {
+ a = 5;
+ }
+ assert(a == 3);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+}
+
+BOOST_AUTO_TEST_CASE(while_loop_simple)
+{
+ // Check that variables are cleared
+ string text = R"(
+ contract C {
+ function f(uint x) public pure {
+ x = 2;
+ while (x > 1) {
+ x = 2;
+ }
+ assert(x == 2);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+ // Check that condition is assumed.
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ while (x == 2) {
+ assert(x == 2);
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ // Check that condition is not assumed after the body anymore
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ while (x == 2) {
+ }
+ assert(x == 2);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+ // Check that negation of condition is not assumed after the body anymore
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ while (x == 2) {
+ }
+ assert(x != 2);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+ // Check that side-effects of condition are taken into account
+ text = R"(
+ contract C {
+ function f(uint x, uint y) public pure {
+ x = 7;
+ while ((x = y) > 0) {
+ }
+ assert(x == 7);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation happens here");
+}
+
+BOOST_AUTO_TEST_CASE(constant_condition)
+{
+ string text = R"(
+ contract C {
+ function f(uint x) public pure {
+ if (x >= 0) { revert(); }
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Condition is always true");
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ if (x >= 10) { if (x < 10) { revert(); } }
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Condition is always false");
+ // a plain literal constant is fine
+ text = R"(
+ contract C {
+ function f(uint) public pure {
+ if (true) { revert(); }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+
+BOOST_AUTO_TEST_CASE(for_loop)
+{
+ string text = R"(
+ contract C {
+ function f(uint x) public pure {
+ require(x == 2);
+ for (;;) {}
+ assert(x == 2);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ for (; x == 2; ) {
+ assert(x == 2);
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ for (uint y = 2; x < 10; ) {
+ assert(y == 2);
+ }
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ for (uint y = 2; x < 10; y = 3) {
+ assert(y == 2);
+ }
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation");
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ for (uint y = 2; x < 10; ) {
+ y = 3;
+ }
+ assert(y == 3);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation");
+ text = R"(
+ contract C {
+ function f(uint x) public pure {
+ for (uint y = 2; x < 10; ) {
+ y = 3;
+ }
+ assert(y == 2);
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Assertion violation");
+}
+
+BOOST_AUTO_TEST_CASE(division)
+{
+ string text = R"(
+ contract C {
+ function f(uint x, uint y) public pure returns (uint) {
+ return x / y;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Division by zero");
+ text = R"(
+ contract C {
+ function f(uint x, uint y) public pure returns (uint) {
+ require(y != 0);
+ return x / y;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(int x, int y) public pure returns (int) {
+ require(y != 0);
+ return x / y;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "Overflow");
+ text = R"(
+ contract C {
+ function f(int x, int y) public pure returns (int) {
+ require(y != 0);
+ require(y != -1);
+ return x / y;
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
+BOOST_AUTO_TEST_CASE(division_truncates_correctly)
+{
+ string text = R"(
+ contract C {
+ function f(uint x, uint y) public pure {
+ x = 7;
+ y = 2;
+ assert(x / y == 3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(int x, int y) public pure {
+ x = 7;
+ y = 2;
+ assert(x / y == 3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(int x, int y) public pure {
+ x = -7;
+ y = 2;
+ assert(x / y == -3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(int x, int y) public pure {
+ x = 7;
+ y = -2;
+ assert(x / y == -3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+ text = R"(
+ contract C {
+ function f(int x, int y) public pure {
+ x = -7;
+ y = -2;
+ assert(x / y == 3);
+ }
+ }
+ )";
+ CHECK_SUCCESS_NO_WARNINGS(text);
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp
index 33962730..26bfb6d0 100644
--- a/test/libsolidity/SolidityABIJSON.cpp
+++ b/test/libsolidity/SolidityABIJSON.cpp
@@ -942,6 +942,7 @@ BOOST_AUTO_TEST_CASE(function_type)
BOOST_AUTO_TEST_CASE(return_structs)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
contract C {
struct S { uint a; T[] sub; }
struct T { uint[2] x; }
@@ -991,6 +992,7 @@ BOOST_AUTO_TEST_CASE(return_structs)
BOOST_AUTO_TEST_CASE(return_structs_with_contracts)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
contract C {
struct S { C[] x; C y; }
function f() returns (S s, C c) {
@@ -1090,6 +1092,7 @@ BOOST_AUTO_TEST_CASE(event_structs)
BOOST_AUTO_TEST_CASE(structs_in_libraries)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
library L {
struct S { uint a; T[] sub; bytes b; }
struct T { uint[2] x; }
diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp
index 7082b702..f5f7e64a 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -2345,6 +2345,24 @@ BOOST_AUTO_TEST_CASE(constructor_static_array_argument)
ABI_CHECK(callContractFunction("b(uint256)", u256(2)), encodeArgs(u256(4)));
}
+BOOST_AUTO_TEST_CASE(constant_var_as_array_length)
+{
+ char const* sourceCode = R"(
+ contract C {
+ uint constant LEN = 3;
+ uint[LEN] public a;
+
+ function C(uint[LEN] _a) {
+ a = _a;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C", encodeArgs(u256(1), u256(2), u256(3)));
+ ABI_CHECK(callContractFunction("a(uint256)", u256(0)), encodeArgs(u256(1)));
+ ABI_CHECK(callContractFunction("a(uint256)", u256(1)), encodeArgs(u256(2)));
+ ABI_CHECK(callContractFunction("a(uint256)", u256(2)), encodeArgs(u256(3)));
+}
+
BOOST_AUTO_TEST_CASE(functions_called_by_constructor)
{
char const* sourceCode = R"(
@@ -8014,6 +8032,24 @@ BOOST_AUTO_TEST_CASE(inline_assembly_embedded_function_call)
ABI_CHECK(callContractFunction("f()"), encodeArgs(u256(1), u256(4), u256(7), u256(0x10)));
}
+BOOST_AUTO_TEST_CASE(inline_assembly_if)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f(uint a) returns (uint b) {
+ assembly {
+ if gt(a, 1) { b := 2 }
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ ABI_CHECK(callContractFunction("f(uint256)", u256(0)), encodeArgs(u256(0)));
+ ABI_CHECK(callContractFunction("f(uint256)", u256(1)), encodeArgs(u256(0)));
+ ABI_CHECK(callContractFunction("f(uint256)", u256(2)), encodeArgs(u256(2)));
+ ABI_CHECK(callContractFunction("f(uint256)", u256(3)), encodeArgs(u256(2)));
+}
+
BOOST_AUTO_TEST_CASE(inline_assembly_switch)
{
char const* sourceCode = R"(
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index 30624260..39dba0de 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -602,6 +602,7 @@ BOOST_AUTO_TEST_CASE(enum_external_type)
BOOST_AUTO_TEST_CASE(external_structs)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
contract Test {
enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
struct Empty {}
@@ -629,6 +630,7 @@ BOOST_AUTO_TEST_CASE(external_structs)
BOOST_AUTO_TEST_CASE(external_structs_in_libraries)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
library Test {
enum ActionChoices { GoLeft, GoRight, GoStraight, Sit }
struct Empty {}
@@ -1038,7 +1040,7 @@ BOOST_AUTO_TEST_CASE(function_modifier_double_invocation)
modifier mod(uint a) { if (a > 0) _; }
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(base_constructor_double_invocation)
@@ -1392,7 +1394,7 @@ BOOST_AUTO_TEST_CASE(events_with_same_name)
event A(uint i);
}
)";
- BOOST_CHECK(success(text));
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(events_with_same_name_unnamed_arguments)
@@ -2107,7 +2109,7 @@ BOOST_AUTO_TEST_CASE(array_with_nonconstant_length)
function f(uint a) public { uint8[a] x; }
}
)";
- CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal.");
+ CHECK_ERROR(text, TypeError, "Identifier must be declared constant.");
}
BOOST_AUTO_TEST_CASE(array_with_negative_length)
@@ -3511,6 +3513,7 @@ BOOST_AUTO_TEST_CASE(using_for_not_used)
BOOST_AUTO_TEST_CASE(library_memory_struct)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
library c {
struct S { uint x; }
function f() public returns (S ) {}
@@ -4033,7 +4036,7 @@ BOOST_AUTO_TEST_CASE(varM_disqualified_as_keyword)
}
}
)";
- BOOST_CHECK(!success(text));
+ CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique.");
}
BOOST_AUTO_TEST_CASE(modifier_is_not_a_valid_typename)
@@ -4074,7 +4077,7 @@ BOOST_AUTO_TEST_CASE(long_uint_variable_fails)
}
}
)";
- BOOST_CHECK(!success(text));
+ CHECK_ERROR(text, DeclarationError, "Identifier not found or not unique.");
}
BOOST_AUTO_TEST_CASE(bytes10abc_is_identifier)
@@ -4113,7 +4116,7 @@ BOOST_AUTO_TEST_CASE(library_functions_do_not_have_value)
}
}
)";
- BOOST_CHECK(!success(text));
+ CHECK_ERROR(text, TypeError, "Member \"value\" not found or not visible after argument-dependent lookup in function ()");
}
BOOST_AUTO_TEST_CASE(invalid_fixed_types_0x7_mxn)
@@ -5690,12 +5693,13 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor)
function D() public { }
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(return_structs)
{
char const* text = R"(
+ pragma experimental ABIEncoderV2;
contract C {
struct S { uint a; T[] sub; }
struct T { uint[] x; }
@@ -5703,7 +5707,7 @@ BOOST_AUTO_TEST_CASE(return_structs)
}
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(return_recursive_structs)
@@ -5753,7 +5757,7 @@ BOOST_AUTO_TEST_CASE(address_checksum_type_deduction)
}
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(invalid_address_checksum)
@@ -5766,7 +5770,7 @@ BOOST_AUTO_TEST_CASE(invalid_address_checksum)
}
}
)";
- CHECK_WARNING(text, "checksum");
+ CHECK_WARNING(text, "This looks like an address but has an invalid checksum.");
}
BOOST_AUTO_TEST_CASE(invalid_address_no_checksum)
@@ -5779,10 +5783,10 @@ BOOST_AUTO_TEST_CASE(invalid_address_no_checksum)
}
}
)";
- CHECK_WARNING(text, "checksum");
+ CHECK_WARNING(text, "This looks like an address but has an invalid checksum.");
}
-BOOST_AUTO_TEST_CASE(invalid_address_length)
+BOOST_AUTO_TEST_CASE(invalid_address_length_short)
{
char const* text = R"(
contract C {
@@ -5792,7 +5796,20 @@ BOOST_AUTO_TEST_CASE(invalid_address_length)
}
}
)";
- CHECK_WARNING(text, "checksum");
+ CHECK_WARNING(text, "This looks like an address but has an invalid checksum.");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_length_long)
+{
+ char const* text = R"(
+ contract C {
+ function f() pure public {
+ address x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E0;
+ x;
+ }
+ }
+ )";
+ CHECK_WARNING_ALLOW_MULTI(text, "This looks like an address but has an invalid checksum.");
}
BOOST_AUTO_TEST_CASE(address_test_for_bug_in_implementation)
@@ -5883,7 +5900,7 @@ BOOST_AUTO_TEST_CASE(interface)
interface I {
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(interface_constructor)
@@ -5904,7 +5921,7 @@ BOOST_AUTO_TEST_CASE(interface_functions)
function f();
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(interface_function_bodies)
@@ -5926,7 +5943,7 @@ BOOST_AUTO_TEST_CASE(interface_function_external)
function f() external;
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(interface_function_public)
@@ -5967,7 +5984,7 @@ BOOST_AUTO_TEST_CASE(interface_events)
event E();
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(interface_inheritance)
@@ -6010,7 +6027,7 @@ BOOST_AUTO_TEST_CASE(interface_function_parameters)
function f(uint a) public returns (bool);
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(interface_enums)
@@ -6034,7 +6051,7 @@ BOOST_AUTO_TEST_CASE(using_interface)
}
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(using_interface_complex)
@@ -6051,7 +6068,7 @@ BOOST_AUTO_TEST_CASE(using_interface_complex)
}
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(warn_about_throw)
@@ -6110,7 +6127,7 @@ BOOST_AUTO_TEST_CASE(pure_statement_check_for_regular_for_loop)
}
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(warn_multiple_storage_storage_copies)
@@ -6226,7 +6243,7 @@ BOOST_AUTO_TEST_CASE(warn_unused_function_parameter)
}
}
)";
- success(text);
+ CHECK_SUCCESS(text);
}
BOOST_AUTO_TEST_CASE(warn_unused_return_parameter)
@@ -7250,6 +7267,151 @@ BOOST_AUTO_TEST_CASE(array_length_not_convertible_to_integer)
CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal.");
}
+BOOST_AUTO_TEST_CASE(array_length_constant_var)
+{
+ char const* text = R"(
+ contract C {
+ uint constant LEN = 10;
+ uint[LEN] ids;
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(array_length_non_integer_constant_var)
+{
+ char const* text = R"(
+ contract C {
+ bool constant LEN = true;
+ uint[LEN] ids;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal.");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_cannot_be_function)
+{
+ char const* text = R"(
+ contract C {
+ function f() {}
+ uint[f] ids;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal.");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_can_be_recursive_constant)
+{
+ char const* text = R"(
+ contract C {
+ uint constant L = 5;
+ uint constant LEN = L + 4 * L;
+ uint[LEN] ids;
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(array_length_cannot_be_function_call)
+{
+ char const* text = R"(
+ contract C {
+ function f(uint x) {}
+ uint constant LEN = f();
+ uint[LEN] ids;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal.");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_const_cannot_be_fractional)
+{
+ char const* text = R"(
+ contract C {
+ fixed constant L = 10.5;
+ uint[L] ids;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Array with fractional length specified");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_can_be_constant_in_struct)
+{
+ char const* text = R"(
+ contract C {
+ uint constant LEN = 10;
+ struct Test {
+ uint[LEN] ids;
+ }
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(array_length_can_be_constant_in_function)
+{
+ char const* text = R"(
+ contract C {
+ uint constant LEN = 10;
+ function f() {
+ uint[LEN] a;
+ }
+ }
+ )";
+ CHECK_SUCCESS(text);
+}
+
+BOOST_AUTO_TEST_CASE(array_length_cannot_be_constant_function_parameter)
+{
+ char const* text = R"(
+ contract C {
+ function f(uint constant LEN) {
+ uint[LEN] a;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Constant identifier declaration must have a constant value.");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_with_cyclic_constant)
+{
+ char const* text = R"(
+ contract C {
+ uint constant LEN = LEN;
+ function f() {
+ uint[LEN] a;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Cyclic constant definition (or maximum recursion depth exhausted).");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_with_complex_cyclic_constant)
+{
+ char const* text = R"(
+ contract C {
+ uint constant L2 = LEN - 10;
+ uint constant L1 = L2 / 10;
+ uint constant LEN = 10 + L1 * 5;
+ function f() {
+ uint[LEN] a;
+ }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Cyclic constant definition (or maximum recursion depth exhausted).");
+}
+
+BOOST_AUTO_TEST_CASE(array_length_with_pure_functions)
+{
+ char const* text = R"(
+ contract C {
+ uint constant LEN = keccak256(ripemd160(33));
+ uint[LEN] ids;
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Invalid array length, expected integer literal.");
+}
+
BOOST_AUTO_TEST_CASE(array_length_invalid_expression)
{
char const* text = R"(
diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp
index 4504946b..091207e8 100644
--- a/test/libsolidity/StandardCompiler.cpp
+++ b/test/libsolidity/StandardCompiler.cpp
@@ -179,6 +179,14 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
"fileA": {
"content": "contract A { }"
}
+ },
+ "settings": {
+ "outputSelection": {
+ "fileA": {
+ "A": [ "abi", "devdoc", "userdoc", "evm.bytecode", "evm.assembly", "evm.gasEstimates", "metadata" ],
+ "": [ "legacyAST" ]
+ }
+ }
}
}
)";