Skip to content
Merged
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 Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Language Features:
* Introduce global function ``blobhash(uint)`` for retrieving versioned hashes of blobs, akin to the homonymous Yul builtin.
* Yul: Introduce builtin ``blobbasefee()`` for retrieving the blob base fee of the current block.
* Yul: Introduce builtin ``blobhash()`` for retrieving versioned hashes of blobs associated with the transaction.
* Yul: Introduce builtin ``mcopy()`` for cheaply copying data between memory areas.

Compiler Features:
* EVM: Support for the EVM Version "Cancun".
Expand Down
2 changes: 1 addition & 1 deletion docs/grammar/SolidityLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ YulEVMBuiltin:
| 'pop' | 'mload' | 'mstore' | 'mstore8' | 'sload' | 'sstore' | 'msize' | 'gas'
| 'address' | 'balance' | 'selfbalance' | 'caller' | 'callvalue' | 'calldataload'
| 'calldatasize' | 'calldatacopy' | 'extcodesize' | 'extcodecopy' | 'returndatasize'
| 'returndatacopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode'
| 'returndatacopy' | 'mcopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode'
| 'delegatecall' | 'staticcall' | 'return' | 'revert' | 'selfdestruct' | 'invalid'
| 'log0' | 'log1' | 'log2' | 'log3' | 'log4' | 'chainid' | 'origin' | 'gasprice'
| 'blockhash' | 'blobhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty'
Expand Down
1 change: 1 addition & 0 deletions docs/using-the-compiler.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ at each version. Backward compatibility is not guaranteed between each version.
- ``cancun``
- The block's blob base fee (`EIP-7516 <https://eips.ethereum.org/EIPS/eip-7516>`_ and `EIP-4844 <https://eips.ethereum.org/EIPS/eip-4844>`_) can be accessed via the global ``block.blobbasefee`` or ``blobbasefee()`` in inline assembly.
- Introduces ``blobhash()`` in inline assembly and a corresponding global function to retrieve versioned hashes of blobs associated with the transaction (see `EIP-4844 <https://eips.ethereum.org/EIPS/eip-4844>`_).
- Opcode ``mcopy`` is available in assembly (see `EIP-5656 <https://eips.ethereum.org/EIPS/eip-5656>`_).

.. index:: ! standard JSON, ! --standard-json
.. _compiler-api:
Expand Down
2 changes: 2 additions & 0 deletions docs/yul.rst
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,8 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a
+-------------------------+-----+---+-----------------------------------------------------------------+
| returndatacopy(t, f, s) | `-` | B | copy s bytes from returndata at position f to mem at position t |
+-------------------------+-----+---+-----------------------------------------------------------------+
| mcopy(t, f, s) | `-` | N | copy s bytes from mem at position f to mem at position t |
+-------------------------+-----+---+-----------------------------------------------------------------+
| extcodehash(a) | | C | code hash of address a |
+-------------------------+-----+---+-----------------------------------------------------------------+
| create(v, p, n) | | F | create new contract with code mem[p...(p+n)) and send v wei |
Expand Down
10 changes: 10 additions & 0 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
gas += memoryGas(0, -2);
gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2));
break;
case Instruction::MCOPY:
{
GasConsumption memoryGasFromRead = memoryGas(-1, -2);
GasConsumption memoryGasFromWrite = memoryGas(0, -2);

gas = runGas(_item.instruction(), m_evmVersion);
gas += (memoryGasFromRead < memoryGasFromWrite ? memoryGasFromWrite : memoryGasFromRead);
gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2));
break;
}
case Instruction::EXTCODESIZE:
gas = GasCosts::extCodeGas(m_evmVersion);
break;
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "EXTCODECOPY", Instruction::EXTCODECOPY },
{ "RETURNDATASIZE", Instruction::RETURNDATASIZE },
{ "RETURNDATACOPY", Instruction::RETURNDATACOPY },
{ "MCOPY", Instruction::MCOPY },
{ "EXTCODEHASH", Instruction::EXTCODEHASH },
{ "BLOCKHASH", Instruction::BLOCKHASH },
{ "BLOBHASH", Instruction::BLOBHASH },
Expand Down Expand Up @@ -222,6 +223,7 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{ Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } },
{ Instruction::RETURNDATASIZE, {"RETURNDATASIZE", 0, 0, 1, false, Tier::Base } },
{ Instruction::RETURNDATACOPY, {"RETURNDATACOPY", 0, 3, 0, true, Tier::VeryLow } },
{ Instruction::MCOPY, { "MCOPY", 0, 3, 0, true, Tier::VeryLow } },
{ Instruction::EXTCODEHASH, { "EXTCODEHASH", 0, 1, 1, false, Tier::Balance } },
{ Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } },
{ Instruction::BLOBHASH, { "BLOBHASH", 0, 1, 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 @@ -104,6 +104,7 @@ enum class Instruction: uint8_t
MSIZE, ///< get the size of active memory
GAS, ///< get the amount of available gas
JUMPDEST, ///< set a potential jump destination
MCOPY = 0x5e, ///< copy between memory areas

PUSH0 = 0x5f, ///< place the value 0 on stack
PUSH1 = 0x60, ///< place 1 byte item on stack
Expand Down
22 changes: 21 additions & 1 deletion libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,25 @@ std::vector<SemanticInformation::Operation> SemanticInformation::readWriteOperat
op.lengthParameter = 2;
return {op};
}
case Instruction::MCOPY:
{
assertThrow(memory(_instruction) != Effect::None, OptimizerException, "");
assertThrow(storage(_instruction) == Effect::None, OptimizerException, "");

Operation readOperation;
readOperation.effect = Read;
readOperation.location = Location::Memory;
readOperation.startParameter = 1;
readOperation.lengthParameter = 2;

Operation writeOperation;
writeOperation.effect = Write;
writeOperation.location = Location::Memory;
writeOperation.startParameter = 0;
writeOperation.lengthParameter = 2;

return {readOperation, writeOperation};
}
case Instruction::STATICCALL:
case Instruction::CALL:
case Instruction::CALLCODE:
Expand Down Expand Up @@ -188,7 +207,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
))
return false;
//@todo: We do not handle the following memory instructions for now:
// calldatacopy, codecopy, extcodecopy, mstore8,
// calldatacopy, codecopy, extcodecopy, mcopy, mstore8,
// msize (note that msize also depends on memory read access)

// the second requirement will be lifted once it is implemented
Expand Down Expand Up @@ -363,6 +382,7 @@ SemanticInformation::Effect SemanticInformation::memory(Instruction _instruction
case Instruction::CODECOPY:
case Instruction::EXTCODECOPY:
case Instruction::RETURNDATACOPY:
case Instruction::MCOPY:
case Instruction::MSTORE:
case Instruction::MSTORE8:
case Instruction::CALL:
Expand Down
1 change: 1 addition & 0 deletions libevmasm/SimplificationRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ struct EVMBuiltins
static auto constexpr EXTCODECOPY = PatternGenerator<Instruction::EXTCODECOPY>{};
static auto constexpr RETURNDATASIZE = PatternGenerator<Instruction::RETURNDATASIZE>{};
static auto constexpr RETURNDATACOPY = PatternGenerator<Instruction::RETURNDATACOPY>{};
static auto constexpr MCOPY = PatternGenerator<Instruction::MCOPY>{};
static auto constexpr EXTCODEHASH = PatternGenerator<Instruction::EXTCODEHASH>{};
static auto constexpr BLOCKHASH = PatternGenerator<Instruction::BLOCKHASH>{};
static auto constexpr BLOBHASH = PatternGenerator<Instruction::BLOBHASH>{};
Expand Down
2 changes: 2 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ bool EVMVersion::hasOpcode(Instruction _opcode) const
return hasBlobHash();
case Instruction::BLOBBASEFEE:
return hasBlobBaseFee();
case Instruction::MCOPY:
return hasMcopy();
default:
return true;
}
Expand Down
1 change: 1 addition & 0 deletions liblangutil/EVMVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class EVMVersion:
bool hasPrevRandao() const { return *this >= paris(); }
bool hasPush0() const { return *this >= shanghai(); }
bool hasBlobHash() const { return *this >= cancun(); }
bool hasMcopy() const { return *this >= cancun(); }

bool hasOpcode(evmasm::Instruction _opcode) const;

Expand Down
3 changes: 3 additions & 0 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,9 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
else if (_instr == evmasm::Instruction::BLOBHASH && !m_evmVersion.hasBlobHash())
// TODO: Change this assertion to an error, similar to the ones above, when Cancun becomes the default EVM version.
yulAssert(false);
else if (_instr == evmasm::Instruction::MCOPY && !m_evmVersion.hasMcopy())
// TODO: Change this assertion to an error, similar to the ones above, when Cancun becomes the default EVM version.
yulAssert(false);
else if (_instr == evmasm::Instruction::PC)
m_errorReporter.error(
2450_error,
Expand Down
10 changes: 9 additions & 1 deletion libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ std::set<YulString> createReservedIdentifiers(langutil::EVMVersion _evmVersion)
return _instr == evmasm::Instruction::BLOBBASEFEE && _evmVersion < langutil::EVMVersion::cancun();
};

// TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name
// mcopy for VMs before london.
auto mcopyException = [&](evmasm::Instruction _instr) -> bool
{
return _instr == evmasm::Instruction::MCOPY && _evmVersion < langutil::EVMVersion::cancun();
};

// TODO remove this in 0.9.0. We allow creating functions or identifiers in Yul with the name
// prevrandao for VMs before paris.
auto prevRandaoException = [&](std::string const& _instrName) -> bool
Expand All @@ -150,7 +157,8 @@ std::set<YulString> createReservedIdentifiers(langutil::EVMVersion _evmVersion)
!baseFeeException(instr.second) &&
!prevRandaoException(name) &&
!blobHashException(instr.second) &&
!blobBaseFeeException(instr.second)
!blobBaseFeeException(instr.second) &&
!mcopyException(instr.second)
)
reserved.emplace(name);
}
Expand Down
3 changes: 3 additions & 0 deletions libyul/optimiser/UnusedStoreEliminator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,12 @@ void UnusedStoreEliminator::visit(Statement const& _statement)
*instruction == Instruction::CODECOPY ||
*instruction == Instruction::CALLDATACOPY ||
*instruction == Instruction::RETURNDATACOPY ||
// TODO: Removing MCOPY is complicated because it's not just a store but also a load.
//*instruction == Instruction::MCOPY ||
*instruction == Instruction::MSTORE ||
*instruction == Instruction::MSTORE8;
bool isCandidateForRemoval =
*instruction != Instruction::MCOPY &&
SemanticInformation::otherState(*instruction) != SemanticInformation::Write && (
SemanticInformation::storage(*instruction) == SemanticInformation::Write ||
(!m_ignoreMemory && SemanticInformation::memory(*instruction) == SemanticInformation::Write)
Expand Down
2 changes: 2 additions & 0 deletions scripts/test_antlr_grammar.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ done < <(
grep -v -E 'inlineAssembly/basefee_berlin_function.sol' |
# Skipping a test with "let blobbasefee := ..."
grep -v -E 'inlineAssembly/blobbasefee_shanghai_function.sol' |
# Skipping a test with "let mcopy := ..."
grep -v -E 'inlineAssembly/mcopy_as_identifier_pre_cancun.sol' |
# Skipping tests with "let prevrandao := ..."
grep -v -E 'inlineAssembly/prevrandao_allowed_function_pre_paris.sol' |
grep -v -E 'inlineAssembly/prevrandao_disallowed_function_post_paris.sol' |
Expand Down
108 changes: 108 additions & 0 deletions test/libsolidity/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ using namespace solidity::langutil;
using namespace solidity::evmasm;
using namespace solidity::frontend;
using namespace solidity::frontend::test;
using namespace solidity::test;

namespace solidity::frontend::test
{
Expand Down Expand Up @@ -339,6 +340,113 @@ BOOST_AUTO_TEST_CASE(complex_control_flow)
testRunTimeGas("ln(int128)", std::vector<bytes>{encodeArgs(0), encodeArgs(10), encodeArgs(105), encodeArgs(30000)});
}

BOOST_AUTO_TEST_CASE(
mcopy_memory_expansion_gas,
*boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::cancun()))
)
{
char const* sourceCode = R"(
contract C {
function no_expansion() public {
assembly {
mstore(0xffe0, 1) // expand memory before using mcopy
mcopy(0, 0xffff, 1)
return(0, 1)
}
}

function expansion_on_write() public {
assembly {
mcopy(0xffff, 0, 1)
return(0xffff, 1)
}
}

function expansion_on_read() public {
assembly {
mcopy(0, 0xffff, 1)
return(0, 1)
}
}

function expansion_on_read_write() public {
assembly {
mcopy(0xffff, 0xffff, 1)
return(0, 1)
}
}

function expansion_on_zero_size() public {
assembly {
mcopy(0xffff, 0xffff, 0)
return(0, 1)
}
}

function expansion_on_0_0_0() public {
assembly {
mcopy(0, 0, 0)
return(0, 1)
}
}
}
)";
testCreationTimeGas(sourceCode);
testRunTimeGas("no_expansion()", {encodeArgs()});
testRunTimeGas("expansion_on_write()", {encodeArgs()});
testRunTimeGas("expansion_on_read()", {encodeArgs()});
testRunTimeGas("expansion_on_read_write()", {encodeArgs()});
testRunTimeGas("expansion_on_zero_size()", {encodeArgs()});
testRunTimeGas("expansion_on_0_0_0()", {encodeArgs()});
}

BOOST_AUTO_TEST_CASE(
mcopy_word_gas,
*boost::unit_test::precondition(minEVMVersionCheck(EVMVersion::cancun()))
)
{
char const* sourceCode = R"(
contract C {
function no_overlap() public {
assembly {
mstore(0xffe0, 1) // expand memory before using mcopy
mcopy(0x4000, 0x2000, 0x2000)
return(0, 0x10000)
}
}

function overlap_right() public {
assembly {
mstore(0xffe0, 1) // expand memory before using mcopy
mcopy(0x3000, 0x2000, 0x2000)
return(0, 0x10000)
}
}

function overlap_left() public {
assembly {
mstore(0xffe0, 1) // expand memory before using mcopy
mcopy(0x1000, 0x2000, 0x2000)
return(0, 0x10000)
}
}

function overlap_full() public {
assembly {
mstore(0xffe0, 1) // expand memory before using mcopy
mcopy(0x2000, 0x2000, 0x2000)
return(0, 0x10000)
}
}
}
)";
testCreationTimeGas(sourceCode);
testRunTimeGas("no_overlap()", {encodeArgs()});
testRunTimeGas("overlap_right()", {encodeArgs()});
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have this kind of test for all the Cancun opcodes - BLOBBASEFEE, BLOBHASH. TSTORE, TLOAD. The GasMeter changes in other PRs are simple, so should be ok, but still, we don't have any coverage for them otherwise.

testRunTimeGas("overlap_left()", {encodeArgs()});
testRunTimeGas("overlap_full()", {encodeArgs()});
}

BOOST_AUTO_TEST_SUITE_END()

}
12 changes: 12 additions & 0 deletions test/libsolidity/memoryGuardTests/marked_empty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
contract C {
constructor() {
assembly ("memory-safe") {}
}

function f() public {
assembly ("memory-safe") {}
}
}
// ----
// :C(creation) true
// :C(runtime) true
16 changes: 16 additions & 0 deletions test/libsolidity/memoryGuardTests/marked_with_memory_access.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
contract C {
constructor() {
assembly ("memory-safe") {
mstore(0, 1)
}
}

function store() public {
assembly ("memory-safe") {
mstore(0, 1)
}
}
}
// ----
// :C(creation) true
// :C(runtime) true
12 changes: 12 additions & 0 deletions test/libsolidity/memoryGuardTests/unmarked_empty.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
contract C {
constructor() {
assembly {}
}

function f() public {
assembly {}
}
}
// ----
// :C(creation) true
// :C(runtime) true

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
contract C {
constructor() {
assembly {
mcopy(1000, 2000, 1000)
}
}

function copy() public {
assembly {
mcopy(1000, 2000, 1000)
}
}
}
// ====
// EVMVersion: >=cancun
// ----
// :C(creation) false
// :C(runtime) false
Loading