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

Implement REVERT (EIP140) #1661

Merged
merged 8 commits into from Feb 13, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -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:
@@ -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 |
@@ -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
@@ -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
@@ -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::
@@ -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:
@@ -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 }
};
@@ -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 } }
};
@@ -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
};
@@ -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;

@@ -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;
@@ -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))})
{
}

@@ -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;
@@ -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
@@ -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;
}

@@ -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;
@@ -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:
@@ -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()

}
@@ -9096,6 +9096,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()

}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.