From a5357f62df2452ed0b1dfde7839d6d1b7c74eef5 Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 8 Oct 2024 23:37:28 +0200 Subject: [PATCH] eof: Support EOF asm analysis --- libyul/AsmAnalysis.cpp | 43 +++++++++++++++++-- libyul/AsmAnalysis.h | 4 ++ test/Common.cpp | 2 + test/TestCase.cpp | 30 ++++++++++++- test/TestCase.h | 4 ++ .../eof/call_intruction_in_eof.yul | 13 ++++++ 6 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 test/libyul/yulSyntaxTests/eof/call_intruction_in_eof.yul diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index cf8c720f6b36..682931dc414d 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,42 @@ 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)); return false; + } + // Sanity check + // PC is not available in strict assembly but it is always valid opcode in legacy evm. + solAssert(_instr == evmasm::Instruction::PC || !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..75cbb7e47643 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::processEVMVersionSetting() { std::string versionString = m_reader.stringSetting("EVMVersion", "any"); if (versionString == "any") @@ -139,3 +138,30 @@ EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _f if (!comparisonResult) m_shouldRun = false; } + +void EVMVersionRestrictedTestCase::processBytecodeFormatSetting() +{ + std::optional eofVersion = solidity::test::CommonOptions::get().eofVersion(); + // TODO: Update if EOF moved to Osaka + // EOF only available since Prague + solAssert(!eofVersion.has_value() ||solidity::test::CommonOptions::get().evmVersion() >= langutil::EVMVersion::prague()); + + std::string bytecodeFormatString = m_reader.stringSetting("bytecodeFormat", "legacy"); + if (bytecodeFormatString == "legacy,>=EOFv1" || bytecodeFormatString == ">=EOFv1,legacy") + return; + + // TODO: This is naive implementation because for now we support only one EOF version. + if (bytecodeFormatString == "legacy" && eofVersion.has_value()) + m_shouldRun = false; + else if (bytecodeFormatString == ">=EOFv1" && !eofVersion.has_value()) + m_shouldRun = false; + else + BOOST_THROW_EXCEPTION(std::runtime_error{"Invalid bytecodeFormat flag: \"" + bytecodeFormatString + "\""}); +} + +EVMVersionRestrictedTestCase::EVMVersionRestrictedTestCase(std::string const& _filename): + TestCase(_filename) +{ + processEVMVersionSetting(); + processBytecodeFormatSetting(); +} diff --git a/test/TestCase.h b/test/TestCase.h index 301dbfb658e4..bdf51d2a5566 100644 --- a/test/TestCase.h +++ b/test/TestCase.h @@ -112,6 +112,10 @@ class TestCase class EVMVersionRestrictedTestCase: public TestCase { +private: + void processEVMVersionSetting(); + void processBytecodeFormatSetting(); + 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..526249d3ad22 --- /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: >=EOFv1 +// ---- +// 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.