Skip to content

Commit

Permalink
eof: Support DATALOADN (EIP-7480)
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Oct 10, 2024
1 parent 3edd2e1 commit 97689bc
Show file tree
Hide file tree
Showing 25 changed files with 293 additions and 28 deletions.
65 changes: 59 additions & 6 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,11 @@ AssemblyItem Assembly::newImmutableAssignment(std::string const& _identifier)
return AssemblyItem{AssignImmutable, h};
}

AssemblyItem Assembly::newAuxDataLoadN(size_t _offset)
{
return AssemblyItem{AuxDataLoadN, _offset};
}

Assembly& Assembly::optimise(OptimiserSettings const& _settings)
{
optimiseInternal(_settings, {});
Expand Down Expand Up @@ -923,7 +928,7 @@ std::tuple<bytes, std::vector<size_t>, size_t> Assembly::createEOFHeader(std::se
{
bytes retBytecode;
std::vector<size_t> codeSectionSizeOffsets;
size_t dataSectionSizeOffset;
size_t dataSectionSizePosition;

retBytecode.push_back(0xef);
retBytecode.push_back(0x00);
Expand Down Expand Up @@ -952,7 +957,7 @@ std::tuple<bytes, std::vector<size_t>, size_t> Assembly::createEOFHeader(std::se
}

retBytecode.push_back(0x04); // kind=data
dataSectionSizeOffset = retBytecode.size();
dataSectionSizePosition = retBytecode.size();
appendBigEndianUint16(retBytecode, 0u); // length of data

retBytecode.push_back(0x00); // terminator
Expand All @@ -965,7 +970,7 @@ std::tuple<bytes, std::vector<size_t>, size_t> Assembly::createEOFHeader(std::se
appendBigEndianUint16(retBytecode, 0xFFFFu);
}

return {retBytecode, codeSectionSizeOffsets, dataSectionSizeOffset};
return {retBytecode, codeSectionSizeOffsets, dataSectionSizePosition};
}

LinkerObject const& Assembly::assemble() const
Expand Down Expand Up @@ -1348,6 +1353,23 @@ std::map<uint16_t, uint16_t> Assembly::findReferencedContainers() const
return replacements;
}

std::optional<uint16_t> Assembly::findMaxAuxDataLoadNOffset() const
{
std::optional<unsigned> maxOffset = std::nullopt;
for (auto&& codeSection: m_codeSections)
for (AssemblyItem const& item: codeSection.items)
if (item.type() == AuxDataLoadN)
{
solAssert(item.data() <= std::numeric_limits<uint16_t>::max(), "Invalid auxdataloadn index value.");
auto const offset = static_cast<unsigned>(item.data());
if (!maxOffset.has_value() || offset > maxOffset.value())
maxOffset = offset;

}

return maxOffset;
}

LinkerObject const& Assembly::assembleEOF() const
{
solAssert(m_eofVersion.has_value() && m_eofVersion == 1);
Expand All @@ -1362,11 +1384,14 @@ LinkerObject const& Assembly::assembleEOF() const
"Expected the first code section to have zero inputs and be non-returning."
);

auto const maxAuxDataLoadNOffset = findMaxAuxDataLoadNOffset();

// Insert EOF1 header.
auto [headerBytecode, codeSectionSizeOffsets, dataSectionSizeOffset] = createEOFHeader(referencedSubIds);
auto [headerBytecode, codeSectionSizeOffsets, dataSectionSizePosition] = createEOFHeader(referencedSubIds);
ret.bytecode = headerBytecode;

m_tagPositionsInBytecode = std::vector<size_t>(m_usedTags, std::numeric_limits<size_t>::max());
std::map<size_t, uint16_t> dataSectionRef;

for (auto&& [codeSectionIndex, codeSection]: m_codeSections | ranges::views::enumerate)
{
Expand All @@ -1380,6 +1405,8 @@ LinkerObject const& Assembly::assembleEOF() const
switch (item.type())
{
case Operation:
solAssert(item.instruction() != Instruction::DATALOADN);
solAssert(!(item.instruction() >= Instruction::PUSH0 && item.instruction() <= Instruction::PUSH32));
ret.bytecode += assembleOperation(item);
break;
case Push:
Expand All @@ -1401,6 +1428,15 @@ LinkerObject const& Assembly::assembleEOF() const
case Tag:
ret.bytecode += assembleTag(item, ret.bytecode.size(), false);
break;
case AuxDataLoadN:
{
// In findMaxAuxDataLoadNOffset we already verified that unsigned data value fits 2 bytes
solAssert(item.data() <= std::numeric_limits<uint16_t>::max(), "Invalid auxdataloadn position.");
ret.bytecode.push_back(uint8_t(Instruction::DATALOADN));
dataSectionRef[ret.bytecode.size()] = static_cast<uint16_t>(item.data());
appendBigEndianUint16(ret.bytecode, item.data());
break;
}
default:
solThrow(InvalidOpcode, "Unexpected opcode while assembling.");
}
Expand All @@ -1422,8 +1458,25 @@ LinkerObject const& Assembly::assembleEOF() const

ret.bytecode += m_auxiliaryData;

auto dataLength = ret.bytecode.size() - dataStart;
setBigEndianUint16(ret.bytecode, dataSectionSizeOffset, dataLength);
auto const preDeployDataSectionSize = ret.bytecode.size() - dataStart;
// DATALOADN loads 32 bytes from EOF data section zero padded if reading out of data bounds.
// In our case we do not allow DATALOADN with offsets which reads out of data bounds.
auto const staticAuxDataSize = maxAuxDataLoadNOffset.has_value() ? (*maxAuxDataLoadNOffset + 32u) : 0u;
solRequire(preDeployDataSectionSize + staticAuxDataSize < std::numeric_limits<uint16_t>::max(), AssemblyException,
"Invalid DATALOADN offset.");

// If some data was already added to data section we need to update data section refs accordingly
if (preDeployDataSectionSize > 0)
for (auto [refPosition, staticAuxDataOffset] : dataSectionRef)
{
// staticAuxDataOffset + preDeployDataSectionSize value is already verified to fit 2 bytes because
// staticAuxDataOffset < staticAuxDataSize
setBigEndianUint16(ret.bytecode, refPosition, staticAuxDataOffset + preDeployDataSectionSize);
}

auto const preDeployAndStaticAuxDataSize = preDeployDataSectionSize + staticAuxDataSize;

setBigEndianUint16(ret.bytecode, dataSectionSizePosition, preDeployAndStaticAuxDataSize);

return ret;
}
Expand Down
5 changes: 5 additions & 0 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class Assembly
AssemblyItem newPushLibraryAddress(std::string const& _identifier);
AssemblyItem newPushImmutable(std::string const& _identifier);
AssemblyItem newImmutableAssignment(std::string const& _identifier);
AssemblyItem newAuxDataLoadN(size_t offset);

AssemblyItem const& append(AssemblyItem _i);
AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
Expand All @@ -91,6 +92,7 @@ class Assembly
void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
void appendAuxDataLoadN(size_t offset) { append(newAuxDataLoadN(offset));}

void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables)
{
Expand Down Expand Up @@ -240,6 +242,9 @@ class Assembly

/// Returns map from m_subs to an index of subcontainer in the final EOF bytecode
std::map<uint16_t, uint16_t> findReferencedContainers() const;
/// Returns max AuxDataLoadN offset for the assembly.
/// TODO: Consider merging with findReferencedContainers to avoid additional run
std::optional<uint16_t> findMaxAuxDataLoadNOffset() const;

/// Assemble bytecode for AssemblyItem type.
[[nodiscard]] bytes assembleOperation(AssemblyItem const& _item) const;
Expand Down
38 changes: 27 additions & 11 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,13 @@ std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersi
return {"PUSH data", toStringInHex(data())};
case VerbatimBytecode:
return {"VERBATIM", util::toHex(verbatimData())};
default:
assertThrow(false, InvalidOpcode, "");
case AuxDataLoadN:
return {"AUXDATALOADN", util::toString(data())};
case UndefinedItem:
solAssert(false);
}

util::unreachable();
}

void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag)
Expand Down Expand Up @@ -161,10 +165,13 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
}
case VerbatimBytecode:
return std::get<2>(*m_verbatimBytecode).size();
default:
break;
case AuxDataLoadN:
return 1 + 2;
case UndefinedItem:
solAssert(false);
}
assertThrow(false, InvalidOpcode, "");

util::unreachable();
}

size_t AssemblyItem::arguments() const
Expand Down Expand Up @@ -203,7 +210,10 @@ size_t AssemblyItem::returnValues() const
return 0;
case VerbatimBytecode:
return std::get<1>(*m_verbatimBytecode);
default:
case AuxDataLoadN:
return 1;
case AssignImmutable:
case UndefinedItem:
break;
}
return 0;
Expand All @@ -226,10 +236,13 @@ bool AssemblyItem::canBeFunctional() const
case PushLibraryAddress:
case PushDeployTimeAddress:
case PushImmutable:
case AuxDataLoadN:
return true;
case Tag:
return false;
default:
case AssignImmutable:
case VerbatimBytecode:
case UndefinedItem:
break;
}
return false;
Expand Down Expand Up @@ -327,8 +340,10 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case VerbatimBytecode:
text = std::string("verbatimbytecode_") + util::toHex(std::get<2>(*m_verbatimBytecode));
break;
default:
assertThrow(false, InvalidOpcode, "");
case AuxDataLoadN:
assertThrow(data() <= std::numeric_limits<size_t>::max(), AssemblyException, "Invalid auxdataloadn argument.");
text = "auxdataloadn(" + std::to_string(static_cast<size_t>(data())) + ")";
break;
}
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
{
Expand Down Expand Up @@ -396,11 +411,12 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
case VerbatimBytecode:
_out << " Verbatim " << util::toHex(_item.verbatimData());
break;
case AuxDataLoadN:
_out << "AuxDataLoadN " << util::toString(_item.data());
break;
case UndefinedItem:
_out << " ???";
break;
default:
assertThrow(false, InvalidOpcode, "");
}
return _out;
}
Expand Down
4 changes: 4 additions & 0 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ enum AssemblyItemType
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.

/// Loads 32 bytes from static auxiliary data of EOF data section. The offset does *not* have to be always from the beginning
/// of the data EOF section. More details here: https://github.com/ipsilon/eof/blob/main/spec/eof.md#data-section-lifecycle
AuxDataLoadN,
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
};

Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "LOG2", Instruction::LOG2 },
{ "LOG3", Instruction::LOG3 },
{ "LOG4", Instruction::LOG4 },
{ "DATALOADN", Instruction::DATALOADN },
{ "CREATE", Instruction::CREATE },
{ "CALL", Instruction::CALL },
{ "CALLCODE", Instruction::CALLCODE },
Expand Down Expand Up @@ -252,6 +253,7 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{Instruction::MSIZE, {"MSIZE", 0, 0, 1, false, Tier::Base}},
{Instruction::GAS, {"GAS", 0, 0, 1, false, Tier::Base}},
{Instruction::JUMPDEST, {"JUMPDEST", 0, 0, 0, true, Tier::Special}},
{Instruction::DATALOADN, {"DATALOADN", 2, 0, 1, true, Tier::Low}},
{Instruction::PUSH0, {"PUSH0", 0, 0, 1, false, Tier::Base}},
{Instruction::PUSH1, {"PUSH1", 1, 0, 1, false, Tier::VeryLow}},
{Instruction::PUSH2, {"PUSH2", 2, 0, 1, false, Tier::VeryLow}},
Expand Down
1 change: 1 addition & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ enum class Instruction: uint8_t
LOG3, ///< Makes a log entry; 3 topics.
LOG4, ///< Makes a log entry; 4 topics.

DATALOADN = 0xd1, ///< load data from EOF data section
CREATE = 0xf0, ///< create a new account with associated code
CALL, ///< message-call into an account
CALLCODE, ///< message-call with another account's code only
Expand Down
3 changes: 3 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ bool EVMVersion::hasOpcode(Instruction _opcode, std::optional<uint8_t> _eofVersi
case Instruction::EXTCODECOPY:
case Instruction::GAS:
return !_eofVersion.has_value();
// Instructions below available only in EOF
case Instruction::DATALOADN:
return _eofVersion.has_value();
default:
return true;
}
Expand Down
14 changes: 14 additions & 0 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,20 @@ size_t AsmAnalyzer::operator()(FunctionCall const& _funCall)
expectUnlimitedStringLiteral(std::get<Literal>(arg));
continue;
}
else if (*literalArgumentKind == LiteralKind::Number)
{
std::string functionName = _funCall.functionName.name.str();
if (functionName == "auxdataloadn")
{
auto const& argumentAsLiteral = std::get<Literal>(arg);
if (argumentAsLiteral.value.value() > std::numeric_limits<uint16_t>::max())
m_errorReporter.typeError(
5202_error,
nativeLocationOf(arg),
"Invalid auxdataloadn argument value. It must be <= 0xFFFF"
);
}
}
}
expectExpression(arg);
}
Expand Down
7 changes: 7 additions & 0 deletions libyul/backends/evm/AbstractAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,14 @@ class AbstractAssembly
/// Appends an assignment to an immutable variable.
virtual void appendImmutableAssignment(std::string const& _identifier) = 0;

/// Appends an operation that loads 32 bytes of data from a known offset relative to the start of the static_aux_data area of the EOF data section.
/// Note that static_aux_data is only a part or the data section.
/// It is preceded by the pre_deploy_data, whose size is not determined before the bytecode is assembled, and which cannot be accessed using this function.
/// The function is meant to allow indexing into static_aux_data in a way that's independent of the size of pre_deploy_data.
virtual void appendAuxDataLoadN(size_t _dataOffset) = 0;

/// Appends data to the very end of the bytecode. Repeated calls concatenate.
/// EOF auxiliary data in data section and the auxiliary data are different things.
virtual void appendToAuxiliaryData(bytes const& _data) = 0;

/// Mark this assembly as invalid. Any attempt to request bytecode from it should throw.
Expand Down
24 changes: 23 additions & 1 deletion libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ std::map<YulName, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _ev
opcode != evmasm::Instruction::JUMP &&
opcode != evmasm::Instruction::JUMPI &&
opcode != evmasm::Instruction::JUMPDEST &&
opcode != evmasm::Instruction::DATALOADN &&
_evmVersion.hasOpcode(opcode, _eofVersion) &&
!prevRandaoException(name)
)
Expand Down Expand Up @@ -235,7 +236,7 @@ std::map<YulName, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _ev
})
);

if (!_eofVersion.has_value())
if (!_eofVersion.has_value()) // non-EOF context
{
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {LiteralKind::String}, [](
FunctionCall const& _call,
Expand Down Expand Up @@ -345,6 +346,27 @@ std::map<YulName, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _ev
}
));
}
else // EOF context
{
builtins.emplace(createFunction(
"auxdataloadn",
1,
1,
SideEffects{},
{LiteralKind::Number},
[](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&
) {
yulAssert(_call.arguments.size() == 1, "");
Literal const* literal = std::get_if<Literal>(&_call.arguments.front());
yulAssert(literal, "");
yulAssert(literal->value.value() <= std::numeric_limits<size_t>::max(), "");
_assembly.appendAuxDataLoadN(static_cast<size_t>(literal->value.value()));
}
));
}
}
return builtins;
}
Expand Down
5 changes: 5 additions & 0 deletions libyul/backends/evm/EthAssemblyAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ void EthAssemblyAdapter::appendImmutableAssignment(std::string const& _identifie
m_assembly.appendImmutableAssignment(_identifier);
}

void EthAssemblyAdapter::appendAuxDataLoadN(size_t _dataOffset)
{
m_assembly.appendAuxDataLoadN(_dataOffset);
}

void EthAssemblyAdapter::markAsInvalid()
{
m_assembly.markAsInvalid();
Expand Down
2 changes: 2 additions & 0 deletions libyul/backends/evm/EthAssemblyAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class EthAssemblyAdapter: public AbstractAssembly
void appendImmutable(std::string const& _identifier) override;
void appendImmutableAssignment(std::string const& _identifier) override;

void appendAuxDataLoadN(size_t dataOffset) override;

void markAsInvalid() override;

langutil::EVMVersion evmVersion() const override;
Expand Down
Loading

0 comments on commit 97689bc

Please sign in to comment.