Skip to content

Commit a62a455

Browse files
authored
Merge pull request #15559 from ipsilon/eof-extcalls
EOF: Implement `ext*calls`
2 parents 643a18e + 6263f56 commit a62a455

File tree

16 files changed

+265
-18
lines changed

16 files changed

+265
-18
lines changed

libevmasm/Instruction.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
182182
{ "STATICCALL", Instruction::STATICCALL },
183183
{ "RETURN", Instruction::RETURN },
184184
{ "DELEGATECALL", Instruction::DELEGATECALL },
185+
{ "EXTCALL", Instruction::EXTCALL },
186+
{ "EXTSTATICCALL", Instruction::EXTSTATICCALL },
187+
{ "EXTDELEGATECALL", Instruction::EXTDELEGATECALL },
185188
{ "CREATE2", Instruction::CREATE2 },
186189
{ "REVERT", Instruction::REVERT },
187190
{ "INVALID", Instruction::INVALID },
@@ -344,6 +347,9 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
344347
{Instruction::RETURN, {"RETURN", 0, 2, 0, true, Tier::Zero}},
345348
{Instruction::DELEGATECALL, {"DELEGATECALL", 0, 6, 1, true, Tier::Special}},
346349
{Instruction::STATICCALL, {"STATICCALL", 0, 6, 1, true, Tier::Special}},
350+
{Instruction::EXTCALL, {"EXTCALL", 0, 4, 1, true, Tier::Special}},
351+
{Instruction::EXTDELEGATECALL,{"EXTDELEGATECALL", 0, 3, 1, true, Tier::Special}},
352+
{Instruction::EXTSTATICCALL, {"EXTSTATICCALL", 0, 3, 1, true, Tier::Special}},
347353
{Instruction::CREATE2, {"CREATE2", 0, 4, 1, true, Tier::Special}},
348354
{Instruction::REVERT, {"REVERT", 0, 2, 0, true, Tier::Zero}},
349355
{Instruction::INVALID, {"INVALID", 0, 0, 0, true, Tier::Zero}},

libevmasm/Instruction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ enum class Instruction: uint8_t
198198
RETURN, ///< halt execution returning output data
199199
DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender
200200
CREATE2 = 0xf5, ///< create new account with associated code at address `sha3(0xff + sender + salt + init code) % 2**160`
201+
EXTCALL = 0xf8, ///< EOF message-call into an account
202+
EXTDELEGATECALL = 0xf9, ///< EOF delegate call
201203
STATICCALL = 0xfa, ///< like CALL but disallow state modifications
204+
EXTSTATICCALL = 0xfb, ///< like EXTCALL but disallow state modifications
202205

203206
REVERT = 0xfd, ///< halt execution, revert state and return output data
204207
INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
@@ -214,6 +217,9 @@ constexpr bool isCallInstruction(Instruction _inst) noexcept
214217
case Instruction::CALLCODE:
215218
case Instruction::DELEGATECALL:
216219
case Instruction::STATICCALL:
220+
case Instruction::EXTCALL:
221+
case Instruction::EXTSTATICCALL:
222+
case Instruction::EXTDELEGATECALL:
217223
return true;
218224
default:
219225
return false;

libevmasm/SemanticInformation.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,26 @@ std::vector<SemanticInformation::Operation> SemanticInformation::readWriteOperat
169169
});
170170
return operations;
171171
}
172+
case Instruction::EXTCALL:
173+
case Instruction::EXTSTATICCALL:
174+
case Instruction::EXTDELEGATECALL:
175+
{
176+
size_t paramCount = static_cast<size_t>(instructionInfo(_instruction, langutil::EVMVersion()).args);
177+
size_t const memoryStartParam = _instruction == Instruction::EXTCALL ? paramCount - 3 : paramCount - 2;
178+
size_t const memoryLengthParam = _instruction == Instruction::EXTCALL ? paramCount - 2 : paramCount - 1;
179+
std::vector<Operation> operations{
180+
Operation{Location::Memory, Effect::Read, memoryStartParam, memoryLengthParam, {}},
181+
Operation{Location::Storage, Effect::Read, {}, {}, {}},
182+
Operation{Location::TransientStorage, Effect::Read, {}, {}, {}}
183+
};
184+
if (_instruction == Instruction::EXTDELEGATECALL)
185+
{
186+
operations.emplace_back(Operation{Location::Storage, Effect::Write, {}, {}, {}});
187+
operations.emplace_back(Operation{Location::TransientStorage, Effect::Write, {}, {}, {}});
188+
}
189+
190+
return operations;
191+
}
172192
case Instruction::CREATE:
173193
case Instruction::CREATE2:
174194
return std::vector<Operation>{
@@ -380,6 +400,9 @@ bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
380400
case Instruction::CALLCODE:
381401
case Instruction::DELEGATECALL:
382402
case Instruction::STATICCALL:
403+
case Instruction::EXTCALL:
404+
case Instruction::EXTDELEGATECALL:
405+
case Instruction::EXTSTATICCALL:
383406
case Instruction::CREATE:
384407
case Instruction::CREATE2:
385408
case Instruction::GAS:
@@ -475,6 +498,9 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction
475498
case Instruction::LOG4:
476499
case Instruction::EOFCREATE:
477500
case Instruction::RETURNCONTRACT:
501+
case Instruction::EXTCALL:
502+
case Instruction::EXTDELEGATECALL:
503+
case Instruction::EXTSTATICCALL:
478504
return SemanticInformation::Read;
479505

480506
default:
@@ -514,10 +540,13 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio
514540
case Instruction::SSTORE:
515541
case Instruction::EOFCREATE:
516542
case Instruction::RETURNCONTRACT:
543+
case Instruction::EXTCALL:
544+
case Instruction::EXTDELEGATECALL:
517545
return SemanticInformation::Write;
518546

519547
case Instruction::SLOAD:
520548
case Instruction::STATICCALL:
549+
case Instruction::EXTSTATICCALL:
521550
return SemanticInformation::Read;
522551

523552
default:
@@ -537,10 +566,13 @@ SemanticInformation::Effect SemanticInformation::transientStorage(Instruction _i
537566
case Instruction::TSTORE:
538567
case Instruction::EOFCREATE:
539568
case Instruction::RETURNCONTRACT:
569+
case Instruction::EXTCALL:
570+
case Instruction::EXTDELEGATECALL:
540571
return SemanticInformation::Write;
541572

542573
case Instruction::TLOAD:
543574
case Instruction::STATICCALL:
575+
case Instruction::EXTSTATICCALL:
544576
return SemanticInformation::Read;
545577

546578
default:
@@ -559,7 +591,10 @@ SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruc
559591
case Instruction::CREATE2:
560592
case Instruction::EOFCREATE:
561593
case Instruction::RETURNCONTRACT:
594+
case Instruction::EXTCALL:
595+
case Instruction::EXTDELEGATECALL:
562596
case Instruction::SELFDESTRUCT:
597+
case Instruction::EXTSTATICCALL:
563598
case Instruction::STATICCALL: // because it can affect returndatasize
564599
// Strictly speaking, log0, .., log4 writes to the state, but the EVM cannot read it, so they
565600
// are just marked as having 'other side effects.'
@@ -606,6 +641,7 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction)
606641
case Instruction::NUMBER:
607642
case Instruction::PREVRANDAO:
608643
case Instruction::GASLIMIT:
644+
case Instruction::EXTSTATICCALL:
609645
case Instruction::STATICCALL:
610646
case Instruction::SLOAD:
611647
case Instruction::TLOAD:
@@ -635,6 +671,8 @@ bool SemanticInformation::invalidInViewFunctions(Instruction _instruction)
635671
case Instruction::CALL:
636672
case Instruction::CALLCODE:
637673
case Instruction::DELEGATECALL:
674+
case Instruction::EXTCALL:
675+
case Instruction::EXTDELEGATECALL:
638676
// According to EOF spec https://eips.ethereum.org/EIPS/eip-7620#eofcreate
639677
case Instruction::EOFCREATE:
640678
// According to EOF spec https://eips.ethereum.org/EIPS/eip-7620#returncontract

liblangutil/EVMVersion.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional<uint8_t> _eofVersi
8585
case Instruction::CALLF:
8686
case Instruction::JUMPF:
8787
case Instruction::RETF:
88+
case Instruction::EXTCALL:
89+
case Instruction::EXTSTATICCALL:
90+
case Instruction::EXTDELEGATECALL:
8891
return _eofVersion.has_value();
8992
default:
9093
return true;

libsolidity/codegen/ir/IRGeneratorForStatements.cpp

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1622,11 +1622,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
16221622
Whiskers templ(R"(
16231623
let <gas> := 0
16241624
if iszero(<value>) { <gas> := <callStipend> }
1625-
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
1625+
<?eof>
1626+
let <success> := iszero(extcall(<address>, 0, 0, <value>))
1627+
<!eof>
1628+
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
1629+
</eof>
16261630
<?isTransfer>
16271631
if iszero(<success>) { <forwardingRevert>() }
16281632
</isTransfer>
16291633
)");
1634+
templ("eof", m_context.eofVersion().has_value());
16301635
templ("gas", m_context.newYulVariable());
16311636
templ("callStipend", toString(evmasm::GasCosts::callStipend));
16321637
templ("address", address);
@@ -1669,17 +1674,30 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
16691674
<?isECRecover>
16701675
mstore(0, 0)
16711676
</isECRecover>
1672-
let <success> := <call>(<gas>, <address> <?isCall>, 0</isCall>, <pos>, sub(<end>, <pos>), 0, 32)
1677+
<?eof>
1678+
// EOF always uses extstaticcall
1679+
let <success> := iszero(extstaticcall(<address>, <pos>, sub(<end>, <pos>)))
1680+
<!eof>
1681+
let <success> := <call>(<gas>, <address> <?isCall>, 0</isCall>, <pos>, sub(<end>, <pos>), 0, 32)
1682+
</eof>
16731683
if iszero(<success>) { <forwardingRevert>() }
1684+
<?eof>
1685+
if eq(returndatasize(), 32) { returndatacopy(0, 0, 32) }
1686+
</eof>
16741687
let <retVars> := <shl>(mload(0))
16751688
)");
1676-
templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call");
1677-
templ("isCall", !m_context.evmVersion().hasStaticCall());
1689+
auto const eof = m_context.eofVersion().has_value();
1690+
if (!eof)
1691+
{
1692+
templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call");
1693+
templ("isCall", !m_context.evmVersion().hasStaticCall());
1694+
}
16781695
templ("shl", m_utils.shiftLeftFunction(offset * 8));
16791696
templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
16801697
templ("pos", m_context.newYulVariable());
16811698
templ("end", m_context.newYulVariable());
16821699
templ("isECRecover", FunctionType::Kind::ECRecover == functionType->kind());
1700+
templ("eof", eof);
16831701
if (FunctionType::Kind::ECRecover == functionType->kind())
16841702
templ("encodeArgs", m_context.abiFunctions().tupleEncoder(argumentTypes, parameterTypes));
16851703
else
@@ -2630,7 +2648,6 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
26302648
argumentStrings += IRVariable(*arg).stackSlots();
26312649
}
26322650

2633-
26342651
if (!m_context.evmVersion().canOverchargeGasForCall())
26352652
{
26362653
// 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(
26422659
}
26432660

26442661
// NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically.
2645-
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
2662+
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
26462663
Whiskers templ(R"(
26472664
<?checkExtcodesize>
26482665
if iszero(extcodesize(<address>)) { <revertNoCode>() }
@@ -2652,7 +2669,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
26522669
mstore(<pos>, <shl28>(<funSel>))
26532670
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
26542671
2655-
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <staticReturndataSize>)
2672+
<?eof>
2673+
let <success> := iszero(<call>(<address>, <pos>, sub(<end>, <pos>) <?hasValue>, <value></hasValue>))
2674+
<!eof>
2675+
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <staticReturndataSize>)
2676+
</eof>
26562677
<?noTryCall>
26572678
if iszero(<success>) { <forwardingRevert>() }
26582679
</noTryCall>
@@ -2667,6 +2688,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
26672688
if gt(<returnDataSizeVar>, returndatasize()) {
26682689
<returnDataSizeVar> := returndatasize()
26692690
}
2691+
<?eof>
2692+
returndatacopy(<pos>, 0, <returnDataSizeVar>)
2693+
</eof>
26702694
</supportsReturnData>
26712695
</isReturndataSizeDynamic>
26722696
@@ -2678,16 +2702,22 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
26782702
}
26792703
)");
26802704
templ("revertNoCode", m_utils.revertReasonIfDebugFunction("Target contract does not contain code"));
2705+
auto const eof = m_context.eofVersion().has_value();
2706+
solAssert(!eof || !funType.gasSet());
2707+
templ("eof", eof);
26812708

26822709
// We do not need to check extcodesize if we expect return data: If there is no
26832710
// code, the call will return empty data and the ABI decoder will revert.
26842711
size_t encodedHeadSize = 0;
26852712
for (auto const& t: returnInfo.returnTypes)
26862713
encodedHeadSize += t->decodingType()->calldataHeadSize();
26872714
bool const checkExtcodesize =
2688-
encodedHeadSize == 0 ||
2689-
!m_context.evmVersion().supportsReturndata() ||
2690-
m_context.revertStrings() >= RevertStrings::Debug;
2715+
!eof &&
2716+
(
2717+
encodedHeadSize == 0 ||
2718+
!m_context.evmVersion().supportsReturndata() ||
2719+
m_context.revertStrings() >= RevertStrings::Debug
2720+
);
26912721
templ("checkExtcodesize", checkExtcodesize);
26922722

26932723
templ("pos", m_context.newYulVariable());
@@ -2748,11 +2778,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
27482778
}
27492779
// Order is important here, STATICCALL might overlap with DELEGATECALL.
27502780
if (isDelegateCall)
2751-
templ("call", "delegatecall");
2781+
templ("call", eof ? "extdelegatecall" : "delegatecall");
27522782
else if (useStaticCall)
2753-
templ("call", "staticcall");
2783+
templ("call", eof ? "extstaticcall" : "staticcall");
27542784
else
2755-
templ("call", "call");
2785+
templ("call", eof ? "extcall" : "call");
27562786

27572787
templ("forwardingRevert", m_utils.forwardingRevertFunction());
27582788

@@ -2791,13 +2821,20 @@ void IRGeneratorForStatements::appendBareCall(
27912821
let <length> := mload(<arg>)
27922822
</needsEncoding>
27932823
2794-
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
2824+
<?eof>
2825+
let <success> := iszero(<call>(<address>, <pos>, <length> <?+value>, <value></+value>))
2826+
<!eof>
2827+
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
2828+
</eof>
2829+
27952830
let <returndataVar> := <extractReturndataFunction>()
27962831
)");
27972832

27982833
templ("allocateUnbounded", m_utils.allocateUnboundedFunction());
27992834
templ("pos", m_context.newYulVariable());
28002835
templ("length", m_context.newYulVariable());
2836+
auto const eof = m_context.eofVersion().has_value();
2837+
templ("eof", eof);
28012838

28022839
templ("arg", IRVariable(*_arguments.front()).commaSeparatedList());
28032840
Type const& argType = type(*_arguments.front());
@@ -2819,18 +2856,19 @@ void IRGeneratorForStatements::appendBareCall(
28192856
if (funKind == FunctionType::Kind::BareCall)
28202857
{
28212858
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
2822-
templ("call", "call");
2859+
templ("call", eof ? "extcall" : "call");
28232860
}
28242861
else
28252862
{
28262863
solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall.");
28272864
templ("value", "");
28282865
if (funKind == FunctionType::Kind::BareStaticCall)
2829-
templ("call", "staticcall");
2866+
templ("call", eof ? "extstaticcall" : "staticcall");
28302867
else
2831-
templ("call", "delegatecall");
2868+
templ("call", eof ? "extdelegatecall" : "delegatecall");
28322869
}
28332870

2871+
solAssert(!eof || !funType.gasSet());
28342872
if (funType.gasSet())
28352873
templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
28362874
else if (m_context.evmVersion().canOverchargeGasForCall())

libyul/AsmAnalysis.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,21 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
773773
"PC instruction is a low-level EVM feature. "
774774
"Because of that PC is disallowed in strict assembly."
775775
);
776+
else if (!m_eofVersion.has_value() && (
777+
_instr == evmasm::Instruction::EXTCALL ||
778+
_instr == evmasm::Instruction::EXTDELEGATECALL ||
779+
_instr == evmasm::Instruction::EXTSTATICCALL
780+
))
781+
{
782+
m_errorReporter.typeError(
783+
4328_error,
784+
_location,
785+
fmt::format(
786+
"The \"{}\" instruction is only available on EOF.",
787+
fmt::arg("instruction", boost::to_lower_copy(instructionInfo(_instr, m_evmVersion).name))
788+
)
789+
);
790+
}
776791
else if (m_eofVersion.has_value() && (
777792
_instr == evmasm::Instruction::CALL ||
778793
_instr == evmasm::Instruction::CALLCODE ||

libyul/backends/evm/EVMDialect.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ std::set<std::string, std::less<>> createReservedIdentifiers(langutil::EVMVersio
153153
(_instr == evmasm::Instruction::TSTORE || _instr == evmasm::Instruction::TLOAD);
154154
};
155155

156+
auto eofIdentifiersException = [&](evmasm::Instruction _instr) -> bool
157+
{
158+
solAssert(!_eofVersion.has_value() || _evmVersion >= langutil::EVMVersion::prague());
159+
return
160+
!_eofVersion.has_value() &&
161+
(
162+
_instr == evmasm::Instruction::EXTCALL ||
163+
_instr == evmasm::Instruction::EXTSTATICCALL ||
164+
_instr == evmasm::Instruction::EXTDELEGATECALL
165+
);
166+
};
167+
156168
std::set<std::string, std::less<>> reserved;
157169
for (auto const& instr: evmasm::c_instructions)
158170
{
@@ -163,7 +175,8 @@ std::set<std::string, std::less<>> createReservedIdentifiers(langutil::EVMVersio
163175
!blobHashException(instr.second) &&
164176
!blobBaseFeeException(instr.second) &&
165177
!mcopyException(instr.second) &&
166-
!transientStorageException(instr.second)
178+
!transientStorageException(instr.second) &&
179+
!eofIdentifiersException(instr.second)
167180
)
168181
reserved.emplace(name);
169182
}

test/libsolidity/semanticTests/ecrecover/ecrecover.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ contract test {
33
return ecrecover(h, v, r, s);
44
}
55
}
6+
// ====
7+
// bytecodeFormat: legacy,>=EOFv1
68
// ----
79
// a(bytes32,uint8,bytes32,bytes32):
810
// 0x18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c,

0 commit comments

Comments
 (0)