Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/using-the-compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ at each version. Backward compatibility is not guaranteed between each version.
- ``osaka`` (**default**)
- ``clz`` builtin function is available in inline assembly. (`EIP-7939 <https://eips.ethereum.org/EIPS/eip-7939>`_)
- ``amsterdam`` (**experimental**)
- Makes ``swapn``/``dupn`` available and used to prevent stack too deep errors.

.. index:: ! standard JSON, ! --standard-json
.. _compiler-api:
Expand Down
4 changes: 2 additions & 2 deletions docs/yul.rst
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,8 @@ This document does not want to be a full description of the Ethereum virtual mac
Please refer to a different document if you are interested in the precise semantics.

Opcodes marked with ``-`` do not return a result and all others return exactly one value.
Opcodes marked with ``F``, ``H``, ``B``, ``C``, ``I``, ``L``, ``P``, ``N`` and ``O`` are present since
Frontier, Homestead, Byzantium, Constantinople, Istanbul, London, Paris, Cancun or Osaka respectively.
Opcodes marked with ``F``, ``H``, ``B``, ``C``, ``I``, ``L``, ``P``, ``N``, ``O`` and ``A`` are present since
Frontier, Homestead, Byzantium, Constantinople, Istanbul, London, Paris, Cancun, Osaka or Amsterdam respectively.

In the following, ``mem[a...b)`` signifies the bytes of memory starting at position ``a`` up to
but not including position ``b``, ``storage[p]`` signifies the storage contents at slot ``p``, and
Expand Down
40 changes: 39 additions & 1 deletion libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ using namespace solidity::util;
namespace
{

// https://eips.ethereum.org/EIPS/eip-8024
uint8_t encodeDupSwapNImmediate(size_t _depth)
{
solAssert(_depth >= 17 && _depth <= 235);
return static_cast<uint8_t>((_depth + 111) % 256);
}

/// Produces instruction location info in RAII style. When an assembly instruction is added to the bytecode,
/// this class can be instantiated in that scope. It will record the current bytecode size (before addition)
/// and, at destruction time, record the new bytecode size. This information is then added to an external
Expand Down Expand Up @@ -241,7 +248,31 @@ AssemblyItem Assembly::createAssemblyItemFromJSON(Json const& _json, std::vector

AssemblyItem result(0);

if (c_instructions.count(name))
if (name == "SWAPN" || name == "DUPN")
{
solRequire(
m_evmVersion.hasDupSwapN(),
AssemblyImportException,
"Instruction '" + name + "' is only available starting from the \"amsterdam\" EVM version."
);
solRequire(
jumpType.empty(),
AssemblyImportException,
"Member 'jumpType' set on instruction different from JUMP or JUMPI (was set on instruction '" + name + "')"
);
requireValueDefinedForInstruction(name, value);
u256 const depth{value};
solRequire(
depth >= 17 && depth <= 235,
AssemblyImportException,
"Invalid depth for instruction '" + name + "'."
);
if (name == "SWAPN")
result = AssemblyItem::swap(static_cast<size_t>(depth));
else
result = AssemblyItem::dup(static_cast<size_t>(depth));
}
else if (c_instructions.count(name))
{
AssemblyItem item{c_instructions.at(name), langutil::DebugData::create(location)};
if (!jumpType.empty())
Expand Down Expand Up @@ -1098,6 +1129,13 @@ LinkerObject const& Assembly::assembleLegacy() const
case Operation:
ret.bytecode += assembleOperation(item);
break;
case SwapN:
case DupN:
solAssert(m_evmVersion.hasDupSwapN());
ret.bytecode.push_back(static_cast<uint8_t>(item.instruction()));
solAssert(item.data() < std::numeric_limits<size_t>::max());
ret.bytecode.push_back(encodeDupSwapNImmediate(static_cast<size_t>(item.data())));
break;
case Push:
ret.bytecode += assemblePush(item);
break;
Expand Down
21 changes: 21 additions & 0 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ std::pair<std::string, std::string> AssemblyItem::nameAndData(langutil::EVMVersi
return {"PUSH data", toStringInHex(data())};
case VerbatimBytecode:
return {"VERBATIM", util::toHex(verbatimData())};
case SwapN:
return {"SWAPN", util::toString(data())};
case DupN:
return {"DUPN", util::toString(data())};
case UndefinedItem:
solAssert(false);
}
Expand Down Expand Up @@ -165,6 +169,9 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, langutil::EVMVersion _
}
case VerbatimBytecode:
return std::get<2>(*m_verbatimBytecode).size();
case SwapN:
case DupN:
return 1 + 1; // Instruction + one byte of immediate data. According to https://eips.ethereum.org/EIPS/eip-8024
case UndefinedItem:
solAssert(false);
}
Expand Down Expand Up @@ -205,8 +212,10 @@ size_t AssemblyItem::returnValues() const
case PushLibraryAddress:
case PushImmutable:
case PushDeployTimeAddress:
case DupN:
return 1;
case Tag:
case SwapN:
return 0;
case VerbatimBytecode:
return std::get<1>(*m_verbatimBytecode);
Expand Down Expand Up @@ -240,6 +249,8 @@ bool AssemblyItem::canBeFunctional() const
case AssignImmutable:
case VerbatimBytecode:
case UndefinedItem:
case SwapN:
case DupN:
break;
}
return false;
Expand Down Expand Up @@ -335,6 +346,12 @@ std::string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case VerbatimBytecode:
text = std::string("verbatimbytecode_") + util::toHex(std::get<2>(*m_verbatimBytecode));
break;
case SwapN:
text = "swapn{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
case DupN:
text = "dupn{" + std::to_string(static_cast<size_t>(data())) + "}";
break;
}
if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction)
{
Expand All @@ -357,6 +374,10 @@ std::ostream& solidity::evmasm::operator<<(std::ostream& _out, AssemblyItem cons
if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI)
_out << "\t" << _item.getJumpTypeAsString();
break;
case SwapN:
case DupN:
_out << " " << instructionInfo(_item.instruction(), EVMVersion()).name << " " << std::dec << _item.data();
break;
case Push:
_out << " PUSH " << std::hex << _item.data() << std::dec;
break;
Expand Down
34 changes: 32 additions & 2 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ 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.
SwapN, ///< SWAPN with immediate argument.
DupN, ///< DUPN with immediate argument.

VerbatimBytecode, ///< Contains data that is inserted into the bytecode code section without modification.
};
Expand All @@ -78,7 +80,10 @@ class AssemblyItem
m_type(Operation),
m_instruction(_i),
m_debugData(std::move(_debugData))
{}
{
solAssert(_i != Instruction::SWAPN, "Construct via AssemblyItem::swap");
solAssert(_i != Instruction::DUPN, "Construct via AssemblyItem::dup");
}
AssemblyItem(AssemblyItemType _type, u256 _data = 0, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create()):
m_type(_type),
m_debugData(std::move(_debugData))
Expand All @@ -102,6 +107,29 @@ class AssemblyItem
m_debugData{langutil::DebugData::create()}
{}

/// @returns an item swapping the top of the stack with the value at @a _depth,
/// using SWAP1-16 or SWAPN with an immediate argument, depending on the depth.
/// Depths above 235 cannot be encoded, see https://eips.ethereum.org/EIPS/eip-8024
static AssemblyItem swap(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
if (_depth <= 16)
// Depth 0 is rejected by an assert in swapInstruction().
return AssemblyItem(swapInstruction(static_cast<unsigned>(_depth)), std::move(_debugData));
solAssert(_depth <= 235, "Invalid SWAPN depth.");
return AssemblyItem(SwapN, Instruction::SWAPN, _depth, std::move(_debugData));
}
/// @returns an item duplicating the value at stack depth @a _depth to the top of the stack,
/// using DUP1-16 or DUPN with an immediate argument, depending on the depth.
/// Depths above 235 cannot be encoded, see https://eips.ethereum.org/EIPS/eip-8024
static AssemblyItem dup(size_t _depth, langutil::DebugData::ConstPtr _debugData = langutil::DebugData::create())
{
if (_depth <= 16)
// Depth 0 is rejected by an assert in dupInstruction().
return AssemblyItem(dupInstruction(static_cast<unsigned>(_depth)), std::move(_debugData));
solAssert(_depth <= 235, "Invalid DUPN depth.");
return AssemblyItem(DupN, Instruction::DUPN, _depth, std::move(_debugData));
}

AssemblyItem(AssemblyItem const&) = default;
AssemblyItem(AssemblyItem&&) = default;
AssemblyItem& operator=(AssemblyItem const&) = default;
Expand Down Expand Up @@ -138,7 +166,9 @@ class AssemblyItem
bool hasInstruction() const
{
bool const shouldHaveInstruction =
m_type == Operation;
m_type == Operation ||
m_type == SwapN ||
m_type == DupN;
solAssert(shouldHaveInstruction == m_instruction.has_value());
return shouldHaveInstruction;
}
Expand Down
4 changes: 2 additions & 2 deletions libevmasm/CommonSubexpressionEliminator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ void CSECodeGenerator::appendDup(int _fromPosition, langutil::DebugData::ConstPt
int instructionNum = 1 + m_stackHeight - _fromPosition;
assertThrow(instructionNum <= reachableStackDepth, StackTooDeepException, util::stackTooDeepString);
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(dupInstruction(static_cast<unsigned>(instructionNum)), std::move(_debugData)));
appendItem(AssemblyItem::dup(static_cast<size_t>(instructionNum), std::move(_debugData)));
m_stack[m_stackHeight] = m_stack[_fromPosition];
m_classPositions[m_stack[m_stackHeight]].insert(m_stackHeight);
}
Expand All @@ -492,7 +492,7 @@ void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, langutil::DebugData
int instructionNum = m_stackHeight - _fromPosition;
assertThrow(instructionNum <= reachableStackDepth, StackTooDeepException, util::stackTooDeepString);
assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access.");
appendItem(AssemblyItem(swapInstruction(static_cast<unsigned>(instructionNum)), std::move(_debugData)));
appendItem(AssemblyItem::swap(static_cast<size_t>(instructionNum), std::move(_debugData)));

if (m_stack[m_stackHeight] != m_stack[_fromPosition])
{
Expand Down
34 changes: 34 additions & 0 deletions libevmasm/Disassemble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::evmasm;

namespace
{

/// @returns true if @a _immediate encodes a valid DUPN/SWAPN stack depth.
/// The encoded depth is `immediate + 145 (mod 256)`, so immediates in [0x80, 0xff] encode
/// depths 17-144 and immediates in [0x00, 0x5a] encode depths 145-235. The remaining
/// immediates in [0x5b, 0x7f] would encode the invalid depths 236-255 and 0-16.
/// See https://eips.ethereum.org/EIPS/eip-8024
bool isValidDupSwapNImmediate(uint8_t _immediate)
{
return _immediate <= 0x5a || _immediate >= 0x80;
}

/// @returns the stack depth encoded by the DUPN/SWAPN immediate argument @a _immediate,
/// i.e. `immediate + 145 (mod 256)`. See https://eips.ethereum.org/EIPS/eip-8024
size_t decodeDupSwapNImmediate(uint8_t _immediate)
{
solAssert(isValidDupSwapNImmediate(_immediate));
return static_cast<uint8_t>((_immediate + 145) % 256);
}

}

void solidity::evmasm::eachInstruction(
bytes const& _mem,
Expand Down Expand Up @@ -63,6 +85,18 @@ std::string solidity::evmasm::disassemble(bytes const& _mem, langutil::EVMVersio
eachInstruction(_mem, _evmVersion, [&](Instruction _instr, u256 const& _data) {
if (!isValidInstruction(_instr))
ret << "0x" << std::uppercase << std::hex << static_cast<int>(_instr) << _delimiter;
else if (_instr == Instruction::DUPN || _instr == Instruction::SWAPN)
{
std::string const& name = instructionInfo(_instr, _evmVersion).name;
if (isValidDupSwapNImmediate(static_cast<uint8_t>(_data)))
ret << name
<< " "
<< std::dec
<< decodeDupSwapNImmediate(static_cast<uint8_t>(_data))
<< _delimiter;
else
ret << "INVALID_" << name << _delimiter;
}
else
{
InstructionInfo info = instructionInfo(_instr, _evmVersion);
Expand Down
15 changes: 12 additions & 3 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas = runGas(Instruction::JUMPDEST, m_evmVersion);
break;
case Operation:
case SwapN:
case DupN:
{
ExpressionClasses& classes = m_state->expressionClasses();
switch (_item.instruction())
Expand Down Expand Up @@ -217,7 +219,10 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
}
break;
}
default:
case UndefinedItem:
case PushImmutable:
case AssignImmutable:
case VerbatimBytecode:
gas = GasConsumption::infinite();
break;
}
Expand Down Expand Up @@ -308,14 +313,18 @@ unsigned GasMeter::swapGas(size_t _depth, langutil::EVMVersion _evmVersion)
{
if (_depth <= 16)
return runGas(evmasm::swapInstruction(static_cast<unsigned>(_depth)), _evmVersion);
solAssert(false, "Unexpected swap instruction depth");
auto gasCost = gasCostForTier(instructionInfo(evmasm::Instruction::SWAPN, _evmVersion).gasPriceTier);
solAssert(gasCost.has_value(), "Expected gas cost for SWAPN to be defined.");
return *gasCost;
}

unsigned GasMeter::dupGas(size_t _depth, langutil::EVMVersion _evmVersion)
{
if (_depth <= 16)
return runGas(evmasm::dupInstruction(static_cast<unsigned>(_depth)), _evmVersion);
solAssert(false, "Unexpected dup instruction depth");
auto gasCost = gasCostForTier(instructionInfo(evmasm::Instruction::DUPN, _evmVersion).gasPriceTier);
solAssert(gasCost.has_value(), "Expected gas cost for DUPN to be defined.");
return *gasCost;
}

u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion)
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{Instruction::LOG2, {"LOG2", 0, 4, 0, true, Tier::Special}},
{Instruction::LOG3, {"LOG3", 0, 5, 0, true, Tier::Special}},
{Instruction::LOG4, {"LOG4", 0, 6, 0, true, Tier::Special}},
{Instruction::SWAPN, {"SWAPN", 1, 0, 0, false, Tier::VeryLow}},
{Instruction::DUPN, {"DUPN", 1, 0, 0, false, Tier::VeryLow}},
{Instruction::CREATE, {"CREATE", 0, 3, 1, true, Tier::Special}},
{Instruction::CALL, {"CALL", 0, 7, 1, true, Tier::Special}},
{Instruction::CALLCODE, {"CALLCODE", 0, 7, 1, true, Tier::Special}},
Expand Down
3 changes: 3 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ enum class Instruction: uint8_t
LOG3, ///< Makes a log entry; 3 topics.
LOG4, ///< Makes a log entry; 4 topics.

DUPN = 0xe6, ///< copies a value at the stack depth given as immediate argument to the top of the stack
SWAPN = 0xe7, ///< swaps the highest value with a value at a stack depth given as immediate argument

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
2 changes: 1 addition & 1 deletion libevmasm/KnownState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
m_expressionClasses->newClass(_item.debugData())
);
}
else if (_item.type() != Operation)
else if (_item.type() != Operation && _item.type() != SwapN && _item.type() != DupN)
{
solAssert(_item.deposit() == 1);
if (_item.pushedValue())
Expand Down
Loading