Skip to content

Commit

Permalink
WiP
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Oct 31, 2024
1 parent adc5533 commit 626388d
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 18 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 @@ -197,7 +197,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 @@ -213,6 +216,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
49 changes: 49 additions & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>{
Expand Down Expand Up @@ -391,6 +422,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 @@ -469,6 +503,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:
Expand Down Expand Up @@ -524,10 +561,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 @@ -547,10 +587,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 @@ -569,7 +612,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 @@ -616,6 +662,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 @@ -644,6 +691,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
99 changes: 81 additions & 18 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,29 @@ 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>
let <success> := iszero(<call>(<address>, <pos>, sub(<end>, <pos>) <?isCall>, 0</isCall>))
<!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");
auto const eof = m_context.eofVersion().has_value();
if (!eof)
templ("call", m_context.evmVersion().hasStaticCall() ? "staticcall" : "call");
else
templ("call", "extstaticcall"); // EOF always has static 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 @@ -1844,7 +1861,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");
Expand Down Expand Up @@ -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,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;
!m_context.eofVersion().has_value() &&
(
encodedHeadSize == 0 ||
!m_context.evmVersion().supportsReturndata() ||
m_context.revertStrings() >= RevertStrings::Debug
);
templ("checkExtcodesize", checkExtcodesize);

templ("pos", m_context.newYulVariable());
Expand Down Expand Up @@ -2747,12 +2776,25 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
}
// Order is important here, STATICCALL might overlap with DELEGATECALL.
if (isDelegateCall)
templ("call", "delegatecall");
else if (useStaticCall)
templ("call", "staticcall");
if (!eof)
{
if (isDelegateCall)
templ("call", "delegatecall");
else if (useStaticCall)
templ("call", "staticcall");
else
templ("call", "call");
}
else
templ("call", "call");
{
if (isDelegateCall)
templ("call", "extdelegatecall");
else if (useStaticCall)
templ("call", "extstaticcall");
else
templ("call", "extcall");
}


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

Expand Down Expand Up @@ -2791,13 +2833,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());
Expand All @@ -2819,16 +2869,29 @@ void IRGeneratorForStatements::appendBareCall(
if (funKind == FunctionType::Kind::BareCall)
{
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
templ("call", "call");
if (eof)
templ("call", "extcall");
else
templ("call", "call");
}
else
{
solAssert(!funType.valueSet(), "Value set for delegatecall or staticcall.");
templ("value", "");
if (funKind == FunctionType::Kind::BareStaticCall)
templ("call", "staticcall");
{
if (eof)
templ("call", "extstaticcall");
else
templ("call", "staticcall");
}
else
templ("call", "delegatecall");
{
if (eof)
templ("call", "extdelegatecall");
else
templ("call", "delegatecall");
}
}

if (funType.gasSet())
Expand Down
3 changes: 3 additions & 0 deletions test/tools/yulInterpreter/EVMInstructionInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}

Expand Down

0 comments on commit 626388d

Please sign in to comment.