From feeb58b0e84f3af5df7fc8241977e10d1e5501df Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 18 Oct 2024 14:42:14 +0200 Subject: [PATCH] eof: Support EOF contract creation. --- libevmasm/Assembly.cpp | 31 ++++- libevmasm/Assembly.h | 11 ++ libevmasm/AssemblyItem.cpp | 29 +++++ libevmasm/AssemblyItem.h | 11 ++ libevmasm/Instruction.cpp | 4 + libevmasm/Instruction.h | 2 + libevmasm/SemanticInformation.cpp | 6 + .../codegen/ir/IRGenerationContext.cpp | 6 + libsolidity/codegen/ir/IRGenerationContext.h | 6 + libsolidity/codegen/ir/IRGenerator.cpp | 112 +++++++++++++----- libsolidity/codegen/ir/IRGenerator.h | 1 + .../codegen/ir/IRGeneratorForStatements.cpp | 37 ++++-- .../experimental/codegen/IRGenerator.cpp | 9 +- libyul/backends/evm/AbstractAssembly.h | 5 + libyul/backends/evm/EVMDialect.cpp | 74 ++++++++++++ libyul/backends/evm/EthAssemblyAdapter.cpp | 10 ++ libyul/backends/evm/EthAssemblyAdapter.h | 2 + libyul/backends/evm/NoOutputAssembly.cpp | 10 ++ libyul/backends/evm/NoOutputAssembly.h | 2 + .../objectCompiler/eof/auxdata_load_store.yul | 38 +++--- .../eof/creation_with_immutables.yul | 78 ++++++++++++ test/libyul/yulSyntaxTests/eof/eofcreate.yul | 16 +++ .../yulSyntaxTests/eof/returncontract.yul | 14 +++ .../EVMInstructionInterpreter.cpp | 4 + 24 files changed, 453 insertions(+), 65 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/returncontract.yul diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 56e04e9a092a..4cb6558037a8 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -1340,10 +1340,15 @@ 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() <= std::numeric_limits::max(), + "Invalid EofCreate/ReturnContract index value."); + auto const containerId = static_cast(item.data()); + referencedSubcontainersIds.insert(containerId); + } std::map replacements; uint8_t nUnreferenced = 0; @@ -1428,7 +1433,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; @@ -1442,6 +1451,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 31b49e7ed307..32ae65ba49bd 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -102,6 +102,17 @@ class Assembly append(AssemblyItem(std::move(_data), _arguments, _returnVariables)); } + AssemblyItem appendEOFCreate(uint16_t _containerId) + { + assertThrow(_containerId < m_subs.size(), AssemblyException, "EOF Create of undefined container"); + return append(AssemblyItem::eofCreate(_containerId)); + } + AssemblyItem appendReturnContract(uint16_t _containerId) + { + assertThrow(_containerId < m_subs.size(), AssemblyException, "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 02b84e744bcb..914b6173db8d 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -106,6 +106,10 @@ std::pair AssemblyItem::nameAndData(langutil::EVMVersi return {"AUXDATALOADN", util::toString(data())}; case AuxDataStore: return {"AUXDATASTORE", ""}; + case EofCreate: + return {"EOFCREATE", util::toString(data())}; + case ReturnContract: + return {"RETURNCONTRACT", util::toString(data())}; case UndefinedItem: solAssert(false); } @@ -171,6 +175,10 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _ return 1 + 2; case AuxDataStore: return 1; + case EofCreate: + return 2; + case ReturnContract: + return 2; case UndefinedItem: solAssert(false); } @@ -190,6 +198,10 @@ size_t AssemblyItem::arguments() const return 2; else if (type() == AuxDataStore) return 3; + else if (type() == EofCreate) + return 4; + else if (type() == ReturnContract) + return 2; else return 0; } @@ -216,7 +228,10 @@ size_t AssemblyItem::returnValues() const return 0; case VerbatimBytecode: return std::get<1>(*m_verbatimBytecode); + case ReturnContract: + return 0; case AuxDataLoadN: + case EofCreate: return 1; case AssignImmutable: case AuxDataStore: @@ -244,8 +259,10 @@ bool AssemblyItem::canBeFunctional() const case PushDeployTimeAddress: case PushImmutable: case AuxDataLoadN: + case EofCreate: return true; case Tag: + case ReturnContract: return false; case AssignImmutable: case VerbatimBytecode: @@ -355,6 +372,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const case AuxDataStore: text = "auxdatastore()"; 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) { @@ -428,6 +451,12 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons case AuxDataStore: _out << " AuxDataStore"; break; + case EofCreate: + _out << " EofCreate" << util::toString(_item.data()); + break; + case ReturnContract: + _out << " ReturnContract" << util::toString(_item.data()); + break; case UndefinedItem: _out << " ???"; break; diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index fcc3083d27ee..ef93f90f7f98 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -57,6 +57,8 @@ enum AssemblyItemType AuxDataLoadN, /// Stores value in a memory which which will be appended to EOF data section as static axuliary data. AuxDataStore, + EofCreate, /// Creates new contract using subcontainer as initcode + ReturnContract, /// Returns new container along (with auxiliary data section) to be deployed VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification. }; @@ -94,6 +96,15 @@ class AssemblyItem m_debugData{langutil::DebugData::create()} {} + static AssemblyItem eofCreate(uint16_t _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(EofCreate, _containerID, _debugData); + } + static AssemblyItem returnContract(uint16_t _containerID, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()) + { + return AssemblyItem(ReturnContract, _containerID, _debugData); + } + AssemblyItem(AssemblyItem const&) = default; AssemblyItem(AssemblyItem&&) = default; AssemblyItem& operator=(AssemblyItem const&) = default; 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..6555b5f2bfcf 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 id with axiliary data section to be deployed. 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..afa30d907ffc 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -293,6 +293,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) case Instruction::STOP: case Instruction::INVALID: case Instruction::REVERT: + case Instruction::RETURNCONTRACT: return true; default: return false; @@ -315,6 +316,7 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction) case Instruction::STOP: case Instruction::INVALID: case Instruction::REVERT: + case Instruction::RETURNCONTRACT: return true; default: return false; @@ -473,6 +475,7 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio case Instruction::CREATE: case Instruction::CREATE2: case Instruction::SSTORE: + case Instruction::EOFCREATE: return SemanticInformation::Write; case Instruction::SLOAD: @@ -494,6 +497,7 @@ SemanticInformation::Effect SemanticInformation::transientStorage(Instruction _i case Instruction::CREATE: case Instruction::CREATE2: case Instruction::TSTORE: + case Instruction::EOFCREATE: return SemanticInformation::Write; case Instruction::TLOAD: @@ -514,6 +518,7 @@ SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruc case Instruction::DELEGATECALL: case Instruction::CREATE: case Instruction::CREATE2: + case Instruction::EOFCREATE: 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 +593,7 @@ bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) case Instruction::CALL: case Instruction::CALLCODE: case Instruction::DELEGATECALL: + case Instruction::EOFCREATE: case Instruction::CREATE2: case Instruction::SELFDESTRUCT: return true; diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index e087dc69a575..bea110d41a34 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -115,6 +115,12 @@ size_t IRGenerationContext::reservedMemory() return reservedMemory; } +size_t IRGenerationContext::immutablesMemorySize() const +{ + solAssert(m_reservedMemory.has_value(), "Reserved memory was used before."); + return *m_reservedMemory; +} + void IRGenerationContext::addStateVariable( VariableDeclaration const& _declaration, u256 _storageOffset, diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 2bf837697b47..f77c27bc34d0 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)), @@ -106,6 +108,8 @@ class IRGenerationContext /// Intended to be used only once for initializing the free memory pointer /// to after the area used for immutables. size_t reservedMemory(); + size_t immutablesMemorySize() const; + size_t immutablesMemoryOffset() const; void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } @@ -134,6 +138,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 +165,7 @@ class IRGenerationContext private: langutil::EVMVersion m_evmVersion; + std::optional m_eofVersion; ExecutionContext m_executionContext; RevertStrings m_revertStrings; std::map m_sourceIndices; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index 44fc5a935a86..34980f0532f5 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())) + @@ -200,7 +204,18 @@ 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()); + auto const eof = m_context.eofVersion().has_value(); + t("eof", eof); + if (!eof) + t("library_address", IRNames::libraryAddressImmutable()); + else + { + auto const& immutableVariables = ContractType(_contract).immutableVariables(); + auto const varIt = std::find_if(immutableVariables.begin(), immutableVariables.end(), [](VariableDeclaration const* _varDecl) { return _varDecl->name() == IRNames::libraryAddressImmutable(); }); + solAssert(varIt != immutableVariables.end(), ""); + t("library_address_offset", std::to_string(m_context.immutableMemoryOffset(*(*varIt)))); + } + 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("id", std::to_string(m_context.immutableMemoryOffset(_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("")) + + <#immutables> + auxdatastore(, , ) + + 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,42 @@ 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 + { + auto const& immutableVariables = ContractType(_contract).immutableVariables(); + auto const varIt = std::find_if(immutableVariables.begin(), immutableVariables.end(), [](VariableDeclaration const* _varDecl) { return _varDecl->name() == IRNames::libraryAddressImmutable(); }); + solAssert(varIt != immutableVariables.end(), ""); + immutables.emplace_back(std::map{ + {"immutablesMemoryStart"s, std::to_string(CompilerUtils::generalPurposeMemoryStart)}, + {"immutablesOffset"s, std::to_string(m_context.immutableMemoryOffset(*(*varIt)))}, + {"value"s, "address()"} + }); + } } 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)) + ")"} + }); + else + immutables.emplace_back(std::map{ + {"immutablesMemoryStart"s, std::to_string(CompilerUtils::generalPurposeMemoryStart)}, + {"immutablesOffset"s, std::to_string(m_context.immutableMemoryOffset(*immutable))}, + {"value"s, "address()"} + }); } t("immutables", std::move(immutables)); + return t.render(); } @@ -1095,6 +1150,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 911230d224ce..52595083157e 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"); 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.immutableMemoryOffset(*_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..f143766a34ae 100644 --- a/libsolidity/experimental/codegen/IRGenerator.cpp +++ b/libsolidity/experimental/codegen/IRGenerator.cpp @@ -71,8 +71,12 @@ std::string IRGenerator::run( Whiskers t(R"( object "" { code { - codecopy(0, dataoffset(""), datasize("")) - return(0, datasize("")) + + returncontract("", 0, 0) + + codecopy(0, dataoffset(""), datasize("")) + return(0, datasize("")) + } object "" { code { @@ -81,6 +85,7 @@ std::string IRGenerator::run( } } )"); + t("eof", m_eofVersion.has_value()); t("CreationObject", IRNames::creationObject(_contract)); t("DeployedObject", IRNames::deployedObject(_contract)); t("code", generate(_contract)); diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index b9093ef7a40e..d5ac3dc23453 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; @@ -121,6 +122,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 appendEofCreateCall(ContainerID _containerID) = 0; + /// Appends EOF contract return instruction which returns a subcontainer id (_containerID) along with auxiliary data section + virtual void appendReturnContractCall(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 ded5460c6b8d..a1fba95dd597 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -202,6 +202,8 @@ std::map createBuiltins(langutil::EVMVersion _ev 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) ) @@ -392,6 +394,78 @@ std::map createBuiltins(langutil::EVMVersion _ev _assembly.appendAuxDataLoadN(static_cast(literal->value.value())); } )); + + builtins.emplace(createFunction( + "eofcreate", + 5, + 1, + SideEffects{ + false, // movable + false, // movableApartFromEffects + false, // canBeRemoved + false, // canBeRemovedIfNotMSize + true, // cannotLoop + SideEffects::Write, // otherState + SideEffects::Write, // storage + SideEffects::Write, // memory + SideEffects::Write // transientStorage + }, + {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()); + yulAssert(literal, ""); + auto const it = context.subIDs.find(formatLiteral(*literal)); + // TODO: Where to verify this? + yulAssert(it != context.subIDs.end(), ""); + yulAssert((*it).second <= std::numeric_limits::max(), ""); + _assembly.appendEofCreateCall(static_cast((*it).second)); + } + )); + + { + auto [createdFunctionIt, success] = builtins.emplace(createFunction( + "returncontract", + 3, + 0, + SideEffects{ + false, // movable + false, // movableApartFromEffects + false, // canBeRemoved + false, // canBeRemovedIfNotMSize + true, // cannotLoop + SideEffects::None, // otherState + SideEffects::None, // storage + SideEffects::Write, // memory + SideEffects::None // transientStorage + }, + {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 it = context.subIDs.find(formatLiteral(*literal)); + // TODO: Where to verify this? + yulAssert(it != context.subIDs.end(), ""); + yulAssert((*it).second <= std::numeric_limits::max(), ""); + _assembly.appendReturnContractCall(static_cast((*it).second)); + + } + )); + yulAssert(success, ""); + // TODO: Can it be done better? For instructions is being done by `createEVMFunction` + createdFunctionIt->second.controlFlowSideEffects.canContinue = false; + createdFunctionIt->second.controlFlowSideEffects.canTerminate = true; + createdFunctionIt->second.controlFlowSideEffects.canRevert = false; + } } } return builtins; diff --git a/libyul/backends/evm/EthAssemblyAdapter.cpp b/libyul/backends/evm/EthAssemblyAdapter.cpp index 9c37c8f5e44a..c814557b868c 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::appendEofCreateCall(ContainerID _containerID) +{ + m_assembly.appendEOFCreate(_containerID); +} + +void EthAssemblyAdapter::appendReturnContractCall(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 66df12c40dd0..cd6b28bb4c35 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 appendEofCreateCall(ContainerID _containerID) override; + void appendReturnContractCall(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 727505870a06..fb4f3606a8be 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -139,6 +139,16 @@ void NoOutputAssembly::appendAuxDataStore() yulAssert(false, "auxdatastore not implemented."); } +void NoOutputAssembly::appendEofCreateCall(ContainerID) +{ + yulAssert(false, "eofcreate not implemented."); + +} +void NoOutputAssembly::appendReturnContractCall(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 15970094136c..0751461b0841 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -77,6 +77,8 @@ class NoOutputAssembly: public AbstractAssembly void appendAuxDataStore() override; void appendAuxDataLoadN(uint16_t) override; + void appendEofCreateCall(ContainerID) override; + void appendReturnContractCall(ContainerID) override; void markAsInvalid() override {} diff --git a/test/libyul/objectCompiler/eof/auxdata_load_store.yul b/test/libyul/objectCompiler/eof/auxdata_load_store.yul index ec67828065a6..9cef0b012f67 100644 --- a/test/libyul/objectCompiler/eof/auxdata_load_store.yul +++ b/test/libyul/objectCompiler/eof/auxdata_load_store.yul @@ -1,7 +1,8 @@ object "a" { code { { - auxdatastore(0, 32, 0x1122334455667788990011223344556677889900112233445566778899001122) + auxdatastore(0, 0, 0x1122334455667788990011223344556677889900112233445566778899001122) + returncontract("b", 0, 32) return(0, 32) } } @@ -23,37 +24,36 @@ object "a" { // EVMVersion: >=prague // ---- // Assembly: -// /* "source":66:132 */ +// /* "source":65:131 */ // 0x1122334455667788990011223344556677889900112233445566778899001122 -// /* "source":62:64 */ -// 0x20 -// /* "source":59:60 */ +// /* "source":62:63 */ // 0x00 -// /* "source":46:133 */ +// /* "source":46:132 */ +// dup1 // auxdatastore() -// /* "source":156:158 */ +// /* "source":168:170 */ // 0x20 -// /* "source":153:154 */ +// /* "source":165:166 */ // 0x00 -// /* "source":146:159 */ -// return +// /* "source":145:171 */ +// returcontract(0) // stop // data_acaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f 48656c6c6f2c20576f726c6421 // // sub_0: assembly { -// /* "source":248:264 */ +// /* "source":286:302 */ // auxdataloadn(32) -// /* "source":245:246 */ +// /* "source":283:284 */ // 0x00 -// /* "source":238:265 */ +// /* "source":276:303 */ // mstore -// /* "source":292:294 */ +// /* "source":330:332 */ // 0x20 -// /* "source":289:290 */ +// /* "source":327:328 */ // 0x00 -// /* "source":282:295 */ +// /* "source":320:333 */ // return // } -// Bytecode: ef0001010004020001002a030001001c04000d000080ffff7f112233445566778899001122334455667788990011223344556677889900112260205f015260205ff3ef00010100040200010009040040000080ffffd100205f5260205ff348656c6c6f2c20576f726c6421 -// Opcodes: 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP 0x2A SUB STOP ADD STOP SHR DIV STOP 0xD STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH32 0x1122334455667788990011223344556677889900112233445566778899001122 PUSH1 0x20 PUSH0 ADD MSTORE PUSH1 0x20 PUSH0 RETURN 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP MULMOD DIV STOP BLOCKHASH STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT DATALOADN 0x20 PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN BASEFEE PUSH6 0x6C6C6F2C2057 PUSH16 0x726C6421000000000000000000000000 -// SourceMappings: 66:66:0:-:0;62:2;59:1;46:87;156:2;153:1;146:13 \ No newline at end of file +// Bytecode: ef0001010004020001002a030001001c04000d000080ffff7f11223344556677889900112233445566778899001122334455667788990011225f80015260205fee00ef00010100040200010009040040000080ffffd100205f5260205ff348656c6c6f2c20576f726c6421 +// Opcodes: 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP 0x2A SUB STOP ADD STOP SHR DIV STOP 0xD STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH32 0x1122334455667788990011223344556677889900112233445566778899001122 PUSH0 DUP1 ADD MSTORE PUSH1 0x20 PUSH0 RETURNCONTRACT 0x0 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP MULMOD DIV STOP BLOCKHASH STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT DATALOADN 0x20 PUSH0 MSTORE PUSH1 0x20 PUSH0 RETURN BASEFEE PUSH6 0x6C6C6F2C2057 PUSH16 0x726C6421000000000000000000000000 +// SourceMappings: 65:66:0:-:0;62:1;46:86;;168:2;165:1;145:26 \ No newline at end of file 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..52940bf840c4 --- /dev/null +++ b/test/libyul/objectCompiler/eof/creation_with_immutables.yul @@ -0,0 +1,78 @@ +object "a" { + code { + mstore(0, eofcreate("b", 0, 0, 0, 0)) + return(0, 32) + } + + object "b" { + code { + auxdatastore(0, 0, 0x1122334455667788990011223344556677889900112233445566778899001122) + auxdatastore(0, 32, 0x1234567890123456789012345678901234567890123456789012345678901234) + returncontract("c", 0, 64) + } + object "c" { + code { + } + } + } + + data "data1" hex"48656c6c6f2c20576f726c6421" +} + +// ==== +// bytecodeFormat: >=EOFv1 +// EVMVersion: >=prague +// ---- +// 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":207:273 */ +// 0x1122334455667788990011223344556677889900112233445566778899001122 +// /* "source":204:205 */ +// 0x00 +// /* "source":188:274 */ +// dup1 +// auxdatastore() +// /* "source":311:377 */ +// 0x1234567890123456789012345678901234567890123456789012345678901234 +// /* "source":307:309 */ +// 0x20 +// /* "source":304:305 */ +// 0x00 +// /* "source":291:378 */ +// auxdatastore() +// /* "source":418:420 */ +// 0x40 +// /* "source":415:416 */ +// 0x00 +// /* "source":395:421 */ +// returcontract(0) +// stop +// +// sub_0: assembly { +// /* "source":484:491 */ +// stop +// } +// } +// Bytecode: ef0001010004020001000c030001007c04000d000080ffff5f808080ec005f5260205ff3ef000101000402000100500300010014040000000080ffff7f11223344556677889900112233445566778899001122334455667788990011225f8001527f123456789012345678901234567890123456789012345678901234567890123460205f015260405fee00ef00010100040200010001040000000080ffff0048656c6c6f2c20576f726c6421 +// Opcodes: 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP 0xC SUB STOP ADD STOP PUSH29 0x4000D000080FFFF5F808080EC005F5260205FF3EF0001010004020001 STOP POP SUB STOP ADD STOP EQ DIV STOP STOP STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH32 0x1122334455667788990011223344556677889900112233445566778899001122 PUSH0 DUP1 ADD MSTORE PUSH32 0x1234567890123456789012345678901234567890123456789012345678901234 PUSH1 0x20 PUSH0 ADD MSTORE PUSH1 0x40 PUSH0 RETURNCONTRACT 0x0 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP ADD DIV STOP STOP STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT STOP 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..2ffa3ff80d60 --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/eofcreate.yul @@ -0,0 +1,16 @@ +object "a" { + code { + let success := eofcreate("b", 0, 0, 0, 0) + return(0, 0) + } + + object "b" { + code {} + } + + data "data1" "Hello, World!" +} + +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 \ No newline at end of file diff --git a/test/libyul/yulSyntaxTests/eof/returncontract.yul b/test/libyul/yulSyntaxTests/eof/returncontract.yul new file mode 100644 index 000000000000..9f7a5c79d95c --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/returncontract.yul @@ -0,0 +1,14 @@ +object "b" { + code { + returncontract("c", 0, 0) + } + object "c" { + code { + } + } +} + + +// ==== +// EVMVersion: >=shanghai +// bytecodeFormat: >=EOFv1 \ No newline at end of file diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 6ac3cd1bd817..aa82eaa93ee7 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -489,6 +489,10 @@ u256 EVMInstructionInterpreter::eval( yulAssert(false, "Impossible in strict assembly."); case Instruction::DATALOADN: solUnimplemented("DATALOADN unimplemented in yul interpreter."); + case Instruction::EOFCREATE: + solUnimplemented("EOFCREATE unimplemented in yul interpreter."); + case Instruction::RETURNCONTRACT: + solUnimplemented("RETURNCONTRACT unimplemented in yul interpreter."); } util::unreachable();