Merge pull request #1661 from ethereum/asm-revert

Implement REVERT (EIP140)
This commit is contained in:
chriseth 2017-02-13 14:56:22 +01:00 committed by GitHub
commit 0d8a9c3289
17 changed files with 67 additions and 8 deletions

View File

@ -2,6 +2,8 @@
Features: Features:
* Add ``assert(condition)``, which throws if condition is false. * 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. * Type system: Support explicit conversion of external function to address.
Bugfixes: Bugfixes:

View File

@ -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)) | | 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 | | selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a |
+-------------------------+------+-----------------------------------------------------------------+ +-------------------------+------+-----------------------------------------------------------------+
| invalid | `-` | end execution with invalid instruction | | invalid | `-` | end execution with invalid instruction |

View File

@ -400,7 +400,7 @@ While a user-provided exception is generated in the following situations:
#. Calling ``throw``. #. Calling ``throw``.
#. The condition of ``assert(condition)`` is not met. #. 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 (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 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 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

View File

@ -435,7 +435,7 @@ The following is the order of precedence for operators, listed in order of evalu
| *16* | Comma operator | ``,`` | | *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 Global Variables
================ ================
@ -453,6 +453,7 @@ Global Variables
- ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``) - ``now`` (``uint``): current block timestamp (alias for ``block.timestamp``)
- ``tx.gasprice`` (``uint``): gas price of the transaction - ``tx.gasprice`` (``uint``): gas price of the transaction
- ``tx.origin`` (``address``): sender of the transaction (full call chain) - ``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 - ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the (tightly packed) arguments
- ``sha3(...) returns (bytes32)``: an alias to `keccak256()` - ``sha3(...) returns (bytes32)``: an alias to `keccak256()`
- ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments - ``sha256(...) returns (bytes32)``: compute the SHA-256 hash of the (tightly packed) arguments

View File

@ -79,7 +79,7 @@ Block and Transaction Properties
You can only access the hashes of the most recent 256 blocks, all other You can only access the hashes of the most recent 256 blocks, all other
values will be zero. 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 Mathematical and Cryptographic Functions
---------------------------------------- ----------------------------------------
@ -100,6 +100,8 @@ Mathematical and Cryptographic Functions
compute RIPEMD-160 hash of the (tightly packed) arguments compute RIPEMD-160 hash of the (tightly packed) arguments
``ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)``: ``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 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. In the above, "tightly packed" means that the arguments are concatenated without padding.
This means that the following are all identical:: This means that the following are all identical::

View File

@ -80,6 +80,7 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas += GasCosts::sloadGas; gas += GasCosts::sloadGas;
break; break;
case Instruction::RETURN: case Instruction::RETURN:
case Instruction::REVERT:
gas += memoryGas(0, -1); gas += memoryGas(0, -1);
break; break;
case Instruction::MLOAD: case Instruction::MLOAD:

View File

@ -159,6 +159,7 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "CALLCODE", Instruction::CALLCODE }, { "CALLCODE", Instruction::CALLCODE },
{ "RETURN", Instruction::RETURN }, { "RETURN", Instruction::RETURN },
{ "DELEGATECALL", Instruction::DELEGATECALL }, { "DELEGATECALL", Instruction::DELEGATECALL },
{ "REVERT", Instruction::REVERT },
{ "INVALID", Instruction::INVALID }, { "INVALID", Instruction::INVALID },
{ "SELFDESTRUCT", Instruction::SELFDESTRUCT } { "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::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },
{ Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } },
{ Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, { 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::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
{ Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } } { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Zero } }
}; };

View File

@ -177,6 +177,7 @@ enum class Instruction: uint8_t
RETURN, ///< halt execution returning output data RETURN, ///< halt execution returning output data
DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender 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) INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion
}; };

View File

@ -200,7 +200,8 @@ struct UnreachableCode
it[0] != Instruction::RETURN && it[0] != Instruction::RETURN &&
it[0] != Instruction::STOP && it[0] != Instruction::STOP &&
it[0] != Instruction::INVALID && it[0] != Instruction::INVALID &&
it[0] != Instruction::SELFDESTRUCT it[0] != Instruction::SELFDESTRUCT &&
it[0] != Instruction::REVERT
) )
return false; return false;

View File

@ -119,6 +119,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
case Instruction::SELFDESTRUCT: case Instruction::SELFDESTRUCT:
case Instruction::STOP: case Instruction::STOP:
case Instruction::INVALID: case Instruction::INVALID:
case Instruction::REVERT:
return true; return true;
default: default:
return false; return false;

View File

@ -67,7 +67,9 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<MagicVariableDeclaration>("ripemd160",
make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)), make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true)),
make_shared<MagicVariableDeclaration>("assert", 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))})
{ {
} }

View File

@ -2095,6 +2095,7 @@ string FunctionType::identifier() const
case Location::Send: id += "send"; break; case Location::Send: id += "send"; break;
case Location::SHA3: id += "sha3"; break; case Location::SHA3: id += "sha3"; break;
case Location::Selfdestruct: id += "selfdestruct"; break; case Location::Selfdestruct: id += "selfdestruct"; break;
case Location::Revert: id += "revert"; break;
case Location::ECRecover: id += "ecrecover"; break; case Location::ECRecover: id += "ecrecover"; break;
case Location::SHA256: id += "sha256"; break; case Location::SHA256: id += "sha256"; break;
case Location::RIPEMD160: id += "ripemd160"; break; case Location::RIPEMD160: id += "ripemd160"; break;

View File

@ -828,6 +828,7 @@ public:
Send, ///< CALL, but without data and gas Send, ///< CALL, but without data and gas
SHA3, ///< SHA3 SHA3, ///< SHA3
Selfdestruct, ///< SELFDESTRUCT Selfdestruct, ///< SELFDESTRUCT
Revert, ///< REVERT
ECRecover, ///< CALL to special contract for ecrecover ECRecover, ///< CALL to special contract for ecrecover
SHA256, ///< CALL to special contract for sha256 SHA256, ///< CALL to special contract for sha256
RIPEMD160, ///< CALL to special contract for ripemd160 RIPEMD160, ///< CALL to special contract for ripemd160

View File

@ -762,7 +762,9 @@ bool ContractCompiler::visit(Return const& _return)
bool ContractCompiler::visit(Throw const& _throw) bool ContractCompiler::visit(Throw const& _throw)
{ {
CompilerContext::LocationSetter locationSetter(m_context, _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; return false;
} }

View File

@ -650,6 +650,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), true);
m_context << Instruction::SELFDESTRUCT; m_context << Instruction::SELFDESTRUCT;
break; break;
case Location::Revert:
// memory offset returned - zero length
m_context << u256(0) << u256(0);
m_context << Instruction::REVERT;
break;
case Location::SHA3: case Location::SHA3:
{ {
TypePointers argumentTypes; TypePointers argumentTypes;
@ -867,8 +872,14 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
arguments.front()->accept(*this); arguments.front()->accept(*this);
utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false); utils().convertType(*arguments.front()->annotation().type, *function.parameterTypes().front(), false);
m_context << Instruction::ISZERO; // jump if condition was met
m_context.appendConditionalJumpTo(m_context.errorTag()); 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; break;
} }
default: default:

View File

@ -205,6 +205,11 @@ BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment)
BOOST_CHECK(!successAssemble("{ gas := 2 }")); BOOST_CHECK(!successAssemble("{ gas := 2 }"));
} }
BOOST_AUTO_TEST_CASE(revert)
{
BOOST_CHECK(successAssemble("{ revert(0, 0) }"));
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} }

View File

@ -9102,6 +9102,30 @@ BOOST_AUTO_TEST_CASE(assert)
BOOST_CHECK(callContractFunction("g(bool)", true) == encodeArgs(true)); 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() BOOST_AUTO_TEST_SUITE_END()
} }