From 148f9233516ff5ad94d81aba9bc9c0440d3afc7b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 20:21:27 +0000 Subject: [PATCH 1/8] Add REVERT to libevmasm --- libevmasm/GasMeter.cpp | 1 + libevmasm/Instruction.cpp | 2 ++ libevmasm/Instruction.h | 1 + libevmasm/PeepholeOptimiser.cpp | 3 ++- libevmasm/SemanticInformation.cpp | 1 + 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 462c09dd0..a0adc35d5 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -80,6 +80,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _ gas += GasCosts::sloadGas; break; case Instruction::RETURN: + case Instruction::REVERT: gas += memoryGas(0, -1); break; case Instruction::MLOAD: diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index f9ee9be1f..de6630f30 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -159,6 +159,7 @@ const std::map dev::solidity::c_instructions = { "CALLCODE", Instruction::CALLCODE }, { "RETURN", Instruction::RETURN }, { "DELEGATECALL", Instruction::DELEGATECALL }, + { "REVERT", Instruction::REVERT }, { "INVALID", Instruction::INVALID }, { "SELFDESTRUCT", Instruction::SELFDESTRUCT } }; @@ -294,6 +295,7 @@ static const std::map c_instructionInfo = { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, + { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } }, { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } } }; diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 7f56ad3a9..d79ec969e 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -177,6 +177,7 @@ enum class Instruction: uint8_t RETURN, ///< halt execution returning output data DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender + REVERT = 0xfd, ///< halt execution, revert state and return output data INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion }; diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 9a8341ab9..6c92d76bc 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -200,7 +200,8 @@ struct UnreachableCode it[0] != Instruction::RETURN && it[0] != Instruction::STOP && it[0] != Instruction::INVALID && - it[0] != Instruction::SELFDESTRUCT + it[0] != Instruction::SELFDESTRUCT && + it[0] != Instruction::REVERT ) return false; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 3a0843b8f..61586e7b7 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -119,6 +119,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::SELFDESTRUCT: case Instruction::STOP: case Instruction::INVALID: + case Instruction::REVERT: return true; default: return false; From f3158f92d6070b6088c6a1b32f2934b9cd7dde1b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 22:11:19 +0000 Subject: [PATCH 2/8] Support revert() --- libsolidity/analysis/GlobalContext.cpp | 4 +++- libsolidity/ast/Types.cpp | 1 + libsolidity/ast/Types.h | 1 + libsolidity/codegen/ExpressionCompiler.cpp | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index cc418c5e0..4f100cd0e 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -67,7 +67,9 @@ m_magicVariables(vector>{make_shared< make_shared("ripemd160", make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), make_shared("assert", - make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert))}) + make_shared(strings{"bool"}, strings{}, FunctionType::Location::Assert)), + make_shared("revert", + make_shared(strings(), strings(), FunctionType::Location::Revert))}) { } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 4a64b4c81..5b7b4a2cd 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2095,6 +2095,7 @@ string FunctionType::identifier() const case Location::Send: id += "send"; break; case Location::SHA3: id += "sha3"; break; case Location::Selfdestruct: id += "selfdestruct"; break; + case Location::Revert: id += "revert"; break; case Location::ECRecover: id += "ecrecover"; break; case Location::SHA256: id += "sha256"; break; case Location::RIPEMD160: id += "ripemd160"; break; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 83d840e06..3546e5220 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -828,6 +828,7 @@ public: Send, ///< CALL, but without data and gas SHA3, ///< SHA3 Selfdestruct, ///< SELFDESTRUCT + Revert, ///< REVERT ECRecover, ///< CALL to special contract for ecrecover SHA256, ///< CALL to special contract for sha256 RIPEMD160, ///< CALL to special contract for ripemd160 diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index f69d61db9..316ae888d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -650,6 +650,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); m_context << Instruction::SELFDESTRUCT; break; + case Location::Revert: + // memory offset returned - zero length + m_context << u256(0) << u256(0); + m_context << Instruction::REVERT; + break; case Location::SHA3: { TypePointers argumentTypes; From 1fcad8b4ab48f63504c69c24372fb34f34a79436 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 6 Feb 2017 22:47:05 +0000 Subject: [PATCH 3/8] Document revert() --- Changelog.md | 2 ++ docs/assembly.rst | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Changelog.md b/Changelog.md index d383ba421..bd514cfe6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ Features: * Add ``assert(condition)``, which throws if condition is false. + * Code generator: Support ``revert()`` to abort with rolling back, but not consuming all gas. + * Inline assembly: Support ``revert`` (EIP140) as an opcode. * Type system: Support explicit conversion of external function to address. Bugfixes: diff --git a/docs/assembly.rst b/docs/assembly.rst index 79137b7e9..23ccfcbe2 100644 --- a/docs/assembly.rst +++ b/docs/assembly.rst @@ -248,6 +248,8 @@ In the grammar, opcodes are represented as pre-defined identifiers. +-------------------------+------+-----------------------------------------------------------------+ | return(p, s) | `-` | end execution, return data mem[p..(p+s)) | +-------------------------+------+-----------------------------------------------------------------+ +| revert(p, s) | `-` | end execution, revert state changes, return data mem[p..(p+s)) | ++-------------------------+------+-----------------------------------------------------------------+ | selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a | +-------------------------+------+-----------------------------------------------------------------+ | invalid | `-` | end execution with invalid instruction | From 586d156f33c78469f3272b4e8b8eae4f6229d04e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 8 Feb 2017 23:16:47 +0000 Subject: [PATCH 4/8] Use the REVERT opcode for throw; --- libsolidity/codegen/ContractCompiler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9d6129a36..6524bd039 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -762,7 +762,9 @@ bool ContractCompiler::visit(Return const& _return) bool ContractCompiler::visit(Throw const& _throw) { CompilerContext::LocationSetter locationSetter(m_context, _throw); - m_context.appendJumpTo(m_context.errorTag()); + // Do not send back an error detail. + m_context << u256(0) << u256(0); + m_context << Instruction::REVERT; return false; } From 28a7b1e019dc6f694d0615d7ef1220f19c10e861 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 16:32:48 +0000 Subject: [PATCH 5/8] Document revert() --- docs/control-structures.rst | 2 +- docs/miscellaneous.rst | 3 ++- docs/units-and-global-variables.rst | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index ff0a48ec1..df8ac729c 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -400,7 +400,7 @@ While a user-provided exception is generated in the following situations: #. Calling ``throw``. #. The condition of ``assert(condition)`` is not met. -Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation +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 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 diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index a64ceeb2b..3c57507ef 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, assert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: block, coinbase, difficulty, number, block;number, timestamp, block;timestamp, msg, data, gas, sender, value, now, gas price, origin, assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Global Variables ================ @@ -453,6 +453,7 @@ 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) +- ``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()` - ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index a6f6613f1..72741b672 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -79,7 +79,7 @@ Block and Transaction Properties You can only access the hashes of the most recent 256 blocks, all other values will be zero. -.. index:: assert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send +.. index:: assert, revert, keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography, this, super, selfdestruct, balance, send Mathematical and Cryptographic Functions ---------------------------------------- @@ -100,6 +100,8 @@ Mathematical and Cryptographic Functions compute RIPEMD-160 hash of the (tightly packed) arguments ``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: recover the address associated with the public key from elliptic curve signature or return zero on error +``revert()``: + abort execution and revert state changes In the above, "tightly packed" means that the arguments are concatenated without padding. This means that the following are all identical:: From f26fe5bc1c835302af27981e96d0ebbf31c80389 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 9 Feb 2017 16:34:48 +0000 Subject: [PATCH 6/8] Add tests for revert() --- test/libsolidity/InlineAssembly.cpp | 5 +++++ test/libsolidity/SolidityEndToEndTest.cpp | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index cf0343a92..37d17495a 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -205,6 +205,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment) BOOST_CHECK(!successAssemble("{ gas := 2 }")); } +BOOST_AUTO_TEST_CASE(revert) +{ + BOOST_CHECK(successAssemble("{ revert(0, 0) }")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index e49db34e9..8c0e29fd2 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9096,6 +9096,25 @@ BOOST_AUTO_TEST_CASE(assert) BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); } +BOOST_AUTO_TEST_CASE(revert) +{ + char const* sourceCode = R"( + contract C { + function f() { + revert(); + } + function g() { + assembly { + revert(0, 0) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("g()") == encodeArgs()); +} + BOOST_AUTO_TEST_SUITE_END() } From 30cfad35484e9b903bf90bb723382aebc832d560 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 13:37:06 +0000 Subject: [PATCH 7/8] Check for state changes in revert() tests --- test/libsolidity/SolidityEndToEndTest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8c0e29fd2..92c1a9a1f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -9100,10 +9100,13 @@ BOOST_AUTO_TEST_CASE(revert) { char const* sourceCode = R"( contract C { + uint public a = 42; function f() { + a = 1; revert(); } function g() { + a = 1; assembly { revert(0, 0) } @@ -9112,7 +9115,9 @@ BOOST_AUTO_TEST_CASE(revert) )"; compileAndRun(sourceCode, 0, "C"); BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42))); BOOST_CHECK(callContractFunction("g()") == encodeArgs()); + BOOST_CHECK(callContractFunction("a()") == encodeArgs(u256(42))); } BOOST_AUTO_TEST_SUITE_END() From c8ec79548b8f8825735ee96f1768e7fc5313d19e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 10 Feb 2017 22:53:32 +0000 Subject: [PATCH 8/8] Use the revert opcode in assert() --- libsolidity/codegen/ExpressionCompiler.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 316ae888d..2ed19a833 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -872,8 +872,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { arguments.front()->accept(*this); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + // jump if condition was met + m_context << Instruction::ISZERO << Instruction::ISZERO; + auto success = m_context.appendConditionalJump(); + // condition was not met, abort + m_context << u256(0) << u256(0); + m_context << Instruction::REVERT; + // the success branch + m_context << success; break; } default: