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

Revert with reason #3364

Merged
merged 19 commits into from
Apr 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Features:
* Code Generator: More specialized and thus optimized implementation for ``x.push(...)``
* Commandline interface: Error when missing or inaccessible file detected. Suppress it with the ``--ignore-missing`` flag.
* Constant Evaluator: Fix evaluation of single element tuples.
* General: Allow providing reason string for ``revert()`` and ``require()``.
* General: Limit the number of errors output in a single run to 256.
* General: Support accessing dynamic return data in post-byzantium EVMs.
* Interfaces: Allow overriding external functions in interfaces with public in an implementing contract.
Expand Down
20 changes: 16 additions & 4 deletions docs/common-patterns.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ restrictions highly readable.
// a certain address.
modifier onlyBy(address _account)
{
require(msg.sender == _account);
require(
msg.sender == _account,
"Sender not authorized."
);
// Do not forget the "_;"! It will
// be replaced by the actual function
// body when the modifier is used.
Expand All @@ -164,7 +167,10 @@ restrictions highly readable.
}

modifier onlyAfter(uint _time) {
require(now >= _time);
require(
now >= _time,
"Function called too early."
);
_;
}

Expand All @@ -186,7 +192,10 @@ restrictions highly readable.
// This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`.
modifier costs(uint _amount) {
require(msg.value >= _amount);
require(
msg.value >= _amount,
"Not enough Ether provided."
);
_;
if (msg.value > _amount)
msg.sender.send(msg.value - _amount);
Expand Down Expand Up @@ -290,7 +299,10 @@ function finishes.
uint public creationTime = now;

modifier atStage(Stages _stage) {
require(stage == _stage);
require(
stage == _stage,
"Function cannot be called at this time."
);
_;
}

Expand Down
10 changes: 8 additions & 2 deletions docs/contracts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,10 @@ inheritable properties of contracts and may be overridden by derived contracts.
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
require(msg.sender == owner);
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
Expand Down Expand Up @@ -360,7 +363,10 @@ inheritable properties of contracts and may be overridden by derived contracts.
contract Mutex {
bool locked;
modifier noReentrancy() {
require(!locked);
require(
!locked,
"Reentrant call."
);
locked = true;
_;
locked = false;
Expand Down
42 changes: 38 additions & 4 deletions docs/control-structures.rst
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,9 @@ The ``require`` function should be used to ensure valid conditions, such as inpu
If used properly, analysis tools can evaluate your contract to identify the conditions and function calls which will reach a failing ``assert``. Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix.

There are two other ways to trigger exceptions: The ``revert`` function can be used to flag an error and
revert the current call. In the future it might be possible to also include details about the error
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``.
revert the current call. It is possible to provide a string message containing details about the error
that will be passed back to the caller.
The deprecated keyword ``throw`` can also be used as an alternative to ``revert()`` (but only without error message).

.. note::
From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future.
Expand All @@ -471,13 +472,16 @@ of an exception instead of "bubbling up".
Catching exceptions is not yet possible.

In the following example, you can see how ``require`` can be used to easily check conditions on inputs
and how ``assert`` can be used for internal error checking::
and how ``assert`` can be used for internal error checking. Note that you can optionally provide
a message string for ``require``, but not for ``assert``.

::

pragma solidity ^0.4.0;

contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % 2 == 0); // Only allow even numbers
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
Expand Down Expand Up @@ -515,3 +519,33 @@ the EVM to revert all changes made to the state. The reason for reverting is tha
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
(or at least call) without effect. Note that ``assert``-style exceptions consume all gas available to the call, while
``require``-style exceptions will not consume any gas starting from the Metropolis release.

The following example shows how an error string can be used together with revert and require:

::

pragma solidity ^0.4.0;

contract VendingMachine {
function buy(uint amount) payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Alternative way to do it:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// Perform the purchase.
}
}

The provided string will be :ref:`abi-encoded <ABI>` as if it were a call to a function ``Error(string)``.
In the above example, ``revert("Not enough Ether provided.");`` will cause the following hexadecimal data be
set as error return data:

.. code::

0x08c379a0 // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
2 changes: 2 additions & 0 deletions docs/miscellaneous.rst
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@ Global Variables
- ``tx.origin`` (``address``): sender of the transaction (full call chain)
- ``assert(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for internal error)
- ``require(bool condition)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component)
- ``require(bool condition, string message)``: abort execution and revert state changes if condition is ``false`` (use for malformed input or error in external component). Also provide error message.
- ``revert()``: abort execution and revert state changes
- ``revert(string message)``: abort execution and revert state changes providing an explanatory string
- ``blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks
- ``keccak256(...) returns (bytes32)``: compute the Ethereum-SHA-3 (Keccak-256) hash of the :ref:`(tightly packed) arguments <abi_packed_mode>`
- ``sha3(...) returns (bytes32)``: an alias to ``keccak256``
Expand Down
66 changes: 44 additions & 22 deletions docs/solidity-by-example.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,28 +87,35 @@ of votes.
// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) public {
// If the argument of `require` evaluates to `false`,
// it terminates and reverts all changes to
// the state and to Ether balances.
// This consumes all gas in old EVM versions, but not anymore.
// It is often a good idea to use this if functions are
// called incorrectly.
// If the first argument of `require` evaluates
// to `false`, execution terminates and all
// changes to the state and to Ether balances
// are reverted.
// This used to consume all gas in old EVM versions, but
// not anymore.
// It is often a good idea to use `require` to check if
// functions are called correctly.
// As a second argument, you can also provide an
// explanation about what went wrong.
require(
(msg.sender == chairperson) &&
!voters[voter].voted &&
(voters[voter].weight == 0)
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}

/// Delegate your vote to the voter `to`.
function delegate(address to) public {
// assigns reference
Voter storage sender = voters[msg.sender];
require(!sender.voted);
require(!sender.voted, "You already voted.");

// Self-delegation is not allowed.
require(to != msg.sender);
require(to != msg.sender, "Self-delegation is disallowed.");

// Forward the delegation as long as
// `to` also delegated.
Expand All @@ -122,7 +129,7 @@ of votes.
to = voters[to].delegate;

// We found a loop in the delegation, not allowed.
require(to != msg.sender);
require(to != msg.sender, "Found loop in delegation.");
}

// Since `sender` is a reference, this
Expand All @@ -145,7 +152,7 @@ of votes.
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) public {
Voter storage sender = voters[msg.sender];
require(!sender.voted);
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;

Expand Down Expand Up @@ -270,11 +277,17 @@ activate themselves.

// Revert the call if the bidding
// period is over.
require(now <= auctionEnd);
require(
now <= auctionEnd,
"Auction already ended."
);

// If the bid is not higher, send the
// money back.
require(msg.value > highestBid);
require(
msg.value > highestBid,
"There already is a higher bid."
);

if (highestBid != 0) {
// Sending back the money by simply using
Expand Down Expand Up @@ -324,8 +337,8 @@ activate themselves.
// external contracts.

// 1. Conditions
require(now >= auctionEnd); // auction did not yet end
require(!ended); // this function has already been called
require(now >= auctionEnd, "Auction not yet ended.");
require(!ended, "auctionEnd has already been called.");

// 2. Effects
ended = true;
Expand Down Expand Up @@ -543,7 +556,7 @@ Safe Remote Purchase
function Purchase() public payable {
seller = msg.sender;
value = msg.value / 2;
require((2 * value) == msg.value);
require((2 * value) == msg.value, "Value has to be even.");
}

modifier condition(bool _condition) {
Expand All @@ -552,17 +565,26 @@ Safe Remote Purchase
}

modifier onlyBuyer() {
require(msg.sender == buyer);
require(
msg.sender == buyer,
"Only buyer can call this."
);
_;
}

modifier onlySeller() {
require(msg.sender == seller);
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}

modifier inState(State _state) {
require(state == _state);
require(
state == _state,
"Invalid state."
);
_;
}

Expand Down
5 changes: 4 additions & 1 deletion docs/structure-of-a-contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ Function modifiers can be used to amend the semantics of functions in a declarat
address public seller;

modifier onlySeller() { // Modifier
require(msg.sender == seller);
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}

Expand Down
5 changes: 4 additions & 1 deletion docs/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,10 @@ Another example that uses external function types::
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) public {
require(msg.sender == address(oracle));
require(
msg.sender == address(oracle),
"Only oracle can call this."
);
// Use the data
}
}
Expand Down
4 changes: 4 additions & 0 deletions docs/units-and-global-variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,12 @@ Error Handling
throws if the condition is not met - to be used for internal errors.
``require(bool condition)``:
throws if the condition is not met - to be used for errors in inputs or external components.
``require(bool condition, string message)``:
throws if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
``revert()``:
abort execution and revert state changes
``revert(string reason)``:
abort execution and revert state changes, providing an explanatory string

.. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,

Expand Down
8 changes: 7 additions & 1 deletion libsolidity/analysis/DeclarationContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ Declaration const* DeclarationContainer::conflictingDeclaration(

if (
dynamic_cast<FunctionDefinition const*>(&_declaration) ||
dynamic_cast<EventDefinition const*>(&_declaration)
dynamic_cast<EventDefinition const*>(&_declaration) ||
dynamic_cast<MagicVariableDeclaration const*>(&_declaration)
)
{
// check that all other declarations with the same name are functions or a public state variable or events.
Expand All @@ -68,6 +69,11 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
!dynamic_cast<EventDefinition const*>(declaration)
)
return declaration;
if (
dynamic_cast<MagicVariableDeclaration const*>(&_declaration) &&
!dynamic_cast<MagicVariableDeclaration const*>(declaration)
)
return declaration;
// Or, continue.
}
}
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/analysis/GlobalContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("mulmod", make_shared<FunctionType>(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("require", make_shared<FunctionType>(strings{"bool", "string memory"}, strings{}, FunctionType::Kind::Require, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("revert", make_shared<FunctionType>(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("ripemd160", make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Kind::RIPEMD160, true, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("selfdestruct", make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)),
Expand Down
9 changes: 6 additions & 3 deletions libsolidity/analysis/NameAndTypeResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ NameAndTypeResolver::NameAndTypeResolver(
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer());
for (Declaration const* declaration: _globals)
m_scopes[nullptr]->registerDeclaration(*declaration);
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous brackets.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some compilers, most notably MSVC have problems with macros if they are the single statement in a loop body. Because of that, I always put braces around macros in such cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, OK, that makes sense.

solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
}
}

bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
Expand Down Expand Up @@ -202,8 +204,9 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration),
"Found overloading involving something not a function or a variable."
dynamic_cast<VariableDeclaration const*>(declaration) ||
dynamic_cast<MagicVariableDeclaration const*>(declaration),
"Found overloading involving something not a function, event or a (magic) variable."
);

FunctionTypePointer functionType { declaration->functionType(false) };
Expand Down
7 changes: 3 additions & 4 deletions libsolidity/analysis/TypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2124,10 +2124,9 @@ bool TypeChecker::visit(Identifier const& _identifier)

for (Declaration const* declaration: annotation.overloadedDeclarations)
{
TypePointer function = declaration->type();
solAssert(!!function, "Requested type not present.");
auto const* functionType = dynamic_cast<FunctionType const*>(function.get());
if (functionType && functionType->canTakeArguments(*annotation.argumentTypes))
FunctionTypePointer functionType = declaration->functionType(true);
solAssert(!!functionType, "Requested type not present.");
if (functionType->canTakeArguments(*annotation.argumentTypes))
candidates.push_back(declaration);
}
if (candidates.empty())
Expand Down
Loading