diff options
-rw-r--r-- | docs/conf.py | 2 | ||||
-rw-r--r-- | docs/control-structures.rst | 33 | ||||
-rw-r--r-- | docs/index.rst | 14 | ||||
-rw-r--r-- | docs/introduction-to-smart-contracts.rst | 4 | ||||
-rw-r--r-- | docs/miscellaneous.rst | 2 | ||||
-rw-r--r-- | docs/security-considerations.rst | 84 | ||||
-rw-r--r-- | docs/types.rst | 2 | ||||
-rw-r--r-- | solc/CommandLineInterface.cpp | 16 |
8 files changed, 90 insertions, 67 deletions
diff --git a/docs/conf.py b/docs/conf.py index 8776ec43..d0e26362 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ master_doc = 'index' # General information about the project. project = 'Solidity' -copyright = '2015, Ethereum' +copyright = '2016, Ethereum' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 6d615caf..f30a5bdd 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -70,15 +70,18 @@ only (locally) sets the value and amount of gas sent with the function call and parentheses at the end perform the actual call. .. warning:: - Any interaction with another contract imposes a certain danger, especially + Any interaction with another contract imposes a potential danger, especially if the source code of the contract is not known in advance. The current - contract hands over control to the called contract and that might do - just about anything. Be prepared that it calls into other contracts of - your system and perhaps even back into the calling contract before your + contract hands over control to the called contract and that may potentially + do just about anything. Even if the called contract inherits from a known parent contract, + the inheriting contract is only required to have a correct interface. The + implementation of the contract, however, can be completely arbitrary and thus, + pose a danger. In addition, be prepared in case it calls into other contracts of + your system or even back into the calling contract before the first call returns. This means that the called contract can change state variables of the calling contract - via its functions. Write your functions in a way that e.g. calls to - external functions happen after any changes to state variables in your contract, + via its functions. Write your functions in a way that, for example, calls to + external functions happen after any changes to state variables in your contract so your contract is not vulnerable to a recursive call exploit. Named Calls and Anonymous Function Parameters @@ -158,14 +161,20 @@ Complications for Arrays and Structs The semantics of assignment are a bit more complicated for non-value types like arrays and structs. Assigning *to* a state variable always creates an independent copy. On the other hand, assigning to a local variable creates an independent copy only for elementary types, i.e. static types that fit into 32 bytes. If structs or arrays (including ``bytes`` and ``string``) are assigned from a state variable to a local variable, the local variable holds a reference to the original state variable. A second assignment to the local variable does not modify the state but only changes the reference. Assignments to members (or elements) of the local variable *do* change the state. -.. index:: ! exception, ! throw +.. index:: ! scoping, declarations, default value + +.. _default-value: Scoping and Declarations ======================== -.. index:: ! scoping, ! declarations +A variable which is declared will have an initial default value whose byte-representation is all zeros. +The "default values" of variables are the typical "zero-state" of whatever the type is. For example, the default value for a ``bool`` +is ``false``. The default value for the ``uint`` or ``int`` types is ``0``. For statically-sized arrays and ``bytes1`` to ``bytes32``, each individual +element will be initialized to the default value corresponding to its type. Finally, for dynamically-sized arrays, ``bytes`` +and ``string``, the default value is an empty array or string. -In Solidity, a variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared. +A variable declared anywhere within a function will be in scope for the *entire function*, regardless of where it is declared. This happens because Solidity inherits its scoping rules from JavaScript. This is in contrast to many languages where variables are only scoped where they are declared until the end of the semantic block. As a result, the following code is illegal and cause the compiler to throw an error, ``Identifier already declared``:: @@ -217,6 +226,8 @@ As a result, the following code is legal, despite being poorly written:: return bar;// returns 5 } +.. index:: ! exception, ! throw + Exceptions ========== @@ -411,7 +422,7 @@ The opcodes ``pushi`` and ``jumpdest`` cannot be used directly. +-------------------------+------+-----------------------------------------------------------------+ | dup1 ... dup16 | | copy ith stack slot to the top (counting from top) | +-------------------------+------+-----------------------------------------------------------------+ -| swap1 ... swap1 | `*` | swap topmost and ith stack slot below it | +| swap1 ... swap16 | `*` | swap topmost and ith stack slot below it | +-------------------------+------+-----------------------------------------------------------------+ | mload(p) | | mem[p..(p+32)) | +-------------------------+------+-----------------------------------------------------------------+ @@ -650,7 +661,7 @@ variables. Take care that when you assign to variables that point to memory or storage, you will only change the pointer and not the data. There are two kinds of assignments: Functional-style and instruction-style. -For functionaly-style assignments (``variable := value``), you need to provide a value in a +For functional-style assignments (``variable := value``), you need to provide a value in a functional-style expression that results in exactly one stack value and for instruction-style (``=: variable``), the value is just taken from the stack top. For both ways, the colon points to the name of the variable. diff --git a/docs/index.rst b/docs/index.rst index a683a4dc..940fe5e7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ Solidity Solidity is a high-level language whose syntax is similar to that of JavaScript and it is designed to compile to code for the Ethereum Virtual Machine. -As you will see, it is quite easy to create contracts for voting, +As you will see, it is possible to create contracts for voting, crowdfunding, blind auctions, multi-signature wallets and more. .. note:: @@ -59,6 +59,18 @@ Available Solidity Integrations * `Vim Solidity <https://github.com/tomlion/vim-solidity/>`_ Plugin for the Vim editor providing syntax highlighting. +Solidity Tools +------------------------------- + +* `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_ + Try Solidity instantly with a command-line Solidity console. + +* `solgraph <https://github.com/raineorshine/solgraph>`_ + Visualize Solidity control flow and highlight potential security vulnerabilities. + +* `evmdis <https://github.com/Arachnid/evmdis>`_ + EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations. + Language Documentation ---------------------- diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index 0cb2b0d0..0122387b 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -136,12 +136,12 @@ single account. .. index:: event -The line ``event Sent(address from, address to, uint value);`` declares +The line ``event Sent(address from, address to, uint amount);`` declares a so-called "event" which is fired in the last line of the function ``send``. User interfaces (as well as server appliances of course) can listen for those events being fired on the blockchain without much cost. As soon as it is fired, the listener will also receive the -arguments ``from``, ``to`` and ``value``, which makes it easy to track +arguments ``from``, ``to`` and ``amount``, which makes it easy to track transactions. In order to listen for this event, you would use :: Coin.Sent().watch({}, '', function(error, result) { diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 85fc286c..825be2ce 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -192,7 +192,7 @@ Function Visibility Specifiers - ``public``: visible externally and internally (creates accessor function for storage/state variables) - ``private``: only visible in the current contract -- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.fun``) +- ``external``: only visible externally (only for functions) - i.e. can only be message-called (via ``this.func``) - ``internal``: only visible internally diff --git a/docs/security-considerations.rst b/docs/security-considerations.rst index 4eb64593..87ee567d 100644 --- a/docs/security-considerations.rst +++ b/docs/security-considerations.rst @@ -6,22 +6,23 @@ While it is usually quite easy to build software that works as expected, it is much harder to check that nobody can use it in a way that was **not** anticipated. In Solidity, this is even more important because you can use smart contracts -to handle tokens or even more valuable things. Furthermore, every execution of a smart -contract happens in public as it is mostly open source. +to handle tokens or, possibly, even more valuable things. Furthermore, every +execution of a smart contract happens in public and, in addition to that, +the source code is often available. Of course you always have to consider how much is at stake: You can compare a smart contract with a web service that is open to the -public (and thus also to malicous actors) and perhaps even open source. +public (and thus, also to malicous actors) and perhaps even open source. If you only store your grocery list on that web service, you might not have to take too much care, but if you manage your bank account using that web service, you should be more careful. This section will list some pitfalls and general security recommendations but -can of course never be complete. Also keep in mind that even if your +can, of course, never be complete. Also, keep in mind that even if your smart contract code is bug-free, the compiler or the platform itself might have a bug. -As always with open source documentation, please help us extend this section +As always, with open source documentation, please help us extend this section (especially, some examples would not hurt)! ******** @@ -50,19 +51,19 @@ complete contract): // 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; - } + /// Mapping of ether shares of the contract. + mapping(address => uint) shares; + /// Withdraw your share. + function withdraw() { + if (msg.sender.send(shares[msg.sender])) + shares[msg.sender] = 0; + } } The problem is not too serious here because of the limited gas as part of ``send``, but it still exposes a weakness: Ether transfer always includes code execution, so the recipient could be a contract that calls -back into ``withdraw``. This would enable it to get a multiple refund and +back into ``withdraw``. This would let it get multiple refunds and basically retrieve all the Ether in the contract. To avoid re-entrancy, you can use the Checks-Effects-Interactions pattern as @@ -71,18 +72,17 @@ outlined further below: :: 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; - if (!msg.sender.send(share)) - throw; - } + /// 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; + if (!msg.sender.send(share)) + throw; + } } - 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 multi-contract situations into account. A called contract could modify the @@ -91,10 +91,10 @@ state of another contract you depend on. Gas Limit and Loops =================== -Loops that do not have a fixed number of iterations, e.g. loops that depends on storage values, have to be used carefully: +Loops that do not have a fixed number of iterations, for example, loops that depend on storage values, have to be used carefully: Due to the block gas limit, transactions can only consume a certain amount of gas. Either explicitly or just due to -normal operation, the number of iterations in a loop can grow beyond the block gas limit, which can cause the complete -contract to be stalled at a certain point. This does not apply at full extent to ``constant`` functions that are only executed +normal operation, the number of iterations in a loop can grow beyond the block gas limit which can cause the complete +contract to be stalled at a certain point. This may not apply to ``constant`` functions that are only executed to read data from the blockchain. Still, such functions may be called by other contracts as part of on-chain operations and stall those. Please be explicit about such cases in the documentation of your contracts. @@ -111,19 +111,19 @@ Sending and Receiving Ether only that it forwards all remaining gas and opens up the ability for the recipient to perform more expensive actions. This might include calling back into the sending contract or other state changes you might not have though of. - So it allows for great flexibility for honest users but also for malicious actors. + So it allows for great flexibility for honest users but also for malicious actors. -- If you want to send ether using ``address.send``, there are certain details to be aware of: +- If you want to send Ether using ``address.send``, there are certain details to be aware of: - 1. If the recipient is a contract, it causes its fallback function to be executed which can in turn call back into the sending contract + 1. If the recipient is a contract, it causes its fallback function to be executed which can, in turn, call back the sending contract. 2. Sending Ether can fail due to the call depth going above 1024. Since the caller is in total control of the call - depth, they can force the transfer to fail, so make sure to always check the return value of ``send``. Better yet, + depth, they can force the transfer to fail; make sure to always check the return value of ``send``. Better yet, write your contract using a pattern where the recipient can withdraw Ether instead. 3. Sending Ether can also fail because the execution of the recipient contract requires more than the allotted amount of gas (explicitly by using ``throw`` or because the operation is just too expensive) - it "runs out of gas" (OOG). If the return value of ``send`` is checked, this might provide a - means for the recipient to block progress in the sending contract. Again, the best practise here is to use + means for the recipient to block progress in the sending contract. Again, the best practice here is to use a "withdraw" pattern instead of a "send" pattern. Callstack Depth @@ -161,7 +161,7 @@ Restrict the Amount of Ether Restrict the amount of Ether (or other tokens) that can be stored in a smart contract. If your source code, the compiler or the platform has a bug, these -funds might be gone. If you want to limit your loss, limit the amount of Ether. +funds may be lost. If you want to limit your loss, limit the amount of Ether. Keep it Small and Modular ========================= @@ -172,30 +172,30 @@ about source code quality of course apply: Limit the amount of local variables, the length of functions and so on. Document your functions so that others can see what your intention was and whether it is different than what the code does. -Use the Checks-Effects-Interactions pattern +Use the Checks-Effects-Interactions Pattern =========================================== Most functions will first perform some checks (who called the function, are the arguments in range, did they send enough Ether, does the person -have tokens, ...). These checks should be done first. +have tokens, etc.). These checks should be done first. As the second step, if all checks passed, effects to the state variables of the current contract should be made. Interaction with other contracts should be the very last step in any function. Early contracts delayed some effects and waited for external function -calls to return in a non-error state. This is often a serious mistake, +calls to return in a non-error state. This is often a serious mistake because of the re-entrancy problem explained above. -Note that also calls to known contracts might in turn cause calls to -unknown contracts, so it is probably better to just always apply this pattern. +Note that, also, calls to known contracts might in turn cause calls to +unknown contracts, so it is probably better to just always apply this pattern. -Include a Failsafe-Mode -======================= +Include a Fail-Safe Mode +======================== While making your system fully decentralised will remove any intermediary, it might be a good idea, especially for new code, to include some kind -of fail-safe-mechanism: +of fail-safe mechanism: You can add a function in your smart contract that performs some self-checks like "Has any Ether leaked?", @@ -204,9 +204,9 @@ Keep in mind that you cannot use too much gas for that, so help through off-chai computations might be needed there. If the self-check fails, the contract automatically switches into some kind -of "failsafe" mode, which e.g. disables most of the features, hands over +of "failsafe" mode, which, for example, disables most of the features, hands over control to a fixed and trusted third party or just converts the contract into -a simple "give me back my money"-contract. +a simple "give me back my money" contract. ******************* diff --git a/docs/types.rst b/docs/types.rst index 1a0de358..50e86ed0 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -577,7 +577,7 @@ can actually be any type, including mappings. Mappings can be seen as hashtables which are virtually initialized such that every possible key exists and is mapped to a value whose byte-representation is -all zeros. The similarity ends here, though: The key data is not actually stored +all zeros: a type's :ref:`default value <default-value>`. The similarity ends here, though: The key data is not actually stored in a mapping, only its ``sha3`` hash used to look up the value. Because of this, mappings do not have a length or a concept of a key or value being "set". diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 17ab5ba4..09c7c8e8 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -531,17 +531,17 @@ bool CommandLineInterface::processInput() CompilerStack::ReadFileCallback fileReader = [this](string const& _path) { - auto boostPath = boost::filesystem::path(_path); - if (!boost::filesystem::exists(boostPath)) + auto path = boost::filesystem::path(_path); + if (!boost::filesystem::exists(path)) return CompilerStack::ReadFileResult{false, "File not found."}; - boostPath = boost::filesystem::canonical(boostPath); + auto canonicalPath = boost::filesystem::canonical(path); bool isAllowed = false; for (auto const& allowedDir: m_allowedDirectories) { // If dir is a prefix of boostPath, we are fine. if ( - std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(boostPath.begin(), boostPath.end()) && - std::equal(allowedDir.begin(), allowedDir.end(), boostPath.begin()) + std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) && + std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin()) ) { isAllowed = true; @@ -550,12 +550,12 @@ bool CommandLineInterface::processInput() } if (!isAllowed) return CompilerStack::ReadFileResult{false, "File outside of allowed directories."}; - else if (!boost::filesystem::is_regular_file(boostPath)) + else if (!boost::filesystem::is_regular_file(canonicalPath)) return CompilerStack::ReadFileResult{false, "Not a valid file."}; else { - auto contents = dev::contentsString(boostPath.string()); - m_sourceCodes[boostPath.string()] = contents; + auto contents = dev::contentsString(canonicalPath.string()); + m_sourceCodes[path.string()] = contents; return CompilerStack::ReadFileResult{true, contents}; } }; |