diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index cf8c720f6b36..af2fabaf0b9b 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -625,12 +625,16 @@ void AsmAnalyzer::expectValidIdentifier(YulName _identifier, SourceLocation cons bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location) { // NOTE: This function uses the default EVM version instead of the currently selected one. - // TODO: Add EOF support auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}, std::nullopt).builtin(YulName(_instructionIdentifier)); if (builtin && builtin->instruction.has_value()) return validateInstructions(builtin->instruction.value(), _location); - else - return false; + + // TODO: Change `prague()` to `EVMVersion{}` once EOF gets deployed + auto const eofBuiltin = EVMDialect::strictAssemblyForEVM(EVMVersion::prague(), 1).builtin(YulName(_instructionIdentifier)); + if (eofBuiltin && eofBuiltin->instruction.has_value()) + return validateInstructions(eofBuiltin->instruction.value(), _location); + + return false; } bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocation const& _location) @@ -702,9 +706,44 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio "PC instruction is a low-level EVM feature. " "Because of that PC is disallowed in strict assembly." ); + else if (m_eofVersion.has_value() && + ( + _instr == evmasm::Instruction::CALL || + _instr == evmasm::Instruction::CALLCODE || + _instr == evmasm::Instruction::DELEGATECALL || + _instr == evmasm::Instruction::SELFDESTRUCT || + _instr == evmasm::Instruction::JUMP || + _instr == evmasm::Instruction::JUMPI || + _instr == evmasm::Instruction::PC || + _instr == evmasm::Instruction::CREATE || + _instr == evmasm::Instruction::CODESIZE || + _instr == evmasm::Instruction::CODECOPY || + _instr == evmasm::Instruction::EXTCODESIZE || + _instr == evmasm::Instruction::EXTCODECOPY || + _instr == evmasm::Instruction::GAS + )) + { + m_errorReporter.typeError( + 9132_error, + _location, + fmt::format( + "The \"{instruction}\" instruction is {kind} VMs (you are currently compiling to EOF).", + fmt::arg("instruction", boost::to_lower_copy(instructionInfo(_instr, m_evmVersion).name)), + fmt::arg("kind", "only available in legacy bytecode") + ) + ); + } else + { + // Sanity check + solAssert(m_evmVersion.hasOpcode(_instr, m_eofVersion) == !m_errorReporter.hasErrors()); + solAssert(m_evmVersion.hasOpcode(_instr, m_eofVersion)); return false; + } + // Sanity check + solAssert(m_evmVersion.hasOpcode(_instr, m_eofVersion) == !m_errorReporter.hasErrors()); + solAssert(!m_evmVersion.hasOpcode(_instr, m_eofVersion)); return true; } diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h index 64cab51f54c5..d8a2b2b1ac5f 100644 --- a/libyul/AsmAnalysis.h +++ b/libyul/AsmAnalysis.h @@ -70,7 +70,10 @@ class AsmAnalyzer m_dataNames(std::move(_dataNames)) { if (EVMDialect const* evmDialect = dynamic_cast(&m_dialect)) + { m_evmVersion = evmDialect->evmVersion(); + m_eofVersion = evmDialect->eofVersion(); + } } bool analyze(Block const& _block); @@ -125,6 +128,7 @@ class AsmAnalyzer AsmAnalysisInfo& m_info; langutil::ErrorReporter& m_errorReporter; langutil::EVMVersion m_evmVersion; + std::optional m_eofVersion; Dialect const& m_dialect; /// Names of data objects to be referenced by builtin functions with literal arguments. std::set m_dataNames; diff --git a/test/Common.cpp b/test/Common.cpp index ae725774c48a..bab95c457b6a 100644 --- a/test/Common.cpp +++ b/test/Common.cpp @@ -155,6 +155,8 @@ void CommonOptions::validate() const std::cout << "- ABI coder: v1 (default: v2)" << std::endl; std::cout << std::endl << "DO NOT COMMIT THE UPDATED EXPECTATIONS." << std::endl << std::endl; } + + assertThrow(!eofVersion().has_value() || evmVersion() >= langutil::EVMVersion::prague(), ConfigException, "EOF is unavailable before Prague fork."); } bool CommonOptions::parse(int argc, char const* const* argv) diff --git a/test/TestCase.cpp b/test/TestCase.cpp index ae89bb56f31a..c2d120f92550 100644 --- a/test/TestCase.cpp +++ b/test/TestCase.cpp @@ -96,8 +96,7 @@ TestCase::TestResult TestCase::checkResult(std::ostream& _stream, const std::str return TestResult::Success; } -EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _filename): - TestCase(_filename) +void EVMVersionRestrictedTestCase::processEVMVersionFlag() { std::string versionString = m_reader.stringSetting("EVMVersion", "any"); if (versionString == "any") @@ -139,3 +138,37 @@ EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _f if (!comparisonResult) m_shouldRun = false; } + +void EVMVersionRestrictedTestCase::processCompileToFlag() +{ + std::optional eofVersion = solidity::test::CommonOptions::get().eofVersion(); + + std::string compileToString = m_reader.stringSetting("bytecodeFormat", "any"); + if (compileToString == "any" || compileToString == "legacy,>=1" || compileToString == ">=1,legacy") + return; + + if (!m_shouldRun) + return; + + // TODO: This is naive implementation because for now we support only one EOF version. + if (compileToString == "legacy" && eofVersion.has_value()) + m_shouldRun = false; + else if (compileToString == ">=1") + { + // TODO: Update if EOF moved to Osaka + // EOF only available since Prague + solAssert(solidity::test::CommonOptions::get().evmVersion() >= langutil::EVMVersion::prague()); + // Do not run if non-EOF test run + if (!eofVersion.has_value()) + m_shouldRun = false; + } + else + BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid bytecodeFormat flag: \"" + compileToString + "\""}); +} + +EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _filename): + TestCase(_filename) +{ + processEVMVersionFlag(); + processCompileToFlag(); +} diff --git a/test/TestCase.h b/test/TestCase.h index 301dbfb658e4..c3d4b16ccfb6 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -112,6 +112,10 @@ class TestCase class EVMVersionRestrictedTestCase: public TestCase { +private: + void processEVMVersionFlag(); + void processCompileToFlag(); + protected: EVMVersionRestrictedTestCase(std::string const& _filename); }; diff --git a/test/libyul/yulSyntaxTests/eof/call_intruction_in_eof.yul b/test/libyul/yulSyntaxTests/eof/call_intruction_in_eof.yul new file mode 100644 index 000000000000..1ff21b86f16e --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/call_intruction_in_eof.yul @@ -0,0 +1,13 @@ +object "a" { + code { + let success := call(gas(), 0x1, 0, 128, 4, 128, 0) + } +} +// ==== +// EVMVersion: >=prague +// bytecodeFormat: >=1 +// ---- +// TypeError 9132: (47-51): The "call" instruction is only available in legacy bytecode VMs (you are currently compiling to EOF). +// TypeError 9132: (52-55): The "gas" instruction is only available in legacy bytecode VMs (you are currently compiling to EOF). +// TypeError 3950: (52-57): Expected expression to evaluate to one value, but got 0 values instead. +// DeclarationError 3812: (32-82): Variable count mismatch for declaration of "success": 1 variables and 0 values.