Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require and Assert. #1765

Merged
merged 1 commit into from
Mar 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
11 changes: 9 additions & 2 deletions docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 3 additions & 1 deletion docs/miscellaneous.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
================
Expand All @@ -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()`
Expand Down
7 changes: 4 additions & 3 deletions libsolidity/analysis/GlobalContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)),
make_shared<MagicVariableDeclaration>("ripemd160",
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)),
// Disabled until decision about semantics of assert is made.
// make_shared<MagicVariableDeclaration>("assert",
// make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)),
make_shared<MagicVariableDeclaration>("assert",
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)),
make_shared<MagicVariableDeclaration>("require",
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Require)),
make_shared<MagicVariableDeclaration>("revert",
make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Revert))})
{
Expand Down
3 changes: 2 additions & 1 deletion libsolidity/ast/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -847,7 +847,8 @@ class FunctionType: public Type
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; }
Expand Down
8 changes: 6 additions & 2 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
42 changes: 24 additions & 18 deletions test/libsolidity/SolidityEndToEndTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down