aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
authorAnurag Dashputre <anurag4u80@gmail.com>2018-08-23 14:26:45 +0800
committerGitHub <noreply@github.com>2018-08-23 14:26:45 +0800
commit8497dcd721ff0a113374c0c1e1778d44265398a6 (patch)
tree2833ab7b3c7513647c0476d0e5d33dc11fcd6951 /libsolidity
parent55524788e2829b3a2b9c6c513f78ba2074aa3385 (diff)
parent410d288dfc2e08c42df58c7e01ad5c332ce92727 (diff)
downloaddexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.tar
dexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.tar.gz
dexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.tar.bz2
dexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.tar.lz
dexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.tar.xz
dexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.tar.zst
dexon-solidity-8497dcd721ff0a113374c0c1e1778d44265398a6.zip
Merge branch 'develop' into anurag_issue_3667
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/analysis/ControlFlowAnalyzer.cpp5
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp7
-rw-r--r--libsolidity/analysis/DocStringAnalyser.cpp34
-rw-r--r--libsolidity/analysis/DocStringAnalyser.h11
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp11
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.h2
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp192
-rw-r--r--libsolidity/analysis/TypeChecker.cpp164
-rw-r--r--libsolidity/analysis/TypeChecker.h8
-rw-r--r--libsolidity/analysis/ViewPureChecker.cpp2
-rw-r--r--libsolidity/ast/AST.cpp123
-rw-r--r--libsolidity/ast/AST.h16
-rw-r--r--libsolidity/ast/Types.cpp187
-rw-r--r--libsolidity/ast/Types.h32
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp42
-rw-r--r--libsolidity/interface/CompilerStack.cpp37
-rw-r--r--libsolidity/interface/CompilerStack.h21
-rw-r--r--libsolidity/interface/Natspec.cpp67
-rw-r--r--libsolidity/interface/Natspec.h6
-rw-r--r--libsolidity/interface/StandardCompiler.cpp9
20 files changed, 689 insertions, 287 deletions
diff --git a/libsolidity/analysis/ControlFlowAnalyzer.cpp b/libsolidity/analysis/ControlFlowAnalyzer.cpp
index 483d08c8..ab6569be 100644
--- a/libsolidity/analysis/ControlFlowAnalyzer.cpp
+++ b/libsolidity/analysis/ControlFlowAnalyzer.cpp
@@ -75,7 +75,10 @@ void ControlFlowAnalyzer::checkUnassignedStorageReturnValues(
{
auto& unassignedAtFunctionEntry = unassigned[_functionEntry];
for (auto const& returnParameter: _function.returnParameterList()->parameters())
- if (returnParameter->type()->dataStoredIn(DataLocation::Storage))
+ if (
+ returnParameter->type()->dataStoredIn(DataLocation::Storage) ||
+ returnParameter->type()->category() == Type::Category::Mapping
+ )
unassignedAtFunctionEntry.insert(returnParameter.get());
}
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index 347daaf8..5f980788 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -138,19 +138,22 @@ vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _n
vector<ASTString> DeclarationContainer::similarNames(ASTString const& _name) const
{
static size_t const MAXIMUM_EDIT_DISTANCE = 2;
+ // because the function below has quadratic runtime - it will not magically improve once a better algorithm is discovered ;)
+ // since 80 is the suggested line length limit, we use 80^2 as length threshold
+ static size_t const MAXIMUM_LENGTH_THRESHOLD = 80 * 80;
vector<ASTString> similar;
for (auto const& declaration: m_declarations)
{
string const& declarationName = declaration.first;
- if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
+ if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE, MAXIMUM_LENGTH_THRESHOLD))
similar.push_back(declarationName);
}
for (auto const& declaration: m_invisibleDeclarations)
{
string const& declarationName = declaration.first;
- if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE))
+ if (stringWithinDistance(_name, declarationName, MAXIMUM_EDIT_DISTANCE, MAXIMUM_LENGTH_THRESHOLD))
similar.push_back(declarationName);
}
diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp
index b3fb5258..c1b97def 100644
--- a/libsolidity/analysis/DocStringAnalyser.cpp
+++ b/libsolidity/analysis/DocStringAnalyser.cpp
@@ -48,7 +48,10 @@ bool DocStringAnalyser::visit(ContractDefinition const& _contract)
bool DocStringAnalyser::visit(FunctionDefinition const& _function)
{
- handleCallable(_function, _function, _function.annotation());
+ if (_function.isConstructor())
+ handleConstructor(_function, _function, _function.annotation());
+ else
+ handleCallable(_function, _function, _function.annotation());
return true;
}
@@ -66,15 +69,11 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
return true;
}
-void DocStringAnalyser::handleCallable(
+void DocStringAnalyser::checkParameters(
CallableDeclaration const& _callable,
- Documented const& _node,
DocumentedAnnotation& _annotation
)
{
- static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"};
- parseDocStrings(_node, _annotation, validTags, "functions");
-
set<string> validParams;
for (auto const& p: _callable.parameters())
validParams.insert(p->name());
@@ -89,6 +88,29 @@ void DocStringAnalyser::handleCallable(
i->second.paramName +
"\" not found in the parameter list of the function."
);
+
+}
+
+void DocStringAnalyser::handleConstructor(
+ CallableDeclaration const& _callable,
+ Documented const& _node,
+ DocumentedAnnotation& _annotation
+)
+{
+ static const set<string> validTags = set<string>{"author", "dev", "notice", "param"};
+ parseDocStrings(_node, _annotation, validTags, "constructor");
+ checkParameters(_callable, _annotation);
+}
+
+void DocStringAnalyser::handleCallable(
+ CallableDeclaration const& _callable,
+ Documented const& _node,
+ DocumentedAnnotation& _annotation
+)
+{
+ static const set<string> validTags = set<string>{"author", "dev", "notice", "return", "param"};
+ parseDocStrings(_node, _annotation, validTags, "functions");
+ checkParameters(_callable, _annotation);
}
void DocStringAnalyser::parseDocStrings(
diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h
index 158b4060..5d339428 100644
--- a/libsolidity/analysis/DocStringAnalyser.h
+++ b/libsolidity/analysis/DocStringAnalyser.h
@@ -48,6 +48,17 @@ private:
virtual bool visit(ModifierDefinition const& _modifier) override;
virtual bool visit(EventDefinition const& _event) override;
+ void checkParameters(
+ CallableDeclaration const& _callable,
+ DocumentedAnnotation& _annotation
+ );
+
+ void handleConstructor(
+ CallableDeclaration const& _callable,
+ Documented const& _node,
+ DocumentedAnnotation& _annotation
+ );
+
void handleCallable(
CallableDeclaration const& _callable,
Documented const& _node,
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index c5c429ce..b452a49a 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -626,6 +626,17 @@ void DeclarationRegistrationHelper::endVisit(ModifierDefinition&)
closeCurrentScope();
}
+bool DeclarationRegistrationHelper::visit(FunctionTypeName& _funTypeName)
+{
+ enterNewSubScope(_funTypeName);
+ return true;
+}
+
+void DeclarationRegistrationHelper::endVisit(FunctionTypeName&)
+{
+ closeCurrentScope();
+}
+
bool DeclarationRegistrationHelper::visit(Block& _block)
{
_block.setScope(m_currentScope);
diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h
index 8178ee17..a72c21e3 100644
--- a/libsolidity/analysis/NameAndTypeResolver.h
+++ b/libsolidity/analysis/NameAndTypeResolver.h
@@ -171,6 +171,8 @@ private:
void endVisit(FunctionDefinition& _function) override;
bool visit(ModifierDefinition& _modifier) override;
void endVisit(ModifierDefinition& _modifier) override;
+ bool visit(FunctionTypeName& _funTypeName) override;
+ void endVisit(FunctionTypeName& _funTypeName) override;
bool visit(Block& _block) override;
void endVisit(Block& _block) override;
bool visit(ForStatement& _forLoop) override;
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index fa0888dd..f33de7b7 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -30,7 +30,10 @@
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/interface/ErrorReporter.h>
+#include <libdevcore/StringUtils.h>
+
#include <boost/algorithm/string.hpp>
+#include <boost/range/adaptor/transformed.hpp>
using namespace std;
using namespace dev;
@@ -155,7 +158,10 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName)
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
_typeName.annotation().type = make_shared<ContractType>(*contract);
else
+ {
+ _typeName.annotation().type = make_shared<TupleType>();
typeError(_typeName.location(), "Name has to refer to a struct, enum or contract.");
+ }
}
void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
@@ -166,13 +172,13 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
case VariableDeclaration::Visibility::External:
break;
default:
- typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
+ fatalTypeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\".");
return;
}
if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External)
{
- typeError(_typeName.location(), "Only external function types can be payable.");
+ fatalTypeError(_typeName.location(), "Only external function types can be payable.");
return;
}
@@ -182,7 +188,7 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
solAssert(t->annotation().type, "Type not set for parameter.");
if (!t->annotation().type->canBeUsedExternally(false))
{
- typeError(t->location(), "Internal type cannot be used for external function type.");
+ fatalTypeError(t->location(), "Internal type cannot be used for external function type.");
return;
}
}
@@ -300,6 +306,9 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
if (_variable.annotation().type)
return;
+ if (_variable.isConstant() && !_variable.isStateVariable())
+ m_errorReporter.declarationError(_variable.location(), "The \"constant\" keyword can only be used for state variables.");
+
if (!_variable.typeName())
{
// This can still happen in very unusual cases where a developer uses constructs, such as
@@ -309,127 +318,92 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
// after this step.
return;
}
-
- TypePointer type;
- type = _variable.typeName()->annotation().type;
using Location = VariableDeclaration::Location;
Location varLoc = _variable.referenceLocation();
DataLocation typeLoc = DataLocation::Memory;
- // References are forced to calldata for external function parameters (not return)
- // and memory for parameters (also return) of publicly visible functions.
- // They default to memory for function parameters and storage for local variables.
- // As an exception, "storage" is allowed for library functions.
- if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
+
+ set<Location> allowedDataLocations = _variable.allowedDataLocations();
+ if (!allowedDataLocations.count(varLoc))
{
- bool isPointer = true;
- if (_variable.isExternalCallableParameter())
+ auto locationToString = [](VariableDeclaration::Location _location) -> string
{
- auto const& contract = dynamic_cast<ContractDefinition const&>(
- *dynamic_cast<Declaration const&>(*_variable.scope()).scope()
- );
- if (contract.isLibrary())
- {
- if (varLoc == Location::Memory)
- fatalTypeError(_variable.location(),
- "Location has to be calldata or storage for external "
- "library functions (remove the \"memory\" keyword)."
- );
- }
- else
+ switch (_location)
{
- // force location of external function parameters (not return) to calldata
- if (varLoc != Location::CallData && varLoc != Location::Default)
- fatalTypeError(_variable.location(),
- "Location has to be calldata for external functions "
- "(remove the \"memory\" or \"storage\" keyword)."
- );
+ case Location::Memory: return "\"memory\"";
+ case Location::Storage: return "\"storage\"";
+ case Location::CallData: return "\"calldata\"";
+ case Location::Default: return "none";
}
- if (varLoc == Location::Default || varLoc == Location::CallData)
- typeLoc = DataLocation::CallData;
- else
- typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
- }
- else if (_variable.isCallableParameter() && dynamic_cast<Declaration const&>(*_variable.scope()).isPublic())
+ return {};
+ };
+
+ string errorString;
+ if (!_variable.hasReferenceOrMappingType())
+ errorString = "Data location can only be specified for array, struct or mapping types";
+ else
{
- auto const& contract = dynamic_cast<ContractDefinition const&>(
- *dynamic_cast<Declaration const&>(*_variable.scope()).scope()
+ errorString = "Data location must be " +
+ joinHumanReadable(
+ allowedDataLocations | boost::adaptors::transformed(locationToString),
+ ", ",
+ " or "
);
- // force locations of public or external function (return) parameters to memory
- if (varLoc != Location::Memory && varLoc != Location::Default && !contract.isLibrary())
- fatalTypeError(_variable.location(),
- "Location has to be memory for publicly visible functions "
- "(remove the \"storage\" or \"calldata\" keyword)."
- );
- if (varLoc == Location::Default || !contract.isLibrary())
- typeLoc = DataLocation::Memory;
+ if (_variable.isCallableParameter())
+ errorString +=
+ " for " +
+ string(_variable.isReturnParameter() ? "return " : "") +
+ "parameter in" +
+ string(_variable.isExternalCallableParameter() ? " external" : "") +
+ " function";
else
- {
- if (varLoc == Location::CallData)
- fatalTypeError(_variable.location(),
- "Location cannot be calldata for non-external functions "
- "(remove the \"calldata\" keyword)."
- );
- typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
- }
+ errorString += " for variable";
}
- else
+ errorString += ", but " + locationToString(varLoc) + " was given.";
+ typeError(_variable.location(), errorString);
+
+ solAssert(!allowedDataLocations.empty(), "");
+ varLoc = *allowedDataLocations.begin();
+ }
+
+ // Find correct data location.
+ if (_variable.isEventParameter())
+ {
+ solAssert(varLoc == Location::Default, "");
+ typeLoc = DataLocation::Memory;
+ }
+ else if (_variable.isStateVariable())
+ {
+ solAssert(varLoc == Location::Default, "");
+ typeLoc = _variable.isConstant() ? DataLocation::Memory : DataLocation::Storage;
+ }
+ else if (
+ dynamic_cast<StructDefinition const*>(_variable.scope()) ||
+ dynamic_cast<EnumDefinition const*>(_variable.scope())
+ )
+ // The actual location will later be changed depending on how the type is used.
+ typeLoc = DataLocation::Storage;
+ else
+ switch (varLoc)
{
- if (_variable.isConstant())
- {
- if (varLoc != Location::Default && varLoc != Location::Memory)
- fatalTypeError(
- _variable.location(),
- "Data location has to be \"memory\" (or unspecified) for constants."
- );
- typeLoc = DataLocation::Memory;
- }
- else if (varLoc == Location::Default)
- {
- if (_variable.isCallableParameter())
- typeLoc = DataLocation::Memory;
- else
- {
- typeLoc = DataLocation::Storage;
- if (_variable.isLocalVariable())
- typeError(
- _variable.location(),
- "Data location must be specified as either \"memory\" or \"storage\"."
- );
- }
- }
- else
- {
- switch (varLoc)
- {
- case Location::Memory:
- typeLoc = DataLocation::Memory;
- break;
- case Location::Storage:
- typeLoc = DataLocation::Storage;
- break;
- case Location::CallData:
- fatalTypeError(_variable.location(),
- "Variable cannot be declared as \"calldata\" (remove the \"calldata\" keyword)."
- );
- break;
- default:
- solAssert(false, "Unknown data location");
- }
- }
- isPointer = !_variable.isStateVariable();
+ case Location::Memory:
+ typeLoc = DataLocation::Memory;
+ break;
+ case Location::Storage:
+ typeLoc = DataLocation::Storage;
+ break;
+ case Location::CallData:
+ typeLoc = DataLocation::CallData;
+ break;
+ case Location::Default:
+ solAssert(!_variable.hasReferenceOrMappingType(), "Data location not properly set.");
}
- type = ref->copyForLocation(typeLoc, isPointer);
- }
- else if (dynamic_cast<MappingType const*>(type.get()))
+
+ TypePointer type = _variable.typeName()->annotation().type;
+ if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
{
- if (_variable.isLocalVariable() && varLoc != Location::Storage)
- typeError(
- _variable.location(),
- "Data location for mappings must be specified as \"storage\"."
- );
+ bool isPointer = !_variable.isStateVariable();
+ type = ref->copyForLocation(typeLoc, isPointer);
}
- else if (varLoc != Location::Default && !ref)
- typeError(_variable.location(), "Data location can only be given for array or struct types.");
_variable.annotation().type = type;
}
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index bcc3757a..fca64f6a 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -525,6 +525,75 @@ void TypeChecker::checkDoubleStorageAssignment(Assignment const& _assignment)
);
}
+TypePointer TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2)
+{
+ vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
+ if (arguments.size() != 2)
+ m_errorReporter.typeError(
+ _functionCall.location(),
+ "This function takes two arguments, but " +
+ toString(arguments.size()) +
+ " were provided."
+ );
+ if (arguments.size() >= 1 && !type(*arguments.front())->isImplicitlyConvertibleTo(ArrayType(DataLocation::Memory)))
+ m_errorReporter.typeError(
+ arguments.front()->location(),
+ "Invalid type for argument in function call. "
+ "Invalid implicit conversion from " +
+ type(*arguments.front())->toString() +
+ " to bytes memory requested."
+ );
+
+ TypePointer returnType = make_shared<TupleType>();
+
+ if (arguments.size() < 2)
+ return returnType;
+
+ // The following is a rather syntactic restriction, but we check it here anyway:
+ // The second argument has to be a tuple expression containing type names.
+ TupleExpression const* tupleExpression = dynamic_cast<TupleExpression const*>(arguments[1].get());
+ if (!tupleExpression)
+ {
+ m_errorReporter.typeError(
+ arguments[1]->location(),
+ "The second argument to \"abi.decode\" has to be a tuple of types."
+ );
+ return returnType;
+ }
+
+ vector<TypePointer> components;
+ for (auto const& typeArgument: tupleExpression->components())
+ {
+ solAssert(typeArgument, "");
+ if (TypeType const* argTypeType = dynamic_cast<TypeType const*>(type(*typeArgument).get()))
+ {
+ TypePointer actualType = argTypeType->actualType();
+ solAssert(actualType, "");
+ // We force memory because the parser currently cannot handle
+ // data locations. Furthermore, storage can be a little dangerous and
+ // calldata is not really implemented anyway.
+ actualType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, actualType);
+ solAssert(
+ !actualType->dataStoredIn(DataLocation::CallData) &&
+ !actualType->dataStoredIn(DataLocation::Storage),
+ ""
+ );
+ if (!actualType->fullEncodingType(false, _abiEncoderV2, false))
+ m_errorReporter.typeError(
+ typeArgument->location(),
+ "Decoding type " + actualType->toString(false) + " not supported."
+ );
+ components.push_back(actualType);
+ }
+ else
+ {
+ m_errorReporter.typeError(typeArgument->location(), "Argument has to be a type name.");
+ components.push_back(make_shared<TupleType>());
+ }
+ }
+ return make_shared<TupleType>(components);
+}
+
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
@@ -580,9 +649,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor)
bool TypeChecker::visit(StructDefinition const& _struct)
{
- if (m_scope->contractKind() == ContractDefinition::ContractKind::Interface)
- m_errorReporter.typeError(_struct.location(), "Structs cannot be defined in interfaces.");
-
for (ASTPointer<VariableDeclaration> const& member: _struct.members())
if (!type(*member)->canBeStored())
m_errorReporter.typeError(member->location(), "Type cannot be used in struct.");
@@ -610,7 +676,10 @@ bool TypeChecker::visit(StructDefinition const& _struct)
if (CycleDetector<StructDefinition>(visitor).run(_struct) != nullptr)
m_errorReporter.fatalTypeError(_struct.location(), "Recursive struct definition.");
+ bool insideStruct = true;
+ swap(insideStruct, m_insideStruct);
ASTNode::listAccept(_struct.members(), *this);
+ m_insideStruct = insideStruct;
return false;
}
@@ -629,7 +698,15 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
}
for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters())
{
- if (!type(*var)->canLiveOutsideStorage())
+ if (
+ type(*var)->category() == Type::Category::Mapping &&
+ !type(*var)->dataStoredIn(DataLocation::Storage)
+ )
+ m_errorReporter.typeError(var->location(), "Mapping types can only have a data location of \"storage\".");
+ else if (
+ !type(*var)->canLiveOutsideStorage() &&
+ _function.visibility() > FunctionDefinition::Visibility::Internal
+ )
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction)))
m_errorReporter.fatalTypeError(var->location(), "Internal or recursive type is not allowed for public or external functions.");
@@ -690,10 +767,12 @@ bool TypeChecker::visit(FunctionDefinition const& _function)
bool TypeChecker::visit(VariableDeclaration const& _variable)
{
// Forbid any variable declarations inside interfaces unless they are part of
- // a function's input/output parameters.
+ // * a function's input/output parameters,
+ // * or inside of a struct definition.
if (
m_scope->contractKind() == ContractDefinition::ContractKind::Interface
&& !_variable.isCallableParameter()
+ && !m_insideStruct
)
m_errorReporter.typeError(_variable.location(), "Variables cannot be declared in interfaces.");
@@ -711,8 +790,6 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
expectType(*_variable.value(), *varType);
if (_variable.isConstant())
{
- if (!_variable.isStateVariable())
- m_errorReporter.typeError(_variable.location(), "Illegal use of \"constant\" specifier.");
if (!_variable.type()->isValueType())
{
bool allowed = false;
@@ -742,7 +819,9 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
)
m_errorReporter.typeError(_variable.location(), "Internal or recursive type is not allowed for public state variables.");
- if (varType->category() == Type::Category::Array)
+ switch (varType->category())
+ {
+ case Type::Category::Array:
if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get()))
if (
((arrayType->location() == DataLocation::Memory) ||
@@ -750,6 +829,18 @@ bool TypeChecker::visit(VariableDeclaration const& _variable)
!arrayType->validForCalldata()
)
m_errorReporter.typeError(_variable.location(), "Array is too large to be encoded.");
+ break;
+ case Type::Category::Mapping:
+ if (auto mappingType = dynamic_cast<MappingType const*>(varType.get()))
+ if (
+ mappingType->keyType()->isDynamicallySized() &&
+ _variable.visibility() == Declaration::Visibility::Public
+ )
+ m_errorReporter.typeError(_variable.location(), "Dynamically-sized keys for public mappings are not supported.");
+ break;
+ default:
+ break;
+ }
return false;
}
@@ -818,7 +909,17 @@ bool TypeChecker::visit(EventDefinition const& _eventDef)
for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters())
{
if (var->isIndexed())
+ {
numIndexed++;
+ if (
+ _eventDef.sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2) &&
+ dynamic_cast<ReferenceType const*>(type(*var).get())
+ )
+ m_errorReporter.typeError(
+ var->location(),
+ "Indexed reference types cannot yet be used with ABIEncoderV2."
+ );
+ }
if (!type(*var)->canLiveOutsideStorage())
m_errorReporter.typeError(var->location(), "Type is required to live outside storage.");
if (!type(*var)->interfaceType(false))
@@ -1282,7 +1383,8 @@ void TypeChecker::endVisit(ExpressionStatement const& _statement)
if (
kind == FunctionType::Kind::BareCall ||
kind == FunctionType::Kind::BareCallCode ||
- kind == FunctionType::Kind::BareDelegateCall
+ kind == FunctionType::Kind::BareDelegateCall ||
+ kind == FunctionType::Kind::BareStaticCall
)
m_errorReporter.warning(_statement.location(), "Return value of low-level calls not used.");
else if (kind == FunctionType::Kind::Send)
@@ -1626,10 +1728,13 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
else
{
TypePointer const& argType = type(*arguments.front());
+ // Resulting data location is memory unless we are converting from a reference
+ // type with a different data location.
+ // (data location cannot yet be specified for type conversions)
+ DataLocation dataLoc = DataLocation::Memory;
if (auto argRefType = dynamic_cast<ReferenceType const*>(argType.get()))
- // do not change the data location when converting
- // (data location cannot yet be specified for type conversions)
- resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType);
+ dataLoc = argRefType->location();
+ resultType = ReferenceType::copyForLocationIfReference(dataLoc, resultType);
if (!argType->isExplicitlyConvertibleTo(*resultType))
m_errorReporter.typeError(
_functionCall.location(),
@@ -1674,6 +1779,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
return false;
}
+ if (functionType->kind() == FunctionType::Kind::BareStaticCall && !m_evmVersion.hasStaticCall())
+ m_errorReporter.typeError(_functionCall.location(), "\"staticcall\" is not supported by the VM version.");
+
auto returnTypes =
allowDynamicTypes ?
functionType->returnParameterTypes() :
@@ -1716,7 +1824,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
}
- if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
+ bool const abiEncoderV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2);
+
+ if (functionType->kind() == FunctionType::Kind::ABIDecode)
+ _functionCall.annotation().type = typeCheckABIDecodeAndRetrieveReturnType(_functionCall, abiEncoderV2);
+ else if (functionType->takesArbitraryParameters() && arguments.size() < parameterTypes.size())
{
solAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, "");
m_errorReporter.typeError(
@@ -1750,7 +1862,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
else if (
functionType->kind() == FunctionType::Kind::BareCall ||
functionType->kind() == FunctionType::Kind::BareCallCode ||
- functionType->kind() == FunctionType::Kind::BareDelegateCall
+ functionType->kind() == FunctionType::Kind::BareDelegateCall ||
+ functionType->kind() == FunctionType::Kind::BareStaticCall
)
{
if (arguments.empty())
@@ -1771,8 +1884,6 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
}
else if (isPositionalCall)
{
- bool const abiEncodeV2 = m_scope->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::ABIEncoderV2);
-
for (size_t i = 0; i < arguments.size(); ++i)
{
auto const& argType = type(*arguments[i]);
@@ -1785,7 +1896,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
m_errorReporter.typeError(arguments[i]->location(), "Invalid rational number (too large or division by zero).");
errored = true;
}
- if (!errored && !argType->fullEncodingType(false, abiEncodeV2, !functionType->padArguments()))
+ if (!errored && !argType->fullEncodingType(false, abiEncoderV2, !functionType->padArguments()))
m_errorReporter.typeError(arguments[i]->location(), "This type cannot be encoded.");
}
else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i]))
@@ -1800,7 +1911,8 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
if (
functionType->kind() == FunctionType::Kind::BareCall ||
functionType->kind() == FunctionType::Kind::BareCallCode ||
- functionType->kind() == FunctionType::Kind::BareDelegateCall
+ functionType->kind() == FunctionType::Kind::BareDelegateCall ||
+ functionType->kind() == FunctionType::Kind::BareStaticCall
)
msg += " This function requires a single bytes argument. If all your arguments are value types, you can use abi.encode(...) to properly generate it.";
else if (
@@ -2348,22 +2460,6 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte
"."
);
}
-
- if (
- type(_expression)->category() == Type::Category::RationalNumber &&
- _expectedType.category() == Type::Category::FixedBytes
- )
- {
- auto literal = dynamic_cast<Literal const*>(&_expression);
-
- if (literal && !literal->isHexNumber())
- m_errorReporter.warning(
- _expression.location(),
- "Decimal literal assigned to bytesXX variable will be left-aligned. "
- "Use an explicit conversion to silence this warning."
- );
- }
-
}
void TypeChecker::requireLValue(Expression const& _expression)
diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h
index b696de85..4be0d1e4 100644
--- a/libsolidity/analysis/TypeChecker.h
+++ b/libsolidity/analysis/TypeChecker.h
@@ -91,6 +91,11 @@ private:
// and reports an error, if not.
void checkExpressionAssignment(Type const& _type, Expression const& _expression);
+ /// Performs type checks for ``abi.decode(bytes memory, (...))`` and returns the
+ /// return type (which is basically the second argument) if successful. It returns
+ /// the empty tuple type or error.
+ TypePointer typeCheckABIDecodeAndRetrieveReturnType(FunctionCall const& _functionCall, bool _abiEncoderV2);
+
virtual void endVisit(InheritanceSpecifier const& _inheritance) override;
virtual void endVisit(UsingForDirective const& _usingFor) override;
virtual bool visit(StructDefinition const& _struct) override;
@@ -149,6 +154,9 @@ private:
/// Flag indicating whether we are currently inside an EmitStatement.
bool m_insideEmitStatement = false;
+ /// Flag indicating whether we are currently inside a StructDefinition.
+ bool m_insideStruct = false;
+
ErrorReporter& m_errorReporter;
};
diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp
index d936ada0..e92ad2fa 100644
--- a/libsolidity/analysis/ViewPureChecker.cpp
+++ b/libsolidity/analysis/ViewPureChecker.cpp
@@ -295,7 +295,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{
// we can ignore the kind of magic and only look at the name of the member
set<string> static const pureMembers{
- "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "data", "sig", "blockhash"
+ "encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode", "data", "sig", "blockhash"
};
if (!pureMembers.count(member))
mutability = StateMutability::View;
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 23797d52..635ab024 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -397,7 +397,7 @@ SourceUnit const& Scopable::sourceUnit() const
{
ASTNode const* s = scope();
solAssert(s, "");
- // will not always be a declaratoion
+ // will not always be a declaration
while (dynamic_cast<Scopable const*>(s) && dynamic_cast<Scopable const*>(s)->scope())
s = dynamic_cast<Scopable const*>(s)->scope();
return dynamic_cast<SourceUnit const&>(*s);
@@ -418,6 +418,7 @@ bool VariableDeclaration::isLocalVariable() const
{
auto s = scope();
return
+ dynamic_cast<FunctionTypeName const*>(s) ||
dynamic_cast<CallableDeclaration const*>(s) ||
dynamic_cast<Block const*>(s) ||
dynamic_cast<ForStatement const*>(s);
@@ -425,14 +426,18 @@ bool VariableDeclaration::isLocalVariable() const
bool VariableDeclaration::isCallableParameter() const
{
- auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
- if (!callable)
- return false;
- for (auto const& variable: callable->parameters())
- if (variable.get() == this)
- return true;
- if (callable->returnParameterList())
- for (auto const& variable: callable->returnParameterList()->parameters())
+ if (isReturnParameter())
+ return true;
+
+ vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr;
+
+ if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
+ parameters = &funTypeName->parameterTypes();
+ else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ parameters = &callable->parameters();
+
+ if (parameters)
+ for (auto const& variable: *parameters)
if (variable.get() == this)
return true;
return false;
@@ -445,11 +450,16 @@ bool VariableDeclaration::isLocalOrReturn() const
bool VariableDeclaration::isReturnParameter() const
{
- auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
- if (!callable)
- return false;
- if (callable->returnParameterList())
- for (auto const& variable: callable->returnParameterList()->parameters())
+ vector<ASTPointer<VariableDeclaration>> const* returnParameters = nullptr;
+
+ if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
+ returnParameters = &funTypeName->returnParameterTypes();
+ else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ if (callable->returnParameterList())
+ returnParameters = &callable->returnParameterList()->parameters();
+
+ if (returnParameters)
+ for (auto const& variable: *returnParameters)
if (variable.get() == this)
return true;
return false;
@@ -457,15 +467,88 @@ bool VariableDeclaration::isReturnParameter() const
bool VariableDeclaration::isExternalCallableParameter() const
{
- auto const* callable = dynamic_cast<CallableDeclaration const*>(scope());
- if (!callable || callable->visibility() != Declaration::Visibility::External)
+ if (!isCallableParameter())
return false;
- for (auto const& variable: callable->parameters())
- if (variable.get() == this)
- return true;
+
+ if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ if (callable->visibility() == Declaration::Visibility::External)
+ return !isReturnParameter();
+
return false;
}
+bool VariableDeclaration::isInternalCallableParameter() const
+{
+ if (!isCallableParameter())
+ return false;
+
+ if (auto const* funTypeName = dynamic_cast<FunctionTypeName const*>(scope()))
+ return funTypeName->visibility() == Declaration::Visibility::Internal;
+ else if (auto const* callable = dynamic_cast<CallableDeclaration const*>(scope()))
+ return callable->visibility() <= Declaration::Visibility::Internal;
+ return false;
+}
+
+bool VariableDeclaration::isLibraryFunctionParameter() const
+{
+ if (!isCallableParameter())
+ return false;
+ if (auto const* funDef = dynamic_cast<FunctionDefinition const*>(scope()))
+ return dynamic_cast<ContractDefinition const&>(*funDef->scope()).isLibrary();
+ else
+ return false;
+}
+
+bool VariableDeclaration::isEventParameter() const
+{
+ return dynamic_cast<EventDefinition const*>(scope()) != nullptr;
+}
+
+bool VariableDeclaration::hasReferenceOrMappingType() const
+{
+ solAssert(typeName(), "");
+ solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
+ TypePointer const& type = typeName()->annotation().type;
+ return type->category() == Type::Category::Mapping || dynamic_cast<ReferenceType const*>(type.get());
+}
+
+set<VariableDeclaration::Location> VariableDeclaration::allowedDataLocations() const
+{
+ using Location = VariableDeclaration::Location;
+
+ if (!hasReferenceOrMappingType() || isStateVariable() || isEventParameter())
+ return set<Location>{ Location::Default };
+ else if (isStateVariable() && isConstant())
+ return set<Location>{ Location::Memory };
+ else if (isExternalCallableParameter())
+ {
+ set<Location> locations{ Location::CallData };
+ if (isLibraryFunctionParameter())
+ locations.insert(Location::Storage);
+ return locations;
+ }
+ else if (isCallableParameter())
+ {
+ set<Location> locations{ Location::Memory };
+ if (isInternalCallableParameter() || isLibraryFunctionParameter())
+ locations.insert(Location::Storage);
+ return locations;
+ }
+ else if (isLocalVariable())
+ {
+ solAssert(typeName(), "");
+ solAssert(typeName()->annotation().type, "Can only be called after reference resolution");
+ if (typeName()->annotation().type->category() == Type::Category::Mapping)
+ return set<Location>{ Location::Storage };
+ else
+ // TODO: add Location::Calldata once implemented for local variables.
+ return set<Location>{ Location::Memory, Location::Storage };
+ }
+ else
+ // Struct members etc.
+ return set<Location>{ Location::Default };
+}
+
TypePointer VariableDeclaration::type() const
{
return annotation().type;
@@ -580,7 +663,7 @@ bool Literal::passesAddressChecksum() const
return dev::passesAddressChecksum(value(), true);
}
-std::string Literal::getChecksummedAddress() const
+string Literal::getChecksummedAddress() const
{
solAssert(isHexNumber(), "Expected hex number");
/// Pad literal to be a proper hex address.
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 69c6fa05..b953211d 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -685,6 +685,8 @@ public:
virtual bool isLValue() const override;
virtual bool isPartOfExternalInterface() const override { return isPublic(); }
+ /// @returns true iff this variable is the parameter (or return parameter) of a function
+ /// (or function type name or event) or declared inside a function body.
bool isLocalVariable() const;
/// @returns true if this variable is a parameter or return parameter of a function.
bool isCallableParameter() const;
@@ -693,13 +695,27 @@ public:
/// @returns true if this variable is a local variable or return parameter.
bool isLocalOrReturn() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
+ /// This excludes parameters of external function type names.
bool isExternalCallableParameter() const;
+ /// @returns true if this variable is a parameter or return parameter of an internal function
+ /// or a function type of internal visibility.
+ bool isInternalCallableParameter() const;
+ /// @returns true iff this variable is a parameter(or return parameter of a library function
+ bool isLibraryFunctionParameter() const;
/// @returns true if the type of the variable does not need to be specified, i.e. it is declared
/// in the body of a function or modifier.
+ /// @returns true if this variable is a parameter of an event.
+ bool isEventParameter() const;
+ /// @returns true if the type of the variable is a reference or mapping type, i.e.
+ /// array, struct or mapping. These types can take a data location (and often require it).
+ /// Can only be called after reference resolution.
+ bool hasReferenceOrMappingType() const;
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; }
Location referenceLocation() const { return m_location; }
+ /// @returns a set of allowed storage locations for the variable.
+ std::set<Location> allowedDataLocations() const;
virtual TypePointer type() const override;
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index d9eb034d..a725618e 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -355,13 +355,7 @@ TypePointer Type::forLiteral(Literal const& _literal)
case Token::FalseLiteral:
return make_shared<BoolType>();
case Token::Number:
- {
- tuple<bool, rational> validLiteral = RationalNumberType::isValidLiteral(_literal);
- if (get<0>(validLiteral) == true)
- return make_shared<RationalNumberType>(get<1>(validLiteral));
- else
- return TypePointer();
- }
+ return RationalNumberType::forLiteral(_literal);
case Token::StringLiteral:
return make_shared<StringLiteralType>(_literal);
default:
@@ -400,17 +394,17 @@ TypePointer Type::fullEncodingType(bool _inLibraryCall, bool _encoderV2, bool _p
encodingType = encodingType->interfaceType(_inLibraryCall);
if (encodingType)
encodingType = encodingType->encodingType();
- if (auto structType = dynamic_cast<StructType const*>(encodingType.get()))
- {
- // Structs are fine in the following circumstances:
- // - ABIv2 without packed encoding or,
- // - storage struct for a library
- if (!(
- (_encoderV2 && !_packed) ||
- (structType->location() == DataLocation::Storage && _inLibraryCall)
- ))
+ // Structs are fine in the following circumstances:
+ // - ABIv2 without packed encoding or,
+ // - storage struct for a library
+ if (_inLibraryCall && encodingType->dataStoredIn(DataLocation::Storage))
+ return encodingType;
+ TypePointer baseType = encodingType;
+ while (auto const* arrayType = dynamic_cast<ArrayType const*>(baseType.get()))
+ baseType = arrayType->baseType();
+ if (dynamic_cast<StructType const*>(baseType.get()))
+ if (!_encoderV2 || _packed)
return TypePointer();
- }
return encodingType;
}
@@ -627,6 +621,7 @@ MemberList::MemberMap IntegerType::nativeMembers(ContractDefinition const*) cons
{"callcode", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareCallCode, false, StateMutability::Payable)},
{"delegatecall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareDelegateCall, false)},
{"send", make_shared<FunctionType>(strings{"uint"}, strings{"bool"}, FunctionType::Kind::Send)},
+ {"staticcall", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bool"}, FunctionType::Kind::BareStaticCall, false, StateMutability::View)},
{"transfer", make_shared<FunctionType>(strings{"uint"}, strings(), FunctionType::Kind::Transfer)}
};
else
@@ -779,6 +774,30 @@ tuple<bool, rational> RationalNumberType::parseRational(string const& _value)
}
}
+TypePointer RationalNumberType::forLiteral(Literal const& _literal)
+{
+ solAssert(_literal.token() == Token::Number, "");
+ tuple<bool, rational> validLiteral = isValidLiteral(_literal);
+ if (get<0>(validLiteral))
+ {
+ TypePointer compatibleBytesType;
+ if (_literal.isHexNumber())
+ {
+ size_t digitCount = count_if(
+ _literal.value().begin() + 2, // skip "0x"
+ _literal.value().end(),
+ [](unsigned char _c) -> bool { return isxdigit(_c); }
+ );
+ // require even number of digits
+ if (!(digitCount & 1))
+ compatibleBytesType = make_shared<FixedBytesType>(digitCount / 2);
+ }
+
+ return make_shared<RationalNumberType>(get<1>(validLiteral), compatibleBytesType);
+ }
+ return TypePointer();
+}
+
tuple<bool, rational> RationalNumberType::isValidLiteral(Literal const& _literal)
{
rational value;
@@ -918,14 +937,7 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false;
}
case Category::FixedBytes:
- {
- FixedBytesType const& fixedBytes = dynamic_cast<FixedBytesType const&>(_convertTo);
- if (isFractional())
- return false;
- if (integerType())
- return fixedBytes.numBytes() * 8 >= integerType()->numBits();
- return false;
- }
+ return (m_value == rational(0)) || (m_compatibleBytesType && *m_compatibleBytesType == _convertTo);
default:
return false;
}
@@ -933,11 +945,15 @@ bool RationalNumberType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool RationalNumberType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- TypePointer mobType = mobileType();
- return
- (mobType && mobType->isExplicitlyConvertibleTo(_convertTo)) ||
- (!isFractional() && _convertTo.category() == Category::FixedBytes)
- ;
+ if (isImplicitlyConvertibleTo(_convertTo))
+ return true;
+ else if (_convertTo.category() != Category::FixedBytes)
+ {
+ TypePointer mobType = mobileType();
+ return (mobType && mobType->isExplicitlyConvertibleTo(_convertTo));
+ }
+ else
+ return false;
}
TypePointer RationalNumberType::unaryOperatorResult(Token::Value _operator) const
@@ -1263,7 +1279,8 @@ shared_ptr<FixedPointType const> RationalNumberType::fixedPointType() const
return shared_ptr<FixedPointType const>();
// This means we round towards zero for positive and negative values.
bigint v = value.numerator() / value.denominator();
- if (negative)
+
+ if (negative && v != 0)
// modify value to satisfy bit requirements for negative numbers:
// add one bit for sign and decrement because negative numbers can be larger
v = (v - 1) << 1;
@@ -2502,6 +2519,7 @@ string FunctionType::richIdentifier() const
case Kind::BareCall: id += "barecall"; break;
case Kind::BareCallCode: id += "barecallcode"; break;
case Kind::BareDelegateCall: id += "baredelegatecall"; break;
+ case Kind::BareStaticCall: id += "barestaticcall"; break;
case Kind::Creation: id += "creation"; break;
case Kind::Send: id += "send"; break;
case Kind::Transfer: id += "transfer"; break;
@@ -2533,6 +2551,7 @@ string FunctionType::richIdentifier() const
case Kind::ABIEncodePacked: id += "abiencodepacked"; break;
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
+ case Kind::ABIDecode: id += "abidecode"; break;
}
id += "_" + stateMutabilityToString(m_stateMutability);
id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
@@ -2549,28 +2568,10 @@ bool FunctionType::operator==(Type const& _other) const
{
if (_other.category() != category())
return false;
-
FunctionType const& other = dynamic_cast<FunctionType const&>(_other);
- if (
- m_kind != other.m_kind ||
- m_stateMutability != other.stateMutability() ||
- m_parameterTypes.size() != other.m_parameterTypes.size() ||
- m_returnParameterTypes.size() != other.m_returnParameterTypes.size()
- )
+ if (!equalExcludingStateMutability(other))
return false;
-
- auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; };
- if (
- !equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), other.m_parameterTypes.cbegin(), typeCompare) ||
- !equal(m_returnParameterTypes.cbegin(), m_returnParameterTypes.cend(), other.m_returnParameterTypes.cbegin(), typeCompare)
- )
- return false;
- //@todo this is ugly, but cannot be prevented right now
- if (m_gasSet != other.m_gasSet || m_valueSet != other.m_valueSet)
- return false;
- if (bound() != other.bound())
- return false;
- if (bound() && *selfType() != *other.selfType())
+ if (m_stateMutability != other.stateMutability())
return false;
return true;
}
@@ -2586,6 +2587,31 @@ bool FunctionType::isExplicitlyConvertibleTo(Type const& _convertTo) const
return _convertTo.category() == category();
}
+bool FunctionType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+{
+ if (_convertTo.category() != category())
+ return false;
+
+ FunctionType const& convertTo = dynamic_cast<FunctionType const&>(_convertTo);
+
+ if (!equalExcludingStateMutability(convertTo))
+ return false;
+
+ // non-payable should not be convertible to payable
+ if (m_stateMutability != StateMutability::Payable && convertTo.stateMutability() == StateMutability::Payable)
+ return false;
+
+ // payable should be convertible to non-payable, because you are free to pay 0 ether
+ if (m_stateMutability == StateMutability::Payable && convertTo.stateMutability() == StateMutability::NonPayable)
+ return true;
+
+ // e.g. pure should be convertible to view, but not the other way around.
+ if (m_stateMutability > convertTo.stateMutability())
+ return false;
+
+ return true;
+}
+
TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const
{
if (_operator == Token::Value::Delete)
@@ -2676,6 +2702,7 @@ unsigned FunctionType::sizeOnStack() const
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
case Kind::Internal:
case Kind::ArrayPush:
case Kind::ArrayPop:
@@ -2743,6 +2770,7 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
{
MemberList::MemberMap members;
if (m_kind == Kind::External)
@@ -2843,6 +2871,38 @@ bool FunctionType::hasEqualParameterTypes(FunctionType const& _other) const
);
}
+bool FunctionType::hasEqualReturnTypes(FunctionType const& _other) const
+{
+ if (m_returnParameterTypes.size() != _other.m_returnParameterTypes.size())
+ return false;
+ return equal(
+ m_returnParameterTypes.cbegin(),
+ m_returnParameterTypes.cend(),
+ _other.m_returnParameterTypes.cbegin(),
+ [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }
+ );
+}
+
+bool FunctionType::equalExcludingStateMutability(FunctionType const& _other) const
+{
+ if (m_kind != _other.m_kind)
+ return false;
+
+ if (!hasEqualParameterTypes(_other) || !hasEqualReturnTypes(_other))
+ return false;
+
+ //@todo this is ugly, but cannot be prevented right now
+ if (m_gasSet != _other.m_gasSet || m_valueSet != _other.m_valueSet)
+ return false;
+
+ if (bound() != _other.bound())
+ return false;
+
+ solAssert(!bound() || *selfType() == *_other.selfType(), "");
+
+ return true;
+}
+
bool FunctionType::isBareCall() const
{
switch (m_kind)
@@ -2850,6 +2910,7 @@ bool FunctionType::isBareCall() const
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
case Kind::ECRecover:
case Kind::SHA256:
case Kind::RIPEMD160:
@@ -2863,6 +2924,17 @@ string FunctionType::externalSignature() const
{
solAssert(m_declaration != nullptr, "External signature of function needs declaration");
solAssert(!m_declaration->name().empty(), "Fallback function has no signature.");
+ switch (kind())
+ {
+ case Kind::Internal:
+ case Kind::External:
+ case Kind::CallCode:
+ case Kind::DelegateCall:
+ case Kind::Event:
+ break;
+ default:
+ solAssert(false, "Invalid function type for requesting external signature.");
+ }
bool const inLibrary = dynamic_cast<ContractDefinition const&>(*m_declaration->scope()).isLibrary();
FunctionTypePointer external = interfaceFunctionType();
@@ -2899,7 +2971,8 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncode ||
m_kind == Kind::ABIEncodePacked ||
m_kind == Kind::ABIEncodeWithSelector ||
- m_kind == Kind::ABIEncodeWithSignature;
+ m_kind == Kind::ABIEncodeWithSignature ||
+ m_kind == Kind::ABIDecode;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
@@ -2992,6 +3065,7 @@ bool FunctionType::padArguments() const
case Kind::BareCall:
case Kind::BareCallCode:
case Kind::BareDelegateCall:
+ case Kind::BareStaticCall:
case Kind::SHA256:
case Kind::RIPEMD160:
case Kind::KECCAK256:
@@ -3253,6 +3327,15 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
FunctionType::Kind::ABIEncodeWithSignature,
true,
StateMutability::Pure
+ )},
+ {"decode", make_shared<FunctionType>(
+ TypePointers(),
+ TypePointers(),
+ strings{},
+ strings{},
+ FunctionType::Kind::ABIDecode,
+ true,
+ StateMutability::Pure
)}
});
default:
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 3b57109d..0b1b5d6d 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -423,12 +423,12 @@ public:
virtual Category category() const override { return Category::RationalNumber; }
- /// @returns true if the literal is a valid integer.
- static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
+ static TypePointer forLiteral(Literal const& _literal);
- explicit RationalNumberType(rational const& _value):
- m_value(_value)
+ explicit RationalNumberType(rational const& _value, TypePointer const& _compatibleBytesType = TypePointer()):
+ m_value(_value), m_compatibleBytesType(_compatibleBytesType)
{}
+
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -446,7 +446,8 @@ public:
/// @returns the smallest integer type that can hold the value or an empty pointer if not possible.
std::shared_ptr<IntegerType const> integerType() const;
- /// @returns the smallest fixed type that can hold the value or incurs the least precision loss.
+ /// @returns the smallest fixed type that can hold the value or incurs the least precision loss,
+ /// unless the value was truncated, then a suitable type will be chosen to indicate such event.
/// If the integer part does not fit, returns an empty pointer.
std::shared_ptr<FixedPointType const> fixedPointType() const;
@@ -462,6 +463,13 @@ public:
private:
rational m_value;
+ /// Bytes type to which the rational can be explicitly converted.
+ /// Empty for all rationals that are not directly parsed from hex literals.
+ TypePointer m_compatibleBytesType;
+
+ /// @returns true if the literal is a valid integer.
+ static std::tuple<bool, rational> isValidLiteral(Literal const& _literal);
+
/// @returns true if the literal is a valid rational number.
static std::tuple<bool, rational> parseRational(std::string const& _value);
@@ -896,6 +904,7 @@ public:
BareCall, ///< CALL without function hash
BareCallCode, ///< CALLCODE without function hash
BareDelegateCall, ///< DELEGATECALL without function hash
+ BareStaticCall, ///< STATICCALL without function hash
Creation, ///< external call using CREATE
Send, ///< CALL, but without data and gas
Transfer, ///< CALL, but without data and throws on error
@@ -926,7 +935,8 @@ public:
ABIEncodePacked,
ABIEncodeWithSelector,
ABIEncodeWithSignature,
- GasLeft ///< gasleft()
+ ABIDecode,
+ GasLeft, ///< gasleft()
};
virtual Category category() const override { return Category::Function; }
@@ -1005,6 +1015,7 @@ public:
virtual std::string richIdentifier() const override;
virtual bool operator==(Type const& _other) const override;
+ virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override;
@@ -1034,10 +1045,14 @@ public:
/// @param _selfType if the function is bound, this has to be supplied and is the type of the
/// expression the function is called on.
bool canTakeArguments(TypePointers const& _arguments, TypePointer const& _selfType = TypePointer()) const;
- /// @returns true if the types of parameters are equal (doesn't check return parameter types)
+ /// @returns true if the types of parameters are equal (does not check return parameter types)
bool hasEqualParameterTypes(FunctionType const& _other) const;
+ /// @returns true iff the return types are equal (does not check parameter types)
+ bool hasEqualReturnTypes(FunctionType const& _other) const;
+ /// @returns true iff the function type is equal to the given type, ignoring state mutability differences.
+ bool equalExcludingStateMutability(FunctionType const& _other) const;
- /// @returns true if the ABI is used for this call (only meaningful for external calls)
+ /// @returns true if the ABI is NOT used for this call (only meaningful for external calls)
bool isBareCall() const;
Kind const& kind() const { return m_kind; }
StateMutability stateMutability() const { return m_stateMutability; }
@@ -1076,6 +1091,7 @@ public:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
return true;
default:
return false;
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 412a7255..53a06090 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -571,6 +571,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
_functionCall.expression().accept(*this);
appendExternalFunctionCall(function, arguments);
break;
@@ -1070,6 +1071,27 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
// stack now: <memory pointer>
break;
}
+ case FunctionType::Kind::ABIDecode:
+ {
+ arguments.front()->accept(*this);
+ TypePointer firstArgType = arguments.front()->annotation().type;
+ TypePointers const& targetTypes = dynamic_cast<TupleType const&>(*_functionCall.annotation().type).components();
+ if (
+ *firstArgType == ArrayType(DataLocation::CallData) ||
+ *firstArgType == ArrayType(DataLocation::CallData, true)
+ )
+ utils().abiDecode(targetTypes, false);
+ else
+ {
+ utils().convertType(*firstArgType, ArrayType(DataLocation::Memory));
+ m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
+ m_context << Instruction::SWAP1 << Instruction::MLOAD;
+ // stack now: <mem_pos> <length>
+
+ utils().abiDecode(targetTypes, true);
+ }
+ break;
+ }
case FunctionType::Kind::GasLeft:
m_context << Instruction::GAS;
break;
@@ -1143,18 +1165,19 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "event not found");
// no-op, because the parent node will do the job
break;
- case FunctionType::Kind::External:
- case FunctionType::Kind::Creation:
case FunctionType::Kind::DelegateCall:
+ _memberAccess.expression().accept(*this);
+ m_context << funType->externalIdentifier();
+ break;
case FunctionType::Kind::CallCode:
+ case FunctionType::Kind::External:
+ case FunctionType::Kind::Creation:
case FunctionType::Kind::Send:
case FunctionType::Kind::BareCall:
case FunctionType::Kind::BareCallCode:
case FunctionType::Kind::BareDelegateCall:
+ case FunctionType::Kind::BareStaticCall:
case FunctionType::Kind::Transfer:
- _memberAccess.expression().accept(*this);
- m_context << funType->externalIdentifier();
- break;
case FunctionType::Kind::Log0:
case FunctionType::Kind::Log1:
case FunctionType::Kind::Log2:
@@ -1252,7 +1275,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
);
m_context << Instruction::BALANCE;
}
- else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall"}).count(member))
+ else if ((set<string>{"send", "transfer", "call", "callcode", "delegatecall", "staticcall"}).count(member))
utils().convertType(
*_memberAccess.expression().annotation().type,
IntegerType(160, IntegerType::Modifier::Address),
@@ -1804,10 +1827,13 @@ void ExpressionCompiler::appendExternalFunctionCall(
utils().moveToStackTop(gasValueSize, _functionType.selfType()->sizeOnStack());
auto funKind = _functionType.kind();
- bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall;
+
+ solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
+
+ bool returnSuccessCondition = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
bool isCallCode = funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::CallCode;
bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
- bool useStaticCall = _functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall();
+ bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index 836e30d2..e800b278 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -58,22 +58,31 @@ using namespace std;
using namespace dev;
using namespace dev::solidity;
-void CompilerStack::setRemappings(vector<string> const& _remappings)
+boost::optional<CompilerStack::Remapping> CompilerStack::parseRemapping(string const& _remapping)
+{
+ auto eq = find(_remapping.begin(), _remapping.end(), '=');
+ if (eq == _remapping.end())
+ return {};
+
+ auto colon = find(_remapping.begin(), eq, ':');
+
+ Remapping r;
+
+ r.context = colon == eq ? string() : string(_remapping.begin(), colon);
+ r.prefix = colon == eq ? string(_remapping.begin(), eq) : string(colon + 1, eq);
+ r.target = string(eq + 1, _remapping.end());
+
+ if (r.prefix.empty())
+ return {};
+
+ return r;
+}
+
+void CompilerStack::setRemappings(vector<Remapping> const& _remappings)
{
- vector<Remapping> remappings;
for (auto const& remapping: _remappings)
- {
- auto eq = find(remapping.begin(), remapping.end(), '=');
- if (eq == remapping.end())
- continue; // ignore
- auto colon = find(remapping.begin(), eq, ':');
- Remapping r;
- r.context = colon == eq ? string() : string(remapping.begin(), colon);
- r.prefix = colon == eq ? string(remapping.begin(), eq) : string(colon + 1, eq);
- r.target = string(eq + 1, remapping.end());
- remappings.push_back(r);
- }
- swap(m_remappings, remappings);
+ solAssert(!remapping.prefix.empty(), "");
+ m_remappings = _remappings;
}
void CompilerStack::setEVMVersion(EVMVersion _version)
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index 2234a8c9..9a15fbf0 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -84,6 +84,13 @@ public:
CompilationSuccessful
};
+ struct Remapping
+ {
+ std::string context;
+ std::string prefix;
+ std::string target;
+ };
+
/// Creates a new compiler stack.
/// @param _readFile callback to used to read files for import statements. Must return
/// and must not emit exceptions.
@@ -103,8 +110,11 @@ public:
/// All settings, with the exception of remappings, are reset.
void reset(bool _keepSources = false);
- /// Sets path remappings in the format "context:prefix=target"
- void setRemappings(std::vector<std::string> const& _remappings);
+ // Parses a remapping of the format "context:prefix=target".
+ static boost::optional<Remapping> parseRemapping(std::string const& _remapping);
+
+ /// Sets path remappings.
+ void setRemappings(std::vector<Remapping> const& _remappings);
/// Sets library addresses. Addresses are cleared iff @a _libraries is missing.
/// Will not take effect before running compile.
@@ -319,13 +329,6 @@ private:
FunctionDefinition const& _function
) const;
- struct Remapping
- {
- std::string context;
- std::string prefix;
- std::string target;
- };
-
ReadCallback::Callback m_readFile;
ReadCallback::Callback m_smtQuery;
bool m_optimize = false;
diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp
index 29a5b798..a8716862 100644
--- a/libsolidity/interface/Natspec.cpp
+++ b/libsolidity/interface/Natspec.cpp
@@ -36,6 +36,15 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
Json::Value doc;
Json::Value methods(Json::objectValue);
+ auto constructorDefinition(_contractDef.constructor());
+ if (constructorDefinition)
+ {
+ string value = extractDoc(constructorDefinition->annotation().docTags, "notice");
+ if (!value.empty())
+ // add the constructor, only if we have any documentation to add
+ methods["constructor"] = Json::Value(value);
+ }
+
string notice = extractDoc(_contractDef.annotation().docTags, "notice");
if (!notice.empty())
doc["notice"] = Json::Value(notice);
@@ -73,33 +82,21 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
if (!dev.empty())
doc["details"] = Json::Value(dev);
+ auto constructorDefinition(_contractDef.constructor());
+ if (constructorDefinition) {
+ Json::Value constructor(devDocumentation(constructorDefinition->annotation().docTags));
+ if (!constructor.empty())
+ // add the constructor, only if we have any documentation to add
+ methods["constructor"] = constructor;
+ }
+
for (auto const& it: _contractDef.interfaceFunctions())
{
if (!it.second->hasDeclaration())
continue;
- Json::Value method;
if (auto fun = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
{
- auto dev = extractDoc(fun->annotation().docTags, "dev");
- if (!dev.empty())
- method["details"] = Json::Value(dev);
-
- auto author = extractDoc(fun->annotation().docTags, "author");
- if (!author.empty())
- method["author"] = author;
-
- auto ret = extractDoc(fun->annotation().docTags, "return");
- if (!ret.empty())
- method["return"] = ret;
-
- Json::Value params(Json::objectValue);
- auto paramRange = fun->annotation().docTags.equal_range("param");
- for (auto i = paramRange.first; i != paramRange.second; ++i)
- params[i->second.paramName] = Json::Value(i->second.content);
-
- if (!params.empty())
- method["params"] = params;
-
+ Json::Value method(devDocumentation(fun->annotation().docTags));
if (!method.empty())
// add the function, only if we have any documentation to add
methods[it.second->externalSignature()] = method;
@@ -118,3 +115,31 @@ string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const&
value += i->second.content;
return value;
}
+
+Json::Value Natspec::devDocumentation(std::multimap<std::string, DocTag> const &_tags)
+{
+ Json::Value json(Json::objectValue);
+ auto dev = extractDoc(_tags, "dev");
+ if (!dev.empty())
+ json["details"] = Json::Value(dev);
+
+ auto author = extractDoc(_tags, "author");
+ if (!author.empty())
+ json["author"] = author;
+
+ // for constructors, the "return" node will never exist. invalid tags
+ // will already generate an error within dev::solidity::DocStringAnalyzer.
+ auto ret = extractDoc(_tags, "return");
+ if (!ret.empty())
+ json["return"] = ret;
+
+ Json::Value params(Json::objectValue);
+ auto paramRange = _tags.equal_range("param");
+ for (auto i = paramRange.first; i != paramRange.second; ++i)
+ params[i->second.paramName] = Json::Value(i->second.content);
+
+ if (!params.empty())
+ json["params"] = params;
+
+ return json;
+}
diff --git a/libsolidity/interface/Natspec.h b/libsolidity/interface/Natspec.h
index 6a827d3b..0be4dda2 100644
--- a/libsolidity/interface/Natspec.h
+++ b/libsolidity/interface/Natspec.h
@@ -54,6 +54,12 @@ public:
private:
/// @returns concatenation of all content under the given tag name.
static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name);
+
+ /// Helper-function that will create a json object with dev specific annotations, if present.
+ /// @param _tags docTags that are used.
+ /// @return A JSON representation
+ /// of the contract's developer documentation
+ static Json::Value devDocumentation(std::multimap<std::string, DocTag> const &_tags);
};
} //solidity NS
diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp
index 58b84163..c1996777 100644
--- a/libsolidity/interface/StandardCompiler.cpp
+++ b/libsolidity/interface/StandardCompiler.cpp
@@ -326,9 +326,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input)
m_compilerStack.setEVMVersion(*version);
}
- vector<string> remappings;
+ vector<CompilerStack::Remapping> remappings;
for (auto const& remapping: settings.get("remappings", Json::Value()))
- remappings.push_back(remapping.asString());
+ {
+ if (auto r = CompilerStack::parseRemapping(remapping.asString()))
+ remappings.emplace_back(std::move(*r));
+ else
+ return formatFatalError("JSONError", "Invalid remapping: \"" + remapping.asString() + "\"");
+ }
m_compilerStack.setRemappings(remappings);
Json::Value optimizerSettings = settings.get("optimizer", Json::Value());