diff options
Diffstat (limited to 'libsolidity')
24 files changed, 267 insertions, 81 deletions
diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index fd39d860..34cb61d8 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -39,6 +39,7 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{ make_shared<MagicVariableDeclaration>("assert", make_shared<FunctionType>(strings{"bool"}, strings{}, FunctionType::Kind::Assert, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), make_shared<MagicVariableDeclaration>("ecrecover", make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Kind::ECRecover, false, StateMutability::Pure)), + make_shared<MagicVariableDeclaration>("gasleft", make_shared<FunctionType>(strings(), strings{"uint256"}, FunctionType::Kind::GasLeft, false, StateMutability::View)), make_shared<MagicVariableDeclaration>("keccak256", make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Kind::SHA3, true, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("log0", make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Kind::Log0)), make_shared<MagicVariableDeclaration>("log1", make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Kind::Log1)), diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 985c44d0..296a39c2 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -278,8 +278,9 @@ bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) }; // Will be re-generated later with correct information + // We use the latest EVM version because we will re-run it anyway. assembly::AsmAnalysisInfo analysisInfo; - assembly::AsmAnalyzer(analysisInfo, errorsIgnored, assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); + assembly::AsmAnalyzer(analysisInfo, errorsIgnored, EVMVersion(), assembly::AsmFlavour::Loose, resolver).analyze(_inlineAssembly.operations()); return false; } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index cc7492dd..9846a0d0 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -875,6 +875,7 @@ bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) assembly::AsmAnalyzer analyzer( *_inlineAssembly.annotation().analysisInfo, m_errorReporter, + m_evmVersion, assembly::AsmFlavour::Loose, identifierAccess ); diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index 16796b63..2ba31232 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -22,6 +22,8 @@ #pragma once +#include <libsolidity/interface/EVMVersion.h> + #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> #include <libsolidity/ast/ASTForward.h> @@ -43,7 +45,10 @@ class TypeChecker: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - TypeChecker(ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + TypeChecker(EVMVersion _evmVersion, ErrorReporter& _errorReporter): + m_evmVersion(_evmVersion), + m_errorReporter(_errorReporter) + {} /// Performs type checking on the given contract and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings @@ -132,6 +137,8 @@ private: ContractDefinition const* m_scope = nullptr; + EVMVersion m_evmVersion; + /// Flag indicating whether we are currently inside an EmitStatement. bool m_insideEmitStatement = false; diff --git a/libsolidity/ast/ExperimentalFeatures.h b/libsolidity/ast/ExperimentalFeatures.h index 3ecfac7b..a17778b4 100644 --- a/libsolidity/ast/ExperimentalFeatures.h +++ b/libsolidity/ast/ExperimentalFeatures.h @@ -40,6 +40,7 @@ static const std::map<ExperimentalFeature, bool> ExperimentalFeatureOnlyAnalysis { { ExperimentalFeature::SMTChecker, true }, { ExperimentalFeature::TestOnlyAnalysis, true }, + { ExperimentalFeature::V050, true } }; static const std::map<std::string, ExperimentalFeature> ExperimentalFeatureNames = diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 771ae643..26bde1c4 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -3000,8 +3000,10 @@ bool MagicType::operator==(Type const& _other) const return other.m_kind == m_kind; } -MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const +MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const* _contract) const { + solAssert(_contract, ""); + const bool v050 = _contract->sourceUnit().annotation().experimentalFeatures.count(ExperimentalFeature::V050); switch (m_kind) { case Kind::Block: @@ -3014,13 +3016,17 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const {"gaslimit", make_shared<IntegerType>(256)} }); case Kind::Message: - return MemberList::MemberMap({ + { + std::vector<MemberList::Member> members = { {"sender", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, - {"gas", make_shared<IntegerType>(256)}, {"value", make_shared<IntegerType>(256)}, {"data", make_shared<ArrayType>(DataLocation::CallData)}, {"sig", make_shared<FixedBytesType>(4)} - }); + }; + if (!v050) + members.emplace_back("gas", make_shared<IntegerType>(256)); + return members; + } case Kind::Transaction: return MemberList::MemberMap({ {"origin", make_shared<IntegerType>(160, IntegerType::Modifier::Address)}, diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 7985521e..c20a025f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -902,7 +902,8 @@ public: ByteArrayPush, ///< .push() to a dynamically sized byte array in storage ObjectCreation, ///< array creation using new Assert, ///< assert() - Require ///< require() + Require, ///< require() + GasLeft ///< gasleft() }; virtual Category category() const override { return Category::Function; } diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 06654486..f6865d75 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -22,22 +22,25 @@ #pragma once -#include <ostream> -#include <functional> #include <libsolidity/codegen/CompilerContext.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libevmasm/Assembly.h> +#include <ostream> +#include <functional> + namespace dev { namespace solidity { class Compiler { public: - explicit Compiler(bool _optimize = false, unsigned _runs = 200): + explicit Compiler(EVMVersion _evmVersion = EVMVersion{}, bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), m_optimizeRuns(_runs), - m_runtimeContext(), - m_context(&m_runtimeContext) + m_runtimeContext(_evmVersion), + m_context(_evmVersion, &m_runtimeContext) { } /// Compiles a contract. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 0198a107..ebf0213a 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -329,6 +329,7 @@ void CompilerContext::appendInlineAssembly( analyzerResult = assembly::AsmAnalyzer( analysisInfo, errorReporter, + m_evmVersion, assembly::AsmFlavour::Strict, identifierAccess.resolve ).analyze(*parserResult); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index a155a3a5..cf626683 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -24,6 +24,8 @@ #include <libsolidity/codegen/ABIFunctions.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libsolidity/ast/ASTForward.h> #include <libsolidity/ast/Types.h> #include <libsolidity/ast/ASTAnnotations.h> @@ -50,14 +52,17 @@ namespace solidity { class CompilerContext { public: - explicit CompilerContext(CompilerContext* _runtimeContext = nullptr): + explicit CompilerContext(EVMVersion _evmVersion = EVMVersion{}, CompilerContext* _runtimeContext = nullptr): m_asm(std::make_shared<eth::Assembly>()), + m_evmVersion(_evmVersion), m_runtimeContext(_runtimeContext) { if (m_runtimeContext) m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } + EVMVersion const& evmVersion() const { return m_evmVersion; } + /// Update currently enabled set of experimental features. void setExperimentalFeatures(std::set<ExperimentalFeature> const& _features) { m_experimentalFeatures = _features; } /// @returns true if the given feature is enabled. @@ -204,7 +209,7 @@ public: void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } /// Run optimisation step. - void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); } + void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() { return m_runtimeContext; } @@ -287,6 +292,8 @@ private: } m_functionCompilationQueue; eth::AssemblyPointer m_asm; + /// Version of the EVM to compile against. + EVMVersion m_evmVersion; /// Activated experimental features. std::set<ExperimentalFeature> m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index ebb718a5..5a9498f0 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -1059,7 +1059,7 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); } -eth::AssemblyPointer ContractCompiler::cloneRuntime() +eth::AssemblyPointer ContractCompiler::cloneRuntime() const { eth::Assembly a; a << Instruction::CALLDATASIZE; @@ -1070,7 +1070,7 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime() // this is the address which has to be substituted by the linker. //@todo implement as special "marker" AssemblyItem. a << u256("0xcafecafecafecafecafecafecafecafecafecafe"); - a << u256(eth::GasCosts::callGas + 10) << Instruction::GAS << Instruction::SUB; + a << u256(eth::GasCosts::callGas(m_context.evmVersion()) + 10) << Instruction::GAS << Instruction::SUB; a << Instruction::DELEGATECALL; //Propagate error condition (if DELEGATECALL pushes 0 on stack). a << Instruction::ISZERO; diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index d698dc71..8559ea58 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -43,7 +43,7 @@ public: m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); + m_context = CompilerContext(_context.evmVersion(), _runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( @@ -125,7 +125,7 @@ private: void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); /// @returns the runtime assembly for clone contracts. - static eth::AssemblyPointer cloneRuntime(); + eth::AssemblyPointer cloneRuntime() const; bool const m_optimise; /// Pointer to the runtime compiler in case this is a creation compiler. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 61920592..0ce70f38 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -906,6 +906,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << success; break; } + case FunctionType::Kind::GasLeft: + m_context << Instruction::GAS; + break; default: solAssert(false, "Invalid function type."); } @@ -921,6 +924,8 @@ bool ExpressionCompiler::visit(NewExpression const&) bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) { + bool const v050 = m_context.experimentalFeatureActive(ExperimentalFeature::V050); + CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); // Check whether the member is a bound function. ASTString const& member = _memberAccess.memberName(); @@ -1136,7 +1141,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if (member == "origin") m_context << Instruction::ORIGIN; else if (member == "gas") + { + solAssert(!v050, ""); m_context << Instruction::GAS; + } else if (member == "gasprice") m_context << Instruction::GASPRICE; else if (member == "data") @@ -1671,16 +1679,19 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().storeFreeMemoryPointer(); } - // Touch the end of the output area so that we do not pay for memory resize during the call - // (which we would have to subtract from the gas left) - // We could also just use MLOAD; POP right before the gas calculation, but the optimizer - // would remove that, so we use MSTORE here. - if (!_functionType.gasSet() && retSize > 0) + if (!m_context.evmVersion().canOverchargeGasForCall()) { - m_context << u256(0); - utils().fetchFreeMemoryPointer(); - // This touches too much, but that way we save some rounding arithmetics - m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; + // Touch the end of the output area so that we do not pay for memory resize during the call + // (which we would have to subtract from the gas left) + // We could also just use MLOAD; POP right before the gas calculation, but the optimizer + // would remove that, so we use MSTORE here. + if (!_functionType.gasSet() && retSize > 0) + { + m_context << u256(0); + utils().fetchFreeMemoryPointer(); + // This touches too much, but that way we save some rounding arithmetics + m_context << u256(retSize) << Instruction::ADD << Instruction::MSTORE; + } } // Copy function identifier to memory. @@ -1749,14 +1760,14 @@ void ExpressionCompiler::appendExternalFunctionCall( if (_functionType.gasSet()) m_context << dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); - else if (m_context.experimentalFeatureActive(ExperimentalFeature::V050)) + else if (m_context.evmVersion().canOverchargeGasForCall()) // Send all gas (requires tangerine whistle EVM) m_context << Instruction::GAS; else { // send all gas except the amount needed to execute "SUB" and "CALL" // @todo this retains too much gas for now, needs to be fine-tuned. - u256 gasNeededByCaller = eth::GasCosts::callGas + 10; + u256 gasNeededByCaller = eth::GasCosts::callGas(m_context.evmVersion()) + 10; if (_functionType.valueSet()) gasNeededByCaller += eth::GasCosts::callValueTransferGas; if (!existenceChecked) diff --git a/libsolidity/formal/Z3Interface.cpp b/libsolidity/formal/Z3Interface.cpp index 769e6edb..125da00d 100644 --- a/libsolidity/formal/Z3Interface.cpp +++ b/libsolidity/formal/Z3Interface.cpp @@ -28,6 +28,7 @@ using namespace dev::solidity::smt; Z3Interface::Z3Interface(): m_solver(m_context) { + z3::set_param("rewriter.pull_cheap_ite", true); } void Z3Interface::reset() diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index 1030523a..a7f764a5 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -533,33 +533,47 @@ void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _loc void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location) { - static set<solidity::Instruction> futureInstructions{ - solidity::Instruction::CREATE2, - solidity::Instruction::RETURNDATACOPY, - solidity::Instruction::RETURNDATASIZE, - solidity::Instruction::STATICCALL - }; - if (futureInstructions.count(_instr)) + // We assume that returndatacopy, returndatasize and staticcall are either all available + // or all not available. + solAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), ""); + + if (_instr == solidity::Instruction::CREATE2) m_errorReporter.warning( _location, "The \"" + boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is only available after " + - "the Metropolis hard fork. Before that it acts as an invalid instruction." + + "\" instruction is not supported by the VM version \"" + + "" + m_evmVersion.name() + + "\" you are currently compiling for. " + + "It will be interpreted as an invalid instruction on this VM." ); - - static set<solidity::Instruction> experimentalInstructions{ - solidity::Instruction::SHL, - solidity::Instruction::SHR, - solidity::Instruction::SAR - }; - if (experimentalInstructions.count(_instr)) + else if (( + _instr == solidity::Instruction::RETURNDATACOPY || + _instr == solidity::Instruction::RETURNDATASIZE || + _instr == solidity::Instruction::STATICCALL + ) && !m_evmVersion.supportsReturndata()) + m_errorReporter.warning( + _location, + "The \"" + + boost::to_lower_copy(instructionInfo(_instr).name) + + "\" instruction is only available for Byzantium-compatible VMs. " + + "You are currently compiling for \"" + + m_evmVersion.name() + + "\", where it will be interpreted as an invalid instruction." + ); + else if (( + _instr == solidity::Instruction::SHL || + _instr == solidity::Instruction::SHR || + _instr == solidity::Instruction::SAR + ) && !m_evmVersion.hasBitwiseShifting()) m_errorReporter.warning( _location, "The \"" + boost::to_lower_copy(instructionInfo(_instr).name) - + "\" instruction is only available after " + - "the Constantinople hard fork. Before that it acts as an invalid instruction." + + "\" instruction is only available for Constantinople-compatible VMs. " + + "You are currently compiling for \"" + + m_evmVersion.name() + + "\", where it will be interpreted as an invalid instruction." ); if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST) diff --git a/libsolidity/inlineasm/AsmAnalysis.h b/libsolidity/inlineasm/AsmAnalysis.h index 7a81dbf8..867711c7 100644 --- a/libsolidity/inlineasm/AsmAnalysis.h +++ b/libsolidity/inlineasm/AsmAnalysis.h @@ -21,6 +21,7 @@ #pragma once #include <libsolidity/interface/Exceptions.h> +#include <libsolidity/interface/EVMVersion.h> #include <libsolidity/inlineasm/AsmScope.h> @@ -54,9 +55,10 @@ public: explicit AsmAnalyzer( AsmAnalysisInfo& _analysisInfo, ErrorReporter& _errorReporter, + EVMVersion _evmVersion, AsmFlavour _flavour = AsmFlavour::Loose, julia::ExternalIdentifierAccess::Resolver const& _resolver = julia::ExternalIdentifierAccess::Resolver() - ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_flavour(_flavour) {} + ): m_resolver(_resolver), m_info(_analysisInfo), m_errorReporter(_errorReporter), m_evmVersion(_evmVersion), m_flavour(_flavour) {} bool analyze(assembly::Block const& _block); @@ -97,6 +99,7 @@ private: std::set<Scope::Variable const*> m_activeVariables; AsmAnalysisInfo& m_info; ErrorReporter& m_errorReporter; + EVMVersion m_evmVersion; AsmFlavour m_flavour = AsmFlavour::Loose; }; diff --git a/libsolidity/interface/AssemblyStack.cpp b/libsolidity/interface/AssemblyStack.cpp index c9e534c7..7a9fffbf 100644 --- a/libsolidity/interface/AssemblyStack.cpp +++ b/libsolidity/interface/AssemblyStack.cpp @@ -91,7 +91,7 @@ bool AssemblyStack::analyze(assembly::Block const& _block, Scanner const* _scann bool AssemblyStack::analyzeParsed() { m_analysisInfo = make_shared<assembly::AsmAnalysisInfo>(); - assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, languageToAsmFlavour(m_language)); + assembly::AsmAnalyzer analyzer(*m_analysisInfo, m_errorReporter, m_evmVersion, languageToAsmFlavour(m_language)); m_analysisSuccessful = analyzer.analyze(*m_parserResult); return m_analysisSuccessful; } diff --git a/libsolidity/interface/AssemblyStack.h b/libsolidity/interface/AssemblyStack.h index 6ae7e8d1..720220ab 100644 --- a/libsolidity/interface/AssemblyStack.h +++ b/libsolidity/interface/AssemblyStack.h @@ -22,6 +22,8 @@ #pragma once #include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/interface/EVMVersion.h> + #include <libevmasm/LinkerObject.h> #include <string> @@ -54,8 +56,8 @@ public: enum class Language { JULIA, Assembly, StrictAssembly }; enum class Machine { EVM, EVM15, eWasm }; - explicit AssemblyStack(Language _language = Language::Assembly): - m_language(_language), m_errorReporter(m_errors) + explicit AssemblyStack(EVMVersion _evmVersion = EVMVersion(), Language _language = Language::Assembly): + m_language(_language), m_evmVersion(_evmVersion), m_errorReporter(m_errors) {} /// @returns the scanner used during parsing @@ -82,6 +84,7 @@ private: bool analyzeParsed(); Language m_language = Language::Assembly; + EVMVersion m_evmVersion; std::shared_ptr<Scanner> m_scanner; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 3b5e65e8..eacfca9c 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -74,6 +74,12 @@ void CompilerStack::setRemappings(vector<string> const& _remappings) swap(m_remappings, remappings); } +void CompilerStack::setEVMVersion(EVMVersion _version) +{ + solAssert(m_stackState < State::ParsingSuccessful, "Set EVM version after parsing."); + m_evmVersion = _version; +} + void CompilerStack::reset(bool _keepSources) { if (_keepSources) @@ -88,6 +94,7 @@ void CompilerStack::reset(bool _keepSources) m_sources.clear(); } m_libraries.clear(); + m_evmVersion = EVMVersion(); m_optimize = false; m_optimizeRuns = 200; m_globalContext.reset(); @@ -198,7 +205,7 @@ bool CompilerStack::analyze() m_contracts[contract->fullyQualifiedName()].contract = contract; } - TypeChecker typeChecker(m_errorReporter); + TypeChecker typeChecker(m_evmVersion, m_errorReporter); for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) @@ -677,7 +684,7 @@ void CompilerStack::compileContract( for (auto const* dependency: _contract.annotation().contractDependencies) compileContract(*dependency, _compiledContracts); - shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns); + shared_ptr<Compiler> compiler = make_shared<Compiler>(m_evmVersion, m_optimize, m_optimizeRuns); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); string metadata = createMetadata(compiledContract); bytes cborEncodedHash = @@ -736,7 +743,7 @@ void CompilerStack::compileContract( { if (!_contract.isLibrary()) { - Compiler cloneCompiler(m_optimize, m_optimizeRuns); + Compiler cloneCompiler(m_evmVersion, m_optimize, m_optimizeRuns); cloneCompiler.compileClone(_contract, _compiledContracts); compiledContract.cloneObject = cloneCompiler.assembledObject(); } @@ -838,6 +845,7 @@ string CompilerStack::createMetadata(Contract const& _contract) const } meta["settings"]["optimizer"]["enabled"] = m_optimize; meta["settings"]["optimizer"]["runs"] = m_optimizeRuns; + meta["settings"]["evmVersion"] = m_evmVersion.name(); meta["settings"]["compilationTarget"][_contract.contract->sourceUnitName()] = _contract.contract->annotation().canonicalName; @@ -951,11 +959,12 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const return Json::Value(); using Gas = GasEstimator::GasConsumption; + GasEstimator gasEstimator(m_evmVersion); Json::Value output(Json::objectValue); if (eth::AssemblyItems const* items = assemblyItems(_contractName)) { - Gas executionGas = GasEstimator::functionalEstimation(*items); + Gas executionGas = gasEstimator.functionalEstimation(*items); u256 bytecodeSize(runtimeObject(_contractName).bytecode.size()); Gas codeDepositGas = bytecodeSize * eth::GasCosts::createDataGas; @@ -976,14 +985,14 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const for (auto it: contract.interfaceFunctions()) { string sig = it.second->externalSignature(); - externalFunctions[sig] = gasToJson(GasEstimator::functionalEstimation(*items, sig)); + externalFunctions[sig] = gasToJson(gasEstimator.functionalEstimation(*items, sig)); } if (contract.fallbackFunction()) /// This needs to be set to an invalid signature in order to trigger the fallback, /// without the shortcut (of CALLDATSIZE == 0), and therefore to receive the upper bound. /// An empty string ("") would work to trigger the shortcut only. - externalFunctions[""] = gasToJson(GasEstimator::functionalEstimation(*items, "INVALID")); + externalFunctions[""] = gasToJson(gasEstimator.functionalEstimation(*items, "INVALID")); if (!externalFunctions.empty()) output["external"] = externalFunctions; @@ -999,7 +1008,7 @@ Json::Value CompilerStack::gasEstimates(string const& _contractName) const size_t entry = functionEntryPoint(_contractName, *it); GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); + gas = gasEstimator.functionalEstimation(*items, entry, *it); /// TODO: This could move into a method shared with externalSignature() FunctionType type(*it); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index b377b3aa..13c9cc7a 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -23,20 +23,26 @@ #pragma once +#include <libsolidity/interface/ErrorReporter.h> +#include <libsolidity/interface/ReadFile.h> +#include <libsolidity/interface/EVMVersion.h> + +#include <libevmasm/SourceLocation.h> +#include <libevmasm/LinkerObject.h> + +#include <libdevcore/Common.h> +#include <libdevcore/FixedHash.h> + +#include <json/json.h> + +#include <boost/noncopyable.hpp> +#include <boost/filesystem.hpp> + #include <ostream> #include <string> #include <memory> #include <vector> #include <functional> -#include <boost/noncopyable.hpp> -#include <boost/filesystem.hpp> -#include <json/json.h> -#include <libdevcore/Common.h> -#include <libdevcore/FixedHash.h> -#include <libevmasm/SourceLocation.h> -#include <libevmasm/LinkerObject.h> -#include <libsolidity/interface/ErrorReporter.h> -#include <libsolidity/interface/ReadFile.h> namespace dev { @@ -116,6 +122,8 @@ public: m_optimizeRuns = _runs; } + void setEVMVersion(EVMVersion _version = EVMVersion{}); + /// Sets the list of requested contract names. If empty, no filtering is performed and every contract /// found in the supplied sources is compiled. Names are cleared iff @a _contractNames is missing. void setRequestedContractNames(std::set<std::string> const& _contractNames = std::set<std::string>{}) @@ -310,6 +318,7 @@ private: ReadCallback::Callback m_smtQuery; bool m_optimize = false; unsigned m_optimizeRuns = 200; + EVMVersion m_evmVersion; std::set<std::string> m_requestedContractNames; std::map<std::string, h160> m_libraries; /// list of path prefix remappings, e.g. mylibrary: github.com/ethereum = /usr/local/ethereum diff --git a/libsolidity/interface/EVMVersion.h b/libsolidity/interface/EVMVersion.h new file mode 100644 index 00000000..13c4ec94 --- /dev/null +++ b/libsolidity/interface/EVMVersion.h @@ -0,0 +1,93 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * EVM versioning. + */ + +#pragma once + +#include <string> + +#include <boost/optional.hpp> +#include <boost/operators.hpp> + +namespace dev +{ +namespace solidity +{ + +/** + * A version specifier of the EVM we want to compile to. + * Defaults to the latest version. + */ +class EVMVersion: + boost::less_than_comparable<EVMVersion>, + boost::equality_comparable<EVMVersion> +{ +public: + EVMVersion() {} + + static EVMVersion homestead() { return {Version::Homestead}; } + static EVMVersion tangerineWhistle() { return {Version::TangerineWhistle}; } + static EVMVersion spuriousDragon() { return {Version::SpuriousDragon}; } + static EVMVersion byzantium() { return {Version::Byzantium}; } + static EVMVersion constantinople() { return {Version::Constantinople}; } + + static boost::optional<EVMVersion> fromString(std::string const& _version) + { + for (auto const& v: {homestead(), tangerineWhistle(), spuriousDragon(), byzantium()}) + if (_version == v.name()) + return v; + return {}; + } + + bool operator==(EVMVersion const& _other) const { return m_version == _other.m_version; } + bool operator<(EVMVersion const& _other) const { return m_version < _other.m_version; } + + std::string name() const + { + switch (m_version) + { + case Version::Homestead: return "homestead"; + case Version::TangerineWhistle: return "tangerineWhistle"; + case Version::SpuriousDragon: return "spuriousDragon"; + case Version::Byzantium: return "byzantium"; + case Version::Constantinople: return "constantinople"; + } + return "INVALID"; + } + + /// Has the RETURNDATACOPY and RETURNDATASIZE opcodes. + bool supportsReturndata() const { return *this >= byzantium(); } + bool hasStaticCall() const { return *this >= byzantium(); } + bool hasBitwiseShifting() const { return *this >= constantinople(); } + + /// Whether we have to retain the costs for the call opcode itself (false), + /// or whether we can just forward easily all remaining gas (true). + bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } + +private: + enum class Version { Homestead, TangerineWhistle, SpuriousDragon, Byzantium, Constantinople }; + + EVMVersion(Version _version): m_version(_version) {} + + Version m_version = Version::Byzantium; +}; + + +} +} diff --git a/libsolidity/interface/GasEstimator.cpp b/libsolidity/interface/GasEstimator.cpp index 22cc0266..2139395f 100644 --- a/libsolidity/interface/GasEstimator.cpp +++ b/libsolidity/interface/GasEstimator.cpp @@ -40,7 +40,7 @@ using namespace dev::solidity; GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimation( AssemblyItems const& _items, vector<ASTNode const*> const& _ast -) +) const { solAssert(std::count(_ast.begin(), _ast.end(), nullptr) == 0, ""); map<SourceLocation, GasConsumption> particularCosts; @@ -49,7 +49,7 @@ GasEstimator::ASTGasConsumptionSelfAccumulated GasEstimator::structuralEstimatio for (BasicBlock const& block: cfg.optimisedBlocks()) { solAssert(!!block.startState, ""); - GasMeter meter(block.startState->copy()); + GasMeter meter(block.startState->copy(), m_evmVersion); auto const end = _items.begin() + block.end; for (auto iter = _items.begin() + block.begin; iter != end; ++iter) particularCosts[iter->location()] += meter.estimateMax(*iter); @@ -127,7 +127,7 @@ map<ASTNode const*, GasMeter::GasConsumption> GasEstimator::breakToStatementLeve GasEstimator::GasConsumption GasEstimator::functionalEstimation( AssemblyItems const& _items, string const& _signature -) +) const { auto state = make_shared<KnownState>(); @@ -144,7 +144,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( }); } - PathGasMeter meter(_items); + PathGasMeter meter(_items, m_evmVersion); return meter.estimateMax(0, state); } @@ -152,7 +152,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( AssemblyItems const& _items, size_t const& _offset, FunctionDefinition const& _function -) +) const { auto state = make_shared<KnownState>(); @@ -167,7 +167,7 @@ GasEstimator::GasConsumption GasEstimator::functionalEstimation( if (parametersSize > 0) state->feedItem(swapInstruction(parametersSize)); - return PathGasMeter(_items).estimateMax(_offset, state); + return PathGasMeter(_items, m_evmVersion).estimateMax(_offset, state); } set<ASTNode const*> GasEstimator::finestNodesAtLocation( diff --git a/libsolidity/interface/GasEstimator.h b/libsolidity/interface/GasEstimator.h index bf63df96..ea94d988 100644 --- a/libsolidity/interface/GasEstimator.h +++ b/libsolidity/interface/GasEstimator.h @@ -22,11 +22,14 @@ #pragma once +#include <libsolidity/interface/EVMVersion.h> + +#include <libevmasm/GasMeter.h> +#include <libevmasm/Assembly.h> + #include <vector> #include <map> #include <array> -#include <libevmasm/GasMeter.h> -#include <libevmasm/Assembly.h> namespace dev { @@ -44,13 +47,15 @@ public: using ASTGasConsumptionSelfAccumulated = std::map<ASTNode const*, std::array<GasConsumption, 2>>; + explicit GasEstimator(EVMVersion _evmVersion): m_evmVersion(_evmVersion) {} + /// Estimates the gas consumption for every assembly item in the given assembly and stores /// it by source location. /// @returns a mapping from each AST node to a pair of its particular and syntactically accumulated gas costs. - static ASTGasConsumptionSelfAccumulated structuralEstimation( + ASTGasConsumptionSelfAccumulated structuralEstimation( eth::AssemblyItems const& _items, std::vector<ASTNode const*> const& _ast - ); + ) const; /// @returns a mapping from nodes with non-overlapping source locations to gas consumptions such that /// the following source locations are part of the mapping: /// 1. source locations of statements that do not contain other statements @@ -62,23 +67,24 @@ public: /// @returns the estimated gas consumption by the (public or external) function with the /// given signature. If no signature is given, estimates the maximum gas usage. - static GasConsumption functionalEstimation( + GasConsumption functionalEstimation( eth::AssemblyItems const& _items, std::string const& _signature = "" - ); + ) const; /// @returns the estimated gas consumption by the given function which starts at the given /// offset into the list of assembly items. /// @note this does not work correctly for recursive functions. - static GasConsumption functionalEstimation( + GasConsumption functionalEstimation( eth::AssemblyItems const& _items, size_t const& _offset, FunctionDefinition const& _function - ); + ) const; private: /// @returns the set of AST nodes which are the finest nodes at their location. static std::set<ASTNode const*> finestNodesAtLocation(std::vector<ASTNode const*> const& _roots); + EVMVersion m_evmVersion; }; } diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index 91fe72ae..ee9b1440 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -318,6 +318,14 @@ Json::Value StandardCompiler::compileInternal(Json::Value const& _input) Json::Value const& settings = _input.get("settings", Json::Value()); + if (settings.isMember("evmVersion")) + { + boost::optional<EVMVersion> version = EVMVersion::fromString(settings.get("evmVersion", {}).asString()); + if (!version) + return formatFatalError("JSONError", "Invalid EVM version requested."); + m_compilerStack.setEVMVersion(*version); + } + vector<string> remappings; for (auto const& remapping: settings.get("remappings", Json::Value())) remappings.push_back(remapping.asString()); |