Merge pull request #3364 from ethereum/revertWithReason

Revert with reason
This commit is contained in:
chriseth 2018-04-12 21:01:08 +02:00 committed by GitHub
commit 7054defdd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 487 additions and 82 deletions

View File

@ -5,6 +5,7 @@ Features:
* Code Generator: More specialized and thus optimized implementation for ``x.push(...)`` * 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. * Commandline interface: Error when missing or inaccessible file detected. Suppress it with the ``--ignore-missing`` flag.
* Constant Evaluator: Fix evaluation of single element tuples. * 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: Limit the number of errors output in a single run to 256.
* General: Support accessing dynamic return data in post-byzantium EVMs. * General: Support accessing dynamic return data in post-byzantium EVMs.
* Interfaces: Allow overriding external functions in interfaces with public in an implementing contract. * Interfaces: Allow overriding external functions in interfaces with public in an implementing contract.

View File

@ -147,7 +147,10 @@ restrictions highly readable.
// a certain address. // a certain address.
modifier onlyBy(address _account) modifier onlyBy(address _account)
{ {
require(msg.sender == _account); require(
msg.sender == _account,
"Sender not authorized."
);
// Do not forget the "_;"! It will // Do not forget the "_;"! It will
// be replaced by the actual function // be replaced by the actual function
// body when the modifier is used. // body when the modifier is used.
@ -164,7 +167,10 @@ restrictions highly readable.
} }
modifier onlyAfter(uint _time) { modifier onlyAfter(uint _time) {
require(now >= _time); require(
now >= _time,
"Function called too early."
);
_; _;
} }
@ -186,7 +192,10 @@ restrictions highly readable.
// This was dangerous before Solidity version 0.4.0, // This was dangerous before Solidity version 0.4.0,
// where it was possible to skip the part after `_;`. // where it was possible to skip the part after `_;`.
modifier costs(uint _amount) { modifier costs(uint _amount) {
require(msg.value >= _amount); require(
msg.value >= _amount,
"Not enough Ether provided."
);
_; _;
if (msg.value > _amount) if (msg.value > _amount)
msg.sender.send(msg.value - _amount); msg.sender.send(msg.value - _amount);
@ -290,7 +299,10 @@ function finishes.
uint public creationTime = now; uint public creationTime = now;
modifier atStage(Stages _stage) { modifier atStage(Stages _stage) {
require(stage == _stage); require(
stage == _stage,
"Function cannot be called at this time."
);
_; _;
} }

View File

@ -315,7 +315,10 @@ inheritable properties of contracts and may be overridden by derived contracts.
// function is executed and otherwise, an exception is // function is executed and otherwise, an exception is
// thrown. // thrown.
modifier onlyOwner { modifier onlyOwner {
require(msg.sender == owner); require(
msg.sender == owner,
"Only owner can call this function."
);
_; _;
} }
} }
@ -360,7 +363,10 @@ inheritable properties of contracts and may be overridden by derived contracts.
contract Mutex { contract Mutex {
bool locked; bool locked;
modifier noReentrancy() { modifier noReentrancy() {
require(!locked); require(
!locked,
"Reentrant call."
);
locked = true; locked = true;
_; _;
locked = false; locked = false;

View File

@ -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. 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 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 revert the current call. It is possible to provide a string message containing details about the error
in a call to ``revert``. The ``throw`` keyword can also be used as an alternative to ``revert()``. 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:: .. note::
From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future. From version 0.4.13 the ``throw`` keyword is deprecated and will be phased out in the future.
@ -471,13 +472,16 @@ of an exception instead of "bubbling up".
Catching exceptions is not yet possible. Catching exceptions is not yet possible.
In the following example, you can see how ``require`` can be used to easily check conditions on inputs 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; pragma solidity ^0.4.0;
contract Sharer { contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) { 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; uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2); addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and // Since transfer throws an exception on failure and
@ -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 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 (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. ``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

View File

@ -333,7 +333,9 @@ Global Variables
- ``tx.origin`` (``address``): sender of the transaction (full call chain) - ``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) - ``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)``: 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()``: 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 - ``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>` - ``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`` - ``sha3(...) returns (bytes32)``: an alias to ``keccak256``

View File

@ -87,17 +87,25 @@ of votes.
// Give `voter` the right to vote on this ballot. // Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`. // May only be called by `chairperson`.
function giveRightToVote(address voter) public { function giveRightToVote(address voter) public {
// If the argument of `require` evaluates to `false`, // If the first argument of `require` evaluates
// it terminates and reverts all changes to // to `false`, execution terminates and all
// the state and to Ether balances. // changes to the state and to Ether balances
// This consumes all gas in old EVM versions, but not anymore. // are reverted.
// It is often a good idea to use this if functions are // This used to consume all gas in old EVM versions, but
// called incorrectly. // 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( require(
(msg.sender == chairperson) && msg.sender == chairperson,
!voters[voter].voted && "Only chairperson can give right to vote."
(voters[voter].weight == 0)
); );
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1; voters[voter].weight = 1;
} }
@ -105,10 +113,9 @@ of votes.
function delegate(address to) public { function delegate(address to) public {
// assigns reference // assigns reference
Voter storage sender = voters[msg.sender]; Voter storage sender = voters[msg.sender];
require(!sender.voted); require(!sender.voted, "You already voted.");
// Self-delegation is not allowed. require(to != msg.sender, "Self-delegation is disallowed.");
require(to != msg.sender);
// Forward the delegation as long as // Forward the delegation as long as
// `to` also delegated. // `to` also delegated.
@ -122,7 +129,7 @@ of votes.
to = voters[to].delegate; to = voters[to].delegate;
// We found a loop in the delegation, not allowed. // 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 // Since `sender` is a reference, this
@ -145,7 +152,7 @@ of votes.
/// to proposal `proposals[proposal].name`. /// to proposal `proposals[proposal].name`.
function vote(uint proposal) public { function vote(uint proposal) public {
Voter storage sender = voters[msg.sender]; Voter storage sender = voters[msg.sender];
require(!sender.voted); require(!sender.voted, "Already voted.");
sender.voted = true; sender.voted = true;
sender.vote = proposal; sender.vote = proposal;
@ -270,11 +277,17 @@ activate themselves.
// Revert the call if the bidding // Revert the call if the bidding
// period is over. // period is over.
require(now <= auctionEnd); require(
now <= auctionEnd,
"Auction already ended."
);
// If the bid is not higher, send the // If the bid is not higher, send the
// money back. // money back.
require(msg.value > highestBid); require(
msg.value > highestBid,
"There already is a higher bid."
);
if (highestBid != 0) { if (highestBid != 0) {
// Sending back the money by simply using // Sending back the money by simply using
@ -324,8 +337,8 @@ activate themselves.
// external contracts. // external contracts.
// 1. Conditions // 1. Conditions
require(now >= auctionEnd); // auction did not yet end require(now >= auctionEnd, "Auction not yet ended.");
require(!ended); // this function has already been called require(!ended, "auctionEnd has already been called.");
// 2. Effects // 2. Effects
ended = true; ended = true;
@ -543,7 +556,7 @@ Safe Remote Purchase
function Purchase() public payable { function Purchase() public payable {
seller = msg.sender; seller = msg.sender;
value = msg.value / 2; value = msg.value / 2;
require((2 * value) == msg.value); require((2 * value) == msg.value, "Value has to be even.");
} }
modifier condition(bool _condition) { modifier condition(bool _condition) {
@ -552,17 +565,26 @@ Safe Remote Purchase
} }
modifier onlyBuyer() { modifier onlyBuyer() {
require(msg.sender == buyer); require(
msg.sender == buyer,
"Only buyer can call this."
);
_; _;
} }
modifier onlySeller() { modifier onlySeller() {
require(msg.sender == seller); require(
msg.sender == seller,
"Only seller can call this."
);
_; _;
} }
modifier inState(State _state) { modifier inState(State _state) {
require(state == _state); require(
state == _state,
"Invalid state."
);
_; _;
} }

View File

@ -68,7 +68,10 @@ Function modifiers can be used to amend the semantics of functions in a declarat
address public seller; address public seller;
modifier onlySeller() { // Modifier modifier onlySeller() { // Modifier
require(msg.sender == seller); require(
msg.sender == seller,
"Only seller can call this."
);
_; _;
} }

View File

@ -495,7 +495,10 @@ Another example that uses external function types::
oracle.query("USD", this.oracleResponse); oracle.query("USD", this.oracleResponse);
} }
function oracleResponse(bytes response) public { function oracleResponse(bytes response) public {
require(msg.sender == address(oracle)); require(
msg.sender == address(oracle),
"Only oracle can call this."
);
// Use the data // Use the data
} }
} }

View File

@ -99,8 +99,12 @@ Error Handling
throws if the condition is not met - to be used for internal errors. throws if the condition is not met - to be used for internal errors.
``require(bool condition)``: ``require(bool condition)``:
throws if the condition is not met - to be used for errors in inputs or external components. 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()``: ``revert()``:
abort execution and revert state changes 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, .. index:: keccak256, ripemd160, sha256, ecrecover, addmod, mulmod, cryptography,

View File

@ -45,7 +45,8 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if ( if (
dynamic_cast<FunctionDefinition const*>(&_declaration) || 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. // check that all other declarations with the same name are functions or a public state variable or events.
@ -68,6 +69,11 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
!dynamic_cast<EventDefinition const*>(declaration) !dynamic_cast<EventDefinition const*>(declaration)
) )
return declaration; return declaration;
if (
dynamic_cast<MagicVariableDeclaration const*>(&_declaration) &&
!dynamic_cast<MagicVariableDeclaration const*>(declaration)
)
return declaration;
// Or, continue. // Or, continue.
} }
} }

View File

@ -52,7 +52,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>("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>("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"}, 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(), 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>("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>("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)), make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA256, true, StateMutability::Pure)),

View File

@ -47,7 +47,9 @@ NameAndTypeResolver::NameAndTypeResolver(
if (!m_scopes[nullptr]) if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer()); m_scopes[nullptr].reset(new DeclarationContainer());
for (Declaration const* declaration: _globals) for (Declaration const* declaration: _globals)
m_scopes[nullptr]->registerDeclaration(*declaration); {
solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
}
} }
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope) bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
@ -202,8 +204,9 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
solAssert( solAssert(
dynamic_cast<FunctionDefinition const*>(declaration) || dynamic_cast<FunctionDefinition const*>(declaration) ||
dynamic_cast<EventDefinition const*>(declaration) || dynamic_cast<EventDefinition const*>(declaration) ||
dynamic_cast<VariableDeclaration const*>(declaration), dynamic_cast<VariableDeclaration const*>(declaration) ||
"Found overloading involving something not a function or a variable." dynamic_cast<MagicVariableDeclaration const*>(declaration),
"Found overloading involving something not a function, event or a (magic) variable."
); );
FunctionTypePointer functionType { declaration->functionType(false) }; FunctionTypePointer functionType { declaration->functionType(false) };

View File

@ -2146,10 +2146,9 @@ bool TypeChecker::visit(Identifier const& _identifier)
for (Declaration const* declaration: annotation.overloadedDeclarations) for (Declaration const* declaration: annotation.overloadedDeclarations)
{ {
TypePointer function = declaration->type(); FunctionTypePointer functionType = declaration->functionType(true);
solAssert(!!function, "Requested type not present."); solAssert(!!functionType, "Requested type not present.");
auto const* functionType = dynamic_cast<FunctionType const*>(function.get()); if (functionType->canTakeArguments(*annotation.argumentTypes))
if (functionType && functionType->canTakeArguments(*annotation.argumentTypes))
candidates.push_back(declaration); candidates.push_back(declaration);
} }
if (candidates.empty()) if (candidates.empty())

View File

@ -297,7 +297,7 @@ ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
return contractDef->contractKind(); return contractDef->contractKind();
} }
shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
{ {
if (_internal) if (_internal)
{ {
@ -338,6 +338,7 @@ shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
TypePointer FunctionDefinition::type() const TypePointer FunctionDefinition::type() const
{ {
solAssert(visibility() != Declaration::Visibility::External, "");
return make_shared<FunctionType>(*this); return make_shared<FunctionType>(*this);
} }
@ -379,7 +380,7 @@ TypePointer EventDefinition::type() const
return make_shared<FunctionType>(*this); return make_shared<FunctionType>(*this);
} }
std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const FunctionTypePointer EventDefinition::functionType(bool _internal) const
{ {
if (_internal) if (_internal)
return make_shared<FunctionType>(*this); return make_shared<FunctionType>(*this);
@ -484,7 +485,7 @@ TypePointer VariableDeclaration::type() const
return annotation().type; return annotation().type;
} }
shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
{ {
if (_internal) if (_internal)
return {}; return {};

View File

@ -218,7 +218,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function. /// @returns null when it is not accessible as a function.
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const { return {}; } virtual FunctionTypePointer functionType(bool /*_internal*/) const { return {}; }
protected: protected:
virtual Visibility defaultVisibility() const { return Visibility::Public; } virtual Visibility defaultVisibility() const { return Visibility::Public; }
@ -634,7 +634,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function. /// @returns null when it is not accessible as a function.
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override; virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual FunctionDefinitionAnnotation& annotation() const override; virtual FunctionDefinitionAnnotation& annotation() const override;
@ -703,7 +703,7 @@ public:
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned. /// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
/// @returns null when it is not accessible as a function. /// @returns null when it is not accessible as a function.
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override; virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual VariableDeclarationAnnotation& annotation() const override; virtual VariableDeclarationAnnotation& annotation() const override;
@ -805,7 +805,7 @@ public:
bool isAnonymous() const { return m_anonymous; } bool isAnonymous() const { return m_anonymous; }
virtual TypePointer type() const override; virtual TypePointer type() const override;
virtual std::shared_ptr<FunctionType> functionType(bool /*_internal*/) const override; virtual FunctionTypePointer functionType(bool /*_internal*/) const override;
virtual EventDefinitionAnnotation& annotation() const override; virtual EventDefinitionAnnotation& annotation() const override;
@ -831,6 +831,11 @@ public:
solAssert(false, "MagicVariableDeclaration used inside real AST."); solAssert(false, "MagicVariableDeclaration used inside real AST.");
} }
virtual FunctionTypePointer functionType(bool) const override
{
solAssert(m_type->category() == Type::Category::Function, "");
return std::dynamic_pointer_cast<FunctionType const>(m_type);
}
virtual TypePointer type() const override { return m_type; } virtual TypePointer type() const override { return m_type; }
private: private:

View File

@ -262,12 +262,20 @@ CompilerContext& CompilerContext::appendRevert()
return *this << u256(0) << u256(0) << Instruction::REVERT; return *this << u256(0) << u256(0) << Instruction::REVERT;
} }
CompilerContext& CompilerContext::appendConditionalRevert() CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData)
{ {
*this << Instruction::ISZERO; if (_forwardReturnData && m_evmVersion.supportsReturndata())
eth::AssemblyItem afterTag = appendConditionalJump(); appendInlineAssembly(R"({
appendRevert(); if condition {
*this << afterTag; returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
})", {"condition"});
else
appendInlineAssembly(R"({
if condition { revert(0, 0) }
})", {"condition"});
*this << Instruction::POP;
return *this; return *this;
} }

View File

@ -156,8 +156,11 @@ public:
CompilerContext& appendConditionalInvalid(); CompilerContext& appendConditionalInvalid();
/// Appends a REVERT(0, 0) call /// Appends a REVERT(0, 0) call
CompilerContext& appendRevert(); CompilerContext& appendRevert();
/// Appends a conditional REVERT(0, 0) call /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
CompilerContext& appendConditionalRevert(); /// empty string. Consumes the condition.
/// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward
/// the data.
CompilerContext& appendConditionalRevert(bool _forwardReturnData = false);
/// Appends a JUMP to a specific tag /// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag. /// Appends pushing of a new tag and @returns the new tag.

View File

@ -78,6 +78,20 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
m_context << Instruction::SWAP1; m_context << Instruction::SWAP1;
} }
void CompilerUtils::revertWithStringData(Type const& _argumentType)
{
solAssert(_argumentType.isImplicitlyConvertibleTo(*Type::fromElementaryTypeName("string memory")), "");
fetchFreeMemoryPointer();
m_context << (u256(FixedHash<4>::Arith(FixedHash<4>(dev::keccak256("Error(string)")))) << (256 - 32));
m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(4) << Instruction::ADD;
// Stack: <string data> <mem pos of encoding start>
abiEncode({_argumentType.shared_from_this()}, {make_shared<ArrayType>(DataLocation::Memory, true)});
toSizeAfterFreeMemoryPointer();
m_context << Instruction::REVERT;
m_context.adjustStackOffset(_argumentType.sizeOnStack());
}
unsigned CompilerUtils::loadFromMemory( unsigned CompilerUtils::loadFromMemory(
unsigned _offset, unsigned _offset,
Type const& _type, Type const& _type,
@ -691,6 +705,7 @@ void CompilerUtils::convertType(
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
if (_asPartOfArgumentDecoding) if (_asPartOfArgumentDecoding)
// TODO: error message?
m_context.appendConditionalRevert(); m_context.appendConditionalRevert();
else else
m_context.appendConditionalInvalid(); m_context.appendConditionalInvalid();

View File

@ -54,6 +54,13 @@ public:
/// Stack post: <size> <mem_start> /// Stack post: <size> <mem_start>
void toSizeAfterFreeMemoryPointer(); void toSizeAfterFreeMemoryPointer();
/// Appends code that performs a revert, providing the given string data.
/// Will also append an error signature corresponding to Error(string).
/// @param _argumentType the type of the string argument, will be converted to memory string.
/// Stack pre: string data
/// Stack post:
void revertWithStringData(Type const& _argumentType);
/// Loads data from memory to the stack. /// Loads data from memory to the stack.
/// @param _offset offset in memory (or calldata) /// @param _offset offset in memory (or calldata)
/// @param _type data type to load /// @param _type data type to load

View File

@ -128,6 +128,7 @@ void ContractCompiler::appendCallValueCheck()
{ {
// Throw if function is not payable but call contained ether. // Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE; m_context << Instruction::CALLVALUE;
// TODO: error message?
m_context.appendConditionalRevert(); m_context.appendConditionalRevert();
} }
@ -327,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << Instruction::STOP; m_context << Instruction::STOP;
} }
else else
// TODO: error message here?
m_context.appendRevert(); m_context.appendRevert();
for (auto const& it: interfaceFunctions) for (auto const& it: interfaceFunctions)

View File

@ -610,7 +610,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE; m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance). // Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::ISZERO;
m_context.appendConditionalRevert(); // TODO: Can we bubble up here? There might be different reasons for failure, I think.
m_context.appendConditionalRevert(true);
if (function.valueSet()) if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP; m_context << swapInstruction(1) << Instruction::POP;
break; break;
@ -672,8 +673,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (function.kind() == FunctionType::Kind::Transfer) if (function.kind() == FunctionType::Kind::Transfer)
{ {
// Check if zero (out of stack or not enough balance). // Check if zero (out of stack or not enough balance).
// TODO: bubble up here, but might also be different error.
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalRevert(); m_context.appendConditionalRevert(true);
} }
break; break;
case FunctionType::Kind::Selfdestruct: case FunctionType::Kind::Selfdestruct:
@ -682,8 +684,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::SELFDESTRUCT; m_context << Instruction::SELFDESTRUCT;
break; break;
case FunctionType::Kind::Revert: case FunctionType::Kind::Revert:
m_context.appendRevert(); {
if (!arguments.empty())
{
// function-sel(Error(string)) + encoding
solAssert(arguments.size() == 1, "");
solAssert(function.parameterTypes().size() == 1, "");
arguments.front()->accept(*this);
utils().revertWithStringData(*arguments.front()->annotation().type);
}
else
m_context.appendRevert();
break; break;
}
case FunctionType::Kind::SHA3: case FunctionType::Kind::SHA3:
{ {
TypePointers argumentTypes; TypePointers argumentTypes;
@ -902,16 +915,31 @@ 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);
if (arguments.size() > 1)
{
// Users probably expect the second argument to be evaluated
// even if the condition is false, as would be the case for an actual
// function call.
solAssert(arguments.size() == 2, "");
solAssert(function.kind() == FunctionType::Kind::Require, "");
arguments.at(1)->accept(*this);
utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack());
}
// Stack: <error string (unconverted)> <condition>
// jump if condition was met // jump if condition was met
m_context << Instruction::ISZERO << Instruction::ISZERO; m_context << Instruction::ISZERO << Instruction::ISZERO;
auto success = m_context.appendConditionalJump(); auto success = m_context.appendConditionalJump();
if (function.kind() == FunctionType::Kind::Assert) if (function.kind() == FunctionType::Kind::Assert)
// condition was not met, flag an error // condition was not met, flag an error
m_context.appendInvalid(); m_context.appendInvalid();
else if (arguments.size() > 1)
utils().revertWithStringData(*arguments.at(1)->annotation().type);
else else
m_context.appendRevert(); m_context.appendRevert();
// the success branch // the success branch
m_context << success; m_context << success;
if (arguments.size() > 1)
utils().popStackElement(*arguments.at(1)->annotation().type);
break; break;
} }
case FunctionType::Kind::ABIEncode: case FunctionType::Kind::ABIEncode:
@ -1882,6 +1910,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall) if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
{ {
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO; m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
// TODO: error message?
m_context.appendConditionalRevert(); m_context.appendConditionalRevert();
existenceChecked = true; existenceChecked = true;
} }
@ -1924,7 +1953,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{ {
//Propagate error condition (if CALL pushes 0 on stack). //Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO; m_context << Instruction::ISZERO;
m_context.appendConditionalRevert(); m_context.appendConditionalRevert(true);
} }
utils().popStackSlots(remainsSize); utils().popStackSlots(remainsSize);

View File

@ -157,14 +157,23 @@ BOOST_AUTO_TEST_CASE(location_test)
} }
} }
)"; )";
shared_ptr<string const> n = make_shared<string>("");
AssemblyItems items = compileContract(sourceCode); AssemblyItems items = compileContract(sourceCode);
vector<SourceLocation> locations = vector<SourceLocation> locations =
vector<SourceLocation>(24, SourceLocation(2, 75, n)) + vector<SourceLocation>(24, SourceLocation(2, 75, make_shared<string>(""))) +
vector<SourceLocation>(32, SourceLocation(20, 72, n)) + vector<SourceLocation>(2, SourceLocation(20, 72, make_shared<string>(""))) +
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + vector<SourceLocation>(1, SourceLocation(8, 17, make_shared<string>("--CODEGEN--"))) +
vector<SourceLocation>(2, SourceLocation(58, 67, n)) + vector<SourceLocation>(3, SourceLocation(5, 7, make_shared<string>("--CODEGEN--"))) +
vector<SourceLocation>(2, SourceLocation(20, 72, n)); vector<SourceLocation>(1, SourceLocation(30, 31, make_shared<string>("--CODEGEN--"))) +
vector<SourceLocation>(1, SourceLocation(27, 28, make_shared<string>("--CODEGEN--"))) +
vector<SourceLocation>(1, SourceLocation(20, 32, make_shared<string>("--CODEGEN--"))) +
vector<SourceLocation>(1, SourceLocation(5, 7, make_shared<string>("--CODEGEN--"))) +
vector<SourceLocation>(24, SourceLocation(20, 72, make_shared<string>(""))) +
vector<SourceLocation>(1, SourceLocation(42, 51, make_shared<string>(""))) +
vector<SourceLocation>(1, SourceLocation(65, 67, make_shared<string>(""))) +
vector<SourceLocation>(2, SourceLocation(58, 67, make_shared<string>(""))) +
vector<SourceLocation>(2, SourceLocation(20, 72, make_shared<string>("")));
checkAssemblyLocations(items, locations); checkAssemblyLocations(items, locations);
} }

View File

@ -111,7 +111,7 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
BOOST_CHECK(contract["bytecode"].isString()); BOOST_CHECK(contract["bytecode"].isString());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), dev::test::bytecodeSansMetadata(contract["bytecode"].asString()),
"60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00" "6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00"
); );
BOOST_CHECK(contract["runtimeBytecode"].isString()); BOOST_CHECK(contract["runtimeBytecode"].isString());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
BOOST_CHECK(contract["gasEstimates"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::jsonCompactPrint(contract["gasEstimates"]), dev::jsonCompactPrint(contract["gasEstimates"]),
"{\"creation\":[61,10600],\"external\":{},\"internal\":{}}" "{\"creation\":[66,10600],\"external\":{},\"internal\":{}}"
); );
BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(contract["metadata"].isString());
BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString()));
@ -153,7 +153,7 @@ BOOST_AUTO_TEST_CASE(single_compilation)
BOOST_CHECK(contract["bytecode"].isString()); BOOST_CHECK(contract["bytecode"].isString());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::test::bytecodeSansMetadata(contract["bytecode"].asString()), dev::test::bytecodeSansMetadata(contract["bytecode"].asString()),
"60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00" "6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00"
); );
BOOST_CHECK(contract["runtimeBytecode"].isString()); BOOST_CHECK(contract["runtimeBytecode"].isString());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE(single_compilation)
BOOST_CHECK(contract["gasEstimates"].isObject()); BOOST_CHECK(contract["gasEstimates"].isObject());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::jsonCompactPrint(contract["gasEstimates"]), dev::jsonCompactPrint(contract["gasEstimates"]),
"{\"creation\":[61,10600],\"external\":{},\"internal\":{}}" "{\"creation\":[66,10600],\"external\":{},\"internal\":{}}"
); );
BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(contract["metadata"].isString());
BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString()));

View File

@ -47,8 +47,8 @@ BOOST_AUTO_TEST_CASE(does_not_include_creation_time_only_internal_functions)
BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed"); BOOST_REQUIRE_MESSAGE(m_compiler.compile(), "Compiling contract failed");
bytes const& creationBytecode = m_compiler.object("C").bytecode; bytes const& creationBytecode = m_compiler.object("C").bytecode;
bytes const& runtimeBytecode = m_compiler.runtimeObject("C").bytecode; bytes const& runtimeBytecode = m_compiler.runtimeObject("C").bytecode;
BOOST_CHECK(creationBytecode.size() >= 120); BOOST_CHECK(creationBytecode.size() >= 130);
BOOST_CHECK(creationBytecode.size() <= 150); BOOST_CHECK(creationBytecode.size() <= 160);
BOOST_CHECK(runtimeBytecode.size() >= 50); BOOST_CHECK(runtimeBytecode.size() >= 50);
BOOST_CHECK(runtimeBytecode.size() <= 70); BOOST_CHECK(runtimeBytecode.size() <= 70);
} }

View File

@ -10458,6 +10458,214 @@ BOOST_AUTO_TEST_CASE(revert)
ABI_CHECK(callContractFunction("a()"), encodeArgs(u256(42))); ABI_CHECK(callContractFunction("a()"), encodeArgs(u256(42)));
} }
BOOST_AUTO_TEST_CASE(revert_with_cause)
{
char const* sourceCode = R"(
contract D {
function f() public {
revert("test123");
}
function g() public {
revert("test1234567890123456789012345678901234567890");
}
}
contract C {
D d = new D();
function forward(address target, bytes data) internal returns (bool success, bytes retval) {
uint retsize;
assembly {
success := call(not(0), target, 0, add(data, 0x20), mload(data), 0, 0)
retsize := returndatasize()
}
retval = new bytes(retsize);
assembly {
returndatacopy(add(retval, 0x20), 0, returndatasize())
}
}
function f() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
function g() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bool const haveReturndata = dev::test::Options::get().evmVersion().supportsReturndata();
bytes const errorSignature = bytes{0x08, 0xc3, 0x79, 0xa0};
ABI_CHECK(callContractFunction("f()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 7, "test123") + bytes(28, 0) : bytes());
ABI_CHECK(callContractFunction("g()"), haveReturndata ? encodeArgs(0, 0x40, 0x84) + errorSignature + encodeArgs(0x20, 44, "test1234567890123456789012345678901234567890") + bytes(28, 0): bytes());
}
BOOST_AUTO_TEST_CASE(require_with_message)
{
char const* sourceCode = R"(
contract D {
bool flag = false;
string storageError = "abc";
function f(uint x) public {
require(x > 7, "failed");
}
function g() public {
// As a side-effect of internalFun, the flag will be set to true
// (even if the condition is true),
// but it will only throw in the next evaluation.
bool flagCopy = flag;
require(flagCopy == false, internalFun());
}
function internalFun() returns (string) {
flag = true;
return "only on second run";
}
function h() public {
require(false, storageError);
}
}
contract C {
D d = new D();
function forward(address target, bytes data) internal returns (bool success, bytes retval) {
uint retsize;
assembly {
success := call(not(0), target, 0, add(data, 0x20), mload(data), 0, 0)
retsize := returndatasize()
}
retval = new bytes(retsize);
assembly {
returndatacopy(add(retval, 0x20), 0, returndatasize())
}
}
function f(uint x) public returns (bool, bytes) {
return forward(address(d), msg.data);
}
function g() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
function h() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bool const haveReturndata = dev::test::Options::get().evmVersion().supportsReturndata();
bytes const errorSignature = bytes{0x08, 0xc3, 0x79, 0xa0};
ABI_CHECK(callContractFunction("f(uint256)", 8), haveReturndata ? encodeArgs(1, 0x40, 0) : bytes());
ABI_CHECK(callContractFunction("f(uint256)", 5), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 6, "failed") + bytes(28, 0) : bytes());
ABI_CHECK(callContractFunction("g()"), haveReturndata ? encodeArgs(1, 0x40, 0) : bytes());
ABI_CHECK(callContractFunction("g()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 18, "only on second run") + bytes(28, 0) : bytes());
ABI_CHECK(callContractFunction("h()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 3, "abc") + bytes(28, 0): bytes());
}
BOOST_AUTO_TEST_CASE(bubble_up_error_messages)
{
char const* sourceCode = R"(
contract D {
function f() public {
revert("message");
}
function g() public {
this.f();
}
}
contract C {
D d = new D();
function forward(address target, bytes data) internal returns (bool success, bytes retval) {
uint retsize;
assembly {
success := call(not(0), target, 0, add(data, 0x20), mload(data), 0, 0)
retsize := returndatasize()
}
retval = new bytes(retsize);
assembly {
returndatacopy(add(retval, 0x20), 0, returndatasize())
}
}
function f() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
function g() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bool const haveReturndata = dev::test::Options::get().evmVersion().supportsReturndata();
bytes const errorSignature = bytes{0x08, 0xc3, 0x79, 0xa0};
ABI_CHECK(callContractFunction("f()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 7, "message") + bytes(28, 0) : bytes());
ABI_CHECK(callContractFunction("g()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 7, "message") + bytes(28, 0) : bytes());
}
BOOST_AUTO_TEST_CASE(bubble_up_error_messages_through_transfer)
{
char const* sourceCode = R"(
contract D {
function() public payable {
revert("message");
}
function f() public {
this.transfer(0);
}
}
contract C {
D d = new D();
function forward(address target, bytes data) internal returns (bool success, bytes retval) {
uint retsize;
assembly {
success := call(not(0), target, 0, add(data, 0x20), mload(data), 0, 0)
retsize := returndatasize()
}
retval = new bytes(retsize);
assembly {
returndatacopy(add(retval, 0x20), 0, returndatasize())
}
}
function f() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bool const haveReturndata = dev::test::Options::get().evmVersion().supportsReturndata();
bytes const errorSignature = bytes{0x08, 0xc3, 0x79, 0xa0};
ABI_CHECK(callContractFunction("f()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 7, "message") + bytes(28, 0) : bytes());
}
BOOST_AUTO_TEST_CASE(bubble_up_error_messages_through_create)
{
char const* sourceCode = R"(
contract E {
function E() {
revert("message");
}
}
contract D {
function f() public {
var x = new E();
}
}
contract C {
D d = new D();
function forward(address target, bytes data) internal returns (bool success, bytes retval) {
uint retsize;
assembly {
success := call(not(0), target, 0, add(data, 0x20), mload(data), 0, 0)
retsize := returndatasize()
}
retval = new bytes(retsize);
assembly {
returndatacopy(add(retval, 0x20), 0, returndatasize())
}
}
function f() public returns (bool, bytes) {
return forward(address(d), msg.data);
}
}
)";
compileAndRun(sourceCode, 0, "C");
bool const haveReturndata = dev::test::Options::get().evmVersion().supportsReturndata();
bytes const errorSignature = bytes{0x08, 0xc3, 0x79, 0xa0};
ABI_CHECK(callContractFunction("f()"), haveReturndata ? encodeArgs(0, 0x40, 0x64) + errorSignature + encodeArgs(0x20, 7, "message") + bytes(28, 0) : bytes());
}
BOOST_AUTO_TEST_CASE(negative_stack_height) BOOST_AUTO_TEST_CASE(negative_stack_height)
{ {
// This code was causing negative stack height during code generation // This code was causing negative stack height during code generation

View File

@ -5987,14 +5987,30 @@ BOOST_AUTO_TEST_CASE(bare_revert)
} }
} }
)"; )";
CHECK_WARNING(text, "Statement has no effect."); CHECK_ERROR(text, TypeError, "No matching declaration found");
}
BOOST_AUTO_TEST_CASE(revert_with_reason)
{
char const* text = R"(
contract C {
function f(uint x) pure public {
if (x > 7)
revert("abc");
else
revert();
}
}
)";
CHECK_SUCCESS_NO_WARNINGS(text);
} }
BOOST_AUTO_TEST_CASE(bare_others) BOOST_AUTO_TEST_CASE(bare_others)
{ {
CHECK_WARNING("contract C { function f() pure public { selfdestruct; } }", "Statement has no effect."); CHECK_WARNING("contract C { function f() pure public { selfdestruct; } }", "Statement has no effect.");
CHECK_WARNING("contract C { function f() pure public { assert; } }", "Statement has no effect."); CHECK_WARNING("contract C { function f() pure public { assert; } }", "Statement has no effect.");
CHECK_WARNING("contract C { function f() pure public { require; } }", "Statement has no effect."); // This is different because it does have overloads.
CHECK_ERROR("contract C { function f() pure public { require; } }", TypeError, "No matching declaration found after variable lookup.");
CHECK_WARNING("contract C { function f() pure public { suicide; } }", "Statement has no effect."); CHECK_WARNING("contract C { function f() pure public { suicide; } }", "Statement has no effect.");
} }
@ -6493,7 +6509,7 @@ BOOST_AUTO_TEST_CASE(does_not_error_transfer_regular_function)
CHECK_SUCCESS_NO_WARNINGS(text); CHECK_SUCCESS_NO_WARNINGS(text);
} }
BOOST_AUTO_TEST_CASE(returndatacopy_as_variable) BOOST_AUTO_TEST_CASE(returndatasize_as_variable)
{ {
char const* text = R"( char const* text = R"(
contract c { function f() public { uint returndatasize; assembly { returndatasize }}} contract c { function f() public { uint returndatasize; assembly { returndatasize }}}

View File

@ -261,19 +261,24 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()), dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()),
"60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00" "6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00"
); );
BOOST_CHECK(contract["evm"]["assembly"].isString()); BOOST_CHECK(contract["evm"]["assembly"].isString());
BOOST_CHECK(contract["evm"]["assembly"].asString().find( BOOST_CHECK(contract["evm"]["assembly"].asString().find(
" /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n jumpi(tag_1, iszero(callvalue))\n" " /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n "
" 0x0\n dup1\n revert\ntag_1:\n dataSize(sub_0)\n dup1\n dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n" "callvalue\n /* \"--CODEGEN--\":8:17 */\n dup1\n "
" return\nstop\n\nsub_0: assembly {\n /* \"fileA\":0:14 contract A { } */\n" "/* \"--CODEGEN--\":5:7 */\n iszero\n tag_1\n jumpi\n "
" mstore(0x40, 0x80)\n 0x0\n dup1\n revert\n\n" "/* \"--CODEGEN--\":30:31 */\n 0x0\n /* \"--CODEGEN--\":27:28 */\n "
" auxdata: 0xa165627a7a7230582") == 0); "dup1\n /* \"--CODEGEN--\":20:32 */\n revert\n /* \"--CODEGEN--\":5:7 */\n"
"tag_1:\n /* \"fileA\":0:14 contract A { } */\n pop\n dataSize(sub_0)\n dup1\n "
"dataOffset(sub_0)\n 0x0\n codecopy\n 0x0\n return\nstop\n\nsub_0: assembly {\n "
"/* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n 0x0\n "
"dup1\n revert\n\n auxdata: 0xa165627a7a72305820"
) == 0);
BOOST_CHECK(contract["evm"]["gasEstimates"].isObject()); BOOST_CHECK(contract["evm"]["gasEstimates"].isObject());
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::jsonCompactPrint(contract["evm"]["gasEstimates"]), dev::jsonCompactPrint(contract["evm"]["gasEstimates"]),
"{\"creation\":{\"codeDepositCost\":\"10600\",\"executionCost\":\"61\",\"totalCost\":\"10661\"}}" "{\"creation\":{\"codeDepositCost\":\"10600\",\"executionCost\":\"66\",\"totalCost\":\"10666\"}}"
); );
BOOST_CHECK(contract["metadata"].isString()); BOOST_CHECK(contract["metadata"].isString());
BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString())); BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString()));