Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
eof: Implement and use ext*call
Browse files Browse the repository at this point in the history
rodiazet committed Dec 4, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 8da621c commit 216792c
Showing 11 changed files with 310 additions and 17 deletions.
6 changes: 6 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
@@ -182,6 +182,9 @@ std::map<std::string, Instruction> 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<Instruction, InstructionInfo> 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}},
6 changes: 6 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
@@ -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;
49 changes: 49 additions & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
@@ -169,6 +169,37 @@ std::vector<SemanticInformation::Operation> SemanticInformation::readWriteOperat
});
return operations;
}
case Instruction::EXTSTATICCALL:
case Instruction::EXTDELEGATECALL:
{
size_t paramCount = static_cast<size_t>(instructionInfo(_instruction, langutil::EVMVersion()).args);
std::vector<Operation> operations{
Operation{Location::Memory, Effect::Read, paramCount - 2, paramCount - 1, {}},
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::EXTCALL:
{
size_t paramCount = static_cast<size_t>(instructionInfo(_instruction, langutil::EVMVersion()).args);
std::vector<Operation> operations{
Operation{Location::Memory, Effect::Read, paramCount - 3, paramCount - 2, {}},
Operation{Location::Storage, Effect::Read, {}, {}, {}},
Operation{Location::TransientStorage, Effect::Read, {}, {}, {}}
};

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<Operation>{
@@ -380,6 +411,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:
@@ -459,6 +493,9 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::STATICCALL:
case Instruction::EXTCALL:
case Instruction::EXTDELEGATECALL:
case Instruction::EXTSTATICCALL:
return SemanticInformation::Write;

case Instruction::CREATE:
@@ -514,10 +551,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 +577,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 +602,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 +652,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 +682,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
3 changes: 3 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
@@ -85,6 +85,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional<uint8_t> _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;
74 changes: 57 additions & 17 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
@@ -1622,11 +1622,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
Whiskers templ(R"(
let <gas> := 0
if iszero(<value>) { <gas> := <callStipend> }
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
<?eof>
let <success> := iszero(extcall(<address>, 0, 0, <value>))
<!eof>
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
</eof>
<?isTransfer>
if iszero(<success>) { <forwardingRevert>() }
</isTransfer>
)");
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)
<?isECRecover>
mstore(0, 0)
</isECRecover>
let <success> := <call>(<gas>, <address> <?isCall>, 0</isCall>, <pos>, sub(<end>, <pos>), 0, 32)
<?eof>
// EOF always uses extstaticcall
let <success> := iszero(extstaticcall(<address>, <pos>, sub(<end>, <pos>)))
<!eof>
let <success> := <call>(<gas>, <address> <?isCall>, 0</isCall>, <pos>, sub(<end>, <pos>), 0, 32)
</eof>
if iszero(<success>) { <forwardingRevert>() }
<?eof>
if eq(returndatasize(), 32) { returndatacopy(0, 0, 32) }
</eof>
let <retVars> := <shl>(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
@@ -1844,7 +1862,7 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
solAssert(dynamic_cast<AddressType const&>(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable);
define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression());
}
else if (std::set<std::string>{"call", "callcode", "delegatecall", "staticcall"}.count(member))
else if (std::set<std::string>{"call", "extcall", "callcode", "delegatecall" , "extdelegatecall", "staticcall", "extstaticcall"}.count(member))
define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression());
else
solAssert(false, "Invalid member access to address");
@@ -2642,7 +2660,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"(
<?checkExtcodesize>
if iszero(extcodesize(<address>)) { <revertNoCode>() }
@@ -2652,7 +2670,11 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
mstore(<pos>, <shl28>(<funSel>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <staticReturndataSize>)
<?eof>
let <success> := iszero(<call>(<address>, <pos>, sub(<end>, <pos>) <?hasValue>, <value></hasValue>))
<!eof>
let <success> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <staticReturndataSize>)
</eof>
<?noTryCall>
if iszero(<success>) { <forwardingRevert>() }
</noTryCall>
@@ -2667,6 +2689,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
if gt(<returnDataSizeVar>, returndatasize()) {
<returnDataSizeVar> := returndatasize()
}
<?eof>
returndatacopy(<pos>, 0, <returnDataSizeVar>)
</eof>
</supportsReturnData>
</isReturndataSizeDynamic>
@@ -2678,16 +2703,21 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
}
)");
templ("revertNoCode", m_utils.revertReasonIfDebugFunction("Target contract does not contain code"));
auto const eof = m_context.eofVersion().has_value();
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.
size_t encodedHeadSize = 0;
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());
@@ -2747,12 +2777,13 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
// Order is important here, STATICCALL might overlap with DELEGATECALL.
std::string const callPrefix = eof ? "ext" : "";
if (isDelegateCall)
templ("call", "delegatecall");
templ("call", callPrefix + "delegatecall");
else if (useStaticCall)
templ("call", "staticcall");
templ("call", callPrefix + "staticcall");
else
templ("call", "call");
templ("call", callPrefix + "call");

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

@@ -2791,13 +2822,21 @@ void IRGeneratorForStatements::appendBareCall(
let <length> := mload(<arg>)
</needsEncoding>
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
<?eof>
let <success> := <call>(<address>, <pos>, <length> <?+value>, <value></+value>)
<success> := iszero(<success>)
<!eof>
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
</eof>
let <returndataVar> := <extractReturndataFunction>()
)");

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());
@@ -2816,19 +2855,20 @@ void IRGeneratorForStatements::appendBareCall(

templ("address", IRVariable(_functionCall.expression()).part("address").name());

std::string const callPrefix = eof ? "ext" : "";
if (funKind == FunctionType::Kind::BareCall)
{
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
templ("call", "call");
templ("call", callPrefix + "call");
}
else
{
solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall.");
templ("value", "");
if (funKind == FunctionType::Kind::BareStaticCall)
templ("call", "staticcall");
templ("call", callPrefix + "staticcall");
else
templ("call", "delegatecall");
templ("call", callPrefix + "delegatecall");
}

if (funType.gasSet())
17 changes: 17 additions & 0 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
@@ -772,10 +772,27 @@ 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 ||
_instr == evmasm::Instruction::DELEGATECALL ||
_instr == evmasm::Instruction::STATICCALL ||
_instr == evmasm::Instruction::SELFDESTRUCT ||
_instr == evmasm::Instruction::JUMP ||
_instr == evmasm::Instruction::JUMPI ||
Loading

0 comments on commit 216792c

Please sign in to comment.