From 815e93976b7611bb9e4383fd8d881dee373317cf Mon Sep 17 00:00:00 2001 From: rodiazet Date: Wed, 30 Oct 2024 10:43:14 +0100 Subject: [PATCH] eof: Support EOF contract creation. --- libevmasm/Assembly.cpp | 30 ++++- libevmasm/Assembly.h | 11 ++ libevmasm/AssemblyItem.cpp | 23 +++- libevmasm/AssemblyItem.h | 33 +++++- libevmasm/Instruction.cpp | 4 + libevmasm/Instruction.h | 2 + libevmasm/SemanticInformation.cpp | 50 +++++++- liblangutil/EVMVersion.cpp | 2 + .../codegen/ir/IRGenerationContext.cpp | 47 ++++++++ libsolidity/codegen/ir/IRGenerationContext.h | 10 ++ libsolidity/codegen/ir/IRGenerator.cpp | 109 +++++++++++++----- libsolidity/codegen/ir/IRGenerator.h | 1 + .../codegen/ir/IRGeneratorForStatements.cpp | 37 ++++-- .../experimental/codegen/IRGenerator.cpp | 1 + libyul/AsmAnalysis.cpp | 29 +++++ libyul/backends/evm/AbstractAssembly.h | 5 + libyul/backends/evm/EVMDialect.cpp | 56 ++++++++- libyul/backends/evm/EthAssemblyAdapter.cpp | 10 ++ libyul/backends/evm/EthAssemblyAdapter.h | 2 + libyul/backends/evm/NoOutputAssembly.cpp | 10 ++ libyul/backends/evm/NoOutputAssembly.h | 2 + .../debug_info_in_yul_snippet_escaping/output | 1 + .../standard_viair_requested/output.json | 1 + .../strict_asm_eof_container_prague/input.yul | 27 ++--- .../strict_asm_eof_container_prague/output | 87 +++++++------- .../eof/creation_with_immutables.yul | 100 ++++++++++++++++ test/libyul/yulSyntaxTests/eof/eofcreate.yul | 13 +++ .../eof/eofcreate_invalid_object.yul | 11 ++ .../eofcreate_invalid_object_name_data.yul | 13 +++ .../eofcreate_invalid_object_name_path.yul | 18 +++ .../yulSyntaxTests/eof/returncontract.yul | 12 ++ .../eof/returncontract_invalid_object.yul | 10 ++ .../EVMInstructionInterpreter.cpp | 4 +- 33 files changed, 664 insertions(+), 107 deletions(-) create mode 100644 test/libyul/objectCompiler/eof/creation_with_immutables.yul create mode 100644 test/libyul/yulSyntaxTests/eof/eofcreate.yul create mode 100644 test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object.yul create mode 100644 test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_data.yul create mode 100644 test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_path.yul create mode 100644 test/libyul/yulSyntaxTests/eof/returncontract.yul create mode 100644 test/libyul/yulSyntaxTests/eof/returncontract_invalid_object.yul diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 8f645f6fddcb..153ab547e9a8 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -1335,10 +1335,14 @@ std::map Assembly::findReferencedContainers() const std::set referencedSubcontainersIds; solAssert(m_subs.size() <= 0x100); // According to EOF spec - // TODO: Implement properly when opcodes referring sub containers added. - for (uint16_t i = 0; i < m_subs.size(); ++i) - referencedSubcontainersIds.insert(static_cast(i)); - // END TODO + for (auto&& codeSection: m_codeSections) + for (AssemblyItem const& item: codeSection.items) + if (item.type() == EOFCreate || item.type() == ReturnContract) + { + solAssert(item.data() <= m_subs.size(), "Invalid subcontainer index."); + auto const containerId = static_cast(item.data()); + referencedSubcontainersIds.insert(containerId); + } std::map replacements; uint8_t nUnreferenced = 0; @@ -1405,7 +1409,11 @@ LinkerObject const& Assembly::assembleEOF() const switch (item.type()) { case Operation: - solAssert(item.instruction() != Instruction::DATALOADN); + solAssert( + item.instruction() != Instruction::DATALOADN && + item.instruction() != Instruction::RETURNCONTRACT && + item.instruction() != Instruction::EOFCREATE + ); solAssert(!(item.instruction() >= Instruction::PUSH0 && item.instruction() <= Instruction::PUSH32)); ret.bytecode += assembleOperation(item); break; @@ -1419,6 +1427,18 @@ LinkerObject const& Assembly::assembleEOF() const ret.linkReferences.insert(linkRef); break; } + case EOFCreate: + { + ret.bytecode.push_back(static_cast(Instruction::EOFCREATE)); + ret.bytecode.push_back(static_cast(item.data())); + break; + } + case ReturnContract: + { + ret.bytecode.push_back(static_cast(Instruction::RETURNCONTRACT)); + ret.bytecode.push_back(static_cast(item.data())); + break; + } case VerbatimBytecode: ret.bytecode += assembleVerbatimBytecode(item); break; diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 9b15936d642f..daeea6e8cf0e 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -99,6 +99,17 @@ class Assembly append(AssemblyItem(std::move(_data), _arguments, _returnVariables)); } + AssemblyItem appendEOFCreate(ContainerID _containerId) + { + solAssert(_containerId < m_subs.size(), "EOF Create of undefined container."); + return append(AssemblyItem::eofCreate(_containerId)); + } + AssemblyItem appendReturnContract(ContainerID _containerId) + { + solAssert(_containerId < m_subs.size(), "Return undefined container ID."); + return append(AssemblyItem::returnContract(_containerId)); + } + AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 3ce6238bc8be..71fd98a042b2 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -74,6 +74,8 @@ std::pair AssemblyItem::nameAndData(langutil::EVMVersi switch (type()) { case Operation: + case EOFCreate: + case ReturnContract: return {instructionInfo(instruction(), _evmVersion).name, m_data != nullptr ? toStringInHex(*m_data) : ""}; case Push: return {"PUSH", toStringInHex(data())}; @@ -167,6 +169,10 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ return std::get<2>(*m_verbatimBytecode).size(); case AuxDataLoadN: return 1 + 2; + case EOFCreate: + return 2; + case ReturnContract: + return 2; case UndefinedItem: solAssert(false); } @@ -176,7 +182,10 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ size_t AssemblyItem::arguments() const { - if (type() == Operation) + if (type() == Operation || + type() == EOFCreate || + type() == ReturnContract + ) // The latest EVMVersion is used here, since the InstructionInfo is assumed to be // the same across all EVM versions except for the instruction name. return static_cast(instructionInfo(instruction(), EVMVersion()).args); @@ -193,6 +202,8 @@ size_t AssemblyItem::returnValues() const switch (m_type) { case Operation: + case EOFCreate: + case ReturnContract: // The latest EVMVersion is used here, since the InstructionInfo is assumed to be // the same across all EVM versions except for the instruction name. return static_cast(instructionInfo(instruction(), EVMVersion()).ret); @@ -226,6 +237,8 @@ bool AssemblyItem::canBeFunctional() const switch (m_type) { case Operation: + case EOFCreate: + case ReturnContract: return !isDupInstruction(instruction()) && !isSwapInstruction(instruction()); case Push: case PushTag: @@ -344,6 +357,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const assertThrow(data() <= std::numeric_limits::max(), AssemblyException, "Invalid auxdataloadn argument."); text = "auxdataloadn{" + std::to_string(static_cast(data())) + "}"; break; + case EOFCreate: + text = "eofcreate{" + std::to_string(static_cast(data())) + "}"; + break; + case ReturnContract: + text = "returcontract{" + std::to_string(static_cast(data())) + "}"; + break; } if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) { @@ -362,6 +381,8 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons switch (_item.type()) { case Operation: + case EOFCreate: + case ReturnContract: _out << " " << instructionInfo(_item.instruction(), EVMVersion()).name; if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI) _out << "\t" << _item.getJumpTypeAsString(); diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 226a126c368c..e51d07b16150 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -55,6 +55,8 @@ enum AssemblyItemType /// Loads 32 bytes from static auxiliary data of EOF data section. The offset does *not* have to be always from the beginning /// of the data EOF section. More details here: https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle AuxDataLoadN, + EOFCreate, ///< Creates new contract using subcontainer as initcode + ReturnContract, ///< Returns new container (with auxiliary data filled in) to be deployed VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification. }; @@ -63,6 +65,7 @@ enum class Precision { Precise , Approximate }; class Assembly; class AssemblyItem; using AssemblyItems = std::vector; +using ContainerID = uint8_t; class AssemblyItem { @@ -85,6 +88,14 @@ class AssemblyItem else m_data = std::make_shared(std::move(_data)); } + + explicit AssemblyItem(AssemblyItemType _type, Instruction _instruction, u256 _data = 0, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()): + m_type(_type), + m_instruction(_instruction), + m_data(std::make_shared(std::move(_data))), + m_debugData(std::move(_debugData)) + {} + explicit AssemblyItem(bytes _verbatimData, size_t _arguments, size_t _returnVariables): m_type(VerbatimBytecode), m_instruction{}, @@ -92,6 +103,15 @@ class AssemblyItem m_debugData{langutil::DebugData::create()} {} + static AssemblyItem eofCreate(ContainerID _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(EOFCreate, Instruction::EOFCREATE, _containerID, std::move(_debugData)); + } + static AssemblyItem returnContract(ContainerID _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(ReturnContract, Instruction::RETURNCONTRACT, _containerID, std::move(_debugData)); + } + AssemblyItem(AssemblyItem const&) = default; AssemblyItem(AssemblyItem&&) = default; AssemblyItem& operator=(AssemblyItem const&) = default; @@ -122,8 +142,17 @@ class AssemblyItem bytes const& verbatimData() const { assertThrow(m_type == VerbatimBytecode, util::Exception, ""); return std::get<2>(*m_verbatimBytecode); } - /// @returns the instruction of this item (only valid if type() == Operation) - Instruction instruction() const { assertThrow(m_type == Operation, util::Exception, ""); return m_instruction; } + /// @returns true if the item has m_instruction properly set. + bool hasInstruction() const + { + return m_type == Operation || m_type == EOFCreate || m_type == ReturnContract; + } + /// @returns the instruction of this item (only valid if type() == Operation || EOFCreate || ReturnContract) + Instruction instruction() const + { + solAssert(hasInstruction()); + return m_instruction; + } /// @returns true if the type and data of the items are equal. bool operator==(AssemblyItem const& _other) const diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index b9bb21227c29..5122fb01f604 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -169,6 +169,8 @@ std::map const solidity::evmasm::c_instructions = { "LOG3", Instruction::LOG3 }, { "LOG4", Instruction::LOG4 }, { "DATALOADN", Instruction::DATALOADN }, + { "EOFCREATE", Instruction::EOFCREATE }, + { "RETURNCONTRACT", Instruction::RETURNCONTRACT }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "CALLCODE", Instruction::CALLCODE }, @@ -324,6 +326,8 @@ static std::map const c_instructionInfo = {Instruction::LOG2, {"LOG2", 0, 4, 0, true, Tier::Special}}, {Instruction::LOG3, {"LOG3", 0, 5, 0, true, Tier::Special}}, {Instruction::LOG4, {"LOG4", 0, 6, 0, true, Tier::Special}}, + {Instruction::EOFCREATE, {"EOFCREATE", 1, 4, 1, true, Tier::Special}}, + {Instruction::RETURNCONTRACT, {"RETURNCONTRACT", 1, 2, 0, true, Tier::Special}}, {Instruction::CREATE, {"CREATE", 0, 3, 1, true, Tier::Special}}, {Instruction::CALL, {"CALL", 0, 7, 1, true, Tier::Special}}, {Instruction::CALLCODE, {"CALLCODE", 0, 7, 1, true, Tier::Special}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 2ed773cc9e7b..d9e96c49ad48 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -183,6 +183,8 @@ enum class Instruction: uint8_t LOG4, ///< Makes a log entry; 4 topics. DATALOADN = 0xd1, ///< load data from EOF data section + EOFCREATE = 0xec, ///< create a new account with associated container code. + RETURNCONTRACT = 0xee, ///< return container to be deployed with axiliary data filled in. CREATE = 0xf0, ///< create a new account with associated code CALL, ///< message-call into an account CALLCODE, ///< message-call with another account's code only diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 87d41f6c13a0..e909a7074dbb 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -184,6 +184,34 @@ std::vector SemanticInformation::readWriteOperat Operation{Location::TransientStorage, Effect::Read, {}, {}, {}}, Operation{Location::TransientStorage, Effect::Write, {}, {}, {}} }; + case Instruction::EOFCREATE: + return std::vector{ + Operation{ + Location::Memory, + Effect::Read, + 2, + 3, + {} + }, + Operation{Location::Storage, Effect::Read, {}, {}, {}}, + Operation{Location::Storage, Effect::Write, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Read, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Write, {}, {}, {}} + }; + case Instruction::RETURNCONTRACT: + return std::vector{ + Operation{ + Location::Memory, + Effect::Read, + 0, + 1, + {} + }, + Operation{Location::Storage, Effect::Read, {}, {}, {}}, + Operation{Location::Storage, Effect::Write, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Read, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Write, {}, {}, {}} + }; case Instruction::MSIZE: // This is just to satisfy the assert below. return std::vector{}; @@ -280,8 +308,9 @@ bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) { - if (_item.type() != evmasm::Operation) + if (!_item.hasInstruction()) return false; + switch (_item.instruction()) { // note that CALL, CALLCODE and CREATE do not really alter the control flow, because we @@ -293,6 +322,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::STOP: case Instruction::INVALID: case Instruction::REVERT: + case Instruction::RETURNCONTRACT: return true; default: return false; @@ -301,7 +331,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item) { - if (_item.type() != evmasm::Operation) + if (!_item.hasInstruction()) return false; return terminatesControlFlow(_item.instruction()); } @@ -315,6 +345,7 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction) case Instruction::STOP: case Instruction::INVALID: case Instruction::REVERT: + case Instruction::RETURNCONTRACT: return true; default: return false; @@ -337,7 +368,7 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) { assertThrow(_item.type() != VerbatimBytecode, AssemblyException, ""); - if (_item.type() != evmasm::Operation) + if (!_item.hasInstruction()) return true; switch (_item.instruction()) @@ -357,6 +388,7 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) case Instruction::EXTCODEHASH: case Instruction::RETURNDATACOPY: // depends on previous calls case Instruction::RETURNDATASIZE: + case Instruction::EOFCREATE: return false; default: return true; @@ -436,6 +468,8 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction case Instruction::LOG2: case Instruction::LOG3: case Instruction::LOG4: + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: return SemanticInformation::Read; default: @@ -473,6 +507,8 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio case Instruction::CREATE: case Instruction::CREATE2: case Instruction::SSTORE: + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: return SemanticInformation::Write; case Instruction::SLOAD: @@ -494,6 +530,8 @@ SemanticInformation::Effect SemanticInformation::transientStorage(Instruction _i case Instruction::CREATE: case Instruction::CREATE2: case Instruction::TSTORE: + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: return SemanticInformation::Write; case Instruction::TLOAD: @@ -514,6 +552,8 @@ SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruc case Instruction::DELEGATECALL: case Instruction::CREATE: case Instruction::CREATE2: + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: case Instruction::SELFDESTRUCT: case Instruction::STATICCALL: // because it can affect returndatasize // Strictly speaking, log0, .., log4 writes to the state, but the EVM cannot read it, so they @@ -588,6 +628,10 @@ bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) case Instruction::CALL: case Instruction::CALLCODE: case Instruction::DELEGATECALL: + // According to EOF spec https://eips.ethereum.org/EIPS/eip-7620#eofcreate + case Instruction::EOFCREATE: + // According to EOF spec https://eips.ethereum.org/EIPS/eip-7620#returncontract + case Instruction::RETURNCONTRACT: case Instruction::CREATE2: case Instruction::SELFDESTRUCT: return true; diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index bdd663c411ac..9041ae793492 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -77,6 +77,8 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional _eofVersi case Instruction::GAS: return !_eofVersion.has_value(); // Instructions below available only in EOF + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: case Instruction::DATALOADN: return _eofVersion.has_value(); default: diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index e087dc69a575..fd47fec10184 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -107,10 +107,57 @@ size_t IRGenerationContext::immutableMemoryOffset(VariableDeclaration const& _va return m_immutableVariables.at(&_variable); } +size_t IRGenerationContext::immutableMemoryOffsetRelative(VariableDeclaration const& _variable) const +{ + auto const absoluteOffset = immutableMemoryOffset(_variable); + solAssert(absoluteOffset >= CompilerUtils::generalPurposeMemoryStart); + return absoluteOffset - CompilerUtils::generalPurposeMemoryStart; +} + +size_t IRGenerationContext::reservedMemorySize() const +{ + solAssert(m_reservedMemory.has_value()); + return *m_reservedMemory; +} + +void IRGenerationContext::registerLibraryAddressImmutable() +{ + solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset."); + solAssert(!m_libraryAddressImmutableOffset.has_value()); + m_libraryAddressImmutableOffset = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; + *m_reservedMemory += 32; +} + +size_t IRGenerationContext::libraryAddressImmutableOffset() const +{ + solAssert(m_libraryAddressImmutableOffset.has_value()); + return *m_libraryAddressImmutableOffset; +} + +size_t IRGenerationContext::libraryAddressImmutableOffsetRelative() const +{ + solAssert(m_libraryAddressImmutableOffset.has_value()); + solAssert(m_libraryAddressImmutableOffset >= CompilerUtils::generalPurposeMemoryStart); + return *m_libraryAddressImmutableOffset - CompilerUtils::generalPurposeMemoryStart; +} + size_t IRGenerationContext::reservedMemory() { solAssert(m_reservedMemory.has_value(), "Reserved memory was used before."); size_t reservedMemory = *m_reservedMemory; + + // We assume reserved memory contains only immutable variables. + // This memory is used i.e. by RETURNCONTRACT to create new EOF container with aux data. + size_t immutableVariablesSize = 0; + for (auto const* var: keys(m_immutableVariables)) + { + solUnimplementedAssert(var->type()->isValueType()); + solUnimplementedAssert(var->type()->sizeOnStack() == 1); + immutableVariablesSize += var->type()->sizeOnStack() * 32; + } + + solAssert(immutableVariablesSize == reservedMemory); + m_reservedMemory = std::nullopt; return reservedMemory; } diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 2bf837697b47..01220e87b86d 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -58,6 +58,7 @@ class IRGenerationContext IRGenerationContext( langutil::EVMVersion _evmVersion, + std::optional _eofVersion, ExecutionContext _executionContext, RevertStrings _revertStrings, std::map _sourceIndices, @@ -65,6 +66,7 @@ class IRGenerationContext langutil::CharStreamProvider const* _soliditySourceProvider ): m_evmVersion(_evmVersion), + m_eofVersion(_eofVersion), m_executionContext(_executionContext), m_revertStrings(_revertStrings), m_sourceIndices(std::move(_sourceIndices)), @@ -99,13 +101,18 @@ class IRGenerationContext /// Registers an immutable variable of the contract. /// Should only be called at construction time. void registerImmutableVariable(VariableDeclaration const& _varDecl); + void registerLibraryAddressImmutable(); + size_t libraryAddressImmutableOffset() const; + size_t libraryAddressImmutableOffsetRelative() const; /// @returns the reserved memory for storing the value of the /// immutable @a _variable during contract creation. size_t immutableMemoryOffset(VariableDeclaration const& _variable) const; + size_t immutableMemoryOffsetRelative(VariableDeclaration const& _variable) const; /// @returns the reserved memory and resets it to mark it as used. /// Intended to be used only once for initializing the free memory pointer /// to after the area used for immutables. size_t reservedMemory(); + size_t reservedMemorySize() const; void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } @@ -134,6 +141,7 @@ class IRGenerationContext YulUtilFunctions utils(); langutil::EVMVersion evmVersion() const { return m_evmVersion; } + std::optional eofVersion() const { return m_eofVersion; } ExecutionContext executionContext() const { return m_executionContext; } void setArithmetic(Arithmetic _value) { m_arithmetic = _value; } @@ -160,6 +168,7 @@ class IRGenerationContext private: langutil::EVMVersion m_evmVersion; + std::optional m_eofVersion; ExecutionContext m_executionContext; RevertStrings m_revertStrings; std::map m_sourceIndices; @@ -169,6 +178,7 @@ class IRGenerationContext /// Memory offsets reserved for the values of immutable variables during contract creation. /// This map is empty in the runtime context. std::map m_immutableVariables; + std::optional m_libraryAddressImmutableOffset; /// Total amount of reserved memory. Reserved memory is used to store /// immutable variables during contract creation. std::optional m_reservedMemory = {0}; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 44fc5a935a86..48b646d3536c 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -141,7 +141,11 @@ std::string IRGenerator::generate( - let called_via_delegatecall := iszero(eq(loadimmutable(""), address())) + + let called_via_delegatecall := iszero(eq(auxdataloadn(), address())) + + let called_via_delegatecall := iszero(eq(loadimmutable(""), address())) + @@ -154,6 +158,9 @@ std::string IRGenerator::generate( )"); resetContext(_contract, ExecutionContext::Creation); + auto const eof = m_context.eofVersion().has_value(); + if (eof && _contract.isLibrary()) + m_context.registerLibraryAddressImmutable(); for (VariableDeclaration const* var: ContractType(_contract).immutableVariables()) m_context.registerImmutableVariable(*var); @@ -200,7 +207,15 @@ std::string IRGenerator::generate( // Do not register immutables to avoid assignment. t("DeployedObject", IRNames::deployedObject(_contract)); t("sourceLocationCommentDeployed", dispenseLocationComment(_contract)); - t("library_address", IRNames::libraryAddressImmutable()); + t("eof", eof); + if (_contract.isLibrary()) + { + if (!eof) + t("library_address", IRNames::libraryAddressImmutable()); + else + t("library_address_immutable_offset", std::to_string(m_context.libraryAddressImmutableOffsetRelative())); + } + t("dispatch", dispatchRoutine(_contract)); std::set deployedFunctionList = generateQueuedFunctions(); generateInternalDispatchFunctions(_contract); @@ -532,27 +547,39 @@ std::string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) { solAssert(paramTypes.empty(), ""); solUnimplementedAssert(type->sizeOnStack() == 1); - return Whiskers(R"( + + auto t = Whiskers(R"( function () -> rval { - rval := loadimmutable("") + + rval := auxdataloadn() + + rval := loadimmutable("") + } - )") - ( + )"); + t( "astIDComment", m_context.debugInfoSelection().astID ? "/// @ast-id " + std::to_string(_varDecl.id()) + "\n" : "" - ) - ("sourceLocationComment", dispenseLocationComment(_varDecl)) - ( + ); + t("sourceLocationComment", dispenseLocationComment(_varDecl)); + t( "contractSourceLocationComment", dispenseLocationComment(m_context.mostDerivedContract()) - ) - ("functionName", functionName) - ("id", std::to_string(_varDecl.id())) - .render(); + ); + t("functionName", functionName); + + auto const eof = m_context.eofVersion().has_value(); + t("eof", eof); + if (!eof) + t("id", std::to_string(_varDecl.id())); + else + t("immutableOffset", std::to_string(m_context.immutableMemoryOffsetRelative(_varDecl))); + + return t.render(); } else if (_varDecl.isConstant()) { @@ -940,13 +967,22 @@ void IRGenerator::generateConstructors(ContractDefinition const& _contract) std::string IRGenerator::deployCode(ContractDefinition const& _contract) { Whiskers t(R"X( - let := () - codecopy(, dataoffset(""), datasize("")) - <#immutables> - setimmutable(, "", ) - - return(, datasize("")) + + + mstore(, address()) + + returncontract("", , ) + + let := () + codecopy(, dataoffset(""), datasize("")) + <#immutables> + setimmutable(, "", ) + + return(, datasize("")) + )X"); + auto const eof = m_context.eofVersion().has_value(); + t("eof", eof); t("allocateUnbounded", m_utils.allocateUnboundedFunction()); t("codeOffset", m_context.newYulVariable()); t("object", IRNames::deployedObject(_contract)); @@ -955,23 +991,37 @@ std::string IRGenerator::deployCode(ContractDefinition const& _contract) if (_contract.isLibrary()) { solAssert(ContractType(_contract).immutableVariables().empty(), ""); - immutables.emplace_back(std::map{ - {"immutableName"s, IRNames::libraryAddressImmutable()}, - {"value"s, "address()"} - }); - + if (!eof) + immutables.emplace_back(std::map{ + {"immutableName"s, IRNames::libraryAddressImmutable()}, + {"value"s, "address()"} + }); + else + t("libraryAddressImmutableOffset", std::to_string(m_context.libraryAddressImmutableOffset())); } else + { for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables()) { solUnimplementedAssert(immutable->type()->isValueType()); solUnimplementedAssert(immutable->type()->sizeOnStack() == 1); - immutables.emplace_back(std::map{ - {"immutableName"s, std::to_string(immutable->id())}, - {"value"s, "mload(" + std::to_string(m_context.immutableMemoryOffset(*immutable)) + ")"} - }); + if (!eof) + immutables.emplace_back(std::map{ + {"immutableName"s, std::to_string(immutable->id())}, + {"value"s, "mload(" + std::to_string(m_context.immutableMemoryOffset(*immutable)) + ")"} + }); } - t("immutables", std::move(immutables)); + } + + if (eof) + { + t("auxDataStart", std::to_string(CompilerUtils::generalPurposeMemoryStart)); + solAssert(m_context.reservedMemorySize() <= 0xFFFF, "Reserved memory size exceeded maximum allowed EOF data section size."); + t("auxDataSize", std::to_string(m_context.reservedMemorySize())); + } + else + t("immutables", std::move(immutables)); + return t.render(); } @@ -1095,6 +1145,7 @@ void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionCon ); IRGenerationContext newContext( m_evmVersion, + m_eofVersion, _context, m_context.revertStrings(), m_context.sourceIndices(), diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index 9f01b74fc246..ed29a8bb3971 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -59,6 +59,7 @@ class IRGenerator m_eofVersion(_eofVersion), m_context( _evmVersion, + _eofVersion, ExecutionContext::Creation, _revertStrings, std::move(_sourceIndices), diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 69d24cf42669..1304eca9a3e8 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1563,22 +1563,30 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) &dynamic_cast(*functionType->returnParameterTypes().front()).contractDefinition(); m_context.addSubObject(contract); - Whiskers t(R"(let := () - let := add(, datasize("")) - if or(gt(, 0xffffffffffffffff), lt(, )) { () } - datacopy(, dataoffset(""), datasize("")) - := () - - let
:= create2(, , sub(, ), ) - - let
:= create(, , sub(, )) - + Whiskers t(R"( + + let := () + let := () + let
:= eofcreate("", , , , sub(, )) + + let := () + let := add(, datasize("")) + if or(gt(, 0xffffffffffffffff), lt(, )) { () } + datacopy(, dataoffset(""), datasize("")) + := () + + let
:= create2(, , sub(, ), ) + + let
:= create(, , sub(, )) + + let := iszero(iszero(
)) if iszero(
) { () } )"); + t("eof", m_context.eofVersion().has_value()); t("memPos", m_context.newYulVariable()); t("memEnd", m_context.newYulVariable()); t("allocateUnbounded", m_utils.allocateUnboundedFunction()); @@ -1592,6 +1600,8 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) t("saltSet", functionType->saltSet()); if (functionType->saltSet()) t("salt", IRVariable(_functionCall.expression()).part("salt").name()); + else if (m_context.eofVersion().has_value()) // Set salt to 0 if not defined. + t("salt", "0"); // TODO: We should reject non-salted creation during analysis and not set here solAssert(IRVariable(_functionCall).stackSlots().size() == 1); t("address", IRVariable(_functionCall).commaSeparatedList()); t("isTryCall", _functionCall.annotation().tryCall); @@ -3245,7 +3255,12 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue) ")\n"; } else - define(result) << "loadimmutable(\"" << std::to_string(_immutable.variable->id()) << "\")\n"; + { + if (!m_context.eofVersion().has_value()) + define(result) << "loadimmutable(\"" << std::to_string(_immutable.variable->id()) << "\")\n"; + else + define(result) << "auxdataloadn(" << std::to_string(m_context.immutableMemoryOffsetRelative(*_immutable.variable)) << ")\n"; + } }, [&](IRLValue::Tuple const&) { solAssert(false, "Attempted to read from tuple lvalue."); diff --git a/libsolidity/experimental/codegen/IRGenerator.cpp b/libsolidity/experimental/codegen/IRGenerator.cpp index 60a93bbf01d9..457b28e37396 100644 --- a/libsolidity/experimental/codegen/IRGenerator.cpp +++ b/libsolidity/experimental/codegen/IRGenerator.cpp @@ -67,6 +67,7 @@ std::string IRGenerator::run( std::map const& /*_otherYulSources*/ ) { + solUnimplementedAssert(!m_eofVersion.has_value(), "Experimental IRGenerator not implemented for EOF"); Whiskers t(R"( object "" { diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index a234fdb813d1..8ed0b414211c 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -428,6 +428,35 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall) "The \"verbatim_*\" builtins cannot be used with empty bytecode." ); } + else if (functionName == "eofcreate" || functionName == "returncontract") + { + auto const& argumentAsLiteral = std::get(arg); + auto const formattedLiteral = formatLiteral(argumentAsLiteral); + + if (util::contains(formattedLiteral, '.')) + m_errorReporter.typeError( + 2186_error, + nativeLocationOf(arg), + "Name required but path given as \"" + functionName + "\" argument." + ); + + if (!m_objectStructure.topLevelSubObjectNames().count(formattedLiteral)) + { + if (m_objectStructure.containsData(formattedLiteral)) + m_errorReporter.typeError( + 7575_error, + nativeLocationOf(arg), + "Data name \"" + formattedLiteral + "\" cannot be used as an argument of eofcreate/returncontract. " + + "An object name is only acceptable." + ); + else + m_errorReporter.typeError( + 8970_error, + nativeLocationOf(arg), + "Unknown object \"" + formattedLiteral + "\"." + ); + } + } expectUnlimitedStringLiteral(std::get(arg)); continue; } diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index ed06bebac90b..7c44dfa78dc1 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -56,6 +56,7 @@ class AbstractAssembly public: using LabelID = size_t; using SubID = size_t; + using ContainerID = uint8_t; enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; virtual ~AbstractAssembly() = default; @@ -118,6 +119,10 @@ class AbstractAssembly /// The function is meant to allow indexing into static_aux_data in a way that's independent of the size of pre_deploy_data. virtual void appendAuxDataLoadN(uint16_t _offset) = 0; + /// Appends EOF contract creation instruction which takes creation code from subcontainer with _containerID. + virtual void appendEOFCreate(ContainerID _containerID) = 0; + /// Appends EOF contract return instruction which returns a subcontainer ID (_containerID) with auxiliary data filled in. + virtual void appendReturnContract(ContainerID _containerID) = 0; /// Appends data to the very end of the bytecode. Repeated calls concatenate. /// EOF auxiliary data in data section and the auxiliary data are different things. virtual void appendToAuxiliaryData(bytes const& _data) = 0; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 27a1f1426d75..9de3e40fc598 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -207,6 +207,8 @@ std::vector> createBuiltins(langutil::EVMVe opcode != evmasm::Instruction::JUMPI && opcode != evmasm::Instruction::JUMPDEST && opcode != evmasm::Instruction::DATALOADN && + opcode != evmasm::Instruction::EOFCREATE && + opcode != evmasm::Instruction::RETURNCONTRACT && _evmVersion.hasOpcode(opcode, _eofVersion) && !prevRandaoException(name) ) @@ -370,7 +372,7 @@ std::vector> createBuiltins(langutil::EVMVe } )); } - else + else // EOF context { builtins.emplace_back(createFunction( "auxdataloadn", @@ -384,13 +386,61 @@ std::vector> createBuiltins(langutil::EVMVe AbstractAssembly& _assembly, BuiltinContext& ) { - yulAssert(_call.arguments.size() == 1, ""); + yulAssert(_call.arguments.size() == 1); Literal const* literal = std::get_if(&_call.arguments.front()); yulAssert(literal, ""); - yulAssert(literal->value.value() <= std::numeric_limits::max(), ""); + yulAssert(literal->value.value() <= std::numeric_limits::max()); _assembly.appendAuxDataLoadN(static_cast(literal->value.value())); } )); + + builtins.emplace_back(createFunction( + "eofcreate", + 5, + 1, + EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::EOFCREATE), + ControlFlowSideEffects::fromInstruction(evmasm::Instruction::EOFCREATE), + {LiteralKind::String, std::nullopt, std::nullopt, std::nullopt, std::nullopt}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& context + ) { + yulAssert(_call.arguments.size() == 5); + Literal const* literal = std::get_if(&_call.arguments.front()); + auto const formattedLiteral = formatLiteral(*literal); + yulAssert(!util::contains(formattedLiteral, '.')); + auto const* containerID = valueOrNullptr(context.subIDs, formattedLiteral); + yulAssert(containerID != nullptr); + yulAssert(*containerID <= std::numeric_limits::max()); + _assembly.appendEOFCreate(static_cast(*containerID)); + } + )); + + if (_objectAccess) + builtins.emplace_back(createFunction( + "returncontract", + 3, + 0, + EVMDialect::sideEffectsOfInstruction(evmasm::Instruction::RETURNCONTRACT), + ControlFlowSideEffects::fromInstruction(evmasm::Instruction::RETURNCONTRACT), + {LiteralKind::String, std::nullopt, std::nullopt}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext& context + ) { + yulAssert(_call.arguments.size() == 3); + Literal const* literal = std::get_if(&_call.arguments.front()); + yulAssert(literal); + auto const formattedLiteral = formatLiteral(*literal); + yulAssert(!util::contains(formattedLiteral, '.')); + auto const* containerID = valueOrNullptr(context.subIDs, formattedLiteral); + yulAssert(containerID != nullptr); + yulAssert(*containerID <= std::numeric_limits::max()); + _assembly.appendReturnContract(static_cast(*containerID)); + } + )); } yulAssert( ranges::all_of(builtins, [](std::optional const& _builtinFunction){ diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index 36a437c8cecf..604177c4f78a 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.cpp +++ b/libyul/backends/evm/EthAssemblyAdapter.cpp @@ -128,6 +128,16 @@ std::pair, AbstractAssembly::SubID> EthAssembl return {std::make_shared(*assembly), static_cast(sub.data())}; } +void EthAssemblyAdapter::appendEOFCreate(ContainerID _containerID) +{ + m_assembly.appendEOFCreate(_containerID); +} + +void EthAssemblyAdapter::appendReturnContract(ContainerID _containerID) +{ + m_assembly.appendReturnContract(_containerID); +} + void EthAssemblyAdapter::appendDataOffset(std::vector const& _subPath) { if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end()) diff --git a/libyul/backends/evm/EthAssemblyAdapter.h b/libyul/backends/evm/EthAssemblyAdapter.h index 7dc89e38e0e9..d0bd3e14aff5 100644 --- a/libyul/backends/evm/EthAssemblyAdapter.h +++ b/libyul/backends/evm/EthAssemblyAdapter.h @@ -56,6 +56,8 @@ class EthAssemblyAdapter: public AbstractAssembly void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override; void appendAssemblySize() override; std::pair, SubID> createSubAssembly(bool _creation, std::string _name = {}) override; + void appendEOFCreate(ContainerID _containerID) override; + void appendReturnContract(ContainerID _containerID) override; void appendDataOffset(std::vector const& _subPath) override; void appendDataSize(std::vector const& _subPath) override; SubID appendData(bytes const& _data) override; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 5e82cc6fd3a5..0497b11b63fb 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -151,6 +151,16 @@ void NoOutputAssembly::appendAuxDataLoadN(uint16_t) yulAssert(false, "auxdataloadn not implemented."); } +void NoOutputAssembly::appendEOFCreate(ContainerID) +{ + yulAssert(false, "eofcreate not implemented."); + +} +void NoOutputAssembly::appendReturnContract(ContainerID) +{ + yulAssert(false, "returncontract not implemented."); +} + NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): EVMDialect(_copyFrom.evmVersion(), _copyFrom.eofVersion(), _copyFrom.providesObjectAccess()) { diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index cc70d75afdae..5af1bd418026 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -76,6 +76,8 @@ class NoOutputAssembly: public AbstractAssembly void appendImmutableAssignment(std::string const& _identifier) override; void appendAuxDataLoadN(uint16_t) override; + void appendEOFCreate(ContainerID) override; + void appendReturnContract(ContainerID) override; void markAsInvalid() override {} diff --git a/test/cmdlineTests/debug_info_in_yul_snippet_escaping/output b/test/cmdlineTests/debug_info_in_yul_snippet_escaping/output index d5c02fc82151..975582f669ba 100644 --- a/test/cmdlineTests/debug_info_in_yul_snippet_escaping/output +++ b/test/cmdlineTests/debug_info_in_yul_snippet_escaping/output @@ -312,6 +312,7 @@ object "D_27" { var__5_mpos := zero_t_string_memory_ptr_1_mpos /// @src 0:446:491 "new /// @src 0:149:156 \"new C()\"..." + let _2 := allocate_unbounded() let _3 := add(_2, datasize("C_2")) if or(gt(_3, 0xffffffffffffffff), lt(_3, _2)) { panic_error_0x41() } diff --git a/test/cmdlineTests/standard_viair_requested/output.json b/test/cmdlineTests/standard_viair_requested/output.json index 0105ea97298a..133815b97dd7 100644 --- a/test/cmdlineTests/standard_viair_requested/output.json +++ b/test/cmdlineTests/standard_viair_requested/output.json @@ -201,6 +201,7 @@ object \"D_16\" { function fun_f_15() { /// @src 0:134:141 \"new C()\" + let _1 := allocate_unbounded() let _2 := add(_1, datasize(\"C_3\")) if or(gt(_2, 0xffffffffffffffff), lt(_2, _1)) { panic_error_0x41() } diff --git a/test/cmdlineTests/strict_asm_eof_container_prague/input.yul b/test/cmdlineTests/strict_asm_eof_container_prague/input.yul index 09306772c9a9..d4d9190ff68d 100644 --- a/test/cmdlineTests/strict_asm_eof_container_prague/input.yul +++ b/test/cmdlineTests/strict_asm_eof_container_prague/input.yul @@ -1,33 +1,34 @@ object "object" { code { - revert(0, 0) + mstore(0, eofcreate("sub0", 0, 0, 0, 0)) + mstore(32, eofcreate("sub1", 0, 0, 0, 0)) + return(0, 64) } object "sub0" { code { - mstore(0, 0) - revert(0, 32) + returncontract("sub00", 0, 0) } - } - object "sub1" { - code { - mstore(0, 1) - revert(0, 32) + object "sub00" { + code { + mstore(0, 1) + revert(0, 32) + } } } - object "sub2" { + + object "sub1" { code { - mstore(0, 2) - revert(0, 32) + returncontract("sub10", 0, 0) } - object "sub20" { + object "sub10" { code { mstore(0, 0x20) revert(0, 32) } } } - object "sub3" { + object "sub2" { code { mstore(0, 3) revert(0, 32) diff --git a/test/cmdlineTests/strict_asm_eof_container_prague/output b/test/cmdlineTests/strict_asm_eof_container_prague/output index 17f6df675585..26d37caf4625 100644 --- a/test/cmdlineTests/strict_asm_eof_container_prague/output +++ b/test/cmdlineTests/strict_asm_eof_container_prague/output @@ -3,31 +3,31 @@ Pretty printed source: object "object" { - code { { revert(0, 0) } } - object "sub0" { - code { - { - mstore(0, 0) - revert(0, 32) - } + code { + { + mstore(0, eofcreate("sub0", 0, 0, 0, 0)) + mstore(32, eofcreate("sub1", 0, 0, 0, 0)) + return(0, 64) } } - object "sub1" { + object "sub0" { code { - { - mstore(0, 1) - revert(0, 32) + { returncontract("sub00", 0, 0) } + } + object "sub00" { + code { + { + mstore(0, 1) + revert(0, 32) + } } } } - object "sub2" { + object "sub1" { code { - { - mstore(0, 2) - revert(0, 32) - } + { returncontract("sub10", 0, 0) } } - object "sub20" { + object "sub10" { code { { mstore(0, 0x20) @@ -36,7 +36,7 @@ object "object" { } } } - object "sub3" { + object "sub2" { code { { mstore(0, 3) @@ -48,39 +48,48 @@ object "object" { Binary representation: -ef00010100040200010003030004001a001b003b001b040000000080ffff5f80fdef00010100040200010007040000000080ffff5f805260205ffdef00010100040200010008040000000080ffff60015f5260205ffdef00010100040200010008030001001b040000000080ffff60025f5260205ffdef00010100040200010008040000000080ffff60205f5260205ffdef00010100040200010008040000000080ffff60035f5260205ffd +ef0001010004020001001503000200370037040000000080ffff5f808080ec005f525f808080ec0160205260405ff3ef00010100040200010004030001001b040000000080ffff5f80ee00ef00010100040200010008040000000080ffff60015f5260205ffdef00010100040200010004030001001b040000000080ffff5f80ee00ef00010100040200010008040000000080ffff60205f5260205ffd Text representation: 0x00 dup1 - revert + dup1 + dup1 + eofcreate{0} + 0x00 + mstore + 0x00 + dup1 + dup1 + dup1 + eofcreate{1} + 0x20 + mstore + 0x40 + 0x00 + return stop sub_0: assembly { 0x00 dup1 - mstore - 0x20 - 0x00 - revert -} + returcontract{0} + stop -sub_1: assembly { - 0x01 - 0x00 - mstore - 0x20 - 0x00 - revert + sub_0: assembly { + 0x01 + 0x00 + mstore + 0x20 + 0x00 + revert + } } -sub_2: assembly { - 0x02 - 0x00 - mstore - 0x20 +sub_1: assembly { 0x00 - revert + dup1 + returcontract{0} stop sub_0: assembly { @@ -93,7 +102,7 @@ sub_2: assembly { } } -sub_3: assembly { +sub_2: assembly { 0x03 0x00 mstore diff --git a/test/libyul/objectCompiler/eof/creation_with_immutables.yul b/test/libyul/objectCompiler/eof/creation_with_immutables.yul new file mode 100644 index 000000000000..94d4acef3e1d --- /dev/null +++ b/test/libyul/objectCompiler/eof/creation_with_immutables.yul @@ -0,0 +1,100 @@ +object "a" { + code { + mstore(0, eofcreate("b", 0, 0, 0, 0)) + return(0, 32) + } + + object "b" { + code { + mstore(0, 0x1122334455667788990011223344556677889900112233445566778899001122) + mstore(32, 0x1122334455667788990011223344556677889900112233445566778899001122) + returncontract("c", 0, 64) + } + object "c" { + code { + let d0 := auxdataloadn(0) + let d1 := auxdataloadn(32) + + mstore(0, d0) + mstore(32, d1) + + return(0, 64) + } + } + } + + data "data1" hex"48656c6c6f2c20576f726c6421" +} + +// ==== +// EVMVersion: >=prague +// bytecodeFormat: >=EOFv1 +// ---- +// Assembly: +// /* "source":80:81 */ +// 0x00 +// /* "source":56:82 */ +// dup1 +// dup1 +// dup1 +// eofcreate{0} +// /* "source":53:54 */ +// 0x00 +// /* "source":46:83 */ +// mstore +// /* "source":106:108 */ +// 0x20 +// /* "source":103:104 */ +// 0x00 +// /* "source":96:109 */ +// return +// stop +// data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 +// +// sub_0: assembly { +// /* "source":198:264 */ +// 0x1122334455667788990011223344556677889900112233445566778899001122 +// /* "source":195:196 */ +// 0x00 +// /* "source":188:265 */ +// mstore +// /* "source":293:359 */ +// 0x1122334455667788990011223344556677889900112233445566778899001122 +// /* "source":289:291 */ +// 0x20 +// /* "source":282:360 */ +// mstore +// /* "source":400:402 */ +// 0x40 +// /* "source":397:398 */ +// 0x00 +// /* "source":377:403 */ +// returcontract{0} +// stop +// +// sub_0: assembly { +// /* "source":516:531 */ +// auxdataloadn(0) +// /* "source":562:578 */ +// auxdataloadn(32) +// /* "source":599:612 */ +// swap1 +// /* "source":606:607 */ +// 0x00 +// /* "source":599:612 */ +// mstore +// /* "source":640:642 */ +// 0x20 +// /* "source":633:647 */ +// mstore +// /* "source":678:680 */ +// 0x40 +// /* "source":675:676 */ +// 0x00 +// /* "source":668:681 */ +// return +// } +// } +// Bytecode: ef0001010004020001000c030001008704000d000080ffff5f808080ec005f5260205ff3ef0001010004020001004c0300010023040000000080ffff7f11223344556677889900112233445566778899001122334455667788990011225f527f112233445566778899001122334455667788990011223344556677889900112260205260405fee00ef00010100040200010010040040000080ffffd10000d10020905f5260205260405ff348656c6c6f2c20576f726c6421 +// Opcodes: 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP 0xC SUB STOP ADD STOP DUP8 DIV STOP 0xD STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH0 DUP1 DUP1 DUP1 EOFCREATE 0x0 PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP 0x4C SUB STOP ADD STOP 0x23 DIV STOP STOP STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH32 0x1122334455667788990011223344556677889900112233445566778899001122 PUSH0 MSTORE PUSH32 0x1122334455667788990011223344556677889900112233445566778899001122 PUSH1 0x20 MSTORE PUSH1 0x40 PUSH0 RETURNCONTRACT 0x0 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP LT DIV STOP BLOCKHASH STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT DATALOADN 0x0 DATALOADN 0x20 SWAP1 PUSH0 MSTORE PUSH1 0x20 MSTORE PUSH1 0x40 PUSH0 RETURN BASEFEE PUSH6 0x6C6C6F2C2057 PUSH16 0x726C6421000000000000000000000000 +// SourceMappings: 80:1:0:-:0;56:26;;;;53:1;46:37;106:2;103:1;96:13 diff --git a/test/libyul/yulSyntaxTests/eof/eofcreate.yul b/test/libyul/yulSyntaxTests/eof/eofcreate.yul new file mode 100644 index 000000000000..4169d52ab3b8 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/eofcreate.yul @@ -0,0 +1,13 @@ +object "a" { + code { + let addr := eofcreate("b", 0, 0, 0, 0) + return(0, 0) + } + + object "b" { + code {} + } +} +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 diff --git a/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object.yul b/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object.yul new file mode 100644 index 000000000000..f3acc8277511 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object.yul @@ -0,0 +1,11 @@ +object "a" { + code { + mstore(0, eofcreate("b", 0, 0, 0, 0)) + return(0, 32) + } +} +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 +// ---- +// TypeError 8970: (52-55): Unknown object "b". diff --git a/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_data.yul b/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_data.yul new file mode 100644 index 000000000000..1e9fad7d0244 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_data.yul @@ -0,0 +1,13 @@ +object "a" { + code { + mstore(0, eofcreate("data1", 0, 0, 0, 0)) + return(0, 32) + } + + data "data1" "Hello, World!" +} +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 +// ---- +// TypeError 7575: (52-59): Data name "data1" cannot be used as an argument of eofcreate/returncontract. An object name is only acceptable. diff --git a/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_path.yul b/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_path.yul new file mode 100644 index 000000000000..7efaada8bcd4 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/eofcreate_invalid_object_name_path.yul @@ -0,0 +1,18 @@ +object "a" { + code { + mstore(0, eofcreate("a.b", 0, 0, 0, 0)) + mstore(0, eofcreate("a", 0, 0, 0, 0)) + return(0, 32) + } + + object "b" { + code {} + } +} +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 +// ---- +// TypeError 2186: (52-57): Name required but path given as "eofcreate" argument. +// TypeError 8970: (52-57): Unknown object "a.b". +// TypeError 8970: (100-103): Unknown object "a". diff --git a/test/libyul/yulSyntaxTests/eof/returncontract.yul b/test/libyul/yulSyntaxTests/eof/returncontract.yul new file mode 100644 index 000000000000..2d193a800d17 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/returncontract.yul @@ -0,0 +1,12 @@ +object "b" { + code { + returncontract("c", 0, 0) + } + + object "c" { + code {} + } +} +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 diff --git a/test/libyul/yulSyntaxTests/eof/returncontract_invalid_object.yul b/test/libyul/yulSyntaxTests/eof/returncontract_invalid_object.yul new file mode 100644 index 000000000000..be345ff0a925 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/returncontract_invalid_object.yul @@ -0,0 +1,10 @@ +object "a" { + code { + returncontract("b", 0, 0) + } +} +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 +// ---- +// TypeError 8970: (47-50): Unknown object "b". diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index b43021ea7a52..0f1f37451569 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -488,7 +488,9 @@ u256 EVMInstructionInterpreter::eval( case Instruction::SWAP16: yulAssert(false, "Impossible in strict assembly."); case Instruction::DATALOADN: - solUnimplemented("DATALOADN unimplemented in yul interpreter."); + case Instruction::EOFCREATE: + case Instruction::RETURNCONTRACT: + solUnimplemented("EOF not yet supported by Yul interpreter."); } util::unreachable();