Skip to content

Commit

Permalink
eof: Implement and use ext*call
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Dec 6, 2024
1 parent b292de4 commit 63c2be6
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 17 deletions.
6 changes: 6 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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}},
Expand Down
6 changes: 6 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;
Expand Down
38 changes: 38 additions & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,26 @@ std::vector<SemanticInformation::Operation> SemanticInformation::readWriteOperat
});
return operations;
}
case Instruction::EXTCALL:
case Instruction::EXTSTATICCALL:
case Instruction::EXTDELEGATECALL:
{
size_t paramCount = static_cast<size_t>(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<Operation> 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<Operation>{
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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.'
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
72 changes: 55 additions & 17 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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"(
<?checkExtcodesize>
if iszero(extcodesize(<address>)) { <revertNoCode>() }
Expand All @@ -2652,7 +2669,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>
Expand All @@ -2667,6 +2688,9 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
if gt(<returnDataSizeVar>, returndatasize()) {
<returnDataSizeVar> := returndatasize()
}
<?eof>
returndatacopy(<pos>, 0, <returnDataSizeVar>)
</eof>
</supportsReturnData>
</isReturndataSizeDynamic>
Expand All @@ -2678,16 +2702,22 @@ 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.
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());
Expand Down Expand Up @@ -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());

Expand Down Expand Up @@ -2791,13 +2821,20 @@ void IRGeneratorForStatements::appendBareCall(
let <length> := mload(<arg>)
</needsEncoding>
let <success> := <call>(<gas>, <address>, <?+value> <value>, </+value> <pos>, <length>, 0, 0)
<?eof>
let <success> := iszero(<call>(<address>, <pos>, <length> <?+value>, <value></+value>))
<!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());
Expand All @@ -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())
Expand Down
16 changes: 16 additions & 0 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down
Loading

0 comments on commit 63c2be6

Please sign in to comment.