diff --git a/Changelog.md b/Changelog.md index 8e9780c0d..7cb1f562e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,9 @@ ### 0.4.10 (unreleased) Features: + * Add ``assert(condition)``, which throws if condition is false (meant for internal errors). + * Add ``require(condition)``, which throws if condition is false (meant for invalid input). * Commandline interface: Do not overwrite files unless forced. - * Add ``assert(condition)``, which throws if condition is false. * Introduce ``.transfer(value)`` for sending Ether. * Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas. * Inline assembly: Support ``revert`` (EIP140) as an opcode. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 83d3eac9c..25bf203bb 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -396,12 +396,19 @@ Currently, Solidity automatically generates a runtime exception in the following #. If your contract receives Ether via a public getter function. #. If you call a zero-initialized variable of internal function type. #. If a ``.transfer()`` fails. +#. If you call ``assert`` with an argument that evaluates to false. While a user-provided exception is generated in the following situations: #. Calling ``throw``. +#. Calling ``require`` with an argument that evaluates to ``false``. -Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown. In contrast, it performs an invalid operation -(instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes +Internally, Solidity performs a revert operation (instruction ``0xfd``) when a user-provided exception is thrown or the condition of +a ``require`` call is not met. In contrast, it performs an invalid operation +(instruction ``0xfe``) if a runtime exception is encountered or the condition of an ``assert`` call is not met. In both cases, this causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. + +If contracts are written so that ``assert`` is only used to test internal conditions and ``require`` +is used in case of malformed input, a formal analysis tool that verifies that the invalid +opcode can never be reached can be used to check for the absence of errors assuming valid inputs. \ No newline at end of file diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index e3ec0efb1..2865d8843 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -435,7 +435,7 @@ The following is the order of precedence for operators, listed in order of evalu | *16* | Comma operator | ``,`` | +------------+-------------------------------------+--------------------------------------------+ -.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: assert, block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, revert, require, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Global Variables ================ @@ -453,6 +453,8 @@ Global Variables - ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``) - ``tx.gasprice`` (``uint``): gas price of the transaction - ``tx.origin`` (``address``): sender of the transaction (full call chain) +- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error) +- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input) - ``revert()``: abort execution and revert state changes - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments - ``sha3(...) returns (bytes32)``: an alias to `keccak256()` diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index 069d10f5f..d8f1603ab 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -66,9 +66,10 @@ m_magicVariables(vector>{make_shared< make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared("ripemd160", make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), -// Disabled until decision about semantics of assert is made. -// make_shared("assert", -// make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert)), + make_shared("assert", + make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert)), + make_shared("require", + make_shared(strings{"bool"}, strings{}, FunctionType::Location::Require)), make_shared("revert", make_shared(strings(), strings(), FunctionType::Location::Revert))}) { diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 022b67c48..960b7e86b 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -847,7 +847,8 @@ public: ArrayPush, ///< .push() to a dynamically sized array in storage ByteArrayPush, ///< .push() to a dynamically sized byte array in storage ObjectCreation, ///< array creation using new - Assert ///< assert() + Assert, ///< assert() + Require ///< require() }; virtual Category category() const override { return Category::Function; } diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 5192ffa6a..744a80c4e 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -879,14 +879,18 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) break; } case Location::Assert: + case Location::Require: { arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); // jump if condition was met m_context << Instruction::ISZERO << Instruction::ISZERO; auto success = m_context.appendConditionalJump(); - // condition was not met, flag an error - m_context << Instruction::INVALID; + if (function.location() == Location::Assert) + // condition was not met, flag an error + m_context << Instruction::INVALID; + else + m_context << u256(0) << u256(0) << Instruction::REVERT; // the success branch m_context << success; break; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index baed3f1e0..2a286cea4 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9133,24 +9133,30 @@ BOOST_AUTO_TEST_CASE(invalid_instruction) BOOST_CHECK(callContractFunction("f()") == encodeArgs()); } -//BOOST_AUTO_TEST_CASE(assert) -//{ -// char const* sourceCode = R"( -// contract C { -// function f() { -// assert(false); -// } -// function g(bool val) returns (bool) { -// assert(val == true); -// return true; -// } -// } -// )"; -// compileAndRun(sourceCode, 0, "C"); -// BOOST_CHECK(callContractFunction("f()") == encodeArgs()); -// BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs()); -// BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); -//} +BOOST_AUTO_TEST_CASE(assert_require) +{ + char const* sourceCode = R"( + contract C { + function f() { + assert(false); + } + function g(bool val) returns (bool) { + assert(val == true); + return true; + } + function h(bool val) returns (bool) { + require(val); + return true; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("g(bool)", false) == encodeArgs()); + BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); + BOOST_CHECK(callContractFunction("h(bool)", false) == encodeArgs()); + BOOST_CHECK(callContractFunction("h(bool)", true) == encodeArgs(true)); +} BOOST_AUTO_TEST_CASE(revert) {