From 63c2be6c6cbbee303cef0c20061f49f057eeb46d Mon Sep 17 00:00:00 2001 From: rodiazet Date: Fri, 6 Dec 2024 15:33:10 +0100 Subject: [PATCH] eof: Implement and use `ext*call` --- libevmasm/Instruction.cpp | 6 ++ libevmasm/Instruction.h | 6 ++ libevmasm/SemanticInformation.cpp | 38 ++++++++++ liblangutil/EVMVersion.cpp | 3 + .../codegen/ir/IRGeneratorForStatements.cpp | 72 ++++++++++++++----- libyul/AsmAnalysis.cpp | 16 +++++ test/libyul/objectCompiler/eof/extcall.yul | 52 ++++++++++++++ test/libyul/yulSyntaxTests/eof/extcalls.yul | 11 +++ .../eof/extcalls_invalid_in_legacy.yul | 17 +++++ .../EVMInstructionInterpreter.cpp | 3 + 10 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 test/libyul/objectCompiler/eof/extcall.yul create mode 100644 test/libyul/yulSyntaxTests/eof/extcalls.yul create mode 100644 test/libyul/yulSyntaxTests/eof/extcalls_invalid_in_legacy.yul diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp index 82ed73cc994e..649a6c863486 100644 --- a/libevmasm/Instruction.cpp +++ b/libevmasm/Instruction.cpp @@ -182,6 +182,9 @@ std::map const solidity::evmasm::c_instructions = { "STATICCALL", Instruction::STATICCALL }, { "RETURN", Instruction::RETURN }, { "DELEGATECALL", Instruction::DELEGATECALL }, + { "EXTCALL", Instruction::EXTCALL }, + { "EXTSTATICCALL", Instruction::EXTSTATICCALL }, + { "EXTDELEGATECALL", Instruction::EXTDELEGATECALL }, { "CREATE2", Instruction::CREATE2 }, { "REVERT", Instruction::REVERT }, { "INVALID", Instruction::INVALID }, @@ -344,6 +347,9 @@ static std::map const c_instructionInfo = {Instruction::RETURN, {"RETURN", 0, 2, 0, true, Tier::Zero}}, {Instruction::DELEGATECALL, {"DELEGATECALL", 0, 6, 1, true, Tier::Special}}, {Instruction::STATICCALL, {"STATICCALL", 0, 6, 1, true, Tier::Special}}, + {Instruction::EXTCALL, {"EXTCALL", 0, 4, 1, true, Tier::Special}}, + {Instruction::EXTDELEGATECALL,{"EXTDELEGATECALL", 0, 3, 1, true, Tier::Special}}, + {Instruction::EXTSTATICCALL, {"EXTSTATICCALL", 0, 3, 1, true, Tier::Special}}, {Instruction::CREATE2, {"CREATE2", 0, 4, 1, true, Tier::Special}}, {Instruction::REVERT, {"REVERT", 0, 2, 0, true, Tier::Zero}}, {Instruction::INVALID, {"INVALID", 0, 0, 0, true, Tier::Zero}}, diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 351a6981c070..a1b9af364a0d 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -198,7 +198,10 @@ enum class Instruction: uint8_t RETURN, ///< halt execution returning output data DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender CREATE2 = 0xf5, ///< create new account with associated code at address `sha3(0xff + sender + salt + init code) % 2**160` + EXTCALL = 0xf8, ///< EOF message-call into an account + EXTDELEGATECALL = 0xf9, ///< EOF delegate call STATICCALL = 0xfa, ///< like CALL but disallow state modifications + EXTSTATICCALL = 0xfb, ///< like EXTCALL but disallow state modifications REVERT = 0xfd, ///< halt execution, revert state and return output data INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) @@ -214,6 +217,9 @@ constexpr bool isCallInstruction(Instruction _inst) noexcept case Instruction::CALLCODE: case Instruction::DELEGATECALL: case Instruction::STATICCALL: + case Instruction::EXTCALL: + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: return true; default: return false; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index c27945b82e51..a9672efa53b0 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -169,6 +169,26 @@ std::vector SemanticInformation::readWriteOperat }); return operations; } + case Instruction::EXTCALL: + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: + { + size_t paramCount = static_cast(instructionInfo(_instruction, langutil::EVMVersion()).args); + size_t const memoryStartParam = _instruction == Instruction::EXTCALL ? paramCount - 3 : paramCount - 2; + size_t const memoryLengthParam = _instruction == Instruction::EXTCALL ? paramCount - 2 : paramCount - 1; + std::vector operations{ + Operation{Location::Memory, Effect::Read, memoryStartParam, memoryLengthParam, {}}, + Operation{Location::Storage, Effect::Read, {}, {}, {}}, + Operation{Location::TransientStorage, Effect::Read, {}, {}, {}} + }; + if (_instruction == Instruction::EXTDELEGATECALL) + { + operations.emplace_back(Operation{Location::Storage, Effect::Write, {}, {}, {}}); + operations.emplace_back(Operation{Location::TransientStorage, Effect::Write, {}, {}, {}}); + } + + return operations; + } case Instruction::CREATE: case Instruction::CREATE2: return std::vector{ @@ -380,6 +400,9 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item) case Instruction::CALLCODE: case Instruction::DELEGATECALL: case Instruction::STATICCALL: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: + case Instruction::EXTSTATICCALL: case Instruction::CREATE: case Instruction::CREATE2: case Instruction::GAS: @@ -475,6 +498,9 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction case Instruction::LOG4: case Instruction::EOFCREATE: case Instruction::RETURNCONTRACT: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: + case Instruction::EXTSTATICCALL: return SemanticInformation::Read; default: @@ -514,10 +540,13 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio case Instruction::SSTORE: case Instruction::EOFCREATE: case Instruction::RETURNCONTRACT: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: return SemanticInformation::Write; case Instruction::SLOAD: case Instruction::STATICCALL: + case Instruction::EXTSTATICCALL: return SemanticInformation::Read; default: @@ -537,10 +566,13 @@ SemanticInformation::Effect SemanticInformation::transientStorage(Instruction _i case Instruction::TSTORE: case Instruction::EOFCREATE: case Instruction::RETURNCONTRACT: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: return SemanticInformation::Write; case Instruction::TLOAD: case Instruction::STATICCALL: + case Instruction::EXTSTATICCALL: return SemanticInformation::Read; default: @@ -559,7 +591,10 @@ SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruc case Instruction::CREATE2: case Instruction::EOFCREATE: case Instruction::RETURNCONTRACT: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: case Instruction::SELFDESTRUCT: + case Instruction::EXTSTATICCALL: case Instruction::STATICCALL: // because it can affect returndatasize // Strictly speaking, log0, .., log4 writes to the state, but the EVM cannot read it, so they // are just marked as having 'other side effects.' @@ -606,6 +641,7 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) case Instruction::NUMBER: case Instruction::PREVRANDAO: case Instruction::GASLIMIT: + case Instruction::EXTSTATICCALL: case Instruction::STATICCALL: case Instruction::SLOAD: case Instruction::TLOAD: @@ -635,6 +671,8 @@ bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) case Instruction::CALL: case Instruction::CALLCODE: case Instruction::DELEGATECALL: + case Instruction::EXTCALL: + case Instruction::EXTDELEGATECALL: // 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 diff --git a/liblangutil/EVMVersion.cpp b/liblangutil/EVMVersion.cpp index 26364f2af2a2..6f7ac91397d9 100644 --- a/liblangutil/EVMVersion.cpp +++ b/liblangutil/EVMVersion.cpp @@ -85,6 +85,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional _eofVersi case Instruction::CALLF: case Instruction::JUMPF: case Instruction::RETF: + case Instruction::EXTCALL: + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: return _eofVersion.has_value(); default: return true; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 9b2e0ea7723e..babe43357fbb 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -1622,11 +1622,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) Whiskers templ(R"( let := 0 if iszero() { := } - let := call(,
, , 0, 0, 0, 0) + + let := iszero(extcall(
, 0, 0, )) + + let := call(,
, , 0, 0, 0, 0) + if iszero() { () } )"); + templ("eof", m_context.eofVersion().has_value()); templ("gas", m_context.newYulVariable()); templ("callStipend", toString(evmasm::GasCosts::callStipend)); templ("address", address); @@ -1669,17 +1674,30 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) mstore(0, 0) - let := (,
, 0, , sub(, ), 0, 32) + + // EOF always uses extstaticcall + let := iszero(extstaticcall(
, , sub(, ))) + + let := (,
, 0, , sub(, ), 0, 32) + if iszero() { () } + + if eq(returndatasize(), 32) { returndatacopy(0, 0, 32) } + let := (mload(0)) )"); - templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call"); - templ("isCall", !m_context.evmVersion().hasStaticCall()); + auto const eof = m_context.eofVersion().has_value(); + if (!eof) + { + templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call"); + templ("isCall", !m_context.evmVersion().hasStaticCall()); + } templ("shl", m_utils.shiftLeftFunction(offset * 8)); templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); templ("pos", m_context.newYulVariable()); templ("end", m_context.newYulVariable()); templ("isECRecover", FunctionType::Kind::ECRecover == functionType->kind()); + templ("eof", eof); if (FunctionType::Kind::ECRecover == functionType->kind()) templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes)); else @@ -2630,7 +2648,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall( argumentStrings += IRVariable(*arg).stackSlots(); } - if (!m_context.evmVersion().canOverchargeGasForCall()) { // Touch the end of the output area so that we do not pay for memory resize during the call @@ -2642,7 +2659,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall( } // NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically. - // When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy(). + // When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy(). Whiskers templ(R"( if iszero(extcodesize(
)) { () } @@ -2652,7 +2669,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall( mstore(, ()) let := (add(, 4) ) - let := (,
, , , sub(, ), , ) + + let := iszero((
, , sub(, ) , )) + + let := (,
, , , sub(, ), , ) + if iszero() { () } @@ -2667,6 +2688,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall( if gt(, returndatasize()) { := returndatasize() } + + returndatacopy(, 0, ) + @@ -2678,6 +2702,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall( } )"); templ("revertNoCode", m_utils.revertReasonIfDebugFunction("Target contract does not contain code")); + auto const eof = m_context.eofVersion().has_value(); + solAssert(!eof || !funType.gasSet()); + templ("eof", eof); // We do not need to check extcodesize if we expect return data: If there is no // code, the call will return empty data and the ABI decoder will revert. @@ -2685,9 +2712,12 @@ void IRGeneratorForStatements::appendExternalFunctionCall( for (auto const& t: returnInfo.returnTypes) encodedHeadSize += t->decodingType()->calldataHeadSize(); bool const checkExtcodesize = - encodedHeadSize == 0 || - !m_context.evmVersion().supportsReturndata() || - m_context.revertStrings() >= RevertStrings::Debug; + !eof && + ( + encodedHeadSize == 0 || + !m_context.evmVersion().supportsReturndata() || + m_context.revertStrings() >= RevertStrings::Debug + ); templ("checkExtcodesize", checkExtcodesize); templ("pos", m_context.newYulVariable()); @@ -2748,11 +2778,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall( } // Order is important here, STATICCALL might overlap with DELEGATECALL. if (isDelegateCall) - templ("call", "delegatecall"); + templ("call", eof ? "extdelegatecall" : "delegatecall"); else if (useStaticCall) - templ("call", "staticcall"); + templ("call", eof ? "extstaticcall" : "staticcall"); else - templ("call", "call"); + templ("call", eof ? "extcall" : "call"); templ("forwardingRevert", m_utils.forwardingRevertFunction()); @@ -2791,13 +2821,20 @@ void IRGeneratorForStatements::appendBareCall( let := mload() - let := (,
, , , , 0, 0) + + let := iszero((
, , , )) + + let := (,
, , , , 0, 0) + + let := () )"); templ("allocateUnbounded", m_utils.allocateUnboundedFunction()); templ("pos", m_context.newYulVariable()); templ("length", m_context.newYulVariable()); + auto const eof = m_context.eofVersion().has_value(); + templ("eof", eof); templ("arg", IRVariable(*_arguments.front()).commaSeparatedList()); Type const& argType = type(*_arguments.front()); @@ -2819,18 +2856,19 @@ void IRGeneratorForStatements::appendBareCall( if (funKind == FunctionType::Kind::BareCall) { templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0"); - templ("call", "call"); + templ("call", eof ? "extcall" : "call"); } else { solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall."); templ("value", ""); if (funKind == FunctionType::Kind::BareStaticCall) - templ("call", "staticcall"); + templ("call", eof ? "extstaticcall" : "staticcall"); else - templ("call", "delegatecall"); + templ("call", eof ? "extdelegatecall" : "delegatecall"); } + solAssert(!eof || !funType.gasSet()); if (funType.gasSet()) templ("gas", IRVariable(_functionCall.expression()).part("gas").name()); else if (m_context.evmVersion().canOverchargeGasForCall()) diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index f1664ad685c8..aab67db5e622 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -772,6 +772,22 @@ 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::EXTCALL || + _instr == evmasm::Instruction::EXTDELEGATECALL || + _instr == evmasm::Instruction::EXTSTATICCALL + )) + { + m_errorReporter.typeError( + 4328_error, + _location, + fmt::format( + "The \"{instruction}\" instruction is {kind} VMs.", + fmt::arg("instruction", boost::to_lower_copy(instructionInfo(_instr, m_evmVersion).name)), + fmt::arg("kind", "not available in legacy bytecode") + ) + ); + } else if (m_eofVersion.has_value() && ( _instr == evmasm::Instruction::CALL || _instr == evmasm::Instruction::CALLCODE || diff --git a/test/libyul/objectCompiler/eof/extcall.yul b/test/libyul/objectCompiler/eof/extcall.yul new file mode 100644 index 000000000000..1ec154d20788 --- /dev/null +++ b/test/libyul/objectCompiler/eof/extcall.yul @@ -0,0 +1,52 @@ +object "a" { + code { + sstore(0, extcall(address(), 0, 0, 10)) + sstore(1, extdelegatecall(address(), 0, 0)) + sstore(2, extstaticcall(address(), 0, 0)) + } +} + +// ==== +// bytecodeFormat: >=EOFv1 +// ---- +// Assembly: +// /* "source":81:83 */ +// 0x0a +// /* "source":78:79 */ +// 0x00 +// /* "source":64:73 */ +// dup1 +// address +// /* "source":56:84 */ +// extcall +// /* "source":53:54 */ +// 0x00 +// /* "source":46:85 */ +// sstore +// /* "source":138:139 */ +// 0x00 +// /* "source":124:133 */ +// dup1 +// address +// /* "source":108:140 */ +// extdelegatecall +// /* "source":105:106 */ +// 0x01 +// /* "source":98:141 */ +// sstore +// /* "source":192:193 */ +// 0x00 +// /* "source":178:187 */ +// dup1 +// address +// /* "source":164:194 */ +// extstaticcall +// /* "source":161:162 */ +// 0x02 +// /* "source":154:195 */ +// sstore +// /* "source":22:211 */ +// stop +// Bytecode: ef00010100040200010017040000000080ffff600a5f8030f85f555f8030f96001555f8030fb60025500 +// Opcodes: 0xEF STOP ADD ADD STOP DIV MUL STOP ADD STOP OR DIV STOP STOP STOP STOP DUP1 SELFDESTRUCT SELFDESTRUCT PUSH1 0xA PUSH0 DUP1 ADDRESS EXTCALL PUSH0 SSTORE PUSH0 DUP1 ADDRESS EXTDELEGATECALL PUSH1 0x1 SSTORE PUSH0 DUP1 ADDRESS EXTSTATICCALL PUSH1 0x2 SSTORE STOP +// SourceMappings: 81:2:0:-:0;78:1;64:9;;56:28;53:1;46:39;138:1;124:9;;108:32;105:1;98:43;192:1;178:9;;164:30;161:1;154:41;22:189 diff --git a/test/libyul/yulSyntaxTests/eof/extcalls.yul b/test/libyul/yulSyntaxTests/eof/extcalls.yul new file mode 100644 index 000000000000..a8352c254d8e --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/extcalls.yul @@ -0,0 +1,11 @@ +object "a" { + code { + pop(extcall(address(), 0, 0, 0)) + pop(extdelegatecall(address(), 0, 0)) + pop(extstaticcall(address(), 0, 0)) + } +} + +// ==== +// bytecodeFormat: >=EOFv1 +// ---- diff --git a/test/libyul/yulSyntaxTests/eof/extcalls_invalid_in_legacy.yul b/test/libyul/yulSyntaxTests/eof/extcalls_invalid_in_legacy.yul new file mode 100644 index 000000000000..469c5a0dbc9a --- /dev/null +++ b/test/libyul/yulSyntaxTests/eof/extcalls_invalid_in_legacy.yul @@ -0,0 +1,17 @@ +object "a" { + code { + pop(extcall(address(), 0, 0, 0)) + pop(extdelegatecall(address(), 0, 0)) + pop(extstaticcall(address(), 0, 0)) + } +} + +// ==== +// bytecodeFormat: legacy +// ---- +// TypeError 4328: (35-42): The "extcall" instruction is not available in legacy bytecode VMs. +// TypeError 3950: (35-62): Expected expression to evaluate to one value, but got 0 values instead. +// TypeError 4328: (113-128): The "extdelegatecall" instruction is not available in legacy bytecode VMs. +// TypeError 3950: (113-145): Expected expression to evaluate to one value, but got 0 values instead. +// TypeError 4328: (196-209): The "extstaticcall" instruction is not available in legacy bytecode VMs. +// TypeError 3950: (196-226): Expected expression to evaluate to one value, but got 0 values instead. diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 92ac56d6919f..94a6c3b1ee3f 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -495,6 +495,9 @@ u256 EVMInstructionInterpreter::eval( case Instruction::RETURNCONTRACT: case Instruction::RJUMP: case Instruction::RJUMPI: + case Instruction::EXTCALL: + case Instruction::EXTSTATICCALL: + case Instruction::EXTDELEGATECALL: solUnimplemented("EOF not yet supported by Yul interpreter."); }