diff options
author | chriseth <chris@ethereum.org> | 2017-11-30 23:08:09 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-30 23:08:09 +0800 |
commit | c4cbbb054b5ed3b8ceaa21ee5b47b0704762ff40 (patch) | |
tree | 27c068f6cd96513a9023e586c209eb9f01309171 | |
parent | 9cf6e910bd2b90d0c9415d9c257f85fe0c518de8 (diff) | |
parent | d0af0c14841648365ad05ecc626e672a16df5b5c (diff) | |
download | dexon-solidity-c4cbbb054b5ed3b8ceaa21ee5b47b0704762ff40.tar.gz dexon-solidity-c4cbbb054b5ed3b8ceaa21ee5b47b0704762ff40.tar.zst dexon-solidity-c4cbbb054b5ed3b8ceaa21ee5b47b0704762ff40.zip |
Merge pull request #3261 from ethereum/develop
Merge develop into release for 0.4.19
99 files changed, 4500 insertions, 666 deletions
@@ -34,6 +34,7 @@ prerelease.txt build/ docs/_build docs/utils/__pycache__ +docs/utils/*.pyc # vim stuff *.swp diff --git a/CMakeLists.txt b/CMakeLists.txt index 537a9521..24bea3b3 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.18") +set(PROJECT_VERSION "0.4.19") project(solidity VERSION ${PROJECT_VERSION}) option(SOLC_LINK_STATIC "Link solc executable statically on supported platforms" OFF) diff --git a/Changelog.md b/Changelog.md index a8a61363..9a30986a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,17 @@ +### 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: + ### 0.4.18 (2017-10-18) Features: @@ -11,6 +25,7 @@ Features: * Type Checker: Do not add members of ``address`` to contracts as experimental 0.5.0 feature. * Type Checker: Force interface functions to be external as experimental 0.5.0 feature. * Type Checker: Require ``storage`` or ``memory`` keyword for local variables as experimental 0.5.0 feature. + * Compiler Interface: Better formatted error message for long source snippets Bugfixes: * Code Generator: Allocate one byte per memory byte array element instead of 32. diff --git a/appveyor.yml b/appveyor.yml index c63414b3..ef5f6907 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -68,15 +68,16 @@ build_script: - cd %APPVEYOR_BUILD_FOLDER% - scripts\release.bat %CONFIGURATION% - ps: $bytecodedir = git show -s --format="%cd-%H" --date=short + +test_script: + - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% + - soltest.exe --show-progress -- --no-ipc --no-smt # Skip bytecode compare if private key is not available + - cd %APPVEYOR_BUILD_FOLDER% - ps: if ($env:priv_key) { scripts\bytecodecompare\storebytecode.bat $Env:CONFIGURATION $bytecodedir } - -test_script: - - cd %APPVEYOR_BUILD_FOLDER% - cd %APPVEYOR_BUILD_FOLDER%\build\test\%CONFIGURATION% - - soltest.exe --show-progress -- --no-ipc --no-smt artifacts: - path: solidity-windows.zip diff --git a/docs/abi-spec.rst b/docs/abi-spec.rst index 43757d24..c93ce25b 100644 --- a/docs/abi-spec.rst +++ b/docs/abi-spec.rst @@ -130,14 +130,14 @@ on the type of ``X`` being Note that in the dynamic case, ``head(X(i))`` is well-defined since the lengths of the head parts only depend on the types and not the values. Its value is the offset of the beginning of ``tail(X(i))`` relative to the start of ``enc(X)``. - + - ``T[k]`` for any ``T`` and ``k``: ``enc(X) = enc((X[0], ..., X[k-1]))`` - + i.e. it is encoded as if it were a tuple with ``k`` elements of the same type. - + - ``T[]`` where ``X`` has ``k`` elements (``k`` is assumed to be of type ``uint256``): ``enc(X) = enc(k) enc([X[1], ..., X[k]])`` @@ -326,19 +326,19 @@ An event description is a JSON object with fairly similar fields: - ``anonymous``: ``true`` if the event was declared as ``anonymous``. -For example, +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,11 @@ 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) { } - } + contract Test { + struct S { uint a; uint[] b; T[] c; } + struct T { uint x; uint y; } + function f(S s, T t, uint a) { } + } would result in the JSON: @@ -451,13 +451,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..c233985b 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:: @@ -523,6 +524,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 +638,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 +685,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 +891,7 @@ Grammar:: FunctionalAssemblyAssignment | AssemblyAssignment | LabelDefinition | + AssemblyIf | AssemblySwitch | AssemblyFunctionDefinition | AssemblyFor | @@ -891,6 +908,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..2b0956bb 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; @@ -570,7 +564,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 @@ -687,12 +680,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 +734,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 +744,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 +752,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 +782,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 +816,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 +841,6 @@ derived override, but this function will bypass } } - contract Base1 is mortal { function kill() { /* do cleanup 1 */ super.kill(); } } @@ -858,7 +850,6 @@ derived override, but this function will bypass function kill() { /* do cleanup 2 */ super.kill(); } } - contract Final is Base2, Base1 { } @@ -888,7 +879,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 +1071,6 @@ more advanced example to implement a set). } } - contract C { Set.Data knownValues; @@ -1157,7 +1146,6 @@ custom types without the overhead of external function calls: } } - contract C { using BigInt for BigInt.bigint; @@ -1250,7 +1238,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 +1263,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/installing-solidity.rst b/docs/installing-solidity.rst index 71607745..b660cf02 100644 --- a/docs/installing-solidity.rst +++ b/docs/installing-solidity.rst @@ -230,6 +230,8 @@ Or, on Windows: Command-Line Build ------------------ +**Be sure to install External Dependencies (see above) before build.** + Solidity project uses CMake to configure the build. Building Solidity is quite similar on Linux, macOS and other Unices: 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/security-considerations.rst b/docs/security-considerations.rst index 6586cb5f..337a3d3f 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -55,18 +55,18 @@ 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 @@ -79,18 +79,18 @@ 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 +179,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 139c8a42..9489665e 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -221,8 +221,7 @@ activate themselves. // absolute unix timestamps (seconds since 1970-01-01) // or time periods in seconds. address public beneficiary; - uint public auctionStart; - uint public biddingTime; + uint public auctionEnd; // Current state of the auction. address public highestBidder; @@ -251,8 +250,7 @@ activate themselves. address _beneficiary ) { beneficiary = _beneficiary; - auctionStart = now; - biddingTime = _biddingTime; + auctionEnd = now + _biddingTime; } /// Bid on the auction with the value sent @@ -268,7 +266,7 @@ activate themselves. // Revert the call if the bidding // period is over. - require(now <= (auctionStart + biddingTime)); + require(now <= auctionEnd); // If the bid is not higher, send the // money back. @@ -322,7 +320,7 @@ activate themselves. // external contracts. // 1. Conditions - require(now >= (auctionStart + biddingTime)); // auction did not yet end + require(now >= auctionEnd); // auction did not yet end require(!ended); // this function has already been called // 2. Effects @@ -382,7 +380,6 @@ high or low invalid bids. } address public beneficiary; - uint public auctionStart; uint public biddingEnd; uint public revealEnd; bool public ended; @@ -410,7 +407,6 @@ high or low invalid bids. address _beneficiary ) { beneficiary = _beneficiary; - auctionStart = now; biddingEnd = now + _biddingTime; revealEnd = biddingEnd + _revealTime; } @@ -496,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; @@ -512,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 0742d2e9..a438b3d0 100644 --- a/docs/style-guide.rst +++ b/docs/style-guide.rst @@ -25,7 +25,7 @@ solidity code. The goal of this guide is *consistency*. A quote from python's captures this concept well. A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is most important. - But most importantly: know when to be inconsistent -- sometimes the style guide just doesn't apply. When in doubt, use your best judgment. Look at other examples and decide what looks best. And don't hesitate to ask! + But most importantly: know when to be inconsistent -- sometimes the style guide just doesn't apply. When in doubt, use your best judgement. Look at other examples and decide what looks best. And don't hesitate to ask! *********** @@ -223,7 +223,7 @@ Whitespace in Expressions Avoid extraneous whitespace in the following situations: -Immediately inside parenthesis, brackets or braces, with the exception of single-line function declarations. +Immediately inside parenthesis, brackets or braces, with the exception of single line function declarations. Yes:: @@ -696,49 +696,51 @@ indistinguishable from the numerals one and zero. Contract and Library Names ========================== -Contracts and libraries should be named using the CapWords style. +Contracts and libraries should be named using the CapWords style. Examples: ``SimpleToken``, ``SmartBank``, ``CertificateHashRepository``, ``Player``. -Events -====== +Event Names +=========== -Events should be named using the CapWords style. +Events should be named using the CapWords style. Examples: ``Deposit``, ``Transfer``, ``Approval``, ``BeforeTransfer``, ``AfterTransfer``. Function Names ============== -Functions should use mixedCase. +Functions should use mixedCase. Examples: ``getBalance``, ``transfer``, ``verifyOwner``, ``addMember``, ``changeOwner``. -Function Arguments -================== +Function Argument Names +======================= + +Function arguments should use mixedCase. Examples: ``initialSupply``, ``account``, ``recipientAddress``, ``senderAddress``, ``newOwner``. When writing library functions that operate on a custom struct, the struct should be the first argument and should always be named ``self``. -Local and State Variables -========================= +Local and State Variable Names +============================== -Use mixedCase. +Use mixedCase. Examples: ``totalSupply``, ``remainingSupply``, ``balancesOf``, ``creatorAddress``, ``isPreSale``, ``tokenExchangeRate``. Constants ========= Constants should be named with all capital letters with underscores separating -words. (for example:``MAX_BLOCKS``) +words. Examples: ``MAX_BLOCKS``, `TOKEN_NAME`, ``TOKEN_TICKER``, ``CONTRACT_VERSION``. -Modifiers -========= +Modifier Names +============== -Use mixedCase. +Use mixedCase. Examples: ``onlyBy``, ``onlyAfter``, ``onlyDuringThePreSale``. -Avoiding Collisions -=================== +Avoiding Naming Collisions +========================== * ``single_trailing_underscore_`` diff --git a/docs/types.rst b/docs/types.rst index 774c1d04..c716b95e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -129,7 +129,7 @@ and to send Ether (in units of wei) to an address using the ``transfer`` functio if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10); .. note:: - If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a limitation of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. + If ``x`` is a contract address, its code (more specifically: its fallback function, if present) will be executed together with the ``transfer`` call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception. * ``send`` @@ -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..c12750c8 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. 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..e76a0949 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -209,4 +209,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/CommonIO.cpp b/libdevcore/CommonIO.cpp index 5d47937b..8c7e08f6 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -39,7 +39,7 @@ namespace { template <typename _T> -inline _T contentsGeneric(std::string const& _file) +inline _T readFile(std::string const& _file) { _T ret; size_t const c_elementSize = sizeof(typename _T::value_type); @@ -61,9 +61,23 @@ inline _T contentsGeneric(std::string const& _file) } -string dev::contentsString(string const& _file) +string dev::readFileAsString(string const& _file) { - return contentsGeneric<string>(_file); + return readFile<string>(_file); +} + +string dev::readStandardInput() +{ + string ret; + while (!cin.eof()) + { + string tmp; + // NOTE: this will read until EOF or NL + getline(cin, tmp); + ret.append(tmp); + ret.append("\n"); + } + return ret; } void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename) diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index d84362cf..33769ec3 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -32,7 +32,10 @@ namespace dev /// Retrieve and returns the contents of the given file as a std::string. /// If the file doesn't exist or isn't readable, returns an empty container / bytes. -std::string contentsString(std::string const& _file); +std::string readFileAsString(std::string const& _file); + +/// Retrieve and returns the contents of standard input (until EOF). +std::string readStandardInput(); /// Write the given binary data into the given file, replacing the file if it pre-exists. /// Throws exception on error. 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/backends/evm/EVMCodeTransform.cpp b/libjulia/backends/evm/EVMCodeTransform.cpp index 66f593e8..13d9d011 100644 --- a/libjulia/backends/evm/EVMCodeTransform.cpp +++ b/libjulia/backends/evm/EVMCodeTransform.cpp @@ -217,6 +217,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? diff --git a/libjulia/backends/evm/EVMCodeTransform.h b/libjulia/backends/evm/EVMCodeTransform.h index 951c8a50..387720a2 100644 --- a/libjulia/backends/evm/EVMCodeTransform.h +++ b/libjulia/backends/evm/EVMCodeTransform.h @@ -104,6 +104,7 @@ public: 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::If const& _if); void operator()(solidity::assembly::Switch const& _switch); void operator()(solidity::assembly::FunctionDefinition const&); void operator()(solidity::assembly::ForLoop const&); diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp index 6636ad97..4d546e68 100644 --- a/libsolidity/analysis/ConstantEvaluator.cpp +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -28,6 +28,7 @@ using namespace std; using namespace dev; using namespace dev::solidity; +/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(UnaryOperation const& _operation) { TypePointer const& subType = _operation.subExpression().annotation().type; @@ -37,6 +38,7 @@ void ConstantEvaluator::endVisit(UnaryOperation const& _operation) _operation.annotation().type = t; } +/// FIXME: this is pretty much a copy of TypeChecker::endVisit(BinaryOperation) void ConstantEvaluator::endVisit(BinaryOperation const& _operation) { TypePointer const& leftType = _operation.leftExpression().annotation().type; @@ -46,9 +48,24 @@ void ConstantEvaluator::endVisit(BinaryOperation const& _operation) if (!dynamic_cast<RationalNumberType const*>(rightType.get())) m_errorReporter.fatalTypeError(_operation.rightExpression().location(), "Invalid constant expression."); TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); - if (Token::isCompareOp(_operation.getOperator())) - commonType = make_shared<BoolType>(); - _operation.annotation().type = commonType; + if (!commonType) + { + m_errorReporter.typeError( + _operation.location(), + "Operator " + + string(Token::toString(_operation.getOperator())) + + " not compatible with types " + + leftType->toString() + + " and " + + rightType->toString() + ); + commonType = leftType; + } + _operation.annotation().commonType = commonType; + _operation.annotation().type = + Token::isCompareOp(_operation.getOperator()) ? + make_shared<BoolType>() : + commonType; } void ConstantEvaluator::endVisit(Literal const& _literal) @@ -57,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.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index ffa538b6..bd8ee597 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -150,10 +150,18 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) if (_memberAccess.memberName() == "callcode") if (auto const* type = dynamic_cast<FunctionType const*>(_memberAccess.annotation().type.get())) if (type->kind() == FunctionType::Kind::BareCallCode) - m_errorReporter.warning( - _memberAccess.location(), - "\"callcode\" has been deprecated in favour of \"delegatecall\"." - ); + { + if (m_currentContract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050)) + m_errorReporter.typeError( + _memberAccess.location(), + "\"callcode\" has been deprecated in favour of \"delegatecall\"." + ); + else + m_errorReporter.warning( + _memberAccess.location(), + "\"callcode\" has been deprecated in favour of \"delegatecall\"." + ); + } if (m_constructor && m_currentContract) if (ContractType const* type = dynamic_cast<ContractType const*>(_memberAccess.expression().annotation().type.get())) 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..73047e76 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -1122,7 +1122,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 +1320,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 +2000,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..7e41fc16 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -72,6 +72,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); 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..1050621e 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()); } 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."); + } - //m_interface->push(); - //m_interface->addAssertion(expr(_node.condition())); - // TDOO clear knowledge (increment sequence numbers and add bounds assertions ) apart from assertions + 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); - // TODO combine similar to if - return true; + m_interface->pop(); + + m_conditionalExecutionHappened = true; + m_currentSequenceCounter = sequenceCountersStart; + + resetVariables(touchedVariables); + + return false; } void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) @@ -160,8 +179,7 @@ void SMTChecker::endVisit(VariableDeclarationStatement const& _varDecl) { 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()); } else m_errorReporter.warning( @@ -190,9 +208,7 @@ 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()); else m_errorReporter.warning( _assignment.location(), @@ -269,23 +285,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())) + m_interface->addAssertion(expr(_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) @@ -298,10 +308,12 @@ void SMTChecker::endVisit(Literal const& _literal) m_interface->addAssertion(expr(_literal) == smt::Expression(type.literalValue(&_literal))); } + else if (type.category() == Type::Category::Bool) + m_interface->addAssertion(expr(_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() + ")." ); @@ -386,6 +398,7 @@ 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())); else @@ -395,7 +408,33 @@ void SMTChecker::booleanOperation(BinaryOperation const& _op) m_errorReporter.warning( _op.location(), "Assertion checker does not yet implement the type " + _op.annotation().commonType->toString() + " for boolean operations" - ); + ); +} + +void SMTChecker::assignment(Declaration const& _variable, Expression const& _value) +{ + // TODO more checks? + // TODO add restrictions about type (might be assignment from smaller type) + m_interface->addAssertion(newValue(_variable) == expr(_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 +472,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 +490,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 +511,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 +624,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 +664,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) diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index faaac639..8e07d74d 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,6 +52,7 @@ 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; @@ -61,6 +66,14 @@ private: void compareOperation(BinaryOperation const& _op); void booleanOperation(BinaryOperation const& _op); + void assignment(Declaration const& _variable, Expression const& _value); + + // 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 +81,24 @@ 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 + ); + + std::pair<smt::CheckResult, std::vector<std::string>> + checkSatisifableAndGenerateModel(std::vector<smt::Expression> const& _expressionsToEvaluate); - void createVariable(VariableDeclaration const& _varDecl, bool _setToZero); + 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,12 +116,16 @@ 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); + using VariableSequenceCounters = std::map<Declaration const*, int>; + /// Returns the expression corresponding to the AST node. Creates a new expression /// if it does not exist yet. smt::Expression expr(Expression const& _e); @@ -101,6 +134,8 @@ private: 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 cbd766fb..0e00665a 100644 --- a/libsolidity/formal/SMTLib2Interface.cpp +++ b/libsolidity/formal/SMTLib2Interface.cpp @@ -145,8 +145,8 @@ string SMTLib2Interface::checkSatAndGetValuesCommand(vector<Expression> const& _ for (size_t i = 0; i < _expressionsToEvaluate.size(); i++) { auto const& e = _expressionsToEvaluate.at(i); - // TODO they don't have to be ints... - 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/SolverInterface.h b/libsolidity/formal/SolverInterface.h index 70dc1585..c9adf863 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,56 @@ 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); } 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 +156,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 ab28baa3..e5c1aef4 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -91,14 +91,14 @@ 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) values.push_back(toString(m.eval(toZ3Expr(e)))); } } - catch (z3::exception const& _e) + catch (z3::exception const&) { result = CheckResult::ERROR; values.clear(); @@ -139,8 +139,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(), ""); diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index e5bdc90f..2804ddfc 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -286,6 +286,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; 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..a792a1b8 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -68,6 +68,8 @@ struct VariableDeclaration { SourceLocation location; TypedNameList variables; s 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; }; +/// 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..d627b41a 100644 --- a/libsolidity/inlineasm/AsmDataForward.h +++ b/libsolidity/inlineasm/AsmDataForward.h @@ -41,11 +41,12 @@ struct VariableDeclaration; struct FunctionalInstruction; struct FunctionDefinition; struct FunctionCall; +struct If; struct Switch; struct ForLoop; struct Block; -using Statement = boost::variant<Instruction, Literal, Label, StackAssignment, Identifier, Assignment, FunctionCall, FunctionalInstruction, VariableDeclaration, FunctionDefinition, Switch, ForLoop, Block>; +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..8f171005 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) diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index a5272808..0f183244 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -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..77ae9102 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -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/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index b99fe4ee..5713256a 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -747,22 +747,32 @@ void CompilerStack::compileContract( } } +string const CompilerStack::lastContractName() const +{ + if (m_contracts.empty()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); + // try to find some user-supplied contract + string contractName; + for (auto const& it: m_sources) + for (ASTPointer<ASTNode> const& node: it.second.ast->nodes()) + if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) + contractName = contract->fullyQualifiedName(); + return contractName; +} + CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const { if (m_contracts.empty()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); - string contractName = _contractName; - if (_contractName.empty()) - // try to find some user-supplied contract - for (auto const& it: m_sources) - for (ASTPointer<ASTNode> const& node: it.second.ast->nodes()) - if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) - contractName = contract->fullyQualifiedName(); - auto it = m_contracts.find(contractName); + + auto it = m_contracts.find(_contractName); + if (it != m_contracts.end()) + return it->second; + // To provide a measure of backward-compatibility, if a contract is not located by its // fully-qualified name, a lookup will be attempted purely on the contract's name to see // if anything will satisfy. - if (it == m_contracts.end() && contractName.find(":") == string::npos) + if (_contractName.find(":") == string::npos) { for (auto const& contractEntry: m_contracts) { @@ -773,12 +783,13 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa string foundName; getline(ss, source, ':'); getline(ss, foundName, ':'); - if (foundName == contractName) return contractEntry.second; + if (foundName == _contractName) + return contractEntry.second; } - // If we get here, both lookup methods failed. - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); } - return it->second; + + // If we get here, both lookup methods failed. + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract \"" + _contractName + "\" not found.")); } CompilerStack::Source const& CompilerStack::source(string const& _sourceName) const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index c567ac2c..b377b3aa 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -155,10 +155,10 @@ public: std::map<std::string, unsigned> sourceIndices() const; /// @returns the previously used scanner, useful for counting lines during error reporting. - Scanner const& scanner(std::string const& _sourceName = "") const; + Scanner const& scanner(std::string const& _sourceName) const; /// @returns the parsed source unit with the supplied name. - SourceUnit const& ast(std::string const& _sourceName = "") const; + SourceUnit const& ast(std::string const& _sourceName) const; /// Helper function for logs printing. Do only use in error cases, it's quite expensive. /// line and columns are numbered starting from 1 with following order: @@ -168,48 +168,51 @@ public: /// @returns a list of the contract names in the sources. std::vector<std::string> contractNames() const; + /// @returns the name of the last contract. + std::string const lastContractName() const; + /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use std::string const filesystemFriendlyName(std::string const& _contractName) const; /// @returns the assembled object for a contract. - eth::LinkerObject const& object(std::string const& _contractName = "") const; + eth::LinkerObject const& object(std::string const& _contractName) const; /// @returns the runtime object for the contract. - eth::LinkerObject const& runtimeObject(std::string const& _contractName = "") const; + eth::LinkerObject const& runtimeObject(std::string const& _contractName) const; /// @returns the bytecode of a contract that uses an already deployed contract via DELEGATECALL. /// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to /// substituted by the actual address. Note that this sequence starts end ends in three X /// characters but can contain anything in between. - eth::LinkerObject const& cloneObject(std::string const& _contractName = "") const; + eth::LinkerObject const& cloneObject(std::string const& _contractName) const; /// @returns normal contract assembly items - eth::AssemblyItems const* assemblyItems(std::string const& _contractName = "") const; + eth::AssemblyItems const* assemblyItems(std::string const& _contractName) const; /// @returns runtime contract assembly items - eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName = "") const; + eth::AssemblyItems const* runtimeAssemblyItems(std::string const& _contractName) const; /// @returns the string that provides a mapping between bytecode and sourcecode or a nullptr /// if the contract does not (yet) have bytecode. - std::string const* sourceMapping(std::string const& _contractName = "") const; + std::string const* sourceMapping(std::string const& _contractName) const; /// @returns the string that provides a mapping between runtime bytecode and sourcecode. /// if the contract does not (yet) have bytecode. - std::string const* runtimeSourceMapping(std::string const& _contractName = "") const; + std::string const* runtimeSourceMapping(std::string const& _contractName) const; /// @return a verbose text representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings /// Prerequisite: Successful compilation. - std::string assemblyString(std::string const& _contractName = "", StringMap _sourceCodes = StringMap()) const; + std::string assemblyString(std::string const& _contractName, StringMap _sourceCodes = StringMap()) const; /// @returns a JSON representation of the assembly. /// @arg _sourceCodes is the map of input files to source code strings /// Prerequisite: Successful compilation. - Json::Value assemblyJSON(std::string const& _contractName = "", StringMap _sourceCodes = StringMap()) const; + Json::Value assemblyJSON(std::string const& _contractName, StringMap _sourceCodes = StringMap()) const; /// @returns a JSON representing the contract ABI. /// Prerequisite: Successful call to parse or compile. - Json::Value const& contractABI(std::string const& _contractName = "") const; + Json::Value const& contractABI(std::string const& _contractName) const; /// @returns a JSON representing the contract's user documentation. /// Prerequisite: Successful call to parse or compile. @@ -276,8 +279,8 @@ private: ); void link(); - Contract const& contract(std::string const& _contractName = "") const; - Source const& source(std::string const& _sourceName = "") const; + Contract const& contract(std::string const& _contractName) const; + Source const& source(std::string const& _sourceName) const; /// @returns the parsed contract with the supplied name. Throws an exception if the contract /// does not exist. diff --git a/libsolidity/interface/SourceReferenceFormatter.cpp b/libsolidity/interface/SourceReferenceFormatter.cpp index 62d22999..aeafaf2d 100644 --- a/libsolidity/interface/SourceReferenceFormatter.cpp +++ b/libsolidity/interface/SourceReferenceFormatter.cpp @@ -49,6 +49,21 @@ void SourceReferenceFormatter::printSourceLocation( if (startLine == endLine) { string line = scanner.lineAtPosition(_location->start); + + int locationLength = endColumn - startColumn; + if (locationLength > 150) + { + line = line.substr(0, startColumn + 35) + " ... " + line.substr(endColumn - 35); + endColumn = startColumn + 75; + locationLength = 75; + } + if (line.length() > 150) + { + line = " ... " + line.substr(startColumn, locationLength) + " ... "; + startColumn = 5; + endColumn = startColumn + locationLength; + } + _stream << line << endl; for_each( line.cbegin(), 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/lllc/main.cpp b/lllc/main.cpp index 912ce16a..5679bc2b 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -35,15 +35,15 @@ using namespace dev::solidity; using namespace dev::eth; static string const VersionString = - string(ETH_PROJECT_VERSION) + - (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + - (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); + string(ETH_PROJECT_VERSION) + + (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + + (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); static void help() { cout << "Usage lllc [OPTIONS] <file>" << endl - << "Options:" << endl + << "Options:" << endl << " -b,--binary Parse, compile and assemble; output byte code in binary." << endl << " -x,--hex Parse, compile and assemble; output byte code in hex." << endl << " -a,--assembly Only parse and compile; show assembly." << endl @@ -51,12 +51,12 @@ static void help() << " -o,--optimise Turn on/off the optimiser; off by default." << endl << " -h,--help Show this help message and exit." << endl << " -V,--version Show the version and exit." << endl; - exit(0); + exit(0); } static void version() { - cout << "LLLC, the Lovely Little Language Compiler " << endl; + cout << "LLLC, the Lovely Little Language Compiler" << endl; cout << "Version: " << VersionString << endl; exit(0); } @@ -118,39 +118,39 @@ int main(int argc, char** argv) string src; if (infile.empty()) - { - string s; - while (!cin.eof()) - { - getline(cin, s); - src.append(s); - } - } + src = readStandardInput(); else - src = contentsString(infile); + src = readFileAsString(infile); vector<string> errors; if (src.empty()) + { errors.push_back("Empty file."); + } else if (mode == Disassemble) { cout << disassemble(fromHex(src)) << endl; } else if (mode == Binary || mode == Hex) { - auto bs = compileLLL(src, optimise ? true : false, &errors, contentsString); + auto bs = compileLLL(src, optimise ? true : false, &errors, readFileAsString); if (mode == Hex) cout << toHex(bs) << endl; else if (mode == Binary) cout.write((char const*)bs.data(), bs.size()); } else if (mode == ParseTree) + { cout << parseLLL(src) << endl; + } else if (mode == Assembly) - cout << compileLLLToAsm(src, optimise ? true : false, &errors, contentsString) << endl; + { + cout << compileLLLToAsm(src, optimise ? true : false, &errors, readFileAsString) << endl; + } + for (auto const& i: errors) cerr << i << endl; - if ( errors.size() ) + if (errors.size()) return 1; return 0; } 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/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1686dc2e..9e2cb77a 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -424,20 +424,13 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings() continue; } - m_sourceCodes[infile.string()] = dev::contentsString(infile.string()); + m_sourceCodes[infile.string()] = dev::readFileAsString(infile.string()); path = boost::filesystem::canonical(infile).string(); } m_allowedDirectories.push_back(boost::filesystem::path(path).remove_filename()); } if (addStdin) - { - string s; - while (!cin.eof()) - { - getline(cin, s); - m_sourceCodes[g_stdinFileName].append(s + '\n'); - } - } + m_sourceCodes[g_stdinFileName] = dev::readStandardInput(); } bool CommandLineInterface::parseLibraryOption(string const& _input) @@ -447,7 +440,7 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) try { if (fs::is_regular_file(_input)) - data = contentsString(_input); + data = readFileAsString(_input); } catch (fs::filesystem_error const&) { @@ -698,7 +691,7 @@ bool CommandLineInterface::processInput() return ReadCallback::Result{false, "Not a valid file."}; else { - auto contents = dev::contentsString(canonicalPath.string()); + auto contents = dev::readFileAsString(canonicalPath.string()); m_sourceCodes[path.string()] = contents; return ReadCallback::Result{true, contents}; } @@ -731,13 +724,7 @@ bool CommandLineInterface::processInput() if (m_args.count(g_argStandardJSON)) { - string input; - while (!cin.eof()) - { - string tmp; - getline(cin, tmp); - input.append(tmp + "\n"); - } + string input = dev::readStandardInput(); StandardCompiler compiler(fileReader); cout << compiler.compile(input) << endl; return true; @@ -951,9 +938,10 @@ void CommandLineInterface::handleAst(string const& _argStr) for (auto const& sourceCode: m_sourceCodes) asts.push_back(&m_compiler->ast(sourceCode.first)); map<ASTNode const*, eth::GasMeter::GasConsumption> gasCosts; - if (m_compiler->runtimeAssemblyItems()) + // FIXME: shouldn't this be done for every contract? + if (m_compiler->runtimeAssemblyItems(m_compiler->lastContractName())) gasCosts = GasEstimator::breakToStatementLevel( - GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(), asts), + GasEstimator::structuralEstimation(*m_compiler->runtimeAssemblyItems(m_compiler->lastContractName()), asts), asts ); diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 7e797a62..23feaa2a 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -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/test/ExecutionFramework.h b/test/ExecutionFramework.h index cc25bea7..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) { @@ -172,78 +182,13 @@ public: { return bytes(); } + //@todo might be extended in the future template <class Arg> static bytes encodeDyn(Arg const& _arg) { return encodeArgs(u256(0x20), u256(_arg.size()), _arg); } - class ContractInterface - { - public: - ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {} - - void setNextValue(u256 const& _value) { m_nextValue = _value; } - - protected: - template <class... Args> - bytes const& call(std::string const& _sig, Args const&... _arguments) - { - auto const& ret = m_framework.callContractFunctionWithValue(_sig, m_nextValue, _arguments...); - m_nextValue = 0; - return ret; - } - - void callString(std::string const& _name, std::string const& _arg) - { - BOOST_CHECK(call(_name + "(string)", u256(0x20), _arg.length(), _arg).empty()); - } - - void callStringAddress(std::string const& _name, std::string const& _arg1, u160 const& _arg2) - { - BOOST_CHECK(call(_name + "(string,address)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); - } - - void callStringAddressBool(std::string const& _name, std::string const& _arg1, u160 const& _arg2, bool _arg3) - { - BOOST_CHECK(call(_name + "(string,address,bool)", u256(0x60), _arg2, _arg3, _arg1.length(), _arg1).empty()); - } - - void callStringBytes32(std::string const& _name, std::string const& _arg1, h256 const& _arg2) - { - BOOST_CHECK(call(_name + "(string,bytes32)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); - } - - u160 callStringReturnsAddress(std::string const& _name, std::string const& _arg) - { - bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); - BOOST_REQUIRE(ret.size() == 0x20); - BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); - return u160(u256(h256(ret))); - } - - std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) - { - bytesConstRef const ret(&call(_name + "(address)", _arg)); - BOOST_REQUIRE(ret.size() >= 0x40); - u256 offset(h256(ret.cropped(0, 0x20))); - BOOST_REQUIRE_EQUAL(offset, 0x20); - u256 len(h256(ret.cropped(0x20, 0x20))); - BOOST_REQUIRE_EQUAL(ret.size(), 0x40 + ((len + 0x1f) / 0x20) * 0x20); - return ret.cropped(0x40, size_t(len)).toString(); - } - - h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) - { - bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); - BOOST_REQUIRE(ret.size() == 0x20); - return h256(ret); - } - - private: - u256 m_nextValue; - ExecutionFramework& m_framework; - }; private: template <class CppFunction, class... Args> diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp index 72b26453..60aafc85 100644 --- a/test/RPCSession.cpp +++ b/test/RPCSession.cpp @@ -74,7 +74,10 @@ IPCSocket::IPCSocket(string const& _path): m_path(_path) BOOST_FAIL("Error creating IPC socket object"); if (connect(m_socket, reinterpret_cast<struct sockaddr const*>(&saun), sizeof(struct sockaddr_un)) < 0) + { + close(m_socket); BOOST_FAIL("Error connecting to IPC socket: " << _path); + } #endif } @@ -237,9 +240,10 @@ void RPCSession::test_setChainParams(vector<string> const& _accounts) "0000000000000000000000000000000000000002": { "wei": "1", "precompiled": { "name": "sha256", "linear": { "base": 60, "word": 12 } } }, "0000000000000000000000000000000000000003": { "wei": "1", "precompiled": { "name": "ripemd160", "linear": { "base": 600, "word": 120 } } }, "0000000000000000000000000000000000000004": { "wei": "1", "precompiled": { "name": "identity", "linear": { "base": 15, "word": 3 } } }, - "0000000000000000000000000000000000000006": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_add", "linear": { "base": 15, "word": 3 } } }, - "0000000000000000000000000000000000000007": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_mul", "linear": { "base": 15, "word": 3 } } }, - "0000000000000000000000000000000000000008": { "wei": "1", "precompiled": { "name": "alt_bn128_pairing_product", "linear": { "base": 15, "word": 3 } } } + "0000000000000000000000000000000000000005": { "wei": "1", "precompiled": { "name": "modexp" } }, + "0000000000000000000000000000000000000006": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_add", "linear": { "base": 500, "word": 0 } } }, + "0000000000000000000000000000000000000007": { "wei": "1", "precompiled": { "name": "alt_bn128_G1_mul", "linear": { "base": 40000, "word": 0 } } }, + "0000000000000000000000000000000000000008": { "wei": "1", "precompiled": { "name": "alt_bn128_pairing_product" } } } } )"; @@ -336,22 +340,25 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector<string> const& return result["result"]; } +string const& RPCSession::accountCreate() +{ + m_accounts.push_back(personal_newAccount("")); + personal_unlockAccount(m_accounts.back(), "", 100000); + return m_accounts.back(); +} + string const& RPCSession::accountCreateIfNotExists(size_t _id) { - if (_id >= m_accounts.size()) - { - m_accounts.push_back(personal_newAccount("")); - personal_unlockAccount(m_accounts.back(), "", 100000); - } + while ((_id + 1) > m_accounts.size()) + accountCreate(); return m_accounts[_id]; } RPCSession::RPCSession(const string& _path): m_ipcSocket(_path) { - string account = personal_newAccount(""); - personal_unlockAccount(account, "", 100000); - m_accounts.push_back(account); + accountCreate(); + // This will pre-fund the accounts create prior. test_setChainParams(m_accounts); } diff --git a/test/RPCSession.h b/test/RPCSession.h index eae6a09c..63f1dd21 100644 --- a/test/RPCSession.h +++ b/test/RPCSession.h @@ -121,6 +121,7 @@ public: Json::Value rpcCall(std::string const& _methodName, std::vector<std::string> const& _args = std::vector<std::string>(), bool _canFail = false); std::string const& account(size_t _id) const { return m_accounts.at(_id); } + std::string const& accountCreate(); std::string const& accountCreateIfNotExists(size_t _id); private: 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/cmdlineTests.sh b/test/cmdlineTests.sh index f12a6686..a249b601 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -32,6 +32,8 @@ REPO_ROOT=$(cd $(dirname "$0")/.. && pwd) echo $REPO_ROOT SOLC="$REPO_ROOT/build/solc/solc" +FULLARGS="--optimize --combined-json abi,asm,ast,bin,bin-runtime,clone-bin,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc" + echo "Checking that the bug list is up to date..." "$REPO_ROOT"/scripts/update_bugs_by_version.py @@ -39,26 +41,37 @@ echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l) test "${output//[[:blank:]]/}" = "3" +function printTask() { echo "$(tput bold)$(tput setaf 2)$1$(tput sgr0)"; } + +function printError() { echo "$(tput setaf 1)$1$(tput sgr0)"; } + function compileFull() { - files="$*" + local files="$*" + local output failed + set +e - "$SOLC" --optimize \ - --combined-json abi,asm,ast,bin,bin-runtime,clone-bin,compact-format,devdoc,hashes,interface,metadata,opcodes,srcmap,srcmap-runtime,userdoc \ - $files >/dev/null 2>&1 + output=$( ("$SOLC" $FULLARGS $files) 2>&1 ) failed=$? set -e + if [ $failed -ne 0 ] then - echo "Compilation failed on:" - cat $files + printError "Compilation failed on:" + echo "$output" + printError "While calling:" + echo "\"$SOLC\" $FULLARGS $files" + printError "Inside directory:" + pwd false fi } function compileWithoutWarning() { - files="$*" + local files="$*" + local output failed + set +e output=$("$SOLC" $files 2>&1) failed=$? @@ -66,10 +79,11 @@ function compileWithoutWarning() output=$(echo "$output" | grep -v 'pre-release') echo "$output" set -e + test -z "$output" -a "$failed" -eq 0 } -echo "Compiling various other contracts and libraries..." +printTask "Compiling various other contracts and libraries..." ( cd "$REPO_ROOT"/test/compilationTests/ for dir in * @@ -84,7 +98,7 @@ do done ) -echo "Compiling all files in std and examples..." +printTask "Compiling all files in std and examples..." for f in "$REPO_ROOT"/std/*.sol do @@ -92,7 +106,7 @@ do compileWithoutWarning "$f" done -echo "Compiling all examples from the documentation..." +printTask "Compiling all examples from the documentation..." TMPDIR=$(mktemp -d) ( set -e @@ -109,14 +123,14 @@ TMPDIR=$(mktemp -d) rm -rf "$TMPDIR" echo "Done." -echo "Testing library checksum..." +printTask "Testing library checksum..." echo '' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222 ! echo '' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null -echo "Testing long library names..." +printTask "Testing long library names..." echo '' | "$SOLC" --link --libraries aveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylonglibraryname:0x90f20564390eAe531E810af625A22f51385Cd222 -echo "Testing overwriting files" +printTask "Testing overwriting files" TMPDIR=$(mktemp -d) ( set -e @@ -129,7 +143,7 @@ TMPDIR=$(mktemp -d) ) rm -rf "$TMPDIR" -echo "Testing soljson via the fuzzer..." +printTask "Testing soljson via the fuzzer..." TMPDIR=$(mktemp -d) ( set -e @@ -143,14 +157,14 @@ TMPDIR=$(mktemp -d) set +e "$REPO_ROOT"/build/test/solfuzzer --quiet < "$f" if [ $? -ne 0 ]; then - echo "Fuzzer failed on:" + printError "Fuzzer failed on:" cat "$f" exit 1 fi "$REPO_ROOT"/build/test/solfuzzer --without-optimizer --quiet < "$f" if [ $? -ne 0 ]; then - echo "Fuzzer (without optimizer) failed on:" + printError "Fuzzer (without optimizer) failed on:" cat "$f" exit 1 fi diff --git a/test/contracts/AuctionRegistrar.cpp b/test/contracts/AuctionRegistrar.cpp index 73a5d1ed..c9c744af 100644 --- a/test/contracts/AuctionRegistrar.cpp +++ b/test/contracts/AuctionRegistrar.cpp @@ -24,6 +24,7 @@ #include <tuple> #include <boost/test/unit_test.hpp> #include <test/libsolidity/SolidityExecutionFramework.h> +#include <test/contracts/ContractInterface.h> using namespace std; using namespace dev::test; @@ -230,7 +231,6 @@ protected: BOOST_REQUIRE(!m_output.empty()); } - using ContractInterface = SolidityExecutionFramework::ContractInterface; class RegistrarInterface: public ContractInterface { public: diff --git a/test/contracts/ContractInterface.h b/test/contracts/ContractInterface.h new file mode 100644 index 00000000..052b0db2 --- /dev/null +++ b/test/contracts/ContractInterface.h @@ -0,0 +1,99 @@ +/* + 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 <boost/test/unit_test.hpp> +#include <test/ExecutionFramework.h> + +#include <functional> + +namespace dev +{ +namespace test +{ + +class ContractInterface +{ +public: + ContractInterface(ExecutionFramework& _framework): m_framework(_framework) {} + + void setNextValue(u256 const& _value) { m_nextValue = _value; } + +protected: + template <class... Args> + bytes const& call(std::string const& _sig, Args const&... _arguments) + { + auto const& ret = m_framework.callContractFunctionWithValue(_sig, m_nextValue, _arguments...); + m_nextValue = 0; + return ret; + } + + void callString(std::string const& _name, std::string const& _arg) + { + BOOST_CHECK(call(_name + "(string)", u256(0x20), _arg.length(), _arg).empty()); + } + + void callStringAddress(std::string const& _name, std::string const& _arg1, u160 const& _arg2) + { + BOOST_CHECK(call(_name + "(string,address)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); + } + + void callStringAddressBool(std::string const& _name, std::string const& _arg1, u160 const& _arg2, bool _arg3) + { + BOOST_CHECK(call(_name + "(string,address,bool)", u256(0x60), _arg2, _arg3, _arg1.length(), _arg1).empty()); + } + + void callStringBytes32(std::string const& _name, std::string const& _arg1, h256 const& _arg2) + { + BOOST_CHECK(call(_name + "(string,bytes32)", u256(0x40), _arg2, _arg1.length(), _arg1).empty()); + } + + u160 callStringReturnsAddress(std::string const& _name, std::string const& _arg) + { + bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); + BOOST_REQUIRE(ret.size() == 0x20); + BOOST_CHECK(std::count(ret.begin(), ret.begin() + 12, 0) == 12); + return u160(u256(h256(ret))); + } + + std::string callAddressReturnsString(std::string const& _name, u160 const& _arg) + { + bytesConstRef const ret(&call(_name + "(address)", _arg)); + BOOST_REQUIRE(ret.size() >= 0x40); + u256 offset(h256(ret.cropped(0, 0x20))); + BOOST_REQUIRE_EQUAL(offset, 0x20); + u256 len(h256(ret.cropped(0x20, 0x20))); + BOOST_REQUIRE_EQUAL(ret.size(), 0x40 + ((len + 0x1f) / 0x20) * 0x20); + return ret.cropped(0x40, size_t(len)).toString(); + } + + h256 callStringReturnsBytes32(std::string const& _name, std::string const& _arg) + { + bytes const& ret = call(_name + "(string)", u256(0x20), _arg.length(), _arg); + BOOST_REQUIRE(ret.size() == 0x20); + return h256(ret); + } + +private: + u256 m_nextValue; + ExecutionFramework& m_framework; +}; + +} +} // end namespaces + diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index bbe603d4..90334ad6 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -559,24 +559,24 @@ BOOST_AUTO_TEST_CASE(multisig_value_transfer) BOOST_REQUIRE(callContractFunction("addOwner(address)", h256(account(3), h256::AlignRight)) == encodeArgs()); // 4 owners, set required to 3 BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); - // check that balance is and stays zero at destination address - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + Address destination = Address("0x5c6d6026d3fb35cd7175fd0054ae8df50d8f8b41"); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(1), 10 * ether); m_sender = account(1); - auto ophash = callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + auto ophash = callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 100, 0x60, 0x00); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(2), 10 * ether); m_sender = account(2); callContractFunction("confirm(bytes32)", ophash); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(3), 10 * ether); m_sender = account(3); callContractFunction("confirm(bytes32)", ophash); // now it should go through - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 100); + BOOST_CHECK_EQUAL(balanceAt(destination), 100); } BOOST_AUTO_TEST_CASE(revoke_addOwner) @@ -622,30 +622,31 @@ BOOST_AUTO_TEST_CASE(revoke_transaction) BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); // create a transaction Address deployer = m_sender; - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + Address destination = Address("0x5c6d6026d3fb35cd7175fd0054ae8df50d8f8b41"); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(1), 10 * ether); m_sender = account(1); - auto opHash = callContractFunction("execute(address,uint256,bytes)", h256(0x05), 100, 0x60, 0x00); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + auto opHash = callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 100, 0x60, 0x00); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(2), 10 * ether); m_sender = account(2); callContractFunction("confirm(bytes32)", opHash); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(1), 10 * ether); m_sender = account(1); BOOST_REQUIRE(callContractFunction("revoke(bytes32)", opHash) == encodeArgs()); m_sender = deployer; callContractFunction("confirm(bytes32)", opHash); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); m_sender = account(0); sendEther(account(3), 10 * ether); m_sender = account(3); callContractFunction("confirm(bytes32)", opHash); // now it should go through - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 100); + BOOST_CHECK_EQUAL(balanceAt(destination), 100); } BOOST_AUTO_TEST_CASE(daylimit) @@ -661,31 +662,32 @@ BOOST_AUTO_TEST_CASE(daylimit) BOOST_REQUIRE(callContractFunction("changeRequirement(uint256)", u256(3)) == encodeArgs()); // try to send tx over daylimit - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + Address destination = Address("0x5c6d6026d3fb35cd7175fd0054ae8df50d8f8b41"); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); sendEther(account(1), 10 * ether); m_sender = account(1); BOOST_REQUIRE( - callContractFunction("execute(address,uint256,bytes)", h256(0x05), 150, 0x60, 0x00) != + callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 150, 0x60, 0x00) != encodeArgs(u256(0)) ); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); // try to send tx under daylimit by stranger m_sender = account(0); sendEther(account(4), 10 * ether); m_sender = account(4); BOOST_REQUIRE( - callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == + callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 90, 0x60, 0x00) == encodeArgs(u256(0)) ); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 0); + BOOST_CHECK_EQUAL(balanceAt(destination), 0); // now send below limit by owner m_sender = account(0); sendEther(account(1), 10 * ether); BOOST_REQUIRE( - callContractFunction("execute(address,uint256,bytes)", h256(0x05), 90, 0x60, 0x00) == + callContractFunction("execute(address,uint256,bytes)", h256(destination, h256::AlignRight), 90, 0x60, 0x00) == encodeArgs(u256(0)) ); - BOOST_CHECK_EQUAL(balanceAt(Address(0x05)), 90); + BOOST_CHECK_EQUAL(balanceAt(destination), 90); } BOOST_AUTO_TEST_CASE(daylimit_constructor) 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/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/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/liblll/Compiler.cpp b/test/liblll/Compiler.cpp index 77d263b8..ace97e15 100644 --- a/test/liblll/Compiler.cpp +++ b/test/liblll/Compiler.cpp @@ -24,6 +24,7 @@ #include <memory> #include <boost/test/unit_test.hpp> #include <liblll/Compiler.h> +#include <libdevcore/FixedHash.h> using namespace std; @@ -37,9 +38,9 @@ namespace test namespace { -bool successCompile(std::string const& _sourceCode) +bool successCompile(string const& _sourceCode) { - std::vector<std::string> errors; + vector<string> errors; bytes bytecode = eth::compileLLL(_sourceCode, false, &errors); if (!errors.empty()) return false; @@ -138,6 +139,516 @@ BOOST_AUTO_TEST_CASE(disallowed_functional_asm_instructions) BOOST_CHECK(!successCompile("(JUMPDEST)")); } +BOOST_AUTO_TEST_CASE(valid_opcodes_functional) +{ + vector<string> opcodes_bytecode { + "00", + "6000600001", + "6000600002", + "6000600003", + "6000600004", + "6000600005", + "6000600006", + "6000600007", + "60006000600008", + "60006000600009", + "600060000a", + "600060000b", + "6000600010", + "6000600011", + "6000600012", + "6000600013", + "6000600014", + "600015", + "6000600016", + "6000600017", + "6000600018", + "600019", + "600060001a", + "6000600020", + "30", + "600031", + "32", + "33", + "34", + "600035", + "36", + "60006000600037", + "38", + "60006000600039", + "3a", + "60003b", + "60006000600060003c", + "3d", + "6000600060003e", + "600040", + "41", + "42", + "43", + "44", + "45", + "600050", + "600051", + "6000600052", + "6000600053", + "600054", + "6000600055", + "600056", + "6000600057", + "58", + "59", + "5a", + "60ff", + "61ffff", + "62ffffff", + "63ffffffff", + "64ffffffffff", + "65ffffffffffff", + "66ffffffffffffff", + "67ffffffffffffffff", + "68ffffffffffffffffff", + "69ffffffffffffffffffff", + "6affffffffffffffffffffff", + "6bffffffffffffffffffffffff", + "6cffffffffffffffffffffffffff", + "6dffffffffffffffffffffffffffff", + "6effffffffffffffffffffffffffffff", + "6fffffffffffffffffffffffffffffffff", + "70ffffffffffffffffffffffffffffffffff", + "71ffffffffffffffffffffffffffffffffffff", + "72ffffffffffffffffffffffffffffffffffffff", + "73ffffffffffffffffffffffffffffffffffffffff", + "74ffffffffffffffffffffffffffffffffffffffffff", + "75ffffffffffffffffffffffffffffffffffffffffffff", + "76ffffffffffffffffffffffffffffffffffffffffffffff", + "77ffffffffffffffffffffffffffffffffffffffffffffffff", + "78ffffffffffffffffffffffffffffffffffffffffffffffffff", + "79ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7affffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "60006000a0", + "600060006000a1", + "6000600060006000a2", + "60006000600060006000a3", + "600060006000600060006000a4", + "600060006000f0", + "6000600060006000600060006000f1", + "6000600060006000600060006000f2", + "60006000f3", + "600060006000600060006000f4", + "600060006000600060006000fa", + "60006000fd", + "fe", + "6000ff" + }; + + vector<string> opcodes_lll { + "{ (STOP) }", + "{ (ADD 0 0) }", + "{ (MUL 0 0) }", + "{ (SUB 0 0) }", + "{ (DIV 0 0) }", + "{ (SDIV 0 0) }", + "{ (MOD 0 0) }", + "{ (SMOD 0 0) }", + "{ (ADDMOD 0 0 0) }", + "{ (MULMOD 0 0 0) }", + "{ (EXP 0 0) }", + "{ (SIGNEXTEND 0 0) }", + "{ (LT 0 0) }", + "{ (GT 0 0) }", + "{ (SLT 0 0) }", + "{ (SGT 0 0) }", + "{ (EQ 0 0) }", + "{ (ISZERO 0) }", + "{ (AND 0 0) }", + "{ (OR 0 0) }", + "{ (XOR 0 0) }", + "{ (NOT 0) }", + "{ (BYTE 0 0) }", + "{ (KECCAK256 0 0) }", + "{ (ADDRESS) }", + "{ (BALANCE 0) }", + "{ (ORIGIN) }", + "{ (CALLER) }", + "{ (CALLVALUE) }", + "{ (CALLDATALOAD 0) }", + "{ (CALLDATASIZE) }", + "{ (CALLDATACOPY 0 0 0) }", + "{ (CODESIZE) }", + "{ (CODECOPY 0 0 0) }", + "{ (GASPRICE) }", + "{ (EXTCODESIZE 0) }", + "{ (EXTCODECOPY 0 0 0 0) }", + "{ (RETURNDATASIZE) }", + "{ (RETURNDATACOPY 0 0 0) }", + "{ (BLOCKHASH 0) }", + "{ (COINBASE) }", + "{ (TIMESTAMP) }", + "{ (NUMBER) }", + "{ (DIFFICULTY) }", + "{ (GASLIMIT) }", + "{ (POP 0) }", + "{ (MLOAD 0) }", + "{ (MSTORE 0 0) }", + "{ (MSTORE8 0 0) }", + "{ (SLOAD 0) }", + "{ (SSTORE 0 0) }", + "{ (JUMP 0) }", + "{ (JUMPI 0 0) }", + "{ (PC) }", + "{ (MSIZE) }", + "{ (GAS) }", + "{ 0xff }", + "{ 0xffff }", + "{ 0xffffff }", + "{ 0xffffffff }", + "{ 0xffffffffff }", + "{ 0xffffffffffff }", + "{ 0xffffffffffffff }", + "{ 0xffffffffffffffff }", + "{ 0xffffffffffffffffff }", + "{ 0xffffffffffffffffffff }", + "{ 0xffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff }", + "{ (LOG0 0 0) }", + "{ (LOG1 0 0 0) }", + "{ (LOG2 0 0 0 0) }", + "{ (LOG3 0 0 0 0 0) }", + "{ (LOG4 0 0 0 0 0 0) }", + "{ (CREATE 0 0 0) }", + "{ (CALL 0 0 0 0 0 0 0) }", + "{ (CALLCODE 0 0 0 0 0 0 0) }", + "{ (RETURN 0 0) }", + "{ (DELEGATECALL 0 0 0 0 0 0) }", + "{ (STATICCALL 0 0 0 0 0 0) }", + "{ (REVERT 0 0) }", + "{ (INVALID) }", + "{ (SELFDESTRUCT 0) }" + }; + + for (size_t i = 0; i < opcodes_bytecode.size(); i++) { + vector<string> errors; + bytes code = eth::compileLLL(opcodes_lll[i], false, &errors); + + BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); + + BOOST_CHECK_EQUAL(toHex(code), opcodes_bytecode[i]); + } +} + +BOOST_AUTO_TEST_CASE(valid_opcodes_asm) +{ + vector<string> opcodes_bytecode { + "00", + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "0a", + "0b", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "1a", + "20", + "30", + "31", + "32", + "33", + "34", + "35", + "36", + "37", + "38", + "39", + "3a", + "3b", + "3c", + "3d", + "3e", + "40", + "41", + "42", + "43", + "44", + "45", + "50", + "51", + "52", + "53", + "54", + "55", + "56", + "57", + "58", + "59", + "5a", + "5b", + "60ff", + "61ffff", + "62ffffff", + "63ffffffff", + "64ffffffffff", + "65ffffffffffff", + "66ffffffffffffff", + "67ffffffffffffffff", + "68ffffffffffffffffff", + "69ffffffffffffffffffff", + "6affffffffffffffffffffff", + "6bffffffffffffffffffffffff", + "6cffffffffffffffffffffffffff", + "6dffffffffffffffffffffffffffff", + "6effffffffffffffffffffffffffffff", + "6fffffffffffffffffffffffffffffffff", + "70ffffffffffffffffffffffffffffffffff", + "71ffffffffffffffffffffffffffffffffffff", + "72ffffffffffffffffffffffffffffffffffffff", + "73ffffffffffffffffffffffffffffffffffffffff", + "74ffffffffffffffffffffffffffffffffffffffffff", + "75ffffffffffffffffffffffffffffffffffffffffffff", + "76ffffffffffffffffffffffffffffffffffffffffffffff", + "77ffffffffffffffffffffffffffffffffffffffffffffffff", + "78ffffffffffffffffffffffffffffffffffffffffffffffffff", + "79ffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7affffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7cffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "80", + "81", + "82", + "83", + "84", + "85", + "86", + "87", + "88", + "89", + "8a", + "8b", + "8c", + "8d", + "8e", + "8f", + "90", + "91", + "92", + "93", + "94", + "95", + "96", + "97", + "98", + "99", + "9a", + "9b", + "9c", + "9d", + "9e", + "9f", + "a0", + "a1", + "a2", + "a3", + "a4", + "f0", + "f1", + "f2", + "f3", + "f4", + "fa", + "fd", + "fe", + "ff" + }; + + vector<string> opcodes_lll { + "{ (asm STOP) }", + "{ (asm ADD) }", + "{ (asm MUL) }", + "{ (asm SUB) }", + "{ (asm DIV) }", + "{ (asm SDIV ) }", + "{ (asm MOD) }", + "{ (asm SMOD) }", + "{ (asm ADDMOD) }", + "{ (asm MULMOD) }", + "{ (asm EXP) }", + "{ (asm SIGNEXTEND) }", + "{ (asm LT) }", + "{ (asm GT) }", + "{ (asm SLT) }", + "{ (asm SGT) }", + "{ (asm EQ) }", + "{ (asm ISZERO) }", + "{ (asm AND) }", + "{ (asm OR) }", + "{ (asm XOR) }", + "{ (asm NOT) }", + "{ (asm BYTE) }", + "{ (asm KECCAK256) }", + "{ (asm ADDRESS) }", + "{ (asm BALANCE) }", + "{ (asm ORIGIN) }", + "{ (asm CALLER) }", + "{ (asm CALLVALUE) }", + "{ (asm CALLDATALOAD) }", + "{ (asm CALLDATASIZE) }", + "{ (asm CALLDATACOPY) }", + "{ (asm CODESIZE) }", + "{ (asm CODECOPY) }", + "{ (asm GASPRICE) }", + "{ (asm EXTCODESIZE)}", + "{ (asm EXTCODECOPY) }", + "{ (asm RETURNDATASIZE) }", + "{ (asm RETURNDATACOPY) }", + "{ (asm BLOCKHASH) }", + "{ (asm COINBASE) }", + "{ (asm TIMESTAMP) }", + "{ (asm NUMBER) }", + "{ (asm DIFFICULTY) }", + "{ (asm GASLIMIT) }", + "{ (asm POP) }", + "{ (asm MLOAD) }", + "{ (asm MSTORE) }", + "{ (asm MSTORE8) }", + "{ (asm SLOAD) }", + "{ (asm SSTORE) }", + "{ (asm JUMP ) }", + "{ (asm JUMPI ) }", + "{ (asm PC) }", + "{ (asm MSIZE) }", + "{ (asm GAS) }", + "{ (asm JUMPDEST) }", + "{ (asm 0xff) }", + "{ (asm 0xffff) }", + "{ (asm 0xffffff) }", + "{ (asm 0xffffffff) }", + "{ (asm 0xffffffffff) }", + "{ (asm 0xffffffffffff) }", + "{ (asm 0xffffffffffffff) }", + "{ (asm 0xffffffffffffffff) }", + "{ (asm 0xffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) }", + "{ (asm DUP1) }", + "{ (asm DUP2) }", + "{ (asm DUP3) }", + "{ (asm DUP4) }", + "{ (asm DUP5) }", + "{ (asm DUP6) }", + "{ (asm DUP7) }", + "{ (asm DUP8) }", + "{ (asm DUP9) }", + "{ (asm DUP10) }", + "{ (asm DUP11) }", + "{ (asm DUP12) }", + "{ (asm DUP13) }", + "{ (asm DUP14) }", + "{ (asm DUP15) }", + "{ (asm DUP16) }", + "{ (asm SWAP1) }", + "{ (asm SWAP2) }", + "{ (asm SWAP3) }", + "{ (asm SWAP4) }", + "{ (asm SWAP5) }", + "{ (asm SWAP6) }", + "{ (asm SWAP7) }", + "{ (asm SWAP8) }", + "{ (asm SWAP9) }", + "{ (asm SWAP10) }", + "{ (asm SWAP11) }", + "{ (asm SWAP12) }", + "{ (asm SWAP13) }", + "{ (asm SWAP14) }", + "{ (asm SWAP15) }", + "{ (asm SWAP16) }", + "{ (asm LOG0) }", + "{ (asm LOG1) }", + "{ (asm LOG2) }", + "{ (asm LOG3) }", + "{ (asm LOG4) }", + "{ (asm CREATE) }", + "{ (asm CALL) }", + "{ (asm CALLCODE) }", + "{ (asm RETURN) }", + "{ (asm DELEGATECALL) }", + "{ (asm STATICCALL) }", + "{ (asm REVERT) }", + "{ (asm INVALID) }", + "{ (asm SELFDESTRUCT) }" + }; + + for (size_t i = 0; i < opcodes_bytecode.size(); i++) { + vector<string> errors; + bytes code = eth::compileLLL(opcodes_lll[i], false, &errors); + + BOOST_REQUIRE_MESSAGE(errors.empty(), opcodes_lll[i]); + + BOOST_CHECK_EQUAL(toHex(code), opcodes_bytecode[i]); + } +} + 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/AnalysisFramework.cpp b/test/libsolidity/AnalysisFramework.cpp index 3bdc40a0..ea9703ea 100644 --- a/test/libsolidity/AnalysisFramework.cpp +++ b/test/libsolidity/AnalysisFramework.cpp @@ -81,7 +81,7 @@ AnalysisFramework::parseAnalyseAndReturnError( } } - return make_pair(&m_compiler.ast(), firstError); + return make_pair(&m_compiler.ast(""), firstError); } SourceUnit const* AnalysisFramework::parseAndAnalyse(string const& _source) diff --git a/test/libsolidity/GasMeter.cpp b/test/libsolidity/GasMeter.cpp index c2886f5b..86e8201b 100644 --- a/test/libsolidity/GasMeter.cpp +++ b/test/libsolidity/GasMeter.cpp @@ -51,8 +51,8 @@ public: m_compiler.setOptimiserSettings(dev::test::Options::get().optimize); BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); - AssemblyItems const* items = m_compiler.runtimeAssemblyItems(""); - ASTNode const& sourceUnit = m_compiler.ast(); + AssemblyItems const* items = m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()); + ASTNode const& sourceUnit = m_compiler.ast(""); BOOST_REQUIRE(items != nullptr); m_gasCosts = GasEstimator::breakToStatementLevel( GasEstimator::structuralEstimation(*items, vector<ASTNode const*>({&sourceUnit})), @@ -64,13 +64,13 @@ public: { compileAndRun(_sourceCode); auto state = make_shared<KnownState>(); - PathGasMeter meter(*m_compiler.assemblyItems()); + PathGasMeter meter(*m_compiler.assemblyItems(m_compiler.lastContractName())); GasMeter::GasConsumption gas = meter.estimateMax(0, state); - u256 bytecodeSize(m_compiler.runtimeObject().bytecode.size()); + u256 bytecodeSize(m_compiler.runtimeObject(m_compiler.lastContractName()).bytecode.size()); // costs for deployment gas += bytecodeSize * GasCosts::createDataGas; // costs for transaction - gas += gasForTransaction(m_compiler.object().bytecode, true); + gas += gasForTransaction(m_compiler.object(m_compiler.lastContractName()).bytecode, true); BOOST_REQUIRE(!gas.isInfinite); BOOST_CHECK(gas.value == m_gasUsed); @@ -91,7 +91,7 @@ public: } gas += GasEstimator::functionalEstimation( - *m_compiler.runtimeAssemblyItems(), + *m_compiler.runtimeAssemblyItems(m_compiler.lastContractName()), _sig ); BOOST_REQUIRE(!gas.isInfinite); @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs) if (first->first->location().intersects(second->first->location())) { BOOST_CHECK_MESSAGE(false, "Source locations should not overlap!"); - auto scannerFromSource = [&](string const&) -> Scanner const& { return m_compiler.scanner(); }; + auto scannerFromSource = [&](string const& _sourceName) -> Scanner const& { return m_compiler.scanner(_sourceName); }; SourceReferenceFormatter::printSourceLocation(cout, &first->first->location(), scannerFromSource); SourceReferenceFormatter::printSourceLocation(cout, &second->first->location(), scannerFromSource); } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index da3522b4..e9fb8431 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) { diff --git a/test/libsolidity/SMTChecker.cpp b/test/libsolidity/SMTChecker.cpp index 8d712a80..667d666b 100644 --- a/test/libsolidity/SMTChecker.cpp +++ b/test/libsolidity/SMTChecker.cpp @@ -105,6 +105,359 @@ 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) public pure { + x = 7; + while ((x = 5) > 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_SUITE_END() } diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 42f7525f..33962730 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -46,7 +46,7 @@ public: m_compilerStack.addSource("", "pragma solidity >=0.0;\n" + _code); BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed"); - Json::Value generatedInterface = m_compilerStack.contractABI(""); + Json::Value generatedInterface = m_compilerStack.contractABI(m_compilerStack.lastContractName()); Json::Value expectedInterface; BOOST_REQUIRE(m_reader.parse(_expectedInterfaceString, expectedInterface)); BOOST_CHECK_MESSAGE( diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 9a837113..05dc9ba3 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/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index 342d0875..b0daaba9 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -69,7 +69,7 @@ public: ); BOOST_ERROR("Compiling contract failed"); } - eth::LinkerObject obj = m_compiler.object(_contractName); + eth::LinkerObject obj = m_compiler.object(_contractName.empty() ? m_compiler.lastContractName() : _contractName); BOOST_REQUIRE(obj.linkReferences.empty()); sendMessage(obj.bytecode + _arguments, true, _value); return m_output; diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 9b0647bf..97d359e8 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1038,7 +1038,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 +1392,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 +2107,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) @@ -4033,7 +4033,36 @@ 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) +{ + char const* text = R"( + contract test { + modifier mod() { _; } + + function f() public { + mod g; + } + } + )"; + CHECK_ERROR(text, TypeError, "Name has to refer to a struct, enum or contract."); +} + +BOOST_AUTO_TEST_CASE(function_is_not_a_valid_typename) +{ + char const* text = R"( + contract test { + function foo() public { + } + + function f() public { + foo g; + } + } + )"; + CHECK_ERROR(text, TypeError, "Name has to refer to a struct, enum or contract."); } BOOST_AUTO_TEST_CASE(long_uint_variable_fails) @@ -4045,7 +4074,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) @@ -4084,7 +4113,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) @@ -4783,6 +4812,16 @@ BOOST_AUTO_TEST_CASE(warn_about_callcode) } )"; CHECK_WARNING(text, "\"callcode\" has been deprecated in favour of \"delegatecall\""); + text = R"( + pragma experimental "v0.5.0"; + contract test { + function f() pure public { + var x = address(0x12).callcode; + x; + } + } + )"; + CHECK_ERROR(text, TypeError, "\"callcode\" has been deprecated in favour of \"delegatecall\""); } BOOST_AUTO_TEST_CASE(no_warn_about_callcode_as_function) @@ -5651,7 +5690,7 @@ BOOST_AUTO_TEST_CASE(constructible_internal_constructor) function D() public { } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(return_structs) @@ -5664,7 +5703,7 @@ BOOST_AUTO_TEST_CASE(return_structs) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(return_recursive_structs) @@ -5714,7 +5753,7 @@ BOOST_AUTO_TEST_CASE(address_checksum_type_deduction) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(invalid_address_checksum) @@ -5727,7 +5766,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) @@ -5740,10 +5779,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 { @@ -5753,7 +5792,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) @@ -5844,7 +5896,7 @@ BOOST_AUTO_TEST_CASE(interface) interface I { } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_constructor) @@ -5865,7 +5917,7 @@ BOOST_AUTO_TEST_CASE(interface_functions) function f(); } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_function_bodies) @@ -5887,7 +5939,7 @@ BOOST_AUTO_TEST_CASE(interface_function_external) function f() external; } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_function_public) @@ -5928,7 +5980,7 @@ BOOST_AUTO_TEST_CASE(interface_events) event E(); } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(interface_inheritance) @@ -5971,7 +6023,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) @@ -5995,7 +6047,7 @@ BOOST_AUTO_TEST_CASE(using_interface) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(using_interface_complex) @@ -6012,7 +6064,7 @@ BOOST_AUTO_TEST_CASE(using_interface_complex) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(warn_about_throw) @@ -6071,7 +6123,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) @@ -6187,7 +6239,7 @@ BOOST_AUTO_TEST_CASE(warn_unused_function_parameter) } } )"; - success(text); + CHECK_SUCCESS(text); } BOOST_AUTO_TEST_CASE(warn_unused_return_parameter) @@ -7211,6 +7263,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"( @@ -7237,6 +7434,12 @@ BOOST_AUTO_TEST_CASE(array_length_invalid_expression) } )"; CHECK_ERROR(text, TypeError, "Invalid literal value."); + text = R"( + contract C { + uint[3/0] ids; + } + )"; + CHECK_ERROR(text, TypeError, "Operator / not compatible with types int_const 3 and int_const 0"); } BOOST_AUTO_TEST_CASE(no_address_members_on_contract) diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index d83773bc..fb09451f 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -51,9 +51,9 @@ public: Json::Value generatedDocumentation; if (_userDocumentation) - generatedDocumentation = m_compilerStack.natspecUser(""); + generatedDocumentation = m_compilerStack.natspecUser(m_compilerStack.lastContractName()); else - generatedDocumentation = m_compilerStack.natspecDev(""); + generatedDocumentation = m_compilerStack.natspecDev(m_compilerStack.lastContractName()); Json::Value expectedDocumentation; m_reader.parse(_expectedDocumentationString, expectedDocumentation); BOOST_CHECK_MESSAGE( 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" ] + } + } } } )"; |