Skip to content

Commit

Permalink
Merge pull request #1661 from ethereum/asm-revert
Browse files Browse the repository at this point in the history
Implement REVERT (EIP140)
  • Loading branch information
chriseth authored Feb 13, 2017
2 parents e2349f9 + c8ec795 commit 0d8a9c3
Show file tree
Hide file tree
Showing 17 changed files with 67 additions and 8 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions docs/assembly.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
2 changes: 1 addition & 1 deletion docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 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, 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
================
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion docs/units-and-global-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------------------------------
Expand All @@ -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::
Expand Down
1 change: 1 addition & 0 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "CALLCODE", Instruction::CALLCODE },
{ "RETURN", Instruction::RETURN },
{ "DELEGATECALL", Instruction::DELEGATECALL },
{ "REVERT", Instruction::REVERT },
{ "INVALID", Instruction::INVALID },
{ "SELFDESTRUCT", Instruction::SELFDESTRUCT }
};
Expand Down Expand Up @@ -294,6 +295,7 @@ static const std::map<Instruction, InstructionInfo> 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 } }
};
Expand Down
1 change: 1 addition & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
3 changes: 2 additions & 1 deletion libevmasm/PeepholeOptimiser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/analysis/GlobalContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
make_shared<MagicVariableDeclaration>("ripemd160",
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)),
make_shared<MagicVariableDeclaration>("assert",
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert))})
make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Location::Assert)),
make_shared<MagicVariableDeclaration>("revert",
make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Revert))})
{
}

Expand Down
1 change: 1 addition & 0 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions libsolidity/ast/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,7 @@ class FunctionType: public Type
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
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/codegen/ContractCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
15 changes: 13 additions & 2 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -867,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:
Expand Down
5 changes: 5 additions & 0 deletions test/libsolidity/InlineAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()

}
Expand Down
24 changes: 24 additions & 0 deletions test/libsolidity/SolidityEndToEndTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9102,6 +9102,30 @@ BOOST_AUTO_TEST_CASE(assert)
BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true));
}

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)
}
}
}
)";
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()

}
Expand Down

0 comments on commit 0d8a9c3

Please sign in to comment.