mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8177 from ethereum/new2-2136
Implement `new` with CREATE2 and function call options
This commit is contained in:
commit
45caaf5ad8
@ -4,6 +4,8 @@ Language Features:
|
|||||||
* Allow accessing external functions via contract and interface names to obtain their selector.
|
* Allow accessing external functions via contract and interface names to obtain their selector.
|
||||||
* Inline Assembly: Support literals ``true`` and ``false``.
|
* Inline Assembly: Support literals ``true`` and ``false``.
|
||||||
* Allow interfaces to inherit from other interfaces
|
* Allow interfaces to inherit from other interfaces
|
||||||
|
* Allow gas and value to be set in external function calls using ``f{gas: 10000, value: 4 ether}()``.
|
||||||
|
* Allow specifying the ``salt`` for contract creations and thus the ``create2`` opcode using ``new C{salt: 0x1234, value: 1 ether}(arg1, arg2)``.
|
||||||
|
|
||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
|
@ -335,7 +335,7 @@ operations as long as there is enough gas passed on to it.
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
pragma solidity ^0.6.0;
|
pragma solidity >0.6.1 <0.7.0;
|
||||||
|
|
||||||
contract Test {
|
contract Test {
|
||||||
// This function is called for all messages sent to
|
// This function is called for all messages sent to
|
||||||
@ -382,7 +382,7 @@ operations as long as there is enough gas passed on to it.
|
|||||||
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
|
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
|
||||||
require(success);
|
require(success);
|
||||||
// results in test.x becoming == 1 and test.y becoming 0.
|
// results in test.x becoming == 1 and test.y becoming 0.
|
||||||
(success,) = address(test).call.value(1)(abi.encodeWithSignature("nonExistingFunction()"));
|
(success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));
|
||||||
require(success);
|
require(success);
|
||||||
// results in test.x becoming == 1 and test.y becoming 1.
|
// results in test.x becoming == 1 and test.y becoming 1.
|
||||||
|
|
||||||
|
@ -75,9 +75,10 @@ all function arguments have to be copied to memory.
|
|||||||
it is a message call as part of the overall transaction.
|
it is a message call as part of the overall transaction.
|
||||||
|
|
||||||
When calling functions of other contracts, you can specify the amount of Wei or
|
When calling functions of other contracts, you can specify the amount of Wei or
|
||||||
gas sent with the call with the special options ``.value()`` and ``.gas()``,
|
gas sent with the call with the special options ``{value: 10, gas: 10000}``.
|
||||||
respectively. Any Wei you send to the contract is added to the total balance
|
Note that it is discouraged to specify gas values explicitly, since the gas costs
|
||||||
of the contract:
|
of opcodes can change in the future. Any Wei you send to the contract is added
|
||||||
|
to the total balance of that contract:
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
@ -90,14 +91,14 @@ of the contract:
|
|||||||
contract Consumer {
|
contract Consumer {
|
||||||
InfoFeed feed;
|
InfoFeed feed;
|
||||||
function setFeed(InfoFeed addr) public { feed = addr; }
|
function setFeed(InfoFeed addr) public { feed = addr; }
|
||||||
function callFeed() public { feed.info.value(10).gas(800)(); }
|
function callFeed() public { feed.info{value: 10, gas: 800}(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
You need to use the modifier ``payable`` with the ``info`` function because
|
You need to use the modifier ``payable`` with the ``info`` function because
|
||||||
otherwise, the ``.value()`` option would not be available.
|
otherwise, the ``value`` option would not be available.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
Be careful that ``feed.info.value(10).gas(800)`` only locally sets the
|
Be careful that ``feed.info{value: 10, gas: 800}`` only locally sets the
|
||||||
``value`` and amount of ``gas`` sent with the function call, and the
|
``value`` and amount of ``gas`` sent with the function call, and the
|
||||||
parentheses at the end perform the actual call. So in this case, the
|
parentheses at the end perform the actual call. So in this case, the
|
||||||
function is not called and the ``value`` and ``gas`` settings are lost.
|
function is not called and the ``value`` and ``gas`` settings are lost.
|
||||||
@ -121,6 +122,11 @@ throws an exception or goes out of gas.
|
|||||||
external functions happen after any changes to state variables in your contract
|
external functions happen after any changes to state variables in your contract
|
||||||
so your contract is not vulnerable to a reentrancy exploit.
|
so your contract is not vulnerable to a reentrancy exploit.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Before Solidity 0.6.2, the recommended way to specify the value and gas
|
||||||
|
was to use ``f.value(x).gas(g)()``. This is still possible but deprecated
|
||||||
|
and will be removed with Solidity 0.7.0.
|
||||||
|
|
||||||
Named Calls and Anonymous Function Parameters
|
Named Calls and Anonymous Function Parameters
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
@ -196,17 +202,81 @@ is compiled so recursive creation-dependencies are not possible.
|
|||||||
|
|
||||||
function createAndEndowD(uint arg, uint amount) public payable {
|
function createAndEndowD(uint arg, uint amount) public payable {
|
||||||
// Send ether along with the creation
|
// Send ether along with the creation
|
||||||
D newD = (new D).value(amount)(arg);
|
D newD = new D{value: amount}(arg);
|
||||||
newD.x();
|
newD.x();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
As seen in the example, it is possible to send Ether while creating
|
As seen in the example, it is possible to send Ether while creating
|
||||||
an instance of ``D`` using the ``.value()`` option, but it is not possible
|
an instance of ``D`` using the ``value`` option, but it is not possible
|
||||||
to limit the amount of gas.
|
to limit the amount of gas.
|
||||||
If the creation fails (due to out-of-stack, not enough balance or other problems),
|
If the creation fails (due to out-of-stack, not enough balance or other problems),
|
||||||
an exception is thrown.
|
an exception is thrown.
|
||||||
|
|
||||||
|
Salted contract creations / create2
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
When creating a contract, the address of the contract is computed from
|
||||||
|
the address of the creating contract and a counter that is increased with
|
||||||
|
each contract creation.
|
||||||
|
|
||||||
|
If you specify the option ``salt`` (a bytes32 value), then contract creation will
|
||||||
|
use a different mechanism to come up with the address of the new contract:
|
||||||
|
|
||||||
|
It will compute the address from the address of the creating contract,
|
||||||
|
the given salt value, the (creation) bytecode of the created contract and the constructor
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
In particular, the counter ("nonce") is not used. This allows for more flexibility
|
||||||
|
in creating contracts: You are able to derive the address of the
|
||||||
|
new contract before it is created. Furthermore, you can rely on this address
|
||||||
|
also in case the creating
|
||||||
|
contracts creates other contracts in the meantime.
|
||||||
|
|
||||||
|
The main use-case here is contracts that act as judges for off-chain interactions,
|
||||||
|
which only need to be created if there is a dispute.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pragma solidity >0.6.1 <0.7.0;
|
||||||
|
|
||||||
|
contract D {
|
||||||
|
uint public x;
|
||||||
|
constructor(uint a) public {
|
||||||
|
x = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
function createDSalted(bytes32 salt, uint arg) public {
|
||||||
|
/// This complicated expression just tells you how the address
|
||||||
|
/// can be pre-computed. It is just there for illustration.
|
||||||
|
/// You actually only need ``new D{salt: salt}(arg)``.
|
||||||
|
address predictedAddress = address(bytes20(keccak256(abi.encodePacked(
|
||||||
|
byte(0xff),
|
||||||
|
address(this),
|
||||||
|
salt,
|
||||||
|
keccak256(abi.encodePacked(
|
||||||
|
type(D).creationCode,
|
||||||
|
arg
|
||||||
|
))
|
||||||
|
))));
|
||||||
|
|
||||||
|
D d = new D{salt: salt}(arg);
|
||||||
|
require(address(d) == predictedAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
There are some peculiarities in relation to salted creation. A contract can be
|
||||||
|
re-created at the same address after having been destroyed. Yet, it is possible
|
||||||
|
for that newly created contract to have a different deployed bytecode even
|
||||||
|
though the creation bytecode has been the same (which is a requirement because
|
||||||
|
otherwise the address would change). This is due to the fact that the compiler
|
||||||
|
can query external state that might have changed between the two creations
|
||||||
|
and incorporate that into the deployed bytecode before it is stored.
|
||||||
|
|
||||||
|
|
||||||
Order of Evaluation of Expressions
|
Order of Evaluation of Expressions
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ as it uses ``call`` which forwards all remaining gas by default:
|
|||||||
mapping(address => uint) shares;
|
mapping(address => uint) shares;
|
||||||
/// Withdraw your share.
|
/// Withdraw your share.
|
||||||
function withdraw() public {
|
function withdraw() public {
|
||||||
(bool success,) = msg.sender.call.value(shares[msg.sender])("");
|
(bool success,) = msg.sender.call{value: shares[msg.sender]}("");
|
||||||
if (success)
|
if (success)
|
||||||
shares[msg.sender] = 0;
|
shares[msg.sender] = 0;
|
||||||
}
|
}
|
||||||
@ -149,7 +149,7 @@ Sending and Receiving Ether
|
|||||||
(for example in the "details" section in Remix).
|
(for example in the "details" section in Remix).
|
||||||
|
|
||||||
- There is a way to forward more gas to the receiving contract using
|
- There is a way to forward more gas to the receiving contract using
|
||||||
``addr.call.value(x)("")``. This is essentially the same as ``addr.transfer(x)``,
|
``addr.call{value: x}("")``. This is essentially the same as ``addr.transfer(x)``,
|
||||||
only that it forwards all remaining gas and opens up the ability for the
|
only that it forwards all remaining gas and opens up the ability for the
|
||||||
recipient to perform more expensive actions (and it returns a failure code
|
recipient to perform more expensive actions (and it returns a failure code
|
||||||
instead of automatically propagating the error). This might include calling back
|
instead of automatically propagating the error). This might include calling back
|
||||||
|
@ -276,17 +276,17 @@ Example::
|
|||||||
arbitrary arguments and would also handle a first argument of type
|
arbitrary arguments and would also handle a first argument of type
|
||||||
``bytes4`` differently. These edge cases were removed in version 0.5.0.
|
``bytes4`` differently. These edge cases were removed in version 0.5.0.
|
||||||
|
|
||||||
It is possible to adjust the supplied gas with the ``.gas()`` modifier::
|
It is possible to adjust the supplied gas with the ``gas`` modifier::
|
||||||
|
|
||||||
address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));
|
address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));
|
||||||
|
|
||||||
Similarly, the supplied Ether value can be controlled too::
|
Similarly, the supplied Ether value can be controlled too::
|
||||||
|
|
||||||
address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
|
address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
|
||||||
|
|
||||||
Lastly, these modifiers can be combined. Their order does not matter::
|
Lastly, these modifiers can be combined. Their order does not matter::
|
||||||
|
|
||||||
address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
|
address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
|
||||||
|
|
||||||
In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used.
|
In a similar way, the function ``delegatecall`` can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, ...) are taken from the current contract. The purpose of ``delegatecall`` is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used.
|
||||||
|
|
||||||
@ -297,7 +297,8 @@ Since byzantium ``staticcall`` can be used as well. This is basically the same a
|
|||||||
|
|
||||||
All three functions ``call``, ``delegatecall`` and ``staticcall`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
|
All three functions ``call``, ``delegatecall`` and ``staticcall`` are very low-level functions and should only be used as a *last resort* as they break the type-safety of Solidity.
|
||||||
|
|
||||||
The ``.gas()`` option is available on all three methods, while the ``.value()`` option is not supported for ``delegatecall``.
|
The ``gas`` option is available on all three methods, while the ``value`` option is not
|
||||||
|
supported for ``delegatecall``.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
All contracts can be converted to ``address`` type, so it is possible to query the balance of the
|
All contracts can be converted to ``address`` type, so it is possible to query the balance of the
|
||||||
@ -635,8 +636,12 @@ External (or public) functions have the following members:
|
|||||||
|
|
||||||
* ``.address`` returns the address of the contract of the function.
|
* ``.address`` returns the address of the contract of the function.
|
||||||
* ``.selector`` returns the :ref:`ABI function selector <abi_function_selector>`
|
* ``.selector`` returns the :ref:`ABI function selector <abi_function_selector>`
|
||||||
* ``.gas(uint)`` returns a callable function object which, when called, will send the specified amount of gas to the target function. See :ref:`External Function Calls <external-function-calls>` for more information.
|
* ``.gas(uint)`` returns a callable function object which, when called, will send
|
||||||
* ``.value(uint)`` returns a callable function object which, when called, will send the specified amount of wei to the target function. See :ref:`External Function Calls <external-function-calls>` for more information.
|
the specified amount of gas to the target function. Deprecated - use ``{gas: ...}`` instead.
|
||||||
|
See :ref:`External Function Calls <external-function-calls>` for more information.
|
||||||
|
* ``.value(uint)`` returns a callable function object which, when called, will
|
||||||
|
send the specified amount of wei to the target function. Deprecated - use ``{value: ...}`` instead.
|
||||||
|
See :ref:`External Function Calls <external-function-calls>` for more information.
|
||||||
|
|
||||||
Example that shows how to use the members::
|
Example that shows how to use the members::
|
||||||
|
|
||||||
@ -651,6 +656,8 @@ Example that shows how to use the members::
|
|||||||
|
|
||||||
function g() public {
|
function g() public {
|
||||||
this.f.gas(10).value(800)();
|
this.f.gas(10).value(800)();
|
||||||
|
// New syntax:
|
||||||
|
// this.f{gas: 10, value: 800}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2224,6 +2224,124 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions)
|
||||||
|
{
|
||||||
|
solAssert(_functionCallOptions.options().size() == _functionCallOptions.names().size(), "Lengths of name & value arrays differ!");
|
||||||
|
|
||||||
|
_functionCallOptions.expression().accept(*this);
|
||||||
|
|
||||||
|
auto expressionFunctionType = dynamic_cast<FunctionType const*>(type(_functionCallOptions.expression()));
|
||||||
|
if (!expressionFunctionType)
|
||||||
|
{
|
||||||
|
m_errorReporter.fatalTypeError(_functionCallOptions.location(), "Expected callable expression before call options.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setSalt = false;
|
||||||
|
bool setValue = false;
|
||||||
|
bool setGas = false;
|
||||||
|
|
||||||
|
FunctionType::Kind kind = expressionFunctionType->kind();
|
||||||
|
if (
|
||||||
|
kind != FunctionType::Kind::Creation &&
|
||||||
|
kind != FunctionType::Kind::External &&
|
||||||
|
kind != FunctionType::Kind::BareCall &&
|
||||||
|
kind != FunctionType::Kind::BareCallCode &&
|
||||||
|
kind != FunctionType::Kind::BareDelegateCall &&
|
||||||
|
kind != FunctionType::Kind::BareStaticCall
|
||||||
|
)
|
||||||
|
{
|
||||||
|
m_errorReporter.fatalTypeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Function call options can only be set on external function calls or contract creations."
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto setCheckOption = [&](bool& _option, string const&& _name, bool _alreadySet = false)
|
||||||
|
{
|
||||||
|
if (_option || _alreadySet)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
_alreadySet ?
|
||||||
|
"Option \"" + std::move(_name) + "\" has already been set." :
|
||||||
|
"Duplicate option \"" + std::move(_name) + "\"."
|
||||||
|
);
|
||||||
|
|
||||||
|
_option = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _functionCallOptions.names().size(); ++i)
|
||||||
|
{
|
||||||
|
string const& name = *(_functionCallOptions.names()[i]);
|
||||||
|
if (name == "salt")
|
||||||
|
{
|
||||||
|
if (kind == FunctionType::Kind::Creation)
|
||||||
|
{
|
||||||
|
setCheckOption(setSalt, "salt", expressionFunctionType->saltSet());
|
||||||
|
expectType(*_functionCallOptions.options()[i], *TypeProvider::fixedBytes(32));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Function call option \"salt\" can only be used with \"new\"."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (name == "value")
|
||||||
|
{
|
||||||
|
if (kind == FunctionType::Kind::BareDelegateCall)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Cannot set option \"value\" for delegatecall."
|
||||||
|
);
|
||||||
|
else if (kind == FunctionType::Kind::BareStaticCall)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Cannot set option \"value\" for staticcall."
|
||||||
|
);
|
||||||
|
else if (!expressionFunctionType->isPayable())
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Cannot set option \"value\" on a non-payable function type."
|
||||||
|
);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256());
|
||||||
|
|
||||||
|
setCheckOption(setValue, "value", expressionFunctionType->valueSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "gas")
|
||||||
|
{
|
||||||
|
if (kind == FunctionType::Kind::Creation)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Function call option \"gas\" cannot be used with \"new\"."
|
||||||
|
);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256());
|
||||||
|
|
||||||
|
setCheckOption(setGas, "gas", expressionFunctionType->gasSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Unknown call option \"" + name + "\". Valid options are \"salt\", \"value\" and \"gas\"."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setSalt && !m_evmVersion.hasCreate2())
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
_functionCallOptions.location(),
|
||||||
|
"Unsupported call option \"salt\" (requires Constantinople-compatible VMs)."
|
||||||
|
);
|
||||||
|
|
||||||
|
_functionCallOptions.annotation().type = expressionFunctionType->copyAndSetCallOptions(setGas, setValue, setSalt);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void TypeChecker::endVisit(NewExpression const& _newExpression)
|
void TypeChecker::endVisit(NewExpression const& _newExpression)
|
||||||
{
|
{
|
||||||
TypePointer type = _newExpression.typeName().annotation().type;
|
TypePointer type = _newExpression.typeName().annotation().type;
|
||||||
|
@ -135,6 +135,7 @@ private:
|
|||||||
void endVisit(BinaryOperation const& _operation) override;
|
void endVisit(BinaryOperation const& _operation) override;
|
||||||
bool visit(UnaryOperation const& _operation) override;
|
bool visit(UnaryOperation const& _operation) override;
|
||||||
bool visit(FunctionCall const& _functionCall) override;
|
bool visit(FunctionCall const& _functionCall) override;
|
||||||
|
bool visit(FunctionCallOptions const& _functionCallOptions) override;
|
||||||
void endVisit(NewExpression const& _newExpression) override;
|
void endVisit(NewExpression const& _newExpression) override;
|
||||||
bool visit(MemberAccess const& _memberAccess) override;
|
bool visit(MemberAccess const& _memberAccess) override;
|
||||||
bool visit(IndexAccess const& _indexAccess) override;
|
bool visit(IndexAccess const& _indexAccess) override;
|
||||||
|
@ -1761,6 +1761,35 @@ private:
|
|||||||
std::vector<ASTPointer<ASTString>> m_names;
|
std::vector<ASTPointer<ASTString>> m_names;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expression that annotates a function call / a new expression with extra
|
||||||
|
* options like gas, value, salt: new SomeContract{salt=123}(params)
|
||||||
|
*/
|
||||||
|
class FunctionCallOptions: public Expression
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FunctionCallOptions(
|
||||||
|
int64_t _id,
|
||||||
|
SourceLocation const& _location,
|
||||||
|
ASTPointer<Expression> const& _expression,
|
||||||
|
std::vector<ASTPointer<Expression>> const& _options,
|
||||||
|
std::vector<ASTPointer<ASTString>> const& _names
|
||||||
|
):
|
||||||
|
Expression(_id, _location), m_expression(_expression), m_options(_options), m_names(_names) {}
|
||||||
|
void accept(ASTVisitor& _visitor) override;
|
||||||
|
void accept(ASTConstVisitor& _visitor) const override;
|
||||||
|
|
||||||
|
Expression const& expression() const { return *m_expression; }
|
||||||
|
std::vector<ASTPointer<Expression const>> options() const { return {m_options.begin(), m_options.end()}; }
|
||||||
|
std::vector<ASTPointer<ASTString>> const& names() const { return m_names; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ASTPointer<Expression> m_expression;
|
||||||
|
std::vector<ASTPointer<Expression>> m_options;
|
||||||
|
std::vector<ASTPointer<ASTString>> m_names;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expression that creates a new contract or memory-array,
|
* Expression that creates a new contract or memory-array,
|
||||||
* e.g. the "new SomeContract" part in "new SomeContract(1, 2)".
|
* e.g. the "new SomeContract" part in "new SomeContract(1, 2)".
|
||||||
|
@ -724,6 +724,23 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ASTJsonConverter::visit(FunctionCallOptions const& _node)
|
||||||
|
{
|
||||||
|
Json::Value names(Json::arrayValue);
|
||||||
|
for (auto const& name: _node.names())
|
||||||
|
names.append(Json::Value(*name));
|
||||||
|
|
||||||
|
std::vector<pair<string, Json::Value>> attributes = {
|
||||||
|
make_pair("expression", toJson(_node.expression())),
|
||||||
|
make_pair("names", std::move(names)),
|
||||||
|
make_pair("options", toJson(_node.options())),
|
||||||
|
};
|
||||||
|
appendExpressionAttributes(attributes, _node.annotation());
|
||||||
|
|
||||||
|
setJsonNode(_node, "FunctionCallOptions", std::move(attributes));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool ASTJsonConverter::visit(NewExpression const& _node)
|
bool ASTJsonConverter::visit(NewExpression const& _node)
|
||||||
{
|
{
|
||||||
std::vector<pair<string, Json::Value>> attributes = {
|
std::vector<pair<string, Json::Value>> attributes = {
|
||||||
|
@ -111,6 +111,7 @@ public:
|
|||||||
bool visit(UnaryOperation const& _node) override;
|
bool visit(UnaryOperation const& _node) override;
|
||||||
bool visit(BinaryOperation const& _node) override;
|
bool visit(BinaryOperation const& _node) override;
|
||||||
bool visit(FunctionCall const& _node) override;
|
bool visit(FunctionCall const& _node) override;
|
||||||
|
bool visit(FunctionCallOptions const& _node) override;
|
||||||
bool visit(NewExpression const& _node) override;
|
bool visit(NewExpression const& _node) override;
|
||||||
bool visit(MemberAccess const& _node) override;
|
bool visit(MemberAccess const& _node) override;
|
||||||
bool visit(IndexAccess const& _node) override;
|
bool visit(IndexAccess const& _node) override;
|
||||||
|
@ -192,6 +192,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
|
|||||||
return createBinaryOperation(_json);
|
return createBinaryOperation(_json);
|
||||||
if (nodeType == "FunctionCall")
|
if (nodeType == "FunctionCall")
|
||||||
return createFunctionCall(_json);
|
return createFunctionCall(_json);
|
||||||
|
if (nodeType == "FunctionCallOptions")
|
||||||
|
return createFunctionCallOptions(_json);
|
||||||
if (nodeType == "NewExpression")
|
if (nodeType == "NewExpression")
|
||||||
return createNewExpression(_json);
|
return createNewExpression(_json);
|
||||||
if (nodeType == "MemberAccess")
|
if (nodeType == "MemberAccess")
|
||||||
@ -752,6 +754,26 @@ ASTPointer<FunctionCall> ASTJsonImporter::createFunctionCall(Json::Value const&
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ASTPointer<FunctionCallOptions> ASTJsonImporter::createFunctionCallOptions(Json::Value const& _node)
|
||||||
|
{
|
||||||
|
std::vector<ASTPointer<Expression>> options;
|
||||||
|
for (auto& option: member(_node, "options"))
|
||||||
|
options.push_back(convertJsonToASTNode<Expression>(option));
|
||||||
|
std::vector<ASTPointer<ASTString>> names;
|
||||||
|
for (auto& name: member(_node, "names"))
|
||||||
|
{
|
||||||
|
astAssert(name.isString(), "Expected 'names' members to be strings!");
|
||||||
|
names.push_back(make_shared<ASTString>(name.asString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return createASTNode<FunctionCallOptions>(
|
||||||
|
_node,
|
||||||
|
convertJsonToASTNode<Expression>(member(_node, "expression")),
|
||||||
|
options,
|
||||||
|
names
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ASTPointer<NewExpression> ASTJsonImporter::createNewExpression(Json::Value const& _node)
|
ASTPointer<NewExpression> ASTJsonImporter::createNewExpression(Json::Value const& _node)
|
||||||
{
|
{
|
||||||
return createASTNode<NewExpression>(
|
return createASTNode<NewExpression>(
|
||||||
|
@ -111,6 +111,7 @@ private:
|
|||||||
ASTPointer<UnaryOperation> createUnaryOperation(Json::Value const& _node);
|
ASTPointer<UnaryOperation> createUnaryOperation(Json::Value const& _node);
|
||||||
ASTPointer<BinaryOperation> createBinaryOperation(Json::Value const& _node);
|
ASTPointer<BinaryOperation> createBinaryOperation(Json::Value const& _node);
|
||||||
ASTPointer<FunctionCall> createFunctionCall(Json::Value const& _node);
|
ASTPointer<FunctionCall> createFunctionCall(Json::Value const& _node);
|
||||||
|
ASTPointer<FunctionCallOptions> createFunctionCallOptions(Json::Value const& _node);
|
||||||
ASTPointer<NewExpression> createNewExpression(Json::Value const& _node);
|
ASTPointer<NewExpression> createNewExpression(Json::Value const& _node);
|
||||||
ASTPointer<MemberAccess> createMemberAccess(Json::Value const& _node);
|
ASTPointer<MemberAccess> createMemberAccess(Json::Value const& _node);
|
||||||
ASTPointer<IndexAccess> createIndexAccess(Json::Value const& _node);
|
ASTPointer<IndexAccess> createIndexAccess(Json::Value const& _node);
|
||||||
|
@ -84,6 +84,7 @@ public:
|
|||||||
virtual bool visit(UnaryOperation& _node) { return visitNode(_node); }
|
virtual bool visit(UnaryOperation& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(BinaryOperation& _node) { return visitNode(_node); }
|
virtual bool visit(BinaryOperation& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(FunctionCall& _node) { return visitNode(_node); }
|
virtual bool visit(FunctionCall& _node) { return visitNode(_node); }
|
||||||
|
virtual bool visit(FunctionCallOptions& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(NewExpression& _node) { return visitNode(_node); }
|
virtual bool visit(NewExpression& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
|
virtual bool visit(MemberAccess& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(IndexAccess& _node) { return visitNode(_node); }
|
virtual bool visit(IndexAccess& _node) { return visitNode(_node); }
|
||||||
@ -134,6 +135,7 @@ public:
|
|||||||
virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); }
|
virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); }
|
virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); }
|
virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); }
|
||||||
|
virtual void endVisit(FunctionCallOptions& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(NewExpression& _node) { endVisitNode(_node); }
|
virtual void endVisit(NewExpression& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
|
virtual void endVisit(MemberAccess& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); }
|
virtual void endVisit(IndexAccess& _node) { endVisitNode(_node); }
|
||||||
@ -197,6 +199,7 @@ public:
|
|||||||
virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); }
|
virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); }
|
virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(FunctionCall const& _node) { return visitNode(_node); }
|
virtual bool visit(FunctionCall const& _node) { return visitNode(_node); }
|
||||||
|
virtual bool visit(FunctionCallOptions const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(NewExpression const& _node) { return visitNode(_node); }
|
virtual bool visit(NewExpression const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); }
|
virtual bool visit(MemberAccess const& _node) { return visitNode(_node); }
|
||||||
virtual bool visit(IndexAccess const& _node) { return visitNode(_node); }
|
virtual bool visit(IndexAccess const& _node) { return visitNode(_node); }
|
||||||
@ -247,6 +250,7 @@ public:
|
|||||||
virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); }
|
virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); }
|
virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); }
|
virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); }
|
||||||
|
virtual void endVisit(FunctionCallOptions const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); }
|
virtual void endVisit(NewExpression const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
|
virtual void endVisit(MemberAccess const& _node) { endVisitNode(_node); }
|
||||||
virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); }
|
virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); }
|
||||||
|
@ -781,6 +781,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const
|
|||||||
_visitor.endVisit(*this);
|
_visitor.endVisit(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FunctionCallOptions::accept(ASTVisitor& _visitor)
|
||||||
|
{
|
||||||
|
if (_visitor.visit(*this))
|
||||||
|
{
|
||||||
|
m_expression->accept(_visitor);
|
||||||
|
listAccept(m_options, _visitor);
|
||||||
|
}
|
||||||
|
_visitor.endVisit(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FunctionCallOptions::accept(ASTConstVisitor& _visitor) const
|
||||||
|
{
|
||||||
|
if (_visitor.visit(*this))
|
||||||
|
{
|
||||||
|
m_expression->accept(_visitor);
|
||||||
|
listAccept(m_options, _visitor);
|
||||||
|
}
|
||||||
|
_visitor.endVisit(*this);
|
||||||
|
}
|
||||||
|
|
||||||
void NewExpression::accept(ASTVisitor& _visitor)
|
void NewExpression::accept(ASTVisitor& _visitor)
|
||||||
{
|
{
|
||||||
if (_visitor.visit(*this))
|
if (_visitor.visit(*this))
|
||||||
|
@ -459,7 +459,8 @@ FunctionType const* TypeProvider::function(
|
|||||||
Declaration const* _declaration,
|
Declaration const* _declaration,
|
||||||
bool _gasSet,
|
bool _gasSet,
|
||||||
bool _valueSet,
|
bool _valueSet,
|
||||||
bool _bound
|
bool _bound,
|
||||||
|
bool _saltSet
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return createAndGet<FunctionType>(
|
return createAndGet<FunctionType>(
|
||||||
@ -473,7 +474,8 @@ FunctionType const* TypeProvider::function(
|
|||||||
_declaration,
|
_declaration,
|
||||||
_gasSet,
|
_gasSet,
|
||||||
_valueSet,
|
_valueSet,
|
||||||
_bound
|
_bound,
|
||||||
|
_saltSet
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,8 @@ public:
|
|||||||
Declaration const* _declaration = nullptr,
|
Declaration const* _declaration = nullptr,
|
||||||
bool _gasSet = false,
|
bool _gasSet = false,
|
||||||
bool _valueSet = false,
|
bool _valueSet = false,
|
||||||
bool _bound = false
|
bool _bound = false,
|
||||||
|
bool _saltSet = false
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does
|
/// Auto-detect the proper type for a literal. @returns an empty pointer if the literal does
|
||||||
|
@ -2741,6 +2741,8 @@ string FunctionType::richIdentifier() const
|
|||||||
id += "gas";
|
id += "gas";
|
||||||
if (m_valueSet)
|
if (m_valueSet)
|
||||||
id += "value";
|
id += "value";
|
||||||
|
if (m_saltSet)
|
||||||
|
id += "salt";
|
||||||
if (bound())
|
if (bound())
|
||||||
id += "bound_to" + identifierList(selfType());
|
id += "bound_to" + identifierList(selfType());
|
||||||
return id;
|
return id;
|
||||||
@ -2918,6 +2920,8 @@ unsigned FunctionType::sizeOnStack() const
|
|||||||
size++;
|
size++;
|
||||||
if (m_valueSet)
|
if (m_valueSet)
|
||||||
size++;
|
size++;
|
||||||
|
if (m_saltSet)
|
||||||
|
size++;
|
||||||
if (bound())
|
if (bound())
|
||||||
size += m_parameterTypes.front()->sizeOnStack();
|
size += m_parameterTypes.front()->sizeOnStack();
|
||||||
return size;
|
return size;
|
||||||
@ -3001,7 +3005,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
|||||||
"value",
|
"value",
|
||||||
TypeProvider::function(
|
TypeProvider::function(
|
||||||
parseElementaryTypeVector({"uint"}),
|
parseElementaryTypeVector({"uint"}),
|
||||||
TypePointers{copyAndSetGasOrValue(false, true)},
|
TypePointers{copyAndSetCallOptions(false, true, false)},
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
Kind::SetValue,
|
Kind::SetValue,
|
||||||
@ -3009,7 +3013,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
|||||||
StateMutability::Pure,
|
StateMutability::Pure,
|
||||||
nullptr,
|
nullptr,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet
|
m_valueSet,
|
||||||
|
m_saltSet
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3018,7 +3023,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
|||||||
"gas",
|
"gas",
|
||||||
TypeProvider::function(
|
TypeProvider::function(
|
||||||
parseElementaryTypeVector({"uint"}),
|
parseElementaryTypeVector({"uint"}),
|
||||||
TypePointers{copyAndSetGasOrValue(true, false)},
|
TypePointers{copyAndSetCallOptions(true, false, false)},
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
strings(1, ""),
|
strings(1, ""),
|
||||||
Kind::SetGas,
|
Kind::SetGas,
|
||||||
@ -3026,7 +3031,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
|
|||||||
StateMutability::Pure,
|
StateMutability::Pure,
|
||||||
nullptr,
|
nullptr,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet
|
m_valueSet,
|
||||||
|
m_saltSet
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return members;
|
return members;
|
||||||
@ -3149,7 +3155,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
//@todo this is ugly, but cannot be prevented right now
|
//@todo this is ugly, but cannot be prevented right now
|
||||||
if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet)
|
if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet || m_saltSet != _other.m_saltSet)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (bound() != _other.bound())
|
if (bound() != _other.bound())
|
||||||
@ -3250,7 +3256,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
|
|||||||
return pointers;
|
return pointers;
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) const
|
TypePointer FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const
|
||||||
{
|
{
|
||||||
solAssert(m_kind != Kind::Declaration, "");
|
solAssert(m_kind != Kind::Declaration, "");
|
||||||
return TypeProvider::function(
|
return TypeProvider::function(
|
||||||
@ -3264,6 +3270,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
|
|||||||
m_declaration,
|
m_declaration,
|
||||||
m_gasSet || _setGas,
|
m_gasSet || _setGas,
|
||||||
m_valueSet || _setValue,
|
m_valueSet || _setValue,
|
||||||
|
m_saltSet || _setSalt,
|
||||||
m_bound
|
m_bound
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3304,6 +3311,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun
|
|||||||
m_declaration,
|
m_declaration,
|
||||||
m_gasSet,
|
m_gasSet,
|
||||||
m_valueSet,
|
m_valueSet,
|
||||||
|
m_saltSet,
|
||||||
_bound
|
_bound
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1055,7 +1055,7 @@ public:
|
|||||||
/// Refers to a function declaration without calling context
|
/// Refers to a function declaration without calling context
|
||||||
/// (i.e. when accessed directly via the name of the containing contract).
|
/// (i.e. when accessed directly via the name of the containing contract).
|
||||||
/// Cannot be called.
|
/// Cannot be called.
|
||||||
Declaration
|
Declaration,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Creates the type of a function.
|
/// Creates the type of a function.
|
||||||
@ -1098,6 +1098,7 @@ public:
|
|||||||
Declaration const* _declaration = nullptr,
|
Declaration const* _declaration = nullptr,
|
||||||
bool _gasSet = false,
|
bool _gasSet = false,
|
||||||
bool _valueSet = false,
|
bool _valueSet = false,
|
||||||
|
bool _saltSet = false,
|
||||||
bool _bound = false
|
bool _bound = false
|
||||||
):
|
):
|
||||||
m_parameterTypes(_parameterTypes),
|
m_parameterTypes(_parameterTypes),
|
||||||
@ -1110,7 +1111,8 @@ public:
|
|||||||
m_gasSet(_gasSet),
|
m_gasSet(_gasSet),
|
||||||
m_valueSet(_valueSet),
|
m_valueSet(_valueSet),
|
||||||
m_bound(_bound),
|
m_bound(_bound),
|
||||||
m_declaration(_declaration)
|
m_declaration(_declaration),
|
||||||
|
m_saltSet(_saltSet)
|
||||||
{
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
m_parameterNames.size() == m_parameterTypes.size(),
|
m_parameterNames.size() == m_parameterTypes.size(),
|
||||||
@ -1235,11 +1237,12 @@ public:
|
|||||||
|
|
||||||
bool gasSet() const { return m_gasSet; }
|
bool gasSet() const { return m_gasSet; }
|
||||||
bool valueSet() const { return m_valueSet; }
|
bool valueSet() const { return m_valueSet; }
|
||||||
|
bool saltSet() const { return m_saltSet; }
|
||||||
bool bound() const { return m_bound; }
|
bool bound() const { return m_bound; }
|
||||||
|
|
||||||
/// @returns a copy of this type, where gas or value are set manually. This will never set one
|
/// @returns a copy of this type, where gas or value are set manually. This will never set one
|
||||||
/// of the parameters to false.
|
/// of the parameters to false.
|
||||||
TypePointer copyAndSetGasOrValue(bool _setGas, bool _setValue) const;
|
TypePointer copyAndSetCallOptions(bool _setGas, bool _setValue, bool _setSalt) const;
|
||||||
|
|
||||||
/// @returns a copy of this function type where the location of reference types is changed
|
/// @returns a copy of this function type where the location of reference types is changed
|
||||||
/// from CallData to Memory. This is the type that would be used when the function is
|
/// from CallData to Memory. This is the type that would be used when the function is
|
||||||
@ -1264,6 +1267,7 @@ private:
|
|||||||
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
|
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
|
||||||
bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn)
|
bool const m_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn)
|
||||||
Declaration const* m_declaration = nullptr;
|
Declaration const* m_declaration = nullptr;
|
||||||
|
bool m_saltSet = false; ///< true iff the salt value to be used is on the stack
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -620,6 +620,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
case FunctionType::Kind::Creation:
|
case FunctionType::Kind::Creation:
|
||||||
{
|
{
|
||||||
_functionCall.expression().accept(*this);
|
_functionCall.expression().accept(*this);
|
||||||
|
// Stack: [salt], [value]
|
||||||
|
|
||||||
solAssert(!function.gasSet(), "Gas limit set for contract creation.");
|
solAssert(!function.gasSet(), "Gas limit set for contract creation.");
|
||||||
solAssert(function.returnParameterTypes().size() == 1, "");
|
solAssert(function.returnParameterTypes().size() == 1, "");
|
||||||
TypePointers argumentTypes;
|
TypePointers argumentTypes;
|
||||||
@ -633,16 +635,37 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
utils().fetchFreeMemoryPointer();
|
utils().fetchFreeMemoryPointer();
|
||||||
utils().copyContractCodeToMemory(*contract, true);
|
utils().copyContractCodeToMemory(*contract, true);
|
||||||
utils().abiEncode(argumentTypes, function.parameterTypes());
|
utils().abiEncode(argumentTypes, function.parameterTypes());
|
||||||
// now on stack: memory_end_ptr
|
// now on stack: [salt], [value], memory_end_ptr
|
||||||
// need: size, offset, endowment
|
// need: [salt], size, offset, value
|
||||||
|
|
||||||
|
if (function.saltSet())
|
||||||
|
{
|
||||||
|
m_context << dupInstruction(2 + (function.valueSet() ? 1 : 0));
|
||||||
|
m_context << Instruction::SWAP1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now: [salt], [value], [salt], memory_end_ptr
|
||||||
utils().toSizeAfterFreeMemoryPointer();
|
utils().toSizeAfterFreeMemoryPointer();
|
||||||
|
|
||||||
|
// now: [salt], [value], [salt], size, offset
|
||||||
if (function.valueSet())
|
if (function.valueSet())
|
||||||
m_context << dupInstruction(3);
|
m_context << dupInstruction(3 + (function.saltSet() ? 1 : 0));
|
||||||
else
|
else
|
||||||
m_context << u256(0);
|
m_context << u256(0);
|
||||||
m_context << Instruction::CREATE;
|
|
||||||
|
// now: [salt], [value], [salt], size, offset, value
|
||||||
|
if (function.saltSet())
|
||||||
|
m_context << Instruction::CREATE2;
|
||||||
|
else
|
||||||
|
m_context << Instruction::CREATE;
|
||||||
|
|
||||||
|
// now: [salt], [value], address
|
||||||
|
|
||||||
if (function.valueSet())
|
if (function.valueSet())
|
||||||
m_context << swapInstruction(1) << Instruction::POP;
|
m_context << swapInstruction(1) << Instruction::POP;
|
||||||
|
if (function.saltSet())
|
||||||
|
m_context << swapInstruction(1) << Instruction::POP;
|
||||||
|
|
||||||
// Check if zero (reverted)
|
// Check if zero (reverted)
|
||||||
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
m_context << Instruction::DUP1 << Instruction::ISZERO;
|
||||||
if (_functionCall.annotation().tryCall)
|
if (_functionCall.annotation().tryCall)
|
||||||
@ -1192,6 +1215,49 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ExpressionCompiler::visit(FunctionCallOptions const& _functionCallOptions)
|
||||||
|
{
|
||||||
|
_functionCallOptions.expression().accept(*this);
|
||||||
|
|
||||||
|
// Desired Stack: [salt], [gas], [value]
|
||||||
|
enum Option { Salt, Gas, Value };
|
||||||
|
|
||||||
|
vector<Option> presentOptions;
|
||||||
|
FunctionType const& funType = dynamic_cast<FunctionType const&>(
|
||||||
|
*_functionCallOptions.expression().annotation().type
|
||||||
|
);
|
||||||
|
if (funType.saltSet()) presentOptions.emplace_back(Salt);
|
||||||
|
if (funType.gasSet()) presentOptions.emplace_back(Gas);
|
||||||
|
if (funType.valueSet()) presentOptions.emplace_back(Value);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _functionCallOptions.options().size(); ++i)
|
||||||
|
{
|
||||||
|
string const& name = *_functionCallOptions.names()[i];
|
||||||
|
Type const* requiredType = TypeProvider::uint256();
|
||||||
|
Option newOption;
|
||||||
|
if (name == "salt")
|
||||||
|
{
|
||||||
|
newOption = Salt;
|
||||||
|
requiredType = TypeProvider::fixedBytes(32);
|
||||||
|
}
|
||||||
|
else if (name == "gas")
|
||||||
|
newOption = Gas;
|
||||||
|
else if (name == "value")
|
||||||
|
newOption = Value;
|
||||||
|
else
|
||||||
|
solAssert(false, "Unexpected option name!");
|
||||||
|
acceptAndConvert(*_functionCallOptions.options()[i], *requiredType);
|
||||||
|
|
||||||
|
solAssert(!contains(presentOptions, newOption), "");
|
||||||
|
size_t insertPos = presentOptions.end() - lower_bound(presentOptions.begin(), presentOptions.end(), newOption);
|
||||||
|
|
||||||
|
utils().moveIntoStack(insertPos, 1);
|
||||||
|
presentOptions.insert(presentOptions.end() - insertPos, newOption);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool ExpressionCompiler::visit(NewExpression const&)
|
bool ExpressionCompiler::visit(NewExpression const&)
|
||||||
{
|
{
|
||||||
// code is created for the function call (CREATION) only
|
// code is created for the function call (CREATION) only
|
||||||
|
@ -85,6 +85,7 @@ private:
|
|||||||
bool visit(UnaryOperation const& _unaryOperation) override;
|
bool visit(UnaryOperation const& _unaryOperation) override;
|
||||||
bool visit(BinaryOperation const& _binaryOperation) override;
|
bool visit(BinaryOperation const& _binaryOperation) override;
|
||||||
bool visit(FunctionCall const& _functionCall) override;
|
bool visit(FunctionCall const& _functionCall) override;
|
||||||
|
bool visit(FunctionCallOptions const& _functionCallOptions) override;
|
||||||
bool visit(NewExpression const& _newExpression) override;
|
bool visit(NewExpression const& _newExpression) override;
|
||||||
bool visit(MemberAccess const& _memberAccess) override;
|
bool visit(MemberAccess const& _memberAccess) override;
|
||||||
bool visit(IndexAccess const& _indexAccess) override;
|
bool visit(IndexAccess const& _indexAccess) override;
|
||||||
|
@ -1643,6 +1643,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
|
|||||||
// potential postfix expression
|
// potential postfix expression
|
||||||
ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_partiallyParsedExpression);
|
ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_partiallyParsedExpression);
|
||||||
token = m_scanner->currentToken();
|
token = m_scanner->currentToken();
|
||||||
|
|
||||||
if (!TokenTraits::isCountOp(token))
|
if (!TokenTraits::isCountOp(token))
|
||||||
return subExpression;
|
return subExpression;
|
||||||
nodeFactory.markEndPosition();
|
nodeFactory.markEndPosition();
|
||||||
@ -1738,6 +1739,25 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
|
|||||||
expression = nodeFactory.createNode<FunctionCall>(expression, arguments, names);
|
expression = nodeFactory.createNode<FunctionCall>(expression, arguments, names);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Token::LBrace:
|
||||||
|
{
|
||||||
|
// See if this is followed by <identifier>, followed by ":". If not, it is not
|
||||||
|
// a function call options but a Block (from a try statement).
|
||||||
|
if (
|
||||||
|
m_scanner->peekNextToken() != Token::Identifier ||
|
||||||
|
m_scanner->peekNextNextToken() != Token::Colon
|
||||||
|
)
|
||||||
|
return expression;
|
||||||
|
|
||||||
|
expectToken(Token::LBrace);
|
||||||
|
auto optionList = parseNamedArguments();
|
||||||
|
|
||||||
|
nodeFactory.markEndPosition();
|
||||||
|
expectToken(Token::RBrace);
|
||||||
|
|
||||||
|
expression = parseLeftHandSideExpression(nodeFactory.createNode<FunctionCallOptions>(expression, optionList.first, optionList.second));
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
@ -1887,28 +1907,7 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
|
|||||||
{
|
{
|
||||||
// call({arg1 : 1, arg2 : 2 })
|
// call({arg1 : 1, arg2 : 2 })
|
||||||
expectToken(Token::LBrace);
|
expectToken(Token::LBrace);
|
||||||
|
ret = parseNamedArguments();
|
||||||
bool first = true;
|
|
||||||
while (m_scanner->currentToken() != Token::RBrace)
|
|
||||||
{
|
|
||||||
if (!first)
|
|
||||||
expectToken(Token::Comma);
|
|
||||||
|
|
||||||
ret.second.push_back(expectIdentifierToken());
|
|
||||||
expectToken(Token::Colon);
|
|
||||||
ret.first.push_back(parseExpression());
|
|
||||||
|
|
||||||
if (
|
|
||||||
m_scanner->currentToken() == Token::Comma &&
|
|
||||||
m_scanner->peekNextToken() == Token::RBrace
|
|
||||||
)
|
|
||||||
{
|
|
||||||
parserError("Unexpected trailing comma.");
|
|
||||||
m_scanner->next();
|
|
||||||
}
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
expectToken(Token::RBrace);
|
expectToken(Token::RBrace);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1916,6 +1915,35 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::parseNamedArguments()
|
||||||
|
{
|
||||||
|
pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> ret;
|
||||||
|
|
||||||
|
bool first = true;
|
||||||
|
while (m_scanner->currentToken() != Token::RBrace)
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
expectToken(Token::Comma);
|
||||||
|
|
||||||
|
ret.second.push_back(expectIdentifierToken());
|
||||||
|
expectToken(Token::Colon);
|
||||||
|
ret.first.push_back(parseExpression());
|
||||||
|
|
||||||
|
if (
|
||||||
|
m_scanner->currentToken() == Token::Comma &&
|
||||||
|
m_scanner->peekNextToken() == Token::RBrace
|
||||||
|
)
|
||||||
|
{
|
||||||
|
parserError("Unexpected trailing comma.");
|
||||||
|
m_scanner->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
Parser::LookAheadInfo Parser::peekStatementType() const
|
Parser::LookAheadInfo Parser::peekStatementType() const
|
||||||
{
|
{
|
||||||
// Distinguish between variable declaration (and potentially assignment) and expression statement
|
// Distinguish between variable declaration (and potentially assignment) and expression statement
|
||||||
|
@ -148,6 +148,7 @@ private:
|
|||||||
ASTPointer<Expression> parsePrimaryExpression();
|
ASTPointer<Expression> parsePrimaryExpression();
|
||||||
std::vector<ASTPointer<Expression>> parseFunctionCallListArguments();
|
std::vector<ASTPointer<Expression>> parseFunctionCallListArguments();
|
||||||
std::pair<std::vector<ASTPointer<Expression>>, std::vector<ASTPointer<ASTString>>> parseFunctionCallArguments();
|
std::pair<std::vector<ASTPointer<Expression>>, std::vector<ASTPointer<ASTString>>> parseFunctionCallArguments();
|
||||||
|
std::pair<std::vector<ASTPointer<Expression>>, std::vector<ASTPointer<ASTString>>> parseNamedArguments();
|
||||||
///@}
|
///@}
|
||||||
|
|
||||||
///@{
|
///@{
|
||||||
|
@ -172,6 +172,28 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept
|
|||||||
message.destination = convertToEVMC(createAddress);
|
message.destination = convertToEVMC(createAddress);
|
||||||
code = evmc::bytes(message.input_data, message.input_data + message.input_size);
|
code = evmc::bytes(message.input_data, message.input_data + message.input_size);
|
||||||
}
|
}
|
||||||
|
else if (message.kind == EVMC_CREATE2)
|
||||||
|
{
|
||||||
|
Address createAddress(keccak256(
|
||||||
|
bytes(1, 0xff) +
|
||||||
|
bytes(begin(message.sender.bytes), end(message.sender.bytes)) +
|
||||||
|
bytes(begin(message.create2_salt.bytes), end(message.create2_salt.bytes)) +
|
||||||
|
keccak256(bytes(message.input_data, message.input_data + message.input_size)).asBytes()
|
||||||
|
));
|
||||||
|
message.destination = convertToEVMC(createAddress);
|
||||||
|
if (accounts.count(message.destination) && (
|
||||||
|
accounts[message.destination].nonce > 0 ||
|
||||||
|
!accounts[message.destination].code.empty()
|
||||||
|
))
|
||||||
|
{
|
||||||
|
evmc::result result({});
|
||||||
|
result.status_code = EVMC_OUT_OF_GAS;
|
||||||
|
accounts = stateBackup;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
code = evmc::bytes(message.input_data, message.input_data + message.input_size);
|
||||||
|
}
|
||||||
else if (message.kind == EVMC_DELEGATECALL)
|
else if (message.kind == EVMC_DELEGATECALL)
|
||||||
{
|
{
|
||||||
code = accounts[message.destination].code;
|
code = accounts[message.destination].code;
|
||||||
@ -184,7 +206,6 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
code = accounts[message.destination].code;
|
code = accounts[message.destination].code;
|
||||||
//TODO CREATE2
|
|
||||||
|
|
||||||
auto& destination = accounts[message.destination];
|
auto& destination = accounts[message.destination];
|
||||||
|
|
||||||
@ -199,7 +220,7 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept
|
|||||||
evmc::result result = m_vm.execute(*this, m_evmRevision, message, code.data(), code.size());
|
evmc::result result = m_vm.execute(*this, m_evmRevision, message, code.data(), code.size());
|
||||||
m_currentAddress = currentAddress;
|
m_currentAddress = currentAddress;
|
||||||
|
|
||||||
if (message.kind == EVMC_CREATE)
|
if (message.kind == EVMC_CREATE || message.kind == EVMC_CREATE2)
|
||||||
{
|
{
|
||||||
result.gas_left -= evmasm::GasCosts::createDataGas * result.output_size;
|
result.gas_left -= evmasm::GasCosts::createDataGas * result.output_size;
|
||||||
if (result.gas_left < 0)
|
if (result.gas_left < 0)
|
||||||
|
@ -1929,6 +1929,40 @@ BOOST_AUTO_TEST_CASE(gas_and_value_basic)
|
|||||||
BOOST_REQUIRE(callContractFunction("checkState()") == encodeArgs(false, 20 - 5));
|
BOOST_REQUIRE(callContractFunction("checkState()") == encodeArgs(false, 20 - 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(gas_and_value_brace_syntax)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract helper {
|
||||||
|
bool flag;
|
||||||
|
function getBalance() payable public returns (uint256 myBalance) {
|
||||||
|
return address(this).balance;
|
||||||
|
}
|
||||||
|
function setFlag() public { flag = true; }
|
||||||
|
function getFlag() public returns (bool fl) { return flag; }
|
||||||
|
}
|
||||||
|
contract test {
|
||||||
|
helper h;
|
||||||
|
constructor() public payable { h = new helper(); }
|
||||||
|
function sendAmount(uint amount) public payable returns (uint256 bal) {
|
||||||
|
return h.getBalance{value: amount}();
|
||||||
|
}
|
||||||
|
function outOfGas() public returns (bool ret) {
|
||||||
|
h.setFlag{gas: 2}(); // should fail due to OOG
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function checkState() public returns (bool flagAfter, uint myBal) {
|
||||||
|
flagAfter = h.getFlag();
|
||||||
|
myBal = address(this).balance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode, 20);
|
||||||
|
BOOST_REQUIRE(callContractFunction("sendAmount(uint256)", 5) == encodeArgs(5));
|
||||||
|
// call to helper should not succeed but amount should be transferred anyway
|
||||||
|
BOOST_REQUIRE(callContractFunction("outOfGas()") == bytes());
|
||||||
|
BOOST_REQUIRE(callContractFunction("checkState()") == encodeArgs(false, 20 - 5));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(gasleft_decrease)
|
BOOST_AUTO_TEST_CASE(gasleft_decrease)
|
||||||
{
|
{
|
||||||
char const* sourceCode = R"(
|
char const* sourceCode = R"(
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
contract B
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
contract A {
|
||||||
|
function different_salt() public returns (bool) {
|
||||||
|
B x = new B{salt: "abc"}();
|
||||||
|
B y = new B{salt: "abcef"}();
|
||||||
|
return x != y;
|
||||||
|
}
|
||||||
|
function same_salt() public returns (bool) {
|
||||||
|
B x = new B{salt: "xyz"}();
|
||||||
|
try new B{salt: "xyz"}() {} catch {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=constantinople
|
||||||
|
// ----
|
||||||
|
// different_salt() -> true
|
||||||
|
// same_salt() -> true
|
@ -0,0 +1,23 @@
|
|||||||
|
contract B
|
||||||
|
{
|
||||||
|
uint x;
|
||||||
|
function getBalance() public view returns (uint) {
|
||||||
|
return address(this).balance * 1000 + x;
|
||||||
|
}
|
||||||
|
constructor(uint _x) public payable {
|
||||||
|
x = _x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract A {
|
||||||
|
function f() public payable returns (uint, uint, uint) {
|
||||||
|
B x = new B{salt: "abc", value: 3}(7);
|
||||||
|
B y = new B{value: 3}{salt: "abc"}(8);
|
||||||
|
B z = new B{value: 3, salt: "abc"}(9);
|
||||||
|
return (x.getBalance(), y.getBalance(), z.getBalance());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=constantinople
|
||||||
|
// ----
|
||||||
|
// f(), 10 ether -> 3007, 3008, 3009
|
@ -0,0 +1,9 @@
|
|||||||
|
contract C {
|
||||||
|
function f(uint x) external payable { }
|
||||||
|
function f(uint x, uint y) external payable { }
|
||||||
|
function call() internal {
|
||||||
|
this.f{value: 10}(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (148-154): Member "f" not unique after argument-dependent lookup in contract C.
|
@ -0,0 +1,17 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo(int a) payable external {
|
||||||
|
this.foo{gas:2, gas: 5};
|
||||||
|
this.foo{value:2, value: 5};
|
||||||
|
this.foo{gas:2, value: 5, gas:2, value:3};
|
||||||
|
new D{salt:"abc", salt:"efg"}();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=constantinople
|
||||||
|
// ----
|
||||||
|
// TypeError: (78-101): Duplicate option "gas".
|
||||||
|
// TypeError: (111-138): Duplicate option "value".
|
||||||
|
// TypeError: (148-189): Duplicate option "gas".
|
||||||
|
// TypeError: (148-189): Duplicate option "value".
|
||||||
|
// TypeError: (199-228): Duplicate option "salt".
|
@ -0,0 +1,8 @@
|
|||||||
|
contract C {
|
||||||
|
function foo() pure internal {
|
||||||
|
address(10).delegatecall{value: 7, gas: 3}("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (56-98): Cannot set option "value" for delegatecall.
|
||||||
|
// Warning: (56-102): Return value of low-level calls not used.
|
@ -0,0 +1,8 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo(int a) pure internal {
|
||||||
|
foo{gas: 5};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (75-86): Function call options can only be set on external function calls or contract creations.
|
@ -0,0 +1,21 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo(int a) payable external {
|
||||||
|
this.foo{value:2, gas: 5}{gas:2};
|
||||||
|
(this.foo{value:2, gas: 5}){gas:2};
|
||||||
|
this.foo{value:2, gas: 5}{value:6};
|
||||||
|
this.foo.value(4){value:2, gas: 5};
|
||||||
|
this.foo{gas:2, value: 5}{value:2, gas:5};
|
||||||
|
new D{salt:"abc"}{salt:"a"}();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=constantinople
|
||||||
|
// ----
|
||||||
|
// TypeError: (78-110): Option "gas" has already been set.
|
||||||
|
// TypeError: (120-154): Option "gas" has already been set.
|
||||||
|
// TypeError: (164-198): Option "value" has already been set.
|
||||||
|
// TypeError: (208-242): Option "value" has already been set.
|
||||||
|
// TypeError: (252-293): Option "value" has already been set.
|
||||||
|
// TypeError: (252-293): Option "gas" has already been set.
|
||||||
|
// TypeError: (303-330): Option "salt" has already been set.
|
@ -0,0 +1,8 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo(int a) pure internal {
|
||||||
|
a{val:5};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (71-79): Expected callable expression before call options.
|
@ -0,0 +1,8 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo(int a) pure external {
|
||||||
|
this.foo{random:5+5};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (73-93): Unknown call option "random". Valid options are "salt", "value" and "gas".
|
@ -0,0 +1,10 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo(int a) external {
|
||||||
|
this.foo{slt:5, value:3, salt: 8};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError: (64-97): Unknown call option "slt". Valid options are "salt", "value" and "gas".
|
||||||
|
// TypeError: (64-97): Cannot set option "value" on a non-payable function type.
|
||||||
|
// TypeError: (64-97): Function call option "salt" can only be used with "new".
|
@ -0,0 +1,7 @@
|
|||||||
|
contract C {
|
||||||
|
function foo() internal {
|
||||||
|
(bool success, ) = address(10).call{value: 7, gas: 3}("");
|
||||||
|
success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
@ -0,0 +1,12 @@
|
|||||||
|
contract D { constructor() public payable {} }
|
||||||
|
contract C {
|
||||||
|
function foo() pure internal {
|
||||||
|
new D{salt:"abc", value:3};
|
||||||
|
new D{salt:"abc"};
|
||||||
|
new D{value:5+5};
|
||||||
|
new D{salt:"aabbcc"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=constantinople
|
||||||
|
// ----
|
@ -0,0 +1,15 @@
|
|||||||
|
contract D { constructor() public payable {} }
|
||||||
|
contract C {
|
||||||
|
function foo() pure internal {
|
||||||
|
new D{salt:"abc", value:3};
|
||||||
|
new D{salt:"abc"};
|
||||||
|
new D{value:5+5};
|
||||||
|
new D{salt:"aabbcc"};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: <constantinople
|
||||||
|
// ----
|
||||||
|
// TypeError: (97-123): Unsupported call option "salt" (requires Constantinople-compatible VMs).
|
||||||
|
// TypeError: (127-144): Unsupported call option "salt" (requires Constantinople-compatible VMs).
|
||||||
|
// TypeError: (168-188): Unsupported call option "salt" (requires Constantinople-compatible VMs).
|
@ -0,0 +1,27 @@
|
|||||||
|
contract D {}
|
||||||
|
contract C {
|
||||||
|
function foo() pure internal {
|
||||||
|
new D{salt:"abc", value:3, gas: 4};
|
||||||
|
new D{slt:5, value:3};
|
||||||
|
new D{val:5};
|
||||||
|
new D{salt:"xyz", salt:"aaf"};
|
||||||
|
new D{value:3, value:4};
|
||||||
|
new D{random:5+5};
|
||||||
|
new D{what:2130+5};
|
||||||
|
new D{gas: 2};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=constantinople
|
||||||
|
// ----
|
||||||
|
// TypeError: (64-98): Cannot set option "value" on a non-payable function type.
|
||||||
|
// TypeError: (64-98): Function call option "gas" cannot be used with "new".
|
||||||
|
// TypeError: (102-123): Unknown call option "slt". Valid options are "salt", "value" and "gas".
|
||||||
|
// TypeError: (102-123): Cannot set option "value" on a non-payable function type.
|
||||||
|
// TypeError: (127-139): Unknown call option "val". Valid options are "salt", "value" and "gas".
|
||||||
|
// TypeError: (143-172): Duplicate option "salt".
|
||||||
|
// TypeError: (176-199): Cannot set option "value" on a non-payable function type.
|
||||||
|
// TypeError: (176-199): Cannot set option "value" on a non-payable function type.
|
||||||
|
// TypeError: (203-220): Unknown call option "random". Valid options are "salt", "value" and "gas".
|
||||||
|
// TypeError: (224-242): Unknown call option "what". Valid options are "salt", "value" and "gas".
|
||||||
|
// TypeError: (246-259): Function call option "gas" cannot be used with "new".
|
@ -0,0 +1,14 @@
|
|||||||
|
contract C {
|
||||||
|
struct gas { uint a; }
|
||||||
|
function f() public returns (uint, uint) {
|
||||||
|
try this.f() {
|
||||||
|
gas memory x;
|
||||||
|
} catch Error(string memory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ====
|
||||||
|
// EVMVersion: >=byzantium
|
||||||
|
// ----
|
||||||
|
// Warning: (122-134): Unused local variable.
|
Loading…
Reference in New Issue
Block a user