diff --git a/cmake/EthBuildInfo.cmake b/cmake/EthBuildInfo.cmake index 1316a888cd..12ef523241 100644 --- a/cmake/EthBuildInfo.cmake +++ b/cmake/EthBuildInfo.cmake @@ -40,6 +40,7 @@ function(create_build_info NAME) -DPROJECT_VERSION_MAJOR="${PROJECT_VERSION_MAJOR}" -DPROJECT_VERSION_MINOR="${PROJECT_VERSION_MINOR}" -DPROJECT_VERSION_PATCH="${PROJECT_VERSION_PATCH}" + -DSOL_VERSION_ZKSYNC="${SOL_VERSION_ZKSYNC}" -P "${ETH_SCRIPTS_DIR}/buildinfo.cmake" ) include_directories("${PROJECT_BINARY_DIR}/include") diff --git a/cmake/scripts/buildinfo.cmake b/cmake/scripts/buildinfo.cmake index 3fb6beb2b7..48d2e17bd7 100644 --- a/cmake/scripts/buildinfo.cmake +++ b/cmake/scripts/buildinfo.cmake @@ -25,7 +25,6 @@ if (EXISTS ${ETH_SOURCE_DIR}/prerelease.txt) file(READ ${ETH_SOURCE_DIR}/prerelease.txt SOL_VERSION_PRERELEASE) string(STRIP "${SOL_VERSION_PRERELEASE}" SOL_VERSION_PRERELEASE) else() - string(TIMESTAMP SOL_VERSION_PRERELEASE "develop.%Y.%m.%d" UTC) string(REPLACE .0 . SOL_VERSION_PRERELEASE "${SOL_VERSION_PRERELEASE}") endif() @@ -64,6 +63,10 @@ set(SOL_VERSION_COMMIT "commit.${SOL_COMMIT_HASH}") set(SOl_VERSION_PLATFORM ETH_BUILD_PLATFORM) set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}") +if (NOT SOL_VERSION_ZKSYNC) + set(SOL_VERSION_ZKSYNC "undefined") +endif() + set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp") set(OUTFILE "${ETH_DST_DIR}/BuildInfo.h") diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in index 9c295a85f0..48bec71380 100644 --- a/cmake/templates/BuildInfo.h.in +++ b/cmake/templates/BuildInfo.h.in @@ -13,3 +13,4 @@ #define SOL_VERSION_BUILDINFO "@SOL_VERSION_BUILDINFO@" #define SOL_VERSION_COMMIT "@SOL_VERSION_COMMIT@" #define SOL_VERSION_PLATFORM "@SOL_VERSION_PLATFORM@" +#define SOL_VERSION_ZKSYNC "@SOL_VERSION_ZKSYNC@" diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index ab26526e14..64886d1556 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -719,7 +719,7 @@ AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier) Assembly& Assembly::optimise(OptimiserSettings const& _settings) { - optimiseInternal(_settings, {}); + (void) _settings; return *this; } diff --git a/libevmasm/LinkerObject.cpp b/libevmasm/LinkerObject.cpp index 1b09f4ced0..818cd10fff 100644 --- a/libevmasm/LinkerObject.cpp +++ b/libevmasm/LinkerObject.cpp @@ -48,6 +48,7 @@ void LinkerObject::link(std::map const& _libraryAddresses) std::string LinkerObject::toHex() const { + return "The EVM bytecode is unavailable in the ZKsync edition of solc"; std::string hex = solidity::util::toHex(bytecode); for (auto const& ref: linkReferences) { diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 0b796035d3..316de898c8 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -82,6 +82,10 @@ set(sources codegen/ContractCompiler.h codegen/ExpressionCompiler.cpp codegen/ExpressionCompiler.h + codegen/ExtraMetadata.cpp + codegen/ExtraMetadata.h + codegen/FuncPtrTracker.cpp + codegen/FuncPtrTracker.h codegen/LValue.cpp codegen/LValue.h codegen/MultiUseYulFunctionCollector.h diff --git a/libsolidity/analysis/FunctionCallGraph.cpp b/libsolidity/analysis/FunctionCallGraph.cpp index 1d01f1a6bc..98b4126a65 100644 --- a/libsolidity/analysis/FunctionCallGraph.cpp +++ b/libsolidity/analysis/FunctionCallGraph.cpp @@ -120,10 +120,21 @@ bool FunctionCallGraphBuilder::visit(FunctionCall const& _functionCall) solAssert(functionType, ""); if (functionType->kind() == FunctionType::Kind::Internal && !_functionCall.expression().annotation().calledDirectly) + { + for (FunctionDefinition const* funcPtrRef: m_contract.annotation().intFuncPtrRefs) + { + FunctionType const* funcPtrRefType = funcPtrRef->functionType(/*_internal=*/true); + solAssert(funcPtrRefType, ""); + if (!funcPtrRefType->hasEqualParameterTypes(*functionType) + || !funcPtrRefType->hasEqualReturnTypes(*functionType) || !funcPtrRef->isImplemented()) + continue; + m_graph.indirectEdges[m_currentNode].insert(funcPtrRef); + } // If it's not a direct call, we don't really know which function will be called (it may even // change at runtime). All we can do is to add an edge to the dispatch which in turn has // edges to all functions could possibly be called. add(m_currentNode, CallGraph::SpecialNode::InternalDispatch); + } else if (functionType->kind() == FunctionType::Kind::Error) m_graph.usedErrors.insert(&dynamic_cast(functionType->declaration())); diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 81b3b83746..f1bff5559c 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -29,6 +29,8 @@ #include +#include + #include #include #include @@ -164,6 +166,8 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocu util::SetOnce> creationCallGraph; /// A graph with edges representing calls between functions that may happen in a deployed contract. util::SetOnce> deployedCallGraph; + /// Set of internal functions referenced as function pointers + std::set> intFuncPtrRefs; /// List of contracts whose bytecode is referenced by this contract, e.g. through "new". /// The Value represents the ast node that referenced the contract. @@ -226,6 +230,8 @@ struct InlineAssemblyAnnotation: StatementAnnotation bool markedMemorySafe = false; /// True, if the assembly block involves any memory opcode or assigns to variables in memory. util::SetOnce hasMemoryEffects; + /// The yul block of the InlineAssembly::operations() after optimizations. + std::shared_ptr optimizedOperations; }; struct BlockAnnotation: StatementAnnotation, ScopableAnnotation @@ -342,6 +348,13 @@ struct FunctionCallAnnotation: ExpressionAnnotation util::SetOnce kind; /// If true, this is the external call of a try statement. bool tryCall = false; + + // HACK! + // We track the success tag here for the `TryStatement` lowering. This is to avoid the redundant status check and + // the conditional jump. Such patterns can confuse the zksolc translator. + // + // uint32_t since Assembly::new[Push]Tag() asserts that the tag is 32 bits. + std::optional tryCallSuccessTag; }; /// Experimental Solidity annotations. diff --git a/libsolidity/ast/CallGraph.cpp b/libsolidity/ast/CallGraph.cpp index 04275c3740..060f0aeebc 100644 --- a/libsolidity/ast/CallGraph.cpp +++ b/libsolidity/ast/CallGraph.cpp @@ -43,3 +43,132 @@ bool CallGraph::CompareByID::operator()(int64_t _lhs, Node const& _rhs) const return _lhs < std::get(_rhs)->id(); } + +/// Populates reachable cycles from m_src into paths; +class CycleFinder +{ + CallGraph const& m_callGraph; + CallableDeclaration const* m_src; + std::set m_processing; + std::set m_processed; + std::vector m_paths; + + /// Populates `m_paths` with cycles reachable from @a _callable + void getCyclesInternal(CallableDeclaration const* _callable, CallGraph::Path& _path) + { + if (m_processed.count(_callable)) + return; + + auto directCallees = m_callGraph.edges.find(_callable); + auto indirectCallees = m_callGraph.indirectEdges.find(_callable); + // Is _callable a leaf node? + if (directCallees == m_callGraph.edges.end() && indirectCallees == m_callGraph.indirectEdges.end()) + { + solAssert(m_processing.count(_callable) == 0, ""); + m_processed.insert(_callable); + return; + } + + m_processing.insert(_callable); + _path.push_back(_callable); + + // Traverse all the direct and indirect callees + std::set callees; + if (directCallees != m_callGraph.edges.end()) + callees.insert(directCallees->second.begin(), directCallees->second.end()); + if (indirectCallees != m_callGraph.indirectEdges.end()) + callees.insert(indirectCallees->second.begin(), indirectCallees->second.end()); + for (auto const& calleeVariant: callees) + { + if (!std::holds_alternative(calleeVariant)) + continue; + auto* callee = std::get(calleeVariant); + + if (m_processing.count(callee)) + { + // Extract the cycle + auto cycleStart = std::find(_path.begin(), _path.end(), callee); + solAssert(cycleStart != _path.end(), ""); + m_paths.emplace_back(cycleStart, _path.end()); + continue; + } + + getCyclesInternal(callee, _path); + } + + m_processing.erase(_callable); + m_processed.insert(_callable); + _path.pop_back(); + } + +public: + CycleFinder(CallGraph const& _callGraph, CallableDeclaration const* _src): m_callGraph(_callGraph), m_src(_src) {} + + std::vector getCycles() + { + CallGraph::Path p; + getCyclesInternal(m_src, p); + return m_paths; + } + + void dump(std::ostream& _out) + { + for (CallGraph::Path const& path: m_paths) + { + for (CallableDeclaration const* func: path) + _out << func->name() << " -> "; + _out << "\n"; + } + } +}; + +void CallGraph::getReachableFuncs(CallableDeclaration const* _src, std::set& _funcs) const +{ + if (_funcs.count(_src)) + return; + _funcs.insert(_src); + + auto directCallees = edges.find(_src); + auto indirectCallees = indirectEdges.find(_src); + // Is _src a leaf node? + if (directCallees == edges.end() && indirectCallees == indirectEdges.end()) + return; + + // Traverse all the direct and indirect callees + std::set callees; + if (directCallees != edges.end()) + callees.insert(directCallees->second.begin(), directCallees->second.end()); + if (indirectCallees != indirectEdges.end()) + callees.insert(indirectCallees->second.begin(), indirectCallees->second.end()); + + for (auto const& calleeVariant: callees) + { + if (!std::holds_alternative(calleeVariant)) + continue; + auto* callee = std::get(calleeVariant); + getReachableFuncs(callee, _funcs); + } +} + +std::set CallGraph::getReachableFuncs(CallableDeclaration const* _src) const +{ + std::set funcs; + getReachableFuncs(_src, funcs); + return funcs; +} + +std::set CallGraph::getReachableCycleFuncs(CallableDeclaration const* _src) const +{ + std::set funcs; + CycleFinder cf{*this, _src}; + std::vector paths = cf.getCycles(); + + for (CallGraph::Path const& path: paths) + { + for (CallableDeclaration const* func: path) + { + funcs.insert(func); + } + } + return funcs; +} diff --git a/libsolidity/ast/CallGraph.h b/libsolidity/ast/CallGraph.h index c4b4e957a7..d71b727170 100644 --- a/libsolidity/ast/CallGraph.h +++ b/libsolidity/ast/CallGraph.h @@ -47,6 +47,7 @@ struct CallGraph }; using Node = std::variant; + using Path = std::vector; struct CompareByID { @@ -61,6 +62,9 @@ struct CallGraph /// any calls. std::map, CompareByID> edges; + /// Graph edges for indirect calls + std::map, CompareByID> indirectEdges; + /// Contracts that need to be compiled before this one can be compiled. /// The value is the ast node that created the dependency. std::map> bytecodeDependency; @@ -70,6 +74,17 @@ struct CallGraph /// Errors that are used by functions present in the graph. std::set usedErrors; + + /// Returns functions reachable from @a _src that belong to a cycle. Note that the cycle can be due to indirect + /// calls. + std::set getReachableCycleFuncs(CallableDeclaration const* _src) const; + + /// Returns functions reachable (including the ones from indirect calls) from @a _src. + std::set getReachableFuncs(CallableDeclaration const* _src) const; + +private: + /// Populates @a _funcs with the functions reachable (including the ones from indirect calls) from @a _src. + void getReachableFuncs(CallableDeclaration const* _src, std::set& _funcs) const; }; } diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 5dd94aebc4..511828388e 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -296,6 +296,17 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons _context << Instruction::POP; } ); + + if (auto* structType = dynamic_cast(_sourceType.baseType())) + { + if (structType->recursive()) + { + std::string name{"$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier()}; + auto tag = m_context.lowLevelFunctionTagIfExists(name); + solAssert(tag != evmasm::AssemblyItem(evmasm::UndefinedItem), ""); + m_context.addRecursiveLowLevelFunc({name, tag.data().convert_to(), /*ins=*/3, /*outs=*/1}); + } + } } void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const @@ -598,6 +609,17 @@ void ArrayUtils::clearArray(ArrayType const& _typeIn) const solAssert(_context.stackHeight() == stackHeightStart - 2, ""); } ); + + if (auto* structType = dynamic_cast(_typeIn.baseType())) + { + if (structType->recursive()) + { + std::string name{"$clearArray_" + _typeIn.identifier()}; + auto tag = m_context.lowLevelFunctionTagIfExists(name); + solAssert(tag != evmasm::AssemblyItem(evmasm::UndefinedItem), ""); + m_context.addRecursiveLowLevelFunc({name, tag.data().convert_to(), /*ins=*/2, /*outs=*/0}); + } + } } void ArrayUtils::clearDynamicArray(ArrayType const& _type) const diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 3e3f149969..81eddeadea 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -24,6 +24,8 @@ #include #include +#include + #include #include @@ -64,6 +66,9 @@ void Compiler::compileContract( m_context.optimise(m_optimiserSettings); + ExtraMetadataRecorder extraMetadataRecorder{m_context, m_runtimeContext}; + m_extraMetadata = extraMetadataRecorder.run(_contract); + solAssert(m_context.appendYulUtilityFunctionsRan(), "appendYulUtilityFunctions() was not called."); solAssert(m_runtimeContext.appendYulUtilityFunctionsRan(), "appendYulUtilityFunctions() was not called."); } diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 1267c1a2d9..e2747991bd 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -61,8 +61,10 @@ class Compiler std::string generatedYulUtilityCode() const { return m_context.generatedYulUtilityCode(); } std::string runtimeGeneratedYulUtilityCode() const { return m_runtimeContext.generatedYulUtilityCode(); } + Json extraMetadata() const { return m_extraMetadata; } private: + Json m_extraMetadata; OptimiserSettings const m_optimiserSettings; CompilerContext m_runtimeContext; size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 4a0f636dc9..f6ed822c68 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -169,6 +169,15 @@ evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag( return it->second; } +evmasm::AssemblyItem CompilerContext::lowLevelFunctionTagIfExists(std::string const& _name) +{ + auto it = m_lowLevelFunctions.find(_name); + if (it == m_lowLevelFunctions.end()) + return evmasm::AssemblyItem(evmasm::UndefinedItem); + else + return it->second; +} + void CompilerContext::appendMissingLowLevelFunctions() { while (!m_lowLevelFunctionGenerationQueue.empty()) @@ -516,13 +525,14 @@ void CompilerContext::appendInlineAssembly( if (errorReporter.hasErrorsWarningsOrInfos()) reportError("Failed to analyze inline assembly block."); - solAssert(!errorReporter.hasErrorsWarningsOrInfos(), "Failed to analyze inline assembly block."); + std::shared_ptr yulContext; yul::CodeGenerator::assemble( toBeAssembledAST->root(), analysisInfo, *m_asm, m_evmVersion, std::nullopt, + yulContext, identifierAccess.generateCode, _system, _optimiserSettings.optimizeStackAllocation diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 3469ee7d27..e11e53013a 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -40,6 +40,7 @@ #include #include +#include #include #include @@ -163,6 +164,10 @@ class CompilerContext unsigned _outArgs, std::function const& _generator ); + /// Returns the entry tag of the low-level function with the name @a _name if already generated; Returns + /// evmasm::AssemblyItem(evmasm::UndefinedItem) if the entry tag is not generated. + evmasm::AssemblyItem lowLevelFunctionTagIfExists(std::string const& _name); + /// Generates the code for missing low-level functions, i.e. calls the generators passed above. void appendMissingLowLevelFunctions(); ABIFunctions& abiFunctions() { return m_abiFunctions; } @@ -296,6 +301,40 @@ class CompilerContext /// Should be avoided except when adding sub-assemblies. std::shared_ptr assemblyPtr() const { return m_asm; } + /// Adds the @a _p_asm -> @a _context mapping in the internal inline assembly to context mapping + void addInlineAsmContextMapping(InlineAssembly const* _p_asm, std::shared_ptr _context) + { + m_inlineAsmContextMap[_p_asm] = _context; + } + + /// Returns the context for @a _p_asm; nullptr if not found + yul::CodeTransformContext const* findInlineAsmContextMapping(InlineAssembly const* _p_asm) const + { + auto findIt = m_inlineAsmContextMap.find(_p_asm); + if (findIt == m_inlineAsmContextMap.end()) + return nullptr; + return findIt->second.get(); + } + + struct FunctionInfo + { + std::string const name; + unsigned tag; + unsigned ins; + unsigned outs; + + bool operator<(FunctionInfo const& _other) const + { + return tie(name, tag, ins, outs) < tie(_other.name, _other.tag, _other.ins, _other.outs); + } + }; + + /// Adds @a _func to the set of low level utility functions that are recursive + void addRecursiveLowLevelFunc(FunctionInfo _func) { m_recursiveLowLevelFuncs.insert(_func); } + + /// Returns the set of low level utility functions that are recursive + std::set const& recursiveLowLevelFuncs() const { return m_recursiveLowLevelFuncs; } + /** * Helper class to pop the visited nodes stack when a scope closes */ @@ -393,6 +432,10 @@ class CompilerContext std::queue>> m_lowLevelFunctionGenerationQueue; /// Flag to check that appendYulUtilityFunctions() was called exactly once bool m_appendYulUtilityFunctionsRan = false; + /// Maps an InlineAssembly AST node to its CodeTransformContext created during its lowering + std::map> m_inlineAsmContextMap; + /// Set of low level utility functions generated in this context that are recursive + std::set m_recursiveLowLevelFuncs; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 822d9d3dab..562239629c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -1203,12 +1203,21 @@ void CompilerUtils::convertType( _context << Instruction::POP << Instruction::POP; }; if (typeOnStack.recursive()) + { m_context.callLowLevelFunction( "$convertRecursiveArrayStorageToMemory_" + typeOnStack.identifier() + "_to_" + targetType.identifier(), 1, 1, conversionImpl ); + std::string name{ + "$convertRecursiveArrayStorageToMemory_" + typeOnStack.identifier() + "_to_" + + targetType.identifier()}; + auto tag = m_context.lowLevelFunctionTagIfExists(name); + solAssert(tag != evmasm::AssemblyItem(evmasm::UndefinedItem), ""); + m_context.addRecursiveLowLevelFunc( + {name, tag.data().convert_to(), /*ins=*/1, /*outs=*/1}); + } else conversionImpl(m_context); break; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 96b1ea5482..d6ab033d7c 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -720,6 +721,20 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(_context == yul::IdentifierContext::RValue || _context == yul::IdentifierContext::LValue, ""); auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + { + // The yul AST might be copied from the original (In case we ran the Disambiguator, for instance). So we'll + // search for the identifier's name instead. + auto& externalReferences = _inlineAssembly.annotation().externalReferences; + for (auto extRef = externalReferences.begin(); extRef != externalReferences.end(); ++extRef) + { + if (extRef->first->name == _identifier.name) + { + ref = extRef; + break; + } + } + } solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), ""); Declaration const* decl = ref->second.declaration; solAssert(!!decl, ""); @@ -950,19 +965,56 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) m_context.optimizeYul(object, *dialect, m_optimiserSettings); code = object.code().get(); + _inlineAssembly.annotation().optimizedOperations = object.code(); + analysisInfo = object.analysisInfo.get(); + } + else + { + auto const* dialect = dynamic_cast(&_inlineAssembly.dialect()); + solAssert(dialect, ""); + + // Run the disambiguator. + // We need this so that the yul::CallGraphGenerator runs correctly (which is required for setting the + // "recursiveFunctions" record in the extraMetadata for inline assembly) + std::set reservedIdentifiers = dialect->fixedFunctionNames(); + for (auto extRef: _inlineAssembly.annotation().externalReferences) + { + reservedIdentifiers.insert(extRef.first->name); + } + yul::Disambiguator disambiguator(*dialect, *analysisInfo, reservedIdentifiers); + object.setCode(std::make_shared(std::get(disambiguator(code->root())))); + + // Run the AsmAnalyzer on `object.code`. + // Create a resolver that accepts any identifiers. This is OK since the TypeChecker already did the resolution + // and the disambiguator should have left them as it is. + yul::ExternalIdentifierAccess::Resolver resolver + = [](yul::Identifier const& _identifier, yul::IdentifierContext _context, bool) -> bool + { + (void) _identifier; + (void) _context; + return true; + }; + object.analysisInfo = std::make_shared( + yul::AsmAnalyzer::analyzeStrictAssertCorrect(*dialect, object, resolver)); + + code = object.code().get(); + _inlineAssembly.annotation().optimizedOperations = object.code(); analysisInfo = object.analysisInfo.get(); } + std::shared_ptr yulContext; yul::CodeGenerator::assemble( code->root(), *analysisInfo, *m_context.assemblyPtr(), m_context.evmVersion(), std::nullopt, + yulContext, identifierAccessCodeGen, false, m_optimiserSettings.optimizeStackAllocation ); + m_context.addInlineAsmContextMapping(&_inlineAssembly, yulContext); m_context.setStackOffset(static_cast(startStackHeight)); return false; } @@ -972,11 +1024,14 @@ bool ContractCompiler::visit(TryStatement const& _tryStatement) StackHeightChecker checker(m_context); CompilerContext::LocationSetter locationSetter(m_context, _tryStatement); - compileExpression(_tryStatement.externalCall()); + auto* externalCall = dynamic_cast(&_tryStatement.externalCall()); + solAssert(externalCall && externalCall->annotation().tryCall, ""); + compileExpression(*externalCall); + int const returnSize = static_cast(_tryStatement.externalCall().annotation().type->sizeOnStack()); // Stack: [ return values] - evmasm::AssemblyItem successTag = m_context.appendConditionalJump(); + m_context << Instruction::POP; // Catch case. m_context.adjustStackOffset(-returnSize); @@ -985,7 +1040,11 @@ bool ContractCompiler::visit(TryStatement const& _tryStatement) evmasm::AssemblyItem endTag = m_context.appendJumpToNew(); - m_context << successTag; + auto& successTag = externalCall->annotation().tryCallSuccessTag; + solAssert(successTag, ""); + m_context << AssemblyItem(AssemblyItemType::Tag, *successTag); + successTag.reset(); + m_context.adjustStackOffset(returnSize); { // Success case. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 85634906e0..f36fd2c535 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -627,6 +627,68 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) return false; } +void ExpressionCompiler::generateSelector(FunctionType const& _funcType) +{ + // Are we in the creation context? + if (m_context.runtimeContext()) + { + // Extract only the low 32 bits for matching in the tag selector + m_context << u256(0xffffffff) << Instruction::AND; + } + + struct TagInfo + { + evmasm::AssemblyItem const tag; + FunctionDefinition const* func; + }; + std::vector tagInfos; + + for (auto* intFuncPtrRef: m_context.mostDerivedContract().annotation().intFuncPtrRefs) + { + FunctionType const* intFuncPtrRefType = intFuncPtrRef->functionType(true); + // ContractDefinitionAnnotation::intFuncPtrRefs should only contain refs to internal functions + solAssert(intFuncPtrRefType, ""); + if (!intFuncPtrRefType->hasEqualParameterTypes(_funcType) || !intFuncPtrRefType->hasEqualReturnTypes(_funcType) + || !intFuncPtrRef->isImplemented()) + continue; + + // The loaded function pointer + m_context << Instruction::DUP1; + // We don't need to resolve the function here since FuncPtrTracker already did that. + m_context << m_context.functionEntryLabel(*intFuncPtrRef).pushTag(); + m_context << Instruction::EQ; + + evmasm::AssemblyItem newTag = m_context.newTag(); + m_context.appendConditionalJumpTo(newTag); + tagInfos.push_back({newTag, intFuncPtrRef}); + } + + if (tagInfos.empty()) + { + // Pop the original function pointer + m_context << Instruction::POP; + } + // If we can't match the entry tag of any of the internal function + m_context.appendPanic(PanicCode::InvalidInternalFunction); + + unsigned int stkOffsetAfterJumpI = m_context.stackHeight(); + for (TagInfo& tagInfo: tagInfos) + { + // The PC is set to this tag from the jumpi, so we need to set the stack offset correctly + m_context.setStackOffset((int) stkOffsetAfterJumpI); + + m_context << tagInfo.tag; + + // Pop the original function pointer + m_context << Instruction::POP; + + // We don't need to resolve the function here since FuncPtrTracker already did that. + m_context << m_context.functionEntryLabel(*tagInfo.func).pushTag(); + m_context.appendJump(evmasm::AssemblyItem::JumpType::IntoFunction); + // After the call, the vm's pc should be set to the return label since it is pushed to the stack. + } +} + bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { auto functionCallKind = *_functionCall.annotation().kind; @@ -718,6 +780,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) parameterSize += function.selfType()->sizeOnStack(); } + // There can be cases when ExpressionAnnotation::calledDirectly is false but we can infer that it is a + // direct call if the target PC is a literal tag + bool directCallInferred = false; + // TODO: Check for EOF + solAssert(m_context.assembly().codeSections().size() == 1); + auto const& currAsmItems = m_context.assembly().codeSections().front().items; + if (!currAsmItems.empty() && currAsmItems.back().type() == AssemblyItemType::PushTag) + directCallInferred = true; + if (m_context.runtimeContext()) // We have a runtime context, so we need the creation part. utils().rightShiftNumberOnStack(32); @@ -725,7 +796,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // Extract the runtime part. m_context << ((u256(1) << 32) - 1) << Instruction::AND; - m_context.appendJump(evmasm::AssemblyItem::JumpType::IntoFunction); + // Is this a direct call? + if (_functionCall.expression().annotation().calledDirectly || directCallInferred) + m_context.appendJump(evmasm::AssemblyItem::JumpType::IntoFunction); + else + generateSelector(function); + m_context << returnLabel; unsigned returnParametersSize = CompilerUtils::sizeOnStack(function.returnParameterTypes()); @@ -741,7 +817,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::External: case FunctionType::Kind::DelegateCall: _functionCall.expression().accept(*this); - appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall); + appendExternalFunctionCall( + function, arguments, _functionCall.annotation().tryCall, &_functionCall.annotation()); break; case FunctionType::Kind::BareCallCode: solAssert(false, "Callcode has been removed."); @@ -801,7 +878,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // If this is a try call, return "
1" in the success case and // "0" in the error case. AssemblyItem errorCase = m_context.appendConditionalJump(); - m_context << u256(1); + _functionCall.annotation().tryCallSuccessTag + = m_context.appendJumpToNew().data().convert_to(); + m_context.adjustStackOffset(1); m_context << errorCase; } else @@ -2686,7 +2765,8 @@ void ExpressionCompiler::appendExpOperatorCode(Type const& _valueType, Type cons void ExpressionCompiler::appendExternalFunctionCall( FunctionType const& _functionType, std::vector> const& _arguments, - bool _tryCall + bool _tryCall, + FunctionCallAnnotation* _annotation ) { solAssert( @@ -2976,8 +3056,10 @@ void ExpressionCompiler::appendExternalFunctionCall( if (_tryCall) { + solAssert(_annotation, ""); // Success branch will reach this, failure branch will directly jump to endTag. - m_context << u256(1); + _annotation->tryCallSuccessTag = m_context.appendJumpToNew().data().convert_to(); + m_context.adjustStackOffset(1); m_context << endTag; } } diff --git a/libsolidity/codegen/ExpressionCompiler.h b/libsolidity/codegen/ExpressionCompiler.h index 13e1e3106e..00e9a5c671 100644 --- a/libsolidity/codegen/ExpressionCompiler.h +++ b/libsolidity/codegen/ExpressionCompiler.h @@ -110,7 +110,8 @@ class ExpressionCompiler: private ASTConstVisitor void appendExternalFunctionCall( FunctionType const& _functionType, std::vector> const& _arguments, - bool _tryCall + bool _tryCall, + FunctionCallAnnotation* _annotation = nullptr ); /// Appends code that evaluates a single expression and moves the result to memory. The memory offset is /// expected to be on the stack and is updated by this call. @@ -139,6 +140,9 @@ class ExpressionCompiler: private ASTConstVisitor /// @returns the CompilerUtils object containing the current context. CompilerUtils utils(); + /// Generates the selector for internal function pointer with type @a _funcType. + void generateSelector(FunctionType const& _funcType); + bool m_optimiseOrderLiterals; CompilerContext& m_context; std::unique_ptr m_currentLValue; diff --git a/libsolidity/codegen/ExtraMetadata.cpp b/libsolidity/codegen/ExtraMetadata.cpp new file mode 100644 index 0000000000..371cf85caf --- /dev/null +++ b/libsolidity/codegen/ExtraMetadata.cpp @@ -0,0 +1,183 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend; + +class InlineAsmRecursiveFuncRecorder: public ASTConstVisitor +{ +public: + void run() { m_func.accept(*this); } + + InlineAsmRecursiveFuncRecorder( + CallableDeclaration const& _func, + CompilerContext const& _context, + CompilerContext const& _runtimeContext, + Json& _recFuncs) + : m_func(_func), m_context(_context), m_runtimeContext(_runtimeContext), m_recFuncs(_recFuncs) + { + } + +private: + CallableDeclaration const& m_func; + CompilerContext const& m_context; + CompilerContext const& m_runtimeContext; + Json& m_recFuncs; + + // Record recursions in @_asm for the extra metadata + void record(InlineAssembly const& _p_asm, CompilerContext const& _context) + { + auto findRes = _context.findInlineAsmContextMapping(&_p_asm); + if (!findRes) + return; + yul::CodeTransformContext const& yulContext = *findRes; + + set recFuncs; + if (_p_asm.annotation().optimizedOperations) + { + yul::Block const& code = _p_asm.annotation().optimizedOperations->root(); + recFuncs = yul::CallGraphGenerator::callGraph(code).recursiveFunctions(); + } + else + { + recFuncs = yul::CallGraphGenerator::callGraph(_p_asm.operations().root()).recursiveFunctions(); + } + for (auto recFunc: recFuncs) + { + auto findIt = yulContext.functionInfoMap.find(recFunc); + if (findIt == yulContext.functionInfoMap.end()) + continue; + for (auto& func: findIt->second) + { + Json record = Json::object(); + record["name"] = recFunc.str(); + if (_context.runtimeContext()) + record["creationTag"] = Json(static_cast(func.label)); + else + record["runtimeTag"] = Json(static_cast(func.label)); + record["totalParamSize"] = Json(static_cast(func.ast->parameters.size())); + record["totalRetParamSize"] + = Json(static_cast(func.ast->returnVariables.size())); + m_recFuncs.push_back(record); + } + } + } + + void endVisit(InlineAssembly const& _p_asm) + { + record(_p_asm, m_context); + record(_p_asm, m_runtimeContext); + } +}; + +Json ExtraMetadataRecorder::run(ContractDefinition const& _contract) +{ + // Set "recursiveFunctions" + Json recFuncs = Json::array(); + + // Record recursions in low level calls + auto recordRecursiveLowLevelFuncs = [&](CompilerContext const& _context) + { + for (auto fn: _context.recursiveLowLevelFuncs()) + { + Json func = Json::object(); + func["name"] = fn.name; + if (_context.runtimeContext()) + func["creationTag"] = fn.tag; + else + func["runtimeTag"] = fn.tag; + func["totalParamSize"] = fn.ins; + func["totalRetParamSize"] = fn.outs; + recFuncs.push_back(func); + } + }; + recordRecursiveLowLevelFuncs(m_context); + recordRecursiveLowLevelFuncs(m_runtimeContext); + + // Get reachable functions from the call-graphs; And get cycles in the call-graphs + auto& creationCallGraph = _contract.annotation().creationCallGraph; + auto& runtimeCallGraph = _contract.annotation().deployedCallGraph; + + set reachableCycleFuncs, reachableFuncs; + + for (FunctionDefinition const* fn: _contract.definedFunctions()) + { + if (fn->isConstructor() && creationCallGraph.set()) + { + reachableCycleFuncs += (*creationCallGraph)->getReachableCycleFuncs(fn); + reachableFuncs += (*creationCallGraph)->getReachableFuncs(fn); + } + else if (runtimeCallGraph.set()) + { + reachableCycleFuncs += (*runtimeCallGraph)->getReachableCycleFuncs(fn); + reachableFuncs += (*runtimeCallGraph)->getReachableFuncs(fn); + } + } + + // Record recursions in inline assembly + for (auto* fn: reachableFuncs) + { + InlineAsmRecursiveFuncRecorder inAsmRecorder{*fn, m_context, m_runtimeContext, recFuncs}; + inAsmRecorder.run(); + } + + // Record recursions in the solidity source + auto recordRecursiveSolFuncs = [&](CompilerContext const& _context) + { + for (auto* fn: reachableCycleFuncs) + { + evmasm::AssemblyItem const& tag = _context.functionEntryLabelIfExists(*fn); + if (tag == evmasm::AssemblyItem(evmasm::UndefinedItem)) + continue; + + Json func = Json::object(); + func["name"] = fn->name(); + + // Assembly::new[Push]Tag() asserts that the tag is 32 bits + auto tagNum = tag.data().convert_to(); + if (_context.runtimeContext()) + func["creationTag"] = tagNum; + else + func["runtimeTag"] = tagNum; + + unsigned totalParamSize = 0, totalRetParamSize = 0; + for (auto& param: fn->parameters()) + totalParamSize += param->type()->sizeOnStack(); + func["totalParamSize"] = totalParamSize; + for (auto& param: fn->returnParameters()) + totalRetParamSize += param->type()->sizeOnStack(); + func["totalRetParamSize"] = totalRetParamSize; + + recFuncs.push_back(func); + } + }; + recordRecursiveSolFuncs(m_context); + recordRecursiveSolFuncs(m_runtimeContext); + + if (!recFuncs.empty()) + m_metadata["recursiveFunctions"] = recFuncs; + return m_metadata; +} diff --git a/libsolidity/codegen/ExtraMetadata.h b/libsolidity/codegen/ExtraMetadata.h new file mode 100644 index 0000000000..e9598c1abe --- /dev/null +++ b/libsolidity/codegen/ExtraMetadata.h @@ -0,0 +1,53 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * The extra metadata recorder + */ + +#include +#include + +#include + +#include + +#pragma once + +namespace solidity::frontend +{ + +class ExtraMetadataRecorder +{ + CompilerContext const& m_context; + CompilerContext const& m_runtimeContext; + /// The root JSON value of the metadata + /// Current mappings: + /// - "recursiveFunctions": array of functions involved in recursion + Json m_metadata; + +public: + ExtraMetadataRecorder(CompilerContext const& _context, CompilerContext const& _runtimeContext) + : m_context(_context), m_runtimeContext(_runtimeContext) + { + } + + /// Stores the extra metadata of @a _contract in `metadata` + Json run(ContractDefinition const& _contract); +}; + +} diff --git a/libsolidity/codegen/FuncPtrTracker.cpp b/libsolidity/codegen/FuncPtrTracker.cpp new file mode 100644 index 0000000000..393abb577d --- /dev/null +++ b/libsolidity/codegen/FuncPtrTracker.cpp @@ -0,0 +1,106 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend; + +void FuncPtrTracker::endVisit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + FunctionDefinition const* functionDef = dynamic_cast(declaration); + if (!functionDef) + return; + + solAssert(*_identifier.annotation().requiredLookup == VirtualLookup::Virtual); + FunctionDefinition const& resolvedFunctionDef = functionDef->resolveVirtual(m_contract); + + solAssert(resolvedFunctionDef.functionType(true)); + solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal); + if (_identifier.annotation().calledDirectly) + return; + m_contract.annotation().intFuncPtrRefs.insert(&resolvedFunctionDef); +} + +void FuncPtrTracker::endVisit(MemberAccess const& _memberAccess) +{ + auto memberFunctionType = dynamic_cast(_memberAccess.annotation().type); + + if (memberFunctionType && memberFunctionType->hasBoundFirstArgument()) + { + solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static); + if (memberFunctionType->kind() == FunctionType::Kind::Internal) + m_contract.annotation().intFuncPtrRefs.insert( + &dynamic_cast(memberFunctionType->declaration())); + } + + Type::Category objectCategory = _memberAccess.expression().annotation().type->category(); + switch (objectCategory) + { + case Type::Category::TypeType: + { + Type const& actualType + = *dynamic_cast(*_memberAccess.expression().annotation().type).actualType(); + + if (actualType.category() == Type::Category::Contract) + { + ContractType const& contractType = dynamic_cast(actualType); + if (contractType.isSuper()) + { + solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); + ContractDefinition const* super = contractType.contractDefinition().superContract(m_contract); + solAssert(super, "Super contract not available."); + FunctionDefinition const& resolvedFunctionDef + = dynamic_cast(*_memberAccess.annotation().referencedDeclaration) + .resolveVirtual(m_contract, super); + + solAssert(resolvedFunctionDef.functionType(true)); + solAssert(resolvedFunctionDef.functionType(true)->kind() == FunctionType::Kind::Internal); + m_contract.annotation().intFuncPtrRefs.insert(&resolvedFunctionDef); + } + else if (memberFunctionType && memberFunctionType->kind() == FunctionType::Kind::Internal) + { + if (auto const* function + = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) + m_contract.annotation().intFuncPtrRefs.insert(function); + } + } + break; + } + case Type::Category::Module: + { + if (auto const* function + = dynamic_cast(_memberAccess.annotation().referencedDeclaration)) + { + auto funType = dynamic_cast(_memberAccess.annotation().type); + solAssert(function && function->isFree()); + solAssert(function->functionType(true)); + solAssert(function->functionType(true)->kind() == FunctionType::Kind::Internal); + solAssert(funType->kind() == FunctionType::Kind::Internal); + solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static); + + m_contract.annotation().intFuncPtrRefs.insert(function); + } + break; + } + default: + break; + } +} diff --git a/libsolidity/codegen/FuncPtrTracker.h b/libsolidity/codegen/FuncPtrTracker.h new file mode 100644 index 0000000000..4b5621c877 --- /dev/null +++ b/libsolidity/codegen/FuncPtrTracker.h @@ -0,0 +1,55 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Tracks function pointer references + */ + +#pragma once + +#include +#include + +namespace solidity::frontend +{ + +/** + * This class is used to add all the function pointer references in the contract and its ancestor contracts to the + * ContractDefinitionAnnotation::intFuncPtrRefs. The visitor is copied from the yul codegen pipeline's usage of + * IRGeneratorForStatements::assignInternalFunctionIDIfNotCalledDirectly() + */ +class FuncPtrTracker: private ASTConstVisitor +{ +public: + FuncPtrTracker(ContractDefinition const& _contract): m_contract(_contract) {} + + void run() + { + for (ContractDefinition const* base: m_contract.annotation().linearizedBaseContracts) + { + base->accept(*this); + } + } + +private: + ContractDefinition const& m_contract; + + void endVisit(Identifier const& _identifier); + void endVisit(MemberAccess const& _memberAccess); +}; + +} diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c68df205f9..038b528959 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,21 @@ CompilerStack::~CompilerStack() TypeProvider::reset(); } +void CompilerStack::populateFuncPtrRefs() +{ + for (Source const* source: m_sourceOrder) + { + if (!source->ast) + continue; + + for (ContractDefinition const* contract: ASTNode::filteredNodes(source->ast->nodes())) + { + FuncPtrTracker tracker{*contract}; + tracker.run(); + } + } +} + void CompilerStack::createAndAssignCallGraphs() { for (Source const* source: m_sourceOrder) @@ -585,6 +601,7 @@ bool CompilerStack::analyzeLegacy(bool _noErrorsSoFar) // Create & assign callgraphs and check for contract dependency cycles if (noErrors) { + populateFuncPtrRefs(); createAndAssignCallGraphs(); annotateInternalFunctionIDs(); findAndReportCyclicContractDependencies(); @@ -1180,6 +1197,17 @@ std::string const& CompilerStack::metadata(Contract const& _contract) const return _contract.metadata.init([&]{ return createMetadata(_contract, m_viaIR); }); } +Json const& CompilerStack::extraMetadata(std::string const& _contractName) const +{ + Contract const& contr = contract(_contractName); + if (m_stackState < AnalysisSuccessful) + solThrow(CompilerError, "Analysis was not successful."); + + solAssert(contr.contract, ""); + + return contr.extraMetadata; +} + CharStream const& CompilerStack::charStream(std::string const& _sourceName) const { solAssert(m_stackState >= SourcesSet, "No sources set."); @@ -1497,6 +1525,7 @@ void CompilerStack::compileContract( compiledContract.generatedYulUtilityCode = compiler->generatedYulUtilityCode(); compiledContract.runtimeGeneratedYulUtilityCode = compiler->runtimeGeneratedYulUtilityCode(); + compiledContract.extraMetadata = compiler->extraMetadata(); _otherCompilers[compiledContract.contract] = compiler; assembleYul(_contract, compiler->assemblyPtr(), compiler->runtimeAssemblyPtr()); diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 8ce2c5d62d..d6cf26b994 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -394,6 +394,9 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac /// @returns the Contract Metadata matching the pipeline selected using the viaIR setting. std::string const& metadata(std::string const& _contractName) const { return metadata(contract(_contractName)); } + /// @returns the contract metadata containing miscellaneous information + Json const& extraMetadata(std::string const& _contractName) const; + /// @returns the CBOR-encoded metadata matching the pipeline selected using the viaIR setting. bytes cborMetadata(std::string const& _contractName) const { return cborMetadata(_contractName, m_viaIR); } @@ -447,6 +450,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac evmasm::LinkerObject runtimeObject; ///< Runtime object. std::string yulIR; ///< Yul IR code straight from the code generator. std::string yulIROptimized; ///< Reparsed and possibly optimized Yul IR code. + Json extraMetadata; ///< Misc metadata util::LazyInit metadata; ///< The metadata json that will be hashed into the chain. util::LazyInit abi; util::LazyInit storageLayout; @@ -457,6 +461,9 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac mutable std::optional runtimeSourceMapping; }; + /// Populates the function pointer references in the AST annotation of each contract + void populateFuncPtrRefs(); + void createAndAssignCallGraphs(); void findAndReportCyclicContractDependencies(); diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 1ca46ccbc2..11518e1a38 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -72,7 +72,6 @@ struct OptimiserSettings static OptimiserSettings minimal() { OptimiserSettings s = none(); - s.runJumpdestRemover = true; s.runPeephole = true; s.simpleCounterForLoopUncheckedIncrement = true; return s; @@ -82,10 +81,7 @@ struct OptimiserSettings { OptimiserSettings s; s.runOrderLiterals = true; - s.runInliner = true; - s.runJumpdestRemover = true; s.runPeephole = true; - s.runDeduplicate = true; s.runCSE = true; s.runConstantOptimiser = true; s.simpleCounterForLoopUncheckedIncrement = true; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index d8ce4f2d0c..f5ae71552f 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -1503,6 +1503,10 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu if (compilationSuccess && isArtifactRequested(_inputsAndSettings.outputSelection, file, name, "evm.gasEstimates", wildcardMatchesExperimental)) evmData["gasEstimates"] = compilerStack.gasEstimates(contractName); + Json extraMetadata = compilerStack.extraMetadata(contractName); + if (compilationSuccess && !extraMetadata.empty()) + evmData["extraMetadata"] = extraMetadata; + if (compilationSuccess && isArtifactRequested( _inputsAndSettings.outputSelection, file, diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp index 565ff2a60e..0f7620b269 100644 --- a/libsolidity/interface/Version.cpp +++ b/libsolidity/interface/Version.cpp @@ -26,6 +26,7 @@ #include char const* solidity::frontend::VersionNumber = ETH_PROJECT_VERSION; +char const* solidity::frontend::ZKsyncVersionString = SOL_VERSION_ZKSYNC; std::string const solidity::frontend::VersionString = std::string(solidity::frontend::VersionNumber) + diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h index bc05fb4033..cf4fbfb009 100644 --- a/libsolidity/interface/Version.h +++ b/libsolidity/interface/Version.h @@ -39,6 +39,7 @@ extern std::string const VersionString; extern std::string const VersionStringStrict; extern bytes const VersionCompactBytes; extern bool const VersionIsRelease; +extern char const* ZKsyncVersionString; } } diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index cf8c720f6b..52c1e1b846 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -109,6 +109,18 @@ AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect( return analysisInfo; } +AsmAnalysisInfo AsmAnalyzer::analyzeStrictAssertCorrect( + Dialect const& _dialect, Object const& _object, yul::ExternalIdentifierAccess::Resolver _resolver) +{ + ErrorList errorList; + langutil::ErrorReporter errors(errorList); + AsmAnalysisInfo analysisInfo; + bool success = yul::AsmAnalyzer(analysisInfo, errors, _dialect, _resolver, _object.qualifiedDataNames()) + .analyze(_object.code()->root()); + yulAssert(success && !errors.hasErrors(), "Invalid assembly/yul code."); + return analysisInfo; +} + size_t AsmAnalyzer::operator()(Literal const& _literal) { bool erroneousLiteralValue = false; diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 64cab51f54..ea8fb9adf3 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -83,6 +83,8 @@ class AsmAnalyzer Block const& _astRoot, std::set const& _qualifiedDataNames ); + static AsmAnalysisInfo analyzeStrictAssertCorrect( + Dialect const& _dialect, Object const& _object, yul::ExternalIdentifierAccess::Resolver _resolver); size_t operator()(Literal const& _literal); size_t operator()(Identifier const&); diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index f621b2089e..53a5007582 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -78,6 +78,8 @@ add_library(yul backends/evm/StackLayoutGenerator.h backends/evm/VariableReferenceCounter.h backends/evm/VariableReferenceCounter.cpp + backends/evm/ZKEVMIntrinsics.cpp + backends/evm/ZKEVMIntrinsics.h optimiser/ASTCopier.cpp optimiser/ASTCopier.h optimiser/ASTWalker.cpp diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index 91d06e2f08..79d0fba3da 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -39,6 +39,7 @@ void CodeGenerator::assemble( evmasm::Assembly& _assembly, langutil::EVMVersion _evmVersion, std::optional _eofVersion, + std::shared_ptr& _context, // out ExternalIdentifierAccess::CodeGenerator _identifierAccessCodeGen, bool _useNamedLabelsForFunctions, bool _optimizeStackAllocation @@ -59,6 +60,7 @@ void CodeGenerator::assemble( CodeTransform::UseNamedLabels::Never ); transform(_parsedData); + _context = transform.context(); if (!transform.stackErrors().empty()) assertThrow( false, diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index d7a6b5c928..b3a7443287 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include @@ -45,6 +46,7 @@ class CodeGenerator evmasm::Assembly& _assembly, langutil::EVMVersion _evmVersion, std::optional _eofVersion, + std::shared_ptr& _context, // out ExternalIdentifierAccess::CodeGenerator _identifierAccess = {}, bool _useNamedLabelsForFunctions = false, bool _optimizeStackAllocation = false diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 6946395654..3b2338b16b 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -608,6 +608,9 @@ void CodeTransform::createFunctionEntryID(FunctionDefinition const& _function) astID ) : m_assembly.newLabelId(); + + m_context->functionInfoMap[_function.name].emplace( + CodeTransformContext::FunctionInfo{&_function, m_context->functionEntryIDs[&scopeFunction]}); } AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _scopeFunction) const diff --git a/libyul/backends/evm/EVMCodeTransform.h b/libyul/backends/evm/EVMCodeTransform.h index c0bbac81b3..f46168c7ad 100644 --- a/libyul/backends/evm/EVMCodeTransform.h +++ b/libyul/backends/evm/EVMCodeTransform.h @@ -42,7 +42,18 @@ struct AsmAnalysisInfo; struct CodeTransformContext { + struct FunctionInfo + { + FunctionDefinition const* ast; + AbstractAssembly::LabelID label; + bool operator<(FunctionInfo const& _other) const + { + return std::less{}(ast, _other.ast); + } + }; + std::map functionEntryIDs; + std::map> functionInfoMap; std::map variableStackHeights; std::map variableReferences; @@ -143,6 +154,7 @@ class CodeTransform void operator()(Continue const&); void operator()(Leave const&); void operator()(Block const& _block); + std::shared_ptr context() { return m_context; } private: AbstractAssembly::LabelID labelFromIdentifier(Identifier const& _identifier); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 6bfdc9cb9d..f49b2938c7 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -30,6 +30,9 @@ #include #include #include +#include + +#include #include @@ -166,6 +169,10 @@ std::set createReservedIdentifiers(langutil::EVMVersion _evmVersion) ) reserved.emplace(name); } + for (auto const& intr: solidity::zkevm::intrInfos) + { + reserved.emplace(intr.name); + } reserved += std::vector{ "linkersymbol"_yulname, "datasize"_yulname, @@ -177,6 +184,56 @@ std::set createReservedIdentifiers(langutil::EVMVersion _evmVersion) return reserved; } +std::pair createVerbatimWrapper( + const std::string& _name, + size_t _params, + size_t _returns, + bool _sideEffects, + const std::vector>& _literalKinds) +{ + SideEffects sideEffects{}; + if (_sideEffects) + { + sideEffects + = {/*movable=*/false, + /*movableApartFromEffects=*/false, + /*canBeRemoved=*/false, + /*canBeRemovedIfNoMSize=*/false, + /*cannotLoop=*/true, + /*otherState=*/SideEffects::Effect::Write, + /*storage=*/SideEffects::Effect::Write, + /*memory=*/SideEffects::Effect::Write}; + } + + std::function genCode; + if (!_literalKinds.empty()) + { + genCode = [=](FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&) + { + yulAssert(_call.arguments.size() == _literalKinds.size(), ""); + size_t numLits = 0; + for (const auto&& [arg, kind]: ranges::views::zip(_call.arguments, _literalKinds)) + { + if (!kind) + continue; + + yulAssert(std::holds_alternative(arg), "Expected literal"); + yulAssert(std::get(arg).kind == kind, "Unexpected literal kind"); + numLits++; + } + + _assembly.appendVerbatim(asBytes(_name), _params - numLits, _returns); + }; + } + else + { + genCode = [=](FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&) + { _assembly.appendVerbatim(asBytes(_name), _params, _returns); }; + } + + return createFunction(_name, _params, _returns, sideEffects, _literalKinds, genCode); +} + std::map createBuiltins(langutil::EVMVersion _evmVersion, std::optional _eofVersion, bool _objectAccess) { @@ -205,6 +262,11 @@ std::map createBuiltins(langutil::EVMVersion _ev builtins.emplace(createEVMFunction(_evmVersion, name, opcode)); } + for (auto const& intr: solidity::zkevm::intrInfos) + { + builtins.emplace(createVerbatimWrapper(intr.name, intr.args, intr.ret, intr.sideEffects, intr.literalKinds)); + } + if (_objectAccess) { builtins.emplace(createFunction("linkersymbol", 1, 1, SideEffects{}, {LiteralKind::String}, []( diff --git a/libyul/backends/evm/ZKEVMIntrinsics.cpp b/libyul/backends/evm/ZKEVMIntrinsics.cpp new file mode 100644 index 0000000000..fba3237332 --- /dev/null +++ b/libyul/backends/evm/ZKEVMIntrinsics.cpp @@ -0,0 +1,61 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +using namespace solidity::zkevm; + +// clang-format off +const std::array solidity::zkevm::intrInfos{{ +// Name Args Rets SideEffect Literals + {"$zk_to_l1", 3, 0, true}, + {"$zk_code_source", 0, 1, false}, + {"$zk_precompile", 2, 1, true}, + {"$zk_meta", 0, 1, false}, + {"$zk_mimic_call", 3, 1, true}, + {"$zk_system_mimic_call", 5, 1, true}, + {"$zk_mimic_call_byref", 2, 1, true}, + {"$zk_system_mimic_call_byref", 4, 1, true}, + {"$zk_raw_call", 4, 1, true}, + {"$zk_raw_call_byref", 3, 1, true}, + {"$zk_system_call", 6, 1, true}, + {"$zk_system_call_byref", 5, 1, true}, + {"$zk_static_raw_call", 4, 1, true}, + {"$zk_static_raw_call_byref", 3, 1, true}, + {"$zk_static_system_call", 6, 1, true}, + {"$zk_static_system_call_byref", 5, 1, true}, + {"$zk_delegate_raw_call", 4, 1, true}, + {"$zk_delegate_raw_call_byref", 3, 1, true}, + {"$zk_delegate_system_call", 6, 1, true}, + {"$zk_delegate_system_call_byref", 5, 1, true}, + {"$zk_set_context_u128", 1, 0, true}, + {"$zk_set_pubdata_price", 1, 0, true}, + {"$zk_increment_tx_counter", 0, 0, true}, + {"$zk_event_initialize", 2, 0, true}, + {"$zk_event_write", 2, 0, true}, + {"$zk_load_calldata_into_active_ptr", 0, 0, true}, + {"$zk_load_returndata_into_active_ptr", 0, 0, true}, + {"$zk_ptr_add_into_active", 1, 0, true}, + {"$zk_ptr_shrink_into_active", 1, 0, true}, + {"$zk_ptr_pack_into_active", 1, 0, true}, + {"$zk_multiplication_high", 2, 1, false}, + {"$zk_global_load", 1, 1, false, {yul::LiteralKind::String}}, + {"$zk_global_store", 2, 0, true, {yul::LiteralKind::String, std::nullopt}}, + {"$zk_global_extra_abi_data", 1, 1, false} +}}; +// clang-format on diff --git a/libyul/backends/evm/ZKEVMIntrinsics.h b/libyul/backends/evm/ZKEVMIntrinsics.h new file mode 100644 index 0000000000..9261fc0478 --- /dev/null +++ b/libyul/backends/evm/ZKEVMIntrinsics.h @@ -0,0 +1,37 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#pragma once + +#include + +namespace solidity::zkevm +{ + +struct IntrInfo +{ + using LiteralKinds = const std::vector>; + const std::string name; + const size_t args; + const size_t ret; + const bool sideEffects; + LiteralKinds literalKinds = {}; +}; + +extern const std::array intrInfos; +} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index b791de3ef3..10154296eb 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -823,8 +823,9 @@ void CommandLineInterface::processInput() void CommandLineInterface::printVersion() { - sout() << "solc, the solidity compiler commandline interface" << std::endl; + sout() << "solc, the ZKsync Solidity compiler commandline interface" << std::endl; sout() << "Version: " << solidity::frontend::VersionString << std::endl; + sout() << "ZKsync: " << solidity::frontend::ZKsyncVersionString << std::endl; } void CommandLineInterface::printLicense() diff --git a/test/libyul/yulSyntaxTests/zkevm/code_source.yul b/test/libyul/yulSyntaxTests/zkevm/code_source.yul new file mode 100644 index 0000000000..b61f33052b --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/code_source.yul @@ -0,0 +1,6 @@ +{ + let a := $zk_code_source() +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/event.yul b/test/libyul/yulSyntaxTests/zkevm/event.yul new file mode 100644 index 0000000000..4a9ff0c485 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/event.yul @@ -0,0 +1,7 @@ +{ + $zk_event_initialize(0xa, 0xb) + $zk_event_write(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/global.yul b/test/libyul/yulSyntaxTests/zkevm/global.yul new file mode 100644 index 0000000000..0970b9de2d --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/global.yul @@ -0,0 +1,8 @@ +{ + let a := $zk_global_load("memory_pointer") + $zk_global_store("memory_pointer", 0xa) + let b := $zk_global_extra_abi_data(0xb) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/increment_tx_counter.yul b/test/libyul/yulSyntaxTests/zkevm/increment_tx_counter.yul new file mode 100644 index 0000000000..b773fe504c --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/increment_tx_counter.yul @@ -0,0 +1,6 @@ +{ + $zk_increment_tx_counter() +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/code_source.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/code_source.yul new file mode 100644 index 0000000000..df1f062db1 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/code_source.yul @@ -0,0 +1,7 @@ +{ + $zk_code_source() +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-20): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/event.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/event.yul new file mode 100644 index 0000000000..f8261b1d79 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/event.yul @@ -0,0 +1,9 @@ +{ + $zk_event_initialize(0xa) + $zk_event_write(0xa) +} +// ==== +// dialect: evm +// ---- +// TypeError 7000: (3-23): Function "$zk_event_initialize" expects 2 arguments but got 1. +// TypeError 7000: (30-45): Function "$zk_event_write" expects 2 arguments but got 1. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/global.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/global.yul new file mode 100644 index 0000000000..4759716711 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/global.yul @@ -0,0 +1,11 @@ +{ + $zk_global_load("memory_pointer") + $zk_global_store(0xa, 0xa) + $zk_global_extra_abi_data(0xb) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-36): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 5859: (55-58): Function expects string literal. +// TypeError 3083: (66-96): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/increment_tx_counter.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/increment_tx_counter.yul new file mode 100644 index 0000000000..aee27cf1fe --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/increment_tx_counter.yul @@ -0,0 +1,7 @@ +{ + let a := $zk_increment_tx_counter() +} +// ==== +// dialect: evm +// ---- +// DeclarationError 3812: (3-38): Variable count mismatch for declaration of "a": 1 variables and 0 values. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/load_into_active_ptr.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/load_into_active_ptr.yul new file mode 100644 index 0000000000..d76e8bb038 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/load_into_active_ptr.yul @@ -0,0 +1,9 @@ +{ + let a := $zk_load_calldata_into_active_ptr() + let b := $zk_load_returndata_into_active_ptr() +} +// ==== +// dialect: evm +// ---- +// DeclarationError 3812: (3-47): Variable count mismatch for declaration of "a": 1 variables and 0 values. +// DeclarationError 3812: (49-95): Variable count mismatch for declaration of "b": 1 variables and 0 values. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/meta.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/meta.yul new file mode 100644 index 0000000000..eee810f853 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/meta.yul @@ -0,0 +1,7 @@ +{ + $zk_meta() +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-13): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/mimic_call.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/mimic_call.yul new file mode 100644 index 0000000000..7eb798e201 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/mimic_call.yul @@ -0,0 +1,9 @@ +{ + $zk_mimic_call(0xa, 0xb, 0xc) + $zk_mimic_call_byref(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-32): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (34-64): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/multiplication_high.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/multiplication_high.yul new file mode 100644 index 0000000000..623ab875c6 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/multiplication_high.yul @@ -0,0 +1,7 @@ +{ + $zk_multiplication_high(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-36): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/precompile.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/precompile.yul new file mode 100644 index 0000000000..460182adb1 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/precompile.yul @@ -0,0 +1,7 @@ +{ + $zk_precompile(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-27): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/ptr_into_active_ptr.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/ptr_into_active_ptr.yul new file mode 100644 index 0000000000..e0765fbab2 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/ptr_into_active_ptr.yul @@ -0,0 +1,11 @@ +{ + $zk_ptr_add_into_active() + $zk_ptr_shrink_into_active() + $zk_ptr_pack_into_active() +} +// ==== +// dialect: evm +// ---- +// TypeError 7000: (3-26): Function "$zk_ptr_add_into_active" expects 1 arguments but got 0. +// TypeError 7000: (30-56): Function "$zk_ptr_shrink_into_active" expects 1 arguments but got 0. +// TypeError 7000: (60-84): Function "$zk_ptr_pack_into_active" expects 1 arguments but got 0. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/raw_call.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/raw_call.yul new file mode 100644 index 0000000000..e26c99865f --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/raw_call.yul @@ -0,0 +1,17 @@ +{ + $zk_raw_call(0xa, 0xb, 0xc, 0xd) + $zk_raw_call_byref(0xa, 0xb, 0xc) + $zk_static_raw_call(0xa, 0xb, 0xc, 0xd) + $zk_static_raw_call_byref(0xa, 0xb, 0xc) + $zk_delegate_raw_call(0xa, 0xb, 0xc, 0xd) + $zk_delegate_raw_call_byref(0xa, 0xb, 0xc) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-35): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (37-70): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (72-111): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (113-153): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (155-196): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (198-240): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/set_context_u128.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/set_context_u128.yul new file mode 100644 index 0000000000..b5f34b113f --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/set_context_u128.yul @@ -0,0 +1,7 @@ +{ + $zk_set_context_u128() +} +// ==== +// dialect: evm +// ---- +// TypeError 7000: (3-23): Function "$zk_set_context_u128" expects 1 arguments but got 0. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/set_pubdata_price.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/set_pubdata_price.yul new file mode 100644 index 0000000000..834104aa07 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/set_pubdata_price.yul @@ -0,0 +1,7 @@ +{ + $zk_set_pubdata_price() +} +// ==== +// dialect: evm +// ---- +// TypeError 7000: (3-24): Function "$zk_set_pubdata_price" expects 1 arguments but got 0. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/system_call.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/system_call.yul new file mode 100644 index 0000000000..ae5c26644e --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/system_call.yul @@ -0,0 +1,17 @@ +{ + $zk_system_call(0xa, 0xb, 0xc, 0xd, 0xe, 0xf) + $zk_system_call_byref(0xa, 0xb, 0xc, 0xd, 0xe) + $zk_static_system_call(0xa, 0xb, 0xc, 0xd, 0xe, 0xf) + $zk_static_system_call_byref(0xa, 0xb, 0xc, 0xd, 0xe) + $zk_delegate_system_call(0xa, 0xb, 0xc, 0xd, 0xe, 0xf) + $zk_delegate_system_call_byref(0xa, 0xb, 0xc, 0xd, 0xe) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-48): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (50-96): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (98-150): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (152-205): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (207-261): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (263-318): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/system_mimic_call.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/system_mimic_call.yul new file mode 100644 index 0000000000..3894b80bc1 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/system_mimic_call.yul @@ -0,0 +1,9 @@ +{ + $zk_system_mimic_call(0xa, 0xb, 0xc, 0xd, 0xe) + $zk_system_mimic_call_byref(0xa, 0xb, 0xc, 0xd) +} +// ==== +// dialect: evm +// ---- +// TypeError 3083: (3-49): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. +// TypeError 3083: (51-98): Top-level expressions are not supposed to return values (this expression returns 1 value). Use ``pop()`` or assign them. diff --git a/test/libyul/yulSyntaxTests/zkevm/invalid/to_l1.yul b/test/libyul/yulSyntaxTests/zkevm/invalid/to_l1.yul new file mode 100644 index 0000000000..bfd32bdbfe --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/invalid/to_l1.yul @@ -0,0 +1,7 @@ +{ + $zk_to_l1(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- +// TypeError 7000: (3-12): Function "$zk_to_l1" expects 3 arguments but got 2. diff --git a/test/libyul/yulSyntaxTests/zkevm/load_into_active_ptr.yul b/test/libyul/yulSyntaxTests/zkevm/load_into_active_ptr.yul new file mode 100644 index 0000000000..1d467b7344 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/load_into_active_ptr.yul @@ -0,0 +1,7 @@ +{ + $zk_load_calldata_into_active_ptr() + $zk_load_returndata_into_active_ptr() +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/meta.yul b/test/libyul/yulSyntaxTests/zkevm/meta.yul new file mode 100644 index 0000000000..3314db1530 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/meta.yul @@ -0,0 +1,6 @@ +{ + let a := $zk_meta() +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/mimic_call.yul b/test/libyul/yulSyntaxTests/zkevm/mimic_call.yul new file mode 100644 index 0000000000..20a3e15850 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/mimic_call.yul @@ -0,0 +1,7 @@ +{ + let a := $zk_mimic_call(0xa, 0xb, 0xc) + let b := $zk_mimic_call_byref(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/multiplication_high.yul b/test/libyul/yulSyntaxTests/zkevm/multiplication_high.yul new file mode 100644 index 0000000000..0204b49fbc --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/multiplication_high.yul @@ -0,0 +1,6 @@ +{ + let a := $zk_multiplication_high(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/precompile.yul b/test/libyul/yulSyntaxTests/zkevm/precompile.yul new file mode 100644 index 0000000000..88f1561b70 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/precompile.yul @@ -0,0 +1,6 @@ +{ + let a := $zk_precompile(0xa, 0xb) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/ptr_into_active_ptr.yul b/test/libyul/yulSyntaxTests/zkevm/ptr_into_active_ptr.yul new file mode 100644 index 0000000000..1459122782 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/ptr_into_active_ptr.yul @@ -0,0 +1,8 @@ +{ + $zk_ptr_add_into_active(0xa) + $zk_ptr_shrink_into_active(0xa) + $zk_ptr_pack_into_active(0xa) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/raw_call.yul b/test/libyul/yulSyntaxTests/zkevm/raw_call.yul new file mode 100644 index 0000000000..18abff37d5 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/raw_call.yul @@ -0,0 +1,11 @@ +{ + let a := $zk_raw_call(0xa, 0xb, 0xc, 0xd) + let b := $zk_raw_call_byref(0xa, 0xb, 0xc) + let c := $zk_static_raw_call(0xa, 0xb, 0xc, 0xd) + let d := $zk_static_raw_call_byref(0xa, 0xb, 0xc) + let e := $zk_delegate_raw_call(0xa, 0xb, 0xc, 0xd) + let f := $zk_delegate_raw_call_byref(0xa, 0xb, 0xc) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/set_context_u128.yul b/test/libyul/yulSyntaxTests/zkevm/set_context_u128.yul new file mode 100644 index 0000000000..9511745266 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/set_context_u128.yul @@ -0,0 +1,6 @@ +{ + $zk_set_context_u128(0xa) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/set_pubdata_price.yul b/test/libyul/yulSyntaxTests/zkevm/set_pubdata_price.yul new file mode 100644 index 0000000000..4e76f22c07 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/set_pubdata_price.yul @@ -0,0 +1,6 @@ +{ + $zk_set_pubdata_price(0xa) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/system_call.yul b/test/libyul/yulSyntaxTests/zkevm/system_call.yul new file mode 100644 index 0000000000..30ceb553cc --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/system_call.yul @@ -0,0 +1,11 @@ +{ + let a := $zk_system_call(0xa, 0xb, 0xc, 0xd, 0xe, 0xf) + let b := $zk_system_call_byref(0xa, 0xb, 0xc, 0xd, 0xe) + let c := $zk_static_system_call(0xa, 0xb, 0xc, 0xd, 0xe, 0xf) + let d := $zk_static_system_call_byref(0xa, 0xb, 0xc, 0xd, 0xe) + let e := $zk_delegate_system_call(0xa, 0xb, 0xc, 0xd, 0xe, 0xf) + let f := $zk_delegate_system_call_byref(0xa, 0xb, 0xc, 0xd, 0xe) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/system_mimic_call.yul b/test/libyul/yulSyntaxTests/zkevm/system_mimic_call.yul new file mode 100644 index 0000000000..7458ac94a4 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/system_mimic_call.yul @@ -0,0 +1,7 @@ +{ + let a := $zk_system_mimic_call(0xa, 0xb, 0xc, 0xd, 0xe) + let b := $zk_system_mimic_call_byref(0xa, 0xb, 0xc, 0xd) +} +// ==== +// dialect: evm +// ---- diff --git a/test/libyul/yulSyntaxTests/zkevm/to_l1.yul b/test/libyul/yulSyntaxTests/zkevm/to_l1.yul new file mode 100644 index 0000000000..253fefdfb9 --- /dev/null +++ b/test/libyul/yulSyntaxTests/zkevm/to_l1.yul @@ -0,0 +1,6 @@ +{ + $zk_to_l1(true, 0xa, 0xb) +} +// ==== +// dialect: evm +// ---- diff --git a/test/solc/CommandLineInterface.cpp b/test/solc/CommandLineInterface.cpp index 6d19df01b3..0dbe24a1e4 100644 --- a/test/solc/CommandLineInterface.cpp +++ b/test/solc/CommandLineInterface.cpp @@ -141,7 +141,8 @@ BOOST_AUTO_TEST_CASE(version) OptionsReaderAndMessages result = runCLI({"solc", "--version"}, ""); BOOST_TEST(result.success); - BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n")); + BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n" + + "ZKsync: " + solidity::frontend::ZKsyncVersionString + "\n")); BOOST_TEST(result.stderrContent == ""); BOOST_TEST(result.options.input.mode == InputMode::Version); }