Implement new with CREATE2 and function call options.

This commit is contained in:
Mathias Baumann
2020-01-23 21:20:01 +01:00
committed by chriseth
parent 679f729f2f
commit a3f23d3158
28 changed files with 528 additions and 38 deletions
+118
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;
+1
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;
+29
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)".
+17
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 = {
+1
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;
+4
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); }
+20
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))
+4 -2
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
);
}
+2 -1
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
+14 -6
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
);
}
+7 -3
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
};
/**
+70 -4
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
+1
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;
+50 -22
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
+1
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();
///@}
///@{