mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #3364 from ethereum/revertWithReason
Revert with reason
This commit is contained in:
commit
7054defdd6
@ -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.
|
||||
|
@ -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.
|
||||
@ -164,7 +167,10 @@ restrictions highly readable.
|
||||
}
|
||||
|
||||
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,
|
||||
// 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);
|
||||
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
@ -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.
|
||||
@ -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
|
||||
@ -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
|
||||
|
@ -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``
|
||||
|
@ -87,17 +87,25 @@ 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;
|
||||
}
|
||||
|
||||
@ -105,10 +113,9 @@ of votes.
|
||||
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.
|
||||
@ -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
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
@ -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) {
|
||||
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -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."
|
||||
);
|
||||
_;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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.
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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>("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)),
|
||||
|
@ -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);
|
||||
{
|
||||
solAssert(m_scopes[nullptr]->registerDeclaration(*declaration), "Unable to register global declaration.");
|
||||
}
|
||||
}
|
||||
|
||||
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit, ASTNode const* _currentScope)
|
||||
@ -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) };
|
||||
|
@ -2146,10 +2146,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())
|
||||
|
@ -297,7 +297,7 @@ ContractDefinition::ContractKind FunctionDefinition::inContractKind() const
|
||||
return contractDef->contractKind();
|
||||
}
|
||||
|
||||
shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
|
||||
FunctionTypePointer FunctionDefinition::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
{
|
||||
@ -338,6 +338,7 @@ shared_ptr<FunctionType> FunctionDefinition::functionType(bool _internal) const
|
||||
|
||||
TypePointer FunctionDefinition::type() const
|
||||
{
|
||||
solAssert(visibility() != Declaration::Visibility::External, "");
|
||||
return make_shared<FunctionType>(*this);
|
||||
}
|
||||
|
||||
@ -379,7 +380,7 @@ TypePointer EventDefinition::type() const
|
||||
return make_shared<FunctionType>(*this);
|
||||
}
|
||||
|
||||
std::shared_ptr<FunctionType> EventDefinition::functionType(bool _internal) const
|
||||
FunctionTypePointer EventDefinition::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
return make_shared<FunctionType>(*this);
|
||||
@ -484,7 +485,7 @@ TypePointer VariableDeclaration::type() const
|
||||
return annotation().type;
|
||||
}
|
||||
|
||||
shared_ptr<FunctionType> VariableDeclaration::functionType(bool _internal) const
|
||||
FunctionTypePointer VariableDeclaration::functionType(bool _internal) const
|
||||
{
|
||||
if (_internal)
|
||||
return {};
|
||||
|
@ -218,7 +218,7 @@ public:
|
||||
|
||||
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
|
||||
/// @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:
|
||||
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.
|
||||
/// @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;
|
||||
|
||||
@ -703,7 +703,7 @@ public:
|
||||
|
||||
/// @param _internal false indicates external interface is concerned, true indicates internal interface is concerned.
|
||||
/// @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;
|
||||
|
||||
@ -805,7 +805,7 @@ public:
|
||||
bool isAnonymous() const { return m_anonymous; }
|
||||
|
||||
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;
|
||||
|
||||
@ -831,6 +831,11 @@ public:
|
||||
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; }
|
||||
|
||||
private:
|
||||
|
@ -262,12 +262,20 @@ CompilerContext& CompilerContext::appendRevert()
|
||||
return *this << u256(0) << u256(0) << Instruction::REVERT;
|
||||
}
|
||||
|
||||
CompilerContext& CompilerContext::appendConditionalRevert()
|
||||
CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData)
|
||||
{
|
||||
*this << Instruction::ISZERO;
|
||||
eth::AssemblyItem afterTag = appendConditionalJump();
|
||||
appendRevert();
|
||||
*this << afterTag;
|
||||
if (_forwardReturnData && m_evmVersion.supportsReturndata())
|
||||
appendInlineAssembly(R"({
|
||||
if condition {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
})", {"condition"});
|
||||
else
|
||||
appendInlineAssembly(R"({
|
||||
if condition { revert(0, 0) }
|
||||
})", {"condition"});
|
||||
*this << Instruction::POP;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -156,8 +156,11 @@ public:
|
||||
CompilerContext& appendConditionalInvalid();
|
||||
/// Appends a REVERT(0, 0) call
|
||||
CompilerContext& appendRevert();
|
||||
/// Appends a conditional REVERT(0, 0) call
|
||||
CompilerContext& appendConditionalRevert();
|
||||
/// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the
|
||||
/// 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
|
||||
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; }
|
||||
/// Appends pushing of a new tag and @returns the new tag.
|
||||
|
@ -78,6 +78,20 @@ void CompilerUtils::toSizeAfterFreeMemoryPointer()
|
||||
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 _offset,
|
||||
Type const& _type,
|
||||
@ -691,6 +705,7 @@ void CompilerUtils::convertType(
|
||||
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
|
||||
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
|
||||
if (_asPartOfArgumentDecoding)
|
||||
// TODO: error message?
|
||||
m_context.appendConditionalRevert();
|
||||
else
|
||||
m_context.appendConditionalInvalid();
|
||||
|
@ -54,6 +54,13 @@ public:
|
||||
/// Stack post: <size> <mem_start>
|
||||
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.
|
||||
/// @param _offset offset in memory (or calldata)
|
||||
/// @param _type data type to load
|
||||
|
@ -128,6 +128,7 @@ void ContractCompiler::appendCallValueCheck()
|
||||
{
|
||||
// Throw if function is not payable but call contained ether.
|
||||
m_context << Instruction::CALLVALUE;
|
||||
// TODO: error message?
|
||||
m_context.appendConditionalRevert();
|
||||
}
|
||||
|
||||
@ -327,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
m_context << Instruction::STOP;
|
||||
}
|
||||
else
|
||||
// TODO: error message here?
|
||||
m_context.appendRevert();
|
||||
|
||||
for (auto const& it: interfaceFunctions)
|
||||
|
@ -610,7 +610,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
m_context << Instruction::CREATE;
|
||||
// Check if zero (out of stack or not enough balance).
|
||||
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())
|
||||
m_context << swapInstruction(1) << Instruction::POP;
|
||||
break;
|
||||
@ -672,8 +673,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
if (function.kind() == FunctionType::Kind::Transfer)
|
||||
{
|
||||
// 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.appendConditionalRevert();
|
||||
m_context.appendConditionalRevert(true);
|
||||
}
|
||||
break;
|
||||
case FunctionType::Kind::Selfdestruct:
|
||||
@ -682,8 +684,19 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
m_context << Instruction::SELFDESTRUCT;
|
||||
break;
|
||||
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;
|
||||
}
|
||||
case FunctionType::Kind::SHA3:
|
||||
{
|
||||
TypePointers argumentTypes;
|
||||
@ -902,16 +915,31 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
{
|
||||
arguments.front()->accept(*this);
|
||||
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
|
||||
m_context << Instruction::ISZERO << Instruction::ISZERO;
|
||||
auto success = m_context.appendConditionalJump();
|
||||
if (function.kind() == FunctionType::Kind::Assert)
|
||||
// condition was not met, flag an error
|
||||
m_context.appendInvalid();
|
||||
else if (arguments.size() > 1)
|
||||
utils().revertWithStringData(*arguments.at(1)->annotation().type);
|
||||
else
|
||||
m_context.appendRevert();
|
||||
// the success branch
|
||||
m_context << success;
|
||||
if (arguments.size() > 1)
|
||||
utils().popStackElement(*arguments.at(1)->annotation().type);
|
||||
break;
|
||||
}
|
||||
case FunctionType::Kind::ABIEncode:
|
||||
@ -1882,6 +1910,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
if (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::CallCode || funKind == FunctionType::Kind::DelegateCall)
|
||||
{
|
||||
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
|
||||
// TODO: error message?
|
||||
m_context.appendConditionalRevert();
|
||||
existenceChecked = true;
|
||||
}
|
||||
@ -1924,7 +1953,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
|
||||
{
|
||||
//Propagate error condition (if CALL pushes 0 on stack).
|
||||
m_context << Instruction::ISZERO;
|
||||
m_context.appendConditionalRevert();
|
||||
m_context.appendConditionalRevert(true);
|
||||
}
|
||||
|
||||
utils().popStackSlots(remainsSize);
|
||||
|
@ -157,14 +157,23 @@ BOOST_AUTO_TEST_CASE(location_test)
|
||||
}
|
||||
}
|
||||
)";
|
||||
shared_ptr<string const> n = make_shared<string>("");
|
||||
AssemblyItems items = compileContract(sourceCode);
|
||||
vector<SourceLocation> locations =
|
||||
vector<SourceLocation>(24, SourceLocation(2, 75, n)) +
|
||||
vector<SourceLocation>(32, SourceLocation(20, 72, n)) +
|
||||
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} +
|
||||
vector<SourceLocation>(2, SourceLocation(58, 67, n)) +
|
||||
vector<SourceLocation>(2, SourceLocation(20, 72, n));
|
||||
vector<SourceLocation>(24, SourceLocation(2, 75, make_shared<string>(""))) +
|
||||
vector<SourceLocation>(2, SourceLocation(20, 72, make_shared<string>(""))) +
|
||||
vector<SourceLocation>(1, SourceLocation(8, 17, make_shared<string>("--CODEGEN--"))) +
|
||||
vector<SourceLocation>(3, SourceLocation(5, 7, make_shared<string>("--CODEGEN--"))) +
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -111,7 +111,7 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
|
||||
BOOST_CHECK(contract["bytecode"].isString());
|
||||
BOOST_CHECK_EQUAL(
|
||||
dev::test::bytecodeSansMetadata(contract["bytecode"].asString()),
|
||||
"60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00"
|
||||
"6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00"
|
||||
);
|
||||
BOOST_CHECK(contract["runtimeBytecode"].isString());
|
||||
BOOST_CHECK_EQUAL(
|
||||
@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
|
||||
BOOST_CHECK(contract["gasEstimates"].isObject());
|
||||
BOOST_CHECK_EQUAL(
|
||||
dev::jsonCompactPrint(contract["gasEstimates"]),
|
||||
"{\"creation\":[61,10600],\"external\":{},\"internal\":{}}"
|
||||
"{\"creation\":[66,10600],\"external\":{},\"internal\":{}}"
|
||||
);
|
||||
BOOST_CHECK(contract["metadata"].isString());
|
||||
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_EQUAL(
|
||||
dev::test::bytecodeSansMetadata(contract["bytecode"].asString()),
|
||||
"60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00"
|
||||
"6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00"
|
||||
);
|
||||
BOOST_CHECK(contract["runtimeBytecode"].isString());
|
||||
BOOST_CHECK_EQUAL(
|
||||
@ -164,7 +164,7 @@ BOOST_AUTO_TEST_CASE(single_compilation)
|
||||
BOOST_CHECK(contract["gasEstimates"].isObject());
|
||||
BOOST_CHECK_EQUAL(
|
||||
dev::jsonCompactPrint(contract["gasEstimates"]),
|
||||
"{\"creation\":[61,10600],\"external\":{},\"internal\":{}}"
|
||||
"{\"creation\":[66,10600],\"external\":{},\"internal\":{}}"
|
||||
);
|
||||
BOOST_CHECK(contract["metadata"].isString());
|
||||
BOOST_CHECK(dev::test::isValidMetadata(contract["metadata"].asString()));
|
||||
|
@ -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");
|
||||
bytes const& creationBytecode = m_compiler.object("C").bytecode;
|
||||
bytes const& runtimeBytecode = m_compiler.runtimeObject("C").bytecode;
|
||||
BOOST_CHECK(creationBytecode.size() >= 120);
|
||||
BOOST_CHECK(creationBytecode.size() <= 150);
|
||||
BOOST_CHECK(creationBytecode.size() >= 130);
|
||||
BOOST_CHECK(creationBytecode.size() <= 160);
|
||||
BOOST_CHECK(runtimeBytecode.size() >= 50);
|
||||
BOOST_CHECK(runtimeBytecode.size() <= 70);
|
||||
}
|
||||
|
@ -10458,6 +10458,214 @@ BOOST_AUTO_TEST_CASE(revert)
|
||||
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)
|
||||
{
|
||||
// This code was causing negative stack height during code generation
|
||||
|
@ -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)
|
||||
{
|
||||
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 { 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.");
|
||||
}
|
||||
|
||||
@ -6493,7 +6509,7 @@ BOOST_AUTO_TEST_CASE(does_not_error_transfer_regular_function)
|
||||
CHECK_SUCCESS_NO_WARNINGS(text);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(returndatacopy_as_variable)
|
||||
BOOST_AUTO_TEST_CASE(returndatasize_as_variable)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract c { function f() public { uint returndatasize; assembly { returndatasize }}}
|
||||
|
@ -261,19 +261,24 @@ BOOST_AUTO_TEST_CASE(basic_compilation)
|
||||
BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString());
|
||||
BOOST_CHECK_EQUAL(
|
||||
dev::test::bytecodeSansMetadata(contract["evm"]["bytecode"]["object"].asString()),
|
||||
"60806040523415600e57600080fd5b603580601b6000396000f3006080604052600080fd00"
|
||||
"6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00"
|
||||
);
|
||||
BOOST_CHECK(contract["evm"]["assembly"].isString());
|
||||
BOOST_CHECK(contract["evm"]["assembly"].asString().find(
|
||||
" /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n jumpi(tag_1, iszero(callvalue))\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"
|
||||
" 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: 0xa165627a7a7230582") == 0);
|
||||
" /* \"fileA\":0:14 contract A { } */\n mstore(0x40, 0x80)\n "
|
||||
"callvalue\n /* \"--CODEGEN--\":8:17 */\n dup1\n "
|
||||
"/* \"--CODEGEN--\":5:7 */\n iszero\n tag_1\n jumpi\n "
|
||||
"/* \"--CODEGEN--\":30:31 */\n 0x0\n /* \"--CODEGEN--\":27:28 */\n "
|
||||
"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_EQUAL(
|
||||
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(dev::test::isValidMetadata(contract["metadata"].asString()));
|
||||
|
Loading…
Reference in New Issue
Block a user