diff options
-rw-r--r-- | .circleci/config.yml | 24 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | docs/bugs.json | 35 | ||||
-rw-r--r-- | docs/bugs.rst | 17 | ||||
-rw-r--r-- | docs/index.rst | 27 | ||||
-rw-r--r-- | docs/introduction-to-smart-contracts.rst | 39 | ||||
-rw-r--r-- | docs/metadata.rst | 34 | ||||
-rwxr-xr-x | test/buglistTests.js | 131 | ||||
-rw-r--r-- | test/buglist_test_vectors.md | 45 |
9 files changed, 257 insertions, 97 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index aec8be18..3e3d8c0a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -189,15 +189,21 @@ jobs: command: ./scripts/detect_trailing_whitespace.sh test_buglist: - docker: - - image: circleci/node - environment: - TERM: xterm - steps: - - checkout - - run: - name: Test buglist - command: ./test/buglistTests.js + docker: + - image: circleci/node + environment: + TERM: xterm + steps: + - checkout + - run: + name: JS deps + command: | + npm install download + npm install JSONPath + npm install mktemp + - run: + name: Test buglist + command: ./test/buglistTests.js test_x86_linux: docker: @@ -36,6 +36,8 @@ docs/_build docs/utils/__pycache__ docs/utils/*.pyc /deps/downloads/ +deps/install +deps/cache # vim stuff [._]*.sw[a-p] diff --git a/docs/bugs.json b/docs/bugs.json index 839ea128..cf03adfe 100644 --- a/docs/bugs.json +++ b/docs/bugs.json @@ -1,21 +1,22 @@ [ - { - "name": "EventStructWrongData", - "summary": "Using structs in events logged wrong data.", - "description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.", - "introduced": "0.4.17", - "fixed": "0.5.0", - "severity": "very low" - }, - { - "name": "NestedArrayFunctionCallDecoder", - "summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.", - "description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.", - "introduced": "0.1.4", - "fixed": "0.4.22", - "severity": "medium", - "check": {"regex-source": "returns[^;{]*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\]\\s*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\][^{;]*[;{]"} - }, + { + "name": "EventStructWrongData", + "summary": "Using structs in events logged wrong data.", + "description": "If a struct is used in an event, the address of the struct is logged instead of the actual data.", + "introduced": "0.4.17", + "fixed": "0.5.0", + "severity": "very low", + "check": {"ast-compact-json-path": "$..[?(@.nodeType === 'EventDefinition')]..[?(@.nodeType === 'UserDefinedTypeName' && @.typeDescriptions.typeString.startsWith('struct'))]"} + }, + { + "name": "NestedArrayFunctionCallDecoder", + "summary": "Calling functions that return multi-dimensional fixed-size arrays can result in memory corruption.", + "description": "If Solidity code calls a function that returns a multi-dimensional fixed-size array, array elements are incorrectly interpreted as memory pointers and thus can cause memory corruption if the return values are accessed. Calling functions with multi-dimensional fixed-size arrays is unaffected as is returning fixed-size arrays from function calls. The regular expression only checks if such functions are present, not if they are called, which is required for the contract to be affected.", + "introduced": "0.1.4", + "fixed": "0.4.22", + "severity": "medium", + "check": {"regex-source": "returns[^;{]*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\]\\s*\\[\\s*[^\\] \\t\\r\\n\\v\\f][^\\]]*\\][^{;]*[;{]"} + }, { "name": "OneOfTwoConstructorsSkipped", "summary": "If a contract has both a new-style constructor (using the constructor keyword) and an old-style constructor (a function with the same name as the contract) at the same time, one of them will be ignored.", diff --git a/docs/bugs.rst b/docs/bugs.rst index 6f315a65..f7522183 100644 --- a/docs/bugs.rst +++ b/docs/bugs.rst @@ -57,13 +57,18 @@ conditions means that the optimizer has to be switched on to enable the bug. If no conditions are given, assume that the bug is present. check - This field contains JavaScript regular expressions that are to be matched - against the source code ("source-regex") to find out if the - smart contract contains the bug or not. If there is no match, - then the bug is very likely not present. If there is a match, - the bug might be present. For improved accuracy, the regular - expression should be applied to the source code after stripping + This field contains different checks that report whether the smart contract + contains the bug or not. The first type of check are Javascript regular + expressions that are to be matched against the source code ("source-regex") + if the bug is present. If there is no match, then the bug is very likely + not present. If there is a match, the bug might be present. For improved + accuracy, the checks should be applied to the source code after stripping comments. + The second type of check are patterns to be checked on the compact AST of + the Solidity program ("ast-compact-json-path"). The specified search query + is a `JsonPath <https://github.com/json-path/JsonPath>`_ expression. + If at least one path of the Solidity AST matches the query, the bug is + likely present. .. literalinclude:: bugs.json :language: js diff --git a/docs/index.rst b/docs/index.rst index 9f2844c7..20fa1505 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,7 +34,8 @@ crowdfunding, blind auctions, multi-signature wallets and more. Translations ------------ -This documentation is translated into several languages by community volunteers, but the English version stands as a reference. +This documentation is translated into several languages by community volunteers +with varying degrees of completeness and up-to-dateness. The English version stands as a reference. * `Simplified Chinese <http://solidity-cn.readthedocs.io>`_ (in progress) * `Spanish <https://solidity-es.readthedocs.io>`_ @@ -45,20 +46,23 @@ This documentation is translated into several languages by community volunteers, Useful links ------------ +General +~~~~~~~ + * `Ethereum <https://ethereum.org>`_ * `Changelog <https://github.com/ethereum/solidity/blob/develop/Changelog.md>`_ -* `Story Backlog <https://www.pivotaltracker.com/n/projects/1189488>`_ - * `Source Code <https://github.com/ethereum/solidity/>`_ * `Ethereum Stackexchange <https://ethereum.stackexchange.com/>`_ -* `Gitter Chat <https://gitter.im/ethereum/solidity/>`_ +* `Language Users Chat <https://gitter.im/ethereum/solidity/>`_ + +* `Compiler Developers Chat <https://gitter.im/ethereum/solidity-dev/>`_ Available Solidity Integrations -------------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Generic: @@ -82,6 +86,11 @@ Available Solidity Integrations * `Atom Solium Linter <https://atom.io/packages/linter-solium>`_ Configurable Solidty linter for Atom using Solium as a base. +* Eclipse: + + * `YAKINDU Solidity Tools <https://yakindu.github.io/solidity-ide/>`_ + Eclipse based IDE. Features context sensitive code completion and help, code navigation, syntax coloring, built in compiler, quick fixes and templates. + * Emacs: * `Emacs Solidity <https://github.com/ethereum/emacs-solidity/>`_ @@ -122,7 +131,7 @@ Discontinued: Solidity plugin for Microsoft Visual Studio that includes the Solidity compiler. Solidity Tools --------------- +~~~~~~~~~~~~~~ * `Dapp <https://dapp.tools/dapp/>`_ Build tool, package manager, and deployment assistant for Solidity. @@ -155,7 +164,7 @@ Solidity Tools Information like variable names, comments, and source code formatting is lost in the compilation process and it is not possible to completely recover the original source code. Decompiling smart contracts to view the original source code might not be possible, or the end result that useful. Third-Party Solidity Parsers and Grammars ------------------------------------------ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * `solidity-parser <https://github.com/ConsenSys/solidity-parser>`_ Solidity parser for JavaScript @@ -171,11 +180,11 @@ in Solidity followed by the basics about :ref:`blockchains <blockchain-basics>` and the :ref:`Ethereum Virtual Machine <the-ethereum-virtual-machine>`. The next section will explain several *features* of Solidity by giving -useful :ref:`example contracts <voting>` +useful :ref:`example contracts <voting>`. Remember that you can always try out the contracts `in your browser <https://remix.ethereum.org>`_! -The last and most extensive section will cover all aspects of Solidity in depth. +The fourth and most extensive section will cover all aspects of Solidity in depth. If you still have questions, you can try searching or asking on the `Ethereum Stackexchange <https://ethereum.stackexchange.com/>`_ diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index f5769363..7bfee0a1 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -33,14 +33,14 @@ Storage The first line simply tells that the source code is written for Solidity version 0.4.0 or anything newer that does not break functionality (up to, but not including, version 0.5.0). This is to ensure that the -contract does not suddenly behave differently with a new compiler version. The keyword ``pragma`` is called that because, in general, -pragmas are instructions for the compiler about how to treat the +contract is not compilable with a new (breaking) compiler version, where it could behave differently. +So-called pragmas are common instrutions for compilers about how to treat the source code (e.g. `pragma once <https://en.wikipedia.org/wiki/Pragma_once>`_). A contract in the sense of Solidity is a collection of code (its *functions*) and data (its *state*) that resides at a specific address on the Ethereum blockchain. The line ``uint storedData;`` declares a state variable called ``storedData`` of -type ``uint`` (unsigned integer of 256 bits). You can think of it as a single slot +type ``uint`` (*u*nsigned *int*eger of *256* bits). You can think of it as a single slot in a database that can be queried and altered by calling functions of the code that manages the database. In the case of Ethereum, this is always the owning contract. And in this case, the functions ``set`` and ``get`` can be used to modify @@ -49,8 +49,8 @@ or retrieve the value of the variable. To access a state variable, you do not need the prefix ``this.`` as is common in other languages. -This contract does not do much yet (due to the infrastructure -built by Ethereum) apart from allowing anyone to store a single number that is accessible by +This contract does not do much yet apart from (due to the infrastructure +built by Ethereum) allowing anyone to store a single number that is accessible by anyone in the world without a (feasible) way to prevent you from publishing this number. Of course, anyone could just call ``set`` again with a different value and overwrite your number, but the number will still be stored in the history @@ -72,9 +72,9 @@ Subcurrency Example The following contract will implement the simplest form of a cryptocurrency. It is possible to generate coins out of thin air, but -only the person that created the contract will be able to do that (it is trivial +only the person that created the contract will be able to do that (it is easy to implement a different issuance scheme). -Furthermore, anyone can send coins to each other without any need for +Furthermore, anyone can send coins to each other without a need for registering with username and password — all you need is an Ethereum keypair. @@ -84,7 +84,7 @@ registering with username and password — all you need is an Ethereum keypair. contract Coin { // The keyword "public" makes those variables - // readable from outside. + // easily readable from outside. address public minter; mapping (address => uint) public balances; @@ -99,12 +99,13 @@ registering with username and password — all you need is an Ethereum keypair. } function mint(address receiver, uint amount) public { - if (msg.sender != minter) return; + require(msg.sender == minter); + require(amount < 1e60); balances[receiver] += amount; } function send(address receiver, uint amount) public { - if (balances[msg.sender] < amount) return; + require(amount <= balances[msg.sender], "Insufficient balance."); balances[msg.sender] -= amount; balances[receiver] += amount; emit Sent(msg.sender, receiver, amount); @@ -116,15 +117,15 @@ This contract introduces some new concepts, let us go through them one by one. The line ``address public minter;`` declares a state variable of type address that is publicly accessible. The ``address`` type is a 160-bit value that does not allow any arithmetic operations. It is suitable for -storing addresses of contracts or keypairs belonging to external +storing addresses of contracts or of keypairs belonging to external persons. The keyword ``public`` automatically generates a function that allows you to access the current value of the state variable from outside of the contract. Without this keyword, other contracts have no way to access the variable. The code of the function generated by the compiler is roughly equivalent -to the following:: +to the following (ignore ``external`` and ``view`` for now):: - function minter() returns (address) { return minter; } + function minter() external view returns (address) { return minter; } Of course, adding a function exactly like that will not work because we would have a @@ -137,17 +138,17 @@ The next line, ``mapping (address => uint) public balances;`` also creates a public state variable, but it is a more complex datatype. The type maps addresses to unsigned integers. Mappings can be seen as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_ which are -virtually initialized such that every possible key exists and is mapped to a +virtually initialized such that every possible key exists from the start and is mapped to a value whose byte-representation is all zeros. This analogy does not go too far, though, as it is neither possible to obtain a list of all keys of a mapping, nor a list of all values. So either keep in mind (or better, keep a list or use a more advanced data type) what you -added to the mapping or use it in a context where this is not needed, -like this one. The :ref:`getter function<getter-functions>` created by the ``public`` keyword +added to the mapping or use it in a context where this is not needed. +The :ref:`getter function<getter-functions>` created by the ``public`` keyword is a bit more complex in this case. It roughly looks like the following:: - function balances(address _account) public view returns (uint) { + function balances(address _account) external view returns (uint) { return balances[_account]; } @@ -162,7 +163,9 @@ a so-called "event" which is emitted in the last line of the function listen for those events being emitted on the blockchain without much cost. As soon as it is emitted, the listener will also receive the arguments ``from``, ``to`` and ``amount``, which makes it easy to track -transactions. In order to listen for this event, you would use :: +transactions. In order to listen for this event, you would use the following +JavaScript code (which assumes that ``Coin`` is a contract object created via +web3.js or a similar module):: Coin.Sent().watch({}, '', function(error, result) { if (!error) { diff --git a/docs/metadata.rst b/docs/metadata.rst index 059faad2..9c4f2574 100644 --- a/docs/metadata.rst +++ b/docs/metadata.rst @@ -4,28 +4,28 @@ Contract Metadata .. index:: metadata, contract verification -The Solidity compiler automatically generates a JSON file, the -contract metadata, that contains information about the current contract. -It can be used to query the compiler version, the sources used, the ABI -and NatSpec documentation in order to more safely interact with the contract -and to verify its source code. +The Solidity compiler automatically generates a JSON file, the contract +metadata, that contains information about the current contract. You can use +this file to query the compiler version, the sources used, the ABI and NatSpec +documentation to more safely interact with the contract and verify its source +code. The compiler appends a Swarm hash of the metadata file to the end of the bytecode (for details, see below) of each contract, so that you can retrieve the file in an authenticated way without having to resort to a centralized data provider. -Of course, you have to publish the metadata file to Swarm (or some other service) -so that others can access it. The file can be output by using ``solc --metadata`` -and the file will be called ``ContractName_meta.json``. -It will contain Swarm references to the source code, so you have to upload -all source files and the metadata file. +You have to publish the metadata file to Swarm (or another service) so that +others can access it. You create the file by using the ``solc --metadata`` +command that generates a file called ``ContractName_meta.json``. It contains +Swarm references to the source code, so you have to upload all source files and +the metadata file. The metadata file has the following format. The example below is presented in a human-readable way. Properly formatted metadata should use quotes correctly, reduce whitespace to a minimum and sort the keys of all objects to arrive at a -unique formatting. -Comments are of course also not permitted and used here only for explanatory purposes. +unique formatting. Comments are not permitted and used here only for +explanatory purposes. .. code-block:: none @@ -96,11 +96,11 @@ Comments are of course also not permitted and used here only for explanatory pur .. note:: Note the ABI definition above has no fixed order. It can change with compiler versions. -.. note:: - Since the bytecode of the resulting contract contains the metadata hash, any change to - the metadata will result in a change of the bytecode. Furthermore, since the metadata - includes a hash of all the sources used, a single whitespace change in any of the source - codes will result in a different metadata, and subsequently a different bytecode. +Since the bytecode of the resulting contract contains the metadata hash, any +change to the metadata results in a change of the bytecode. This includes +changes to a filename or path, and since the metadata includes a hash of all the +sources used, a single whitespace change results in different metadata, and +different bytecode. Encoding of the Metadata Hash in the Bytecode ============================================= diff --git a/test/buglistTests.js b/test/buglistTests.js index 6b7df2f2..f24f0cb6 100755 --- a/test/buglistTests.js +++ b/test/buglistTests.js @@ -2,6 +2,11 @@ "use strict"; +var util = require('util') +var exec = util.promisify(require('child_process').exec) +var mktemp = require('mktemp'); +var download = require('download') +var JSONPath = require('JSONPath') var fs = require('fs') var bugs = JSON.parse(fs.readFileSync(__dirname + '/../docs/bugs.json', 'utf8')) @@ -19,27 +24,111 @@ var tests = fs.readFileSync(__dirname + '/buglist_test_vectors.md', 'utf8') var testVectorParser = /\s*#\s+(\S+)\s+## buggy\n([^#]*)## fine\n([^#]*)/g -var result; -while ((result = testVectorParser.exec(tests)) !== null) +runTests() + +async function runTests() { - var name = result[1] - var buggy = result[2].split('\n--\n') - var fine = result[3].split('\n--\n') - console.log("Testing " + name + " with " + buggy.length + " buggy and " + fine.length + " fine instances") + var result; + while ((result = testVectorParser.exec(tests)) !== null) + { + var name = result[1] + var buggy = result[2].split('\n--\n') + var fine = result[3].split('\n--\n') + console.log("Testing " + name + " with " + buggy.length + " buggy and " + fine.length + " fine instances") - var regex = RegExp(bugsByName[name].check['regex-source']) - for (var i in buggy) - { - if (!regex.exec(buggy[i])) - { - throw "Bug " + name + ": Buggy source does not match: " + buggy[i] - } - } - for (var i in fine) - { - if (regex.exec(fine[i])) - { - throw "Bug " + name + ": Non-buggy source matches: " + fine[i] - } - } + try { + await checkRegex(name, buggy, fine) + await checkJSONPath(name, buggy, fine) + } catch (err) { + console.error("Error: " + err) + } + } +} + +function checkRegex(name, buggy, fine) +{ + return new Promise(function(resolve, reject) { + var regexStr = bugsByName[name].check['regex-source'] + if (regexStr !== undefined) + { + var regex = RegExp(regexStr) + for (var i in buggy) + { + if (!regex.exec(buggy[i])) + { + reject("Bug " + name + ": Buggy source does not match: " + buggy[i]) + } + } + for (var i in fine) + { + if (regex.exec(fine[i])) + { + reject("Bug " + name + ": Non-buggy source matches: " + fine[i]) + } + } + } + resolve() + }) +} + +async function checkJSONPath(name, buggy, fine) +{ + var jsonPath = bugsByName[name].check['ast-compact-json-path'] + if (jsonPath !== undefined) + { + var url = "http://github.com/ethereum/solidity/releases/download/v" + bugsByName[name].introduced + "/solc-static-linux" + try { + var tmpdir = await mktemp.createDir('XXXXX') + var binary = tmpdir + "/solc-static-linux" + await download(url, tmpdir) + exec("chmod +x " + binary) + for (var i in buggy) + { + var result = await checkJsonPathTest(buggy[i], tmpdir, binary, jsonPath, i) + if (!result) + throw "Bug " + name + ": Buggy source does not contain path: " + buggy[i] + } + for (var i in fine) + { + var result = await checkJsonPathTest(fine[i], tmpdir, binary, jsonPath, i + buggy.length) + if (result) + throw "Bug " + name + ": Non-buggy source contains path: " + fine[i] + } + exec("rm -r " + tmpdir) + } catch (err) { + throw err + } + } +} + +function checkJsonPathTest(code, tmpdir, binary, query, idx) { + return new Promise(function(resolve, reject) { + var solFile = tmpdir + "/jsonPath" + idx + ".sol" + var astFile = tmpdir + "/ast" + idx + ".json" + writeFilePromise(solFile, code) + .then(() => { + return exec(binary + " --ast-compact-json " + solFile + " > " + astFile) + }) + .then(() => { + var jsonRE = /(\{[\s\S]*\})/ + var ast = JSON.parse(jsonRE.exec(fs.readFileSync(astFile, 'utf8'))[0]) + var result = JSONPath({json: ast, path: query}) + if (result.length > 0) + resolve(true) + else + resolve(false) + }) + .catch((err) => { + reject(err) + }) + }) +} + +function writeFilePromise(filename, data) { + return new Promise(function(resolve, reject) { + fs.writeFile(filename, data, 'utf8', function(err) { + if (err) reject(err) + else resolve(data) + }) + }) } diff --git a/test/buglist_test_vectors.md b/test/buglist_test_vectors.md index ce95403b..f15cf151 100644 --- a/test/buglist_test_vectors.md +++ b/test/buglist_test_vectors.md @@ -67,3 +67,48 @@ function f() m(uint[2][2]) { } -- function f() returns (uint, uint) { uint[2][2] memory x; } + +# EventStructWrongData + +## buggy + +pragma experimental ABIEncoderV2; +contract C +{ + struct S { uint x; } + event E(S); + event F(S); + enum A { B, C } + event G(A); + function f(S s); +} + +-- + +pragma experimental ABIEncoderV2; +contract C +{ + struct S { uint x; } + event E(S indexed); + event F(uint, S, bool); +} + +## fine + +pragma experimental ABIEncoderV2; +contract C +{ + struct S { uint x; } + enum A { B, C } + event G(A); +} + +-- + +pragma experimental ABIEncoderV2; +contract C +{ + struct S { uint x; } + function f(S s); + S s1; +} |