aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changelog.md2
-rw-r--r--docs/assembly.rst2
-rw-r--r--docs/control-structures.rst2
-rw-r--r--docs/miscellaneous.rst3
-rw-r--r--docs/units-and-global-variables.rst4
-rw-r--r--libevmasm/GasMeter.cpp1
-rw-r--r--libevmasm/Instruction.cpp2
-rw-r--r--libevmasm/Instruction.h1
-rw-r--r--libevmasm/PeepholeOptimiser.cpp3
-rw-r--r--libevmasm/SemanticInformation.cpp1
-rw-r--r--libsolidity/analysis/GlobalContext.cpp4
-rw-r--r--libsolidity/ast/Types.cpp1
-rw-r--r--libsolidity/ast/Types.h1
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp4
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp15
-rw-r--r--test/ExecutionFramework.cpp10
-rw-r--r--test/ExecutionFramework.h2
-rw-r--r--test/RPCSession.cpp11
-rw-r--r--test/RPCSession.h2
-rw-r--r--test/libsolidity/InlineAssembly.cpp5
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp34
21 files changed, 97 insertions, 13 deletions
diff --git a/Changelog.md b/Changelog.md
index d383ba42..bd514cfe 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 79137b7e..23ccfcbe 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 |
diff --git a/docs/control-structures.rst b/docs/control-structures.rst
index ff0a48ec..df8ac729 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 a64ceeb2..3c57507e 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 a6f6613f..72741b67 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::
diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp
index 462c09dd..a0adc35d 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 f9ee9be1..de6630f3 100644
--- a/libevmasm/Instruction.cpp
+++ b/libevmasm/Instruction.cpp
@@ -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 } }
};
diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h
index 7f56ad3a..d79ec969 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 9a8341ab..6c92d76b 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 3a0843b8..61586e7b 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;
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp
index cc418c5e..4f100cd0 100644
--- a/libsolidity/analysis/GlobalContext.cpp
+++ b/libsolidity/analysis/GlobalContext.cpp
@@ -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))})
{
}
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 4a64b4c8..5b7b4a2c 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 83d840e0..3546e522 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/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index 9d6129a3..6524bd03 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;
}
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index f69d61db..2ed19a83 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;
@@ -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:
diff --git a/test/ExecutionFramework.cpp b/test/ExecutionFramework.cpp
index ddcd9cb6..f4e5fcef 100644
--- a/test/ExecutionFramework.cpp
+++ b/test/ExecutionFramework.cpp
@@ -82,6 +82,8 @@ void ExecutionFramework::sendMessage(bytes const& _data, bool _isCreation, u256
m_rpc.test_mineBlocks(1);
RPCSession::TransactionReceipt receipt(m_rpc.eth_getTransactionReceipt(txHash));
+ m_blockNumber = u256(receipt.blockNumber);
+
if (_isCreation)
{
m_contractAddress = Address(receipt.contractAddress);
@@ -125,7 +127,13 @@ void ExecutionFramework::sendEther(Address const& _to, u256 const& _value)
size_t ExecutionFramework::currentTimestamp()
{
- auto latestBlock = m_rpc.rpcCall("eth_getBlockByNumber", {"\"latest\"", "false"});
+ auto latestBlock = m_rpc.eth_getBlockByNumber("latest", false);
+ return size_t(u256(latestBlock.get("timestamp", "invalid").asString()));
+}
+
+size_t ExecutionFramework::blockTimestamp(u256 _number)
+{
+ auto latestBlock = m_rpc.eth_getBlockByNumber(toString(_number), false);
return size_t(u256(latestBlock.get("timestamp", "invalid").asString()));
}
diff --git a/test/ExecutionFramework.h b/test/ExecutionFramework.h
index 733fd56d..76d0fd8c 100644
--- a/test/ExecutionFramework.h
+++ b/test/ExecutionFramework.h
@@ -262,6 +262,7 @@ protected:
void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0);
void sendEther(Address const& _to, u256 const& _value);
size_t currentTimestamp();
+ size_t blockTimestamp(u256 number);
/// @returns the (potentially newly created) _ith address.
Address account(size_t _i);
@@ -284,6 +285,7 @@ protected:
bool m_showMessages = false;
Address m_sender;
Address m_contractAddress;
+ u256 m_blockNumber;
u256 const m_gasPrice = 100 * szabo;
u256 const m_gas = 100000000;
bytes m_output;
diff --git a/test/RPCSession.cpp b/test/RPCSession.cpp
index b3451528..c27e73d4 100644
--- a/test/RPCSession.cpp
+++ b/test/RPCSession.cpp
@@ -131,6 +131,12 @@ string RPCSession::eth_getCode(string const& _address, string const& _blockNumbe
return rpcCall("eth_getCode", { quote(_address), quote(_blockNumber) }).asString();
}
+Json::Value RPCSession::eth_getBlockByNumber(string const& _blockNumber, bool _fullObjects)
+{
+ // NOTE: to_string() converts bool to 0 or 1
+ return rpcCall("eth_getBlockByNumber", { quote(_blockNumber), _fullObjects ? "true" : "false" });
+}
+
RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string const& _transactionHash)
{
TransactionReceipt receipt;
@@ -138,6 +144,7 @@ RPCSession::TransactionReceipt RPCSession::eth_getTransactionReceipt(string cons
BOOST_REQUIRE(!result.isNull());
receipt.gasUsed = result["gasUsed"].asString();
receipt.contractAddress = result["contractAddress"].asString();
+ receipt.blockNumber = result["blockNumber"].asString();
for (auto const& log: result["logs"])
{
LogEntry entry;
@@ -289,9 +296,9 @@ Json::Value RPCSession::rpcCall(string const& _methodName, vector<string> const&
request += "],\"id\":" + to_string(m_rpcSequence) + "}";
++m_rpcSequence;
- //cout << "Request: " << request << endl;
+ // cout << "Request: " << request << endl;
string reply = m_ipcSocket.sendRequest(request);
- //cout << "Reply: " << reply << endl;
+ // cout << "Reply: " << reply << endl;
Json::Value result;
BOOST_REQUIRE(Json::Reader().parse(reply, result, false));
diff --git a/test/RPCSession.h b/test/RPCSession.h
index f1aee6a8..105ba378 100644
--- a/test/RPCSession.h
+++ b/test/RPCSession.h
@@ -92,11 +92,13 @@ public:
std::string gasUsed;
std::string contractAddress;
std::vector<LogEntry> logEntries;
+ std::string blockNumber;
};
static RPCSession& instance(std::string const& _path);
std::string eth_getCode(std::string const& _address, std::string const& _blockNumber);
+ Json::Value eth_getBlockByNumber(std::string const& _blockNumber, bool _fullObjects);
std::string eth_call(TransactionData const& _td, std::string const& _blockNumber);
TransactionReceipt eth_getTransactionReceipt(std::string const& _transactionHash);
std::string eth_sendTransaction(TransactionData const& _transactionData);
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index cf0343a9..37d17495 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 e49db34e..68f8fbef 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -1482,9 +1482,15 @@ BOOST_AUTO_TEST_CASE(now)
}
}
)";
- m_rpc.test_modifyTimestamp(0x776347e2);
compileAndRun(sourceCode);
- BOOST_CHECK(callContractFunction("someInfo()") == encodeArgs(true, 0x776347e3));
+ u256 startBlock = m_blockNumber;
+ size_t startTime = blockTimestamp(startBlock);
+ auto ret = callContractFunction("someInfo()");
+ u256 endBlock = m_blockNumber;
+ size_t endTime = blockTimestamp(endBlock);
+ BOOST_CHECK(startBlock != endBlock);
+ BOOST_CHECK(startTime != endTime);
+ BOOST_CHECK(ret == encodeArgs(true, endTime));
}
BOOST_AUTO_TEST_CASE(type_conversions_cleanup)
@@ -9096,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()
}