mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1661 from ethereum/asm-revert
Implement REVERT (EIP140)
This commit is contained in:
commit
0d8a9c3289
@ -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:
|
||||||
|
@ -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 |
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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::
|
||||||
|
@ -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:
|
||||||
|
@ -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 } }
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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))})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user