Merge pull request #8177 from ethereum/new2-2136

Implement `new` with CREATE2 and function call options
This commit is contained in:
chriseth 2020-01-23 23:19:39 +01:00 committed by GitHub
commit 45caaf5ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 727 additions and 59 deletions

View File

@ -4,6 +4,8 @@ Language Features:
* Allow accessing external functions via contract and interface names to obtain their selector.
* Inline Assembly: Support literals ``true`` and ``false``.
* 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:

View File

@ -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 {
// 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()"));
require(success);
// 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);
// results in test.x becoming == 1 and test.y becoming 1.

View File

@ -75,9 +75,10 @@ all function arguments have to be copied to memory.
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
gas sent with the call with the special options ``.value()`` and ``.gas()``,
respectively. Any Wei you send to the contract is added to the total balance
of the contract:
gas sent with the call with the special options ``{value: 10, gas: 10000}``.
Note that it is discouraged to specify gas values explicitly, since the gas costs
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 {
InfoFeed feed;
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
otherwise, the ``.value()`` option would not be available.
otherwise, the ``value`` option would not be available.
.. 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
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.
@ -121,6 +122,11 @@ throws an exception or goes out of gas.
external functions happen after any changes to state variables in your contract
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
---------------------------------------------
@ -196,17 +202,81 @@ is compiled so recursive creation-dependencies are not possible.
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = (new D).value(amount)(arg);
D newD = new D{value: amount}(arg);
newD.x();
}
}
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.
If the creation fails (due to out-of-stack, not enough balance or other problems),
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
==================================

View File

@ -89,7 +89,7 @@ as it uses ``call`` which forwards all remaining gas by default:
mapping(address => uint) shares;
/// Withdraw your share.
function withdraw() public {
(bool success,) = msg.sender.call.value(shares[msg.sender])("");
(bool success,) = msg.sender.call{value: shares[msg.sender]}("");
if (success)
shares[msg.sender] = 0;
}
@ -149,7 +149,7 @@ Sending and Receiving Ether
(for example in the "details" section in Remix).
- 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
recipient to perform more expensive actions (and it returns a failure code
instead of automatically propagating the error). This might include calling back

View File

@ -276,17 +276,17 @@ Example::
arbitrary arguments and would also handle a first argument of type
``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::
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::
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.
@ -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.
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::
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.
* ``.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.
* ``.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.
* ``.gas(uint)`` returns a callable function object which, when called, will send
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::
@ -651,6 +656,8 @@ Example that shows how to use the members::
function g() public {
this.f.gas(10).value(800)();
// New syntax:
// this.f{gas: 10, value: 800}()
}
}

View File

@ -2224,6 +2224,124 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
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)
{
TypePointer type = _newExpression.typeName().annotation().type;

View File

@ -135,6 +135,7 @@ private:
void endVisit(BinaryOperation const& _operation) override;
bool visit(UnaryOperation const& _operation) override;
bool visit(FunctionCall const& _functionCall) override;
bool visit(FunctionCallOptions const& _functionCallOptions) override;
void endVisit(NewExpression const& _newExpression) override;
bool visit(MemberAccess const& _memberAccess) override;
bool visit(IndexAccess const& _indexAccess) override;

View File

@ -1761,6 +1761,35 @@ private:
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,
* e.g. the "new SomeContract" part in "new SomeContract(1, 2)".

View File

@ -724,6 +724,23 @@ bool ASTJsonConverter::visit(FunctionCall const& _node)
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)
{
std::vector<pair<string, Json::Value>> attributes = {

View File

@ -111,6 +111,7 @@ public:
bool visit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
bool visit(FunctionCall const& _node) override;
bool visit(FunctionCallOptions const& _node) override;
bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override;

View File

@ -192,6 +192,8 @@ ASTPointer<ASTNode> ASTJsonImporter::convertJsonToASTNode(Json::Value const& _js
return createBinaryOperation(_json);
if (nodeType == "FunctionCall")
return createFunctionCall(_json);
if (nodeType == "FunctionCallOptions")
return createFunctionCallOptions(_json);
if (nodeType == "NewExpression")
return createNewExpression(_json);
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)
{
return createASTNode<NewExpression>(

View File

@ -111,6 +111,7 @@ private:
ASTPointer<UnaryOperation> createUnaryOperation(Json::Value const& _node);
ASTPointer<BinaryOperation> createBinaryOperation(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<MemberAccess> createMemberAccess(Json::Value const& _node);
ASTPointer<IndexAccess> createIndexAccess(Json::Value const& _node);

View File

@ -84,6 +84,7 @@ public:
virtual bool visit(UnaryOperation& _node) { return visitNode(_node); }
virtual bool visit(BinaryOperation& _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(MemberAccess& _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(BinaryOperation& _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(MemberAccess& _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(BinaryOperation 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(MemberAccess 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(BinaryOperation 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(MemberAccess const& _node) { endVisitNode(_node); }
virtual void endVisit(IndexAccess const& _node) { endVisitNode(_node); }

View File

@ -781,6 +781,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const
_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)
{
if (_visitor.visit(*this))

View File

@ -459,7 +459,8 @@ FunctionType const* TypeProvider::function(
Declaration const* _declaration,
bool _gasSet,
bool _valueSet,
bool _bound
bool _bound,
bool _saltSet
)
{
return createAndGet<FunctionType>(
@ -473,7 +474,8 @@ FunctionType const* TypeProvider::function(
_declaration,
_gasSet,
_valueSet,
_bound
_bound,
_saltSet
);
}

View File

@ -153,7 +153,8 @@ public:
Declaration const* _declaration = nullptr,
bool _gasSet = 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

View File

@ -2741,6 +2741,8 @@ string FunctionType::richIdentifier() const
id += "gas";
if (m_valueSet)
id += "value";
if (m_saltSet)
id += "salt";
if (bound())
id += "bound_to" + identifierList(selfType());
return id;
@ -2918,6 +2920,8 @@ unsigned FunctionType::sizeOnStack() const
size++;
if (m_valueSet)
size++;
if (m_saltSet)
size++;
if (bound())
size += m_parameterTypes.front()->sizeOnStack();
return size;
@ -3001,7 +3005,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
"value",
TypeProvider::function(
parseElementaryTypeVector({"uint"}),
TypePointers{copyAndSetGasOrValue(false, true)},
TypePointers{copyAndSetCallOptions(false, true, false)},
strings(1, ""),
strings(1, ""),
Kind::SetValue,
@ -3009,7 +3013,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
StateMutability::Pure,
nullptr,
m_gasSet,
m_valueSet
m_valueSet,
m_saltSet
)
);
}
@ -3018,7 +3023,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
"gas",
TypeProvider::function(
parseElementaryTypeVector({"uint"}),
TypePointers{copyAndSetGasOrValue(true, false)},
TypePointers{copyAndSetCallOptions(true, false, false)},
strings(1, ""),
strings(1, ""),
Kind::SetGas,
@ -3026,7 +3031,8 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const* _sco
StateMutability::Pure,
nullptr,
m_gasSet,
m_valueSet
m_valueSet,
m_saltSet
)
);
return members;
@ -3149,7 +3155,7 @@ bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) con
return false;
//@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;
if (bound() != _other.bound())
@ -3250,7 +3256,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
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, "");
return TypeProvider::function(
@ -3264,6 +3270,7 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con
m_declaration,
m_gasSet || _setGas,
m_valueSet || _setValue,
m_saltSet || _setSalt,
m_bound
);
}
@ -3304,6 +3311,7 @@ FunctionTypePointer FunctionType::asCallableFunction(bool _inLibrary, bool _boun
m_declaration,
m_gasSet,
m_valueSet,
m_saltSet,
_bound
);
}

View File

@ -1055,7 +1055,7 @@ public:
/// Refers to a function declaration without calling context
/// (i.e. when accessed directly via the name of the containing contract).
/// Cannot be called.
Declaration
Declaration,
};
/// Creates the type of a function.
@ -1098,6 +1098,7 @@ public:
Declaration const* _declaration = nullptr,
bool _gasSet = false,
bool _valueSet = false,
bool _saltSet = false,
bool _bound = false
):
m_parameterTypes(_parameterTypes),
@ -1110,7 +1111,8 @@ public:
m_gasSet(_gasSet),
m_valueSet(_valueSet),
m_bound(_bound),
m_declaration(_declaration)
m_declaration(_declaration),
m_saltSet(_saltSet)
{
solAssert(
m_parameterNames.size() == m_parameterTypes.size(),
@ -1235,11 +1237,12 @@ public:
bool gasSet() const { return m_gasSet; }
bool valueSet() const { return m_valueSet; }
bool saltSet() const { return m_saltSet; }
bool bound() const { return m_bound; }
/// @returns a copy of this type, where gas or value are set manually. This will never set one
/// 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
/// 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_bound = false; ///< true iff the function is called as arg1.fun(arg2, ..., argn)
Declaration const* m_declaration = nullptr;
bool m_saltSet = false; ///< true iff the salt value to be used is on the stack
};
/**

View File

@ -620,6 +620,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::Creation:
{
_functionCall.expression().accept(*this);
// Stack: [salt], [value]
solAssert(!function.gasSet(), "Gas limit set for contract creation.");
solAssert(function.returnParameterTypes().size() == 1, "");
TypePointers argumentTypes;
@ -633,16 +635,37 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().fetchFreeMemoryPointer();
utils().copyContractCodeToMemory(*contract, true);
utils().abiEncode(argumentTypes, function.parameterTypes());
// now on stack: memory_end_ptr
// need: size, offset, endowment
// now on stack: [salt], [value], memory_end_ptr
// 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();
// now: [salt], [value], [salt], size, offset
if (function.valueSet())
m_context << dupInstruction(3);
m_context << dupInstruction(3 + (function.saltSet() ? 1 : 0));
else
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())
m_context << swapInstruction(1) << Instruction::POP;
if (function.saltSet())
m_context << swapInstruction(1) << Instruction::POP;
// Check if zero (reverted)
m_context << Instruction::DUP1 << Instruction::ISZERO;
if (_functionCall.annotation().tryCall)
@ -1192,6 +1215,49 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
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&)
{
// code is created for the function call (CREATION) only

View File

@ -85,6 +85,7 @@ private:
bool visit(UnaryOperation const& _unaryOperation) override;
bool visit(BinaryOperation const& _binaryOperation) override;
bool visit(FunctionCall const& _functionCall) override;
bool visit(FunctionCallOptions const& _functionCallOptions) override;
bool visit(NewExpression const& _newExpression) override;
bool visit(MemberAccess const& _memberAccess) override;
bool visit(IndexAccess const& _indexAccess) override;

View File

@ -1643,6 +1643,7 @@ ASTPointer<Expression> Parser::parseUnaryExpression(
// potential postfix expression
ASTPointer<Expression> subExpression = parseLeftHandSideExpression(_partiallyParsedExpression);
token = m_scanner->currentToken();
if (!TokenTraits::isCountOp(token))
return subExpression;
nodeFactory.markEndPosition();
@ -1738,6 +1739,25 @@ ASTPointer<Expression> Parser::parseLeftHandSideExpression(
expression = nodeFactory.createNode<FunctionCall>(expression, arguments, names);
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:
return expression;
}
@ -1887,28 +1907,7 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
{
// call({arg1 : 1, arg2 : 2 })
expectToken(Token::LBrace);
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;
}
ret = parseNamedArguments();
expectToken(Token::RBrace);
}
else
@ -1916,6 +1915,35 @@ pair<vector<ASTPointer<Expression>>, vector<ASTPointer<ASTString>>> Parser::pars
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
{
// Distinguish between variable declaration (and potentially assignment) and expression statement

View File

@ -148,6 +148,7 @@ private:
ASTPointer<Expression> parsePrimaryExpression();
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>>> parseNamedArguments();
///@}
///@{

View File

@ -172,6 +172,28 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept
message.destination = convertToEVMC(createAddress);
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)
{
code = accounts[message.destination].code;
@ -184,7 +206,6 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept
}
else
code = accounts[message.destination].code;
//TODO CREATE2
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());
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;
if (result.gas_left < 0)

View File

@ -1929,6 +1929,40 @@ BOOST_AUTO_TEST_CASE(gas_and_value_basic)
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)
{
char const* sourceCode = R"(

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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".

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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".

View File

@ -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".

View File

@ -0,0 +1,7 @@
contract C {
function foo() internal {
(bool success, ) = address(10).call{value: 7, gas: 3}("");
success;
}
}
// ----

View File

@ -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
// ----

View File

@ -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).

View File

@ -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".

View File

@ -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.