Skip to content

Commit

Permalink
Add blobhash opcode
Browse files Browse the repository at this point in the history
Co-authored-by: Kamil Śliwak <[email protected]>
  • Loading branch information
r0qs and cameel committed Jan 18, 2024
1 parent 9c9eddb commit 269951e
Show file tree
Hide file tree
Showing 28 changed files with 200 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Language Features:
* Introduce global ``block.blobbasefee`` for retrieving the blob base fee of the current block.
* 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.

Compiler Features:
* EVM: Support for the EVM Version "Cancun".
Expand Down
4 changes: 2 additions & 2 deletions docs/grammar/SolidityLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ YulEVMBuiltin:
| 'returndatacopy' | 'extcodehash' | 'create' | 'create2' | 'call' | 'callcode'
| 'delegatecall' | 'staticcall' | 'return' | 'revert' | 'selfdestruct' | 'invalid'
| 'log0' | 'log1' | 'log2' | 'log3' | 'log4' | 'chainid' | 'origin' | 'gasprice'
| 'blockhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty' | 'prevrandao'
| 'gaslimit' | 'basefee' | 'blobbasefee';
| 'blockhash' | 'blobhash' | 'coinbase' | 'timestamp' | 'number' | 'difficulty'
| 'prevrandao' | 'gaslimit' | 'basefee' | 'blobbasefee';
YulLBrace: '{' -> pushMode(YulMode);
YulRBrace: '}' -> popMode;
Expand Down
2 changes: 2 additions & 0 deletions docs/yul.rst
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,8 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a
+-------------------------+-----+---+-----------------------------------------------------------------+
| blockhash(b) | | F | hash of block nr b - only for last 256 blocks excluding current |
+-------------------------+-----+---+-----------------------------------------------------------------+
| blobhash(i) | | N | versioned hash of transaction's i-th blob |
+-------------------------+-----+---+-----------------------------------------------------------------+
| coinbase() | | F | current mining beneficiary |
+-------------------------+-----+---+-----------------------------------------------------------------+
| timestamp() | | F | timestamp of the current block in seconds since the epoch |
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "RETURNDATACOPY", Instruction::RETURNDATACOPY },
{ "EXTCODEHASH", Instruction::EXTCODEHASH },
{ "BLOCKHASH", Instruction::BLOCKHASH },
{ "BLOBHASH", Instruction::BLOBHASH },
{ "COINBASE", Instruction::COINBASE },
{ "TIMESTAMP", Instruction::TIMESTAMP },
{ "NUMBER", Instruction::NUMBER },
Expand Down Expand Up @@ -223,6 +224,7 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{ Instruction::RETURNDATACOPY, {"RETURNDATACOPY", 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 } },
{ Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } },
{ Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } },
{ Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } },
Expand Down
1 change: 1 addition & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ enum class Instruction: uint8_t
CHAINID, ///< get the config's chainid param
SELFBALANCE, ///< get balance of the current account
BASEFEE, ///< get the block's basefee
BLOBHASH = 0x49, ///< get a versioned hash of one of the blobs associated with the transaction
BLOBBASEFEE = 0x4a, ///< get the block's blob basefee

POP = 0x50, ///< remove item from stack
Expand Down
1 change: 1 addition & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction)
case Instruction::EXTCODECOPY:
case Instruction::EXTCODEHASH:
case Instruction::BLOCKHASH:
case Instruction::BLOBHASH:
case Instruction::COINBASE:
case Instruction::TIMESTAMP:
case Instruction::NUMBER:
Expand Down
1 change: 1 addition & 0 deletions libevmasm/SimplificationRule.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ struct EVMBuiltins
static auto constexpr RETURNDATACOPY = PatternGenerator<Instruction::RETURNDATACOPY>{};
static auto constexpr EXTCODEHASH = PatternGenerator<Instruction::EXTCODEHASH>{};
static auto constexpr BLOCKHASH = PatternGenerator<Instruction::BLOCKHASH>{};
static auto constexpr BLOBHASH = PatternGenerator<Instruction::BLOBHASH>{};
static auto constexpr COINBASE = PatternGenerator<Instruction::COINBASE>{};
static auto constexpr TIMESTAMP = PatternGenerator<Instruction::TIMESTAMP>{};
static auto constexpr NUMBER = PatternGenerator<Instruction::NUMBER>{};
Expand Down
2 changes: 2 additions & 0 deletions liblangutil/EVMVersion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ bool EVMVersion::hasOpcode(Instruction _opcode) const
return hasSelfBalance();
case Instruction::BASEFEE:
return hasBaseFee();
case Instruction::BLOBHASH:
return hasBlobHash();
case Instruction::BLOBBASEFEE:
return hasBlobBaseFee();
default:
Expand Down
1 change: 1 addition & 0 deletions liblangutil/EVMVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class EVMVersion:
bool hasBlobBaseFee() const { return *this >= cancun(); }
bool hasPrevRandao() const { return *this >= paris(); }
bool hasPush0() const { return *this >= shanghai(); }
bool hasBlobHash() const { return *this >= cancun(); }

bool hasOpcode(evmasm::Instruction _opcode) const;

Expand Down
7 changes: 7 additions & 0 deletions libyul/AsmAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ void AsmAnalyzer::expectType(YulString _expectedType, YulString _givenType, Sour

bool AsmAnalyzer::validateInstructions(std::string const& _instructionIdentifier, langutil::SourceLocation const& _location)
{
// NOTE: This function uses the default EVM version instead of the currently selected one.
auto const builtin = EVMDialect::strictAssemblyForEVM(EVMVersion{}).builtin(YulString(_instructionIdentifier));
if (builtin && builtin->instruction.has_value())
return validateInstructions(builtin->instruction.value(), _location);
Expand Down Expand Up @@ -705,6 +706,9 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
);
};

// The errors below are meant to be issued when processing an undeclared identifier matching a builtin name
// present on the default EVM version but not on the currently selected one,
// since the other `validateInstructions()` overload uses the default EVM version.
if (_instr == evmasm::Instruction::RETURNDATACOPY && !m_evmVersion.supportsReturndata())
errorForVM(7756_error, "only available for Byzantium-compatible");
else if (_instr == evmasm::Instruction::RETURNDATASIZE && !m_evmVersion.supportsReturndata())
Expand All @@ -729,6 +733,9 @@ bool AsmAnalyzer::validateInstructions(evmasm::Instruction _instr, SourceLocatio
errorForVM(5430_error, "only available for London-compatible");
else if (_instr == evmasm::Instruction::BLOBBASEFEE && !m_evmVersion.hasBlobBaseFee())
errorForVM(6679_error, "only available for Cancun-compatible");
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::PC)
m_errorReporter.error(
2450_error,
Expand Down
12 changes: 10 additions & 2 deletions libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,22 @@ std::set<YulString> createReservedIdentifiers(langutil::EVMVersion _evmVersion)
return _instrName == "prevrandao" && _evmVersion < langutil::EVMVersion::paris();
};

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

std::set<YulString> reserved;
for (auto const& instr: evmasm::c_instructions)
{
std::string name = toLower(instr.first);
if (
!baseFeeException(instr.second) &&
!blobBaseFeeException(instr.second) &&
!prevRandaoException(name)
!prevRandaoException(name) &&
!blobHashException(instr.second) &&
!blobBaseFeeException(instr.second)
)
reserved.emplace(name);
}
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 @@ -126,6 +126,8 @@ done < <(
# 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' |
# Skipping a test with "let blobhash := ..."
grep -v -E 'inlineAssembly/blobhash_pre_cancun.sol' |
# Skipping license error, unrelated to the grammar
grep -v -E 'license/license_double5.sol' |
grep -v -E 'license/license_hidden_unicode.sol' |
Expand Down
7 changes: 7 additions & 0 deletions test/EVMHost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::VM& _vm):
// The minimum value of blobbasefee
tx_context.blob_base_fee = evmc::bytes32{1};

static evmc_bytes32 const blob_hashes_array[] = {
0x0100000000000000000000000000000000000000000000000000000000000001_bytes32,
0x0100000000000000000000000000000000000000000000000000000000000002_bytes32
};
tx_context.blob_hashes = blob_hashes_array;
tx_context.blob_hashes_count = sizeof(blob_hashes_array) / sizeof(blob_hashes_array[0]);

// Reserve space for recording calls.
if (!recorded_calls.capacity())
recorded_calls.reserve(max_recorded_calls);
Expand Down
11 changes: 11 additions & 0 deletions test/libsolidity/semanticTests/inlineAssembly/blobhash.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
contract C {
function f() public view returns (bytes32 ret) {
assembly {
ret := blobhash(0)
}
}
}
// ====
// EVMVersion: >=cancun
// ----
// f() -> 0x0100000000000000000000000000000000000000000000000000000000000001
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
contract C {
function f() public view returns (bytes32 ret) {
assembly {
// EIP-4844 specifies that if `index < len(tx.blob_versioned_hashes)`, `blobhash(index)` should return 0.
// Thus, as we injected only two blob hashes in the transaction context in EVMHost,
// the return value of the function below MUST be zero.
ret := blobhash(2)
}
}
}
// ====
// EVMVersion: >=cancun
// ----
// f() -> 0x00
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
contract C {
function f() public pure returns (uint ret) {
assembly {
let blobhash := 1
ret := blobhash
}
}
function g() public pure returns (uint ret) {
assembly {
function blobhash() -> r {
r := 1000
}
ret := blobhash()
}
}
}
// ====
// EVMVersion: <=shanghai
// ----
// f() -> 1
// g() -> 1000
10 changes: 10 additions & 0 deletions test/libsolidity/syntaxTests/inlineAssembly/blobhash.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
contract C {
function f() public view returns (bytes32 ret) {
assembly {
ret := blobhash(1)
}
}
}
// ====
// EVMVersion: >=cancun
// ----
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
contract C {
function f() pure external returns (bytes32 ret) {
assembly {
ret := blobhash()
}
}
}
// ====
// EVMVersion: <=shanghai
// ----
// DeclarationError 4619: (106-114): Function "blobhash" not found.
// DeclarationError 8678: (99-116): Variable count for assignment to "ret" does not match number of values (1 vs. 0)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
contract C {
function f() public pure returns (uint ret) {
assembly {
function blobhash() -> r {
r := 1000
}
ret := blobhash()
}
}
}
// ====
// EVMVersion: >=cancun
// ----
// ParserError 5568: (103-111): Cannot use builtin function name "blobhash" as identifier name.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
contract C {
function f() public pure {
assembly { pop(blobhash(0)) }
}
}
// ====
// EVMVersion: >=cancun
// ----
// TypeError 2527: (67-78): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
contract C {
function f() public view {
assembly {
pop(blobhash(0))
pop(blobbasefee())
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
contract C {
function f() public pure {
assembly {
pop(blobhash(0))
pop(blobbasefee())
}
}
}
// ====
// EVMVersion: >=cancun
// ----
// TypeError 2527: (79-92): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".
// TypeError 2527: (79-90): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".
// TypeError 2527: (108-121): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view".
13 changes: 13 additions & 0 deletions test/libyul/yulInterpreterTests/blobhash.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
sstore(0, blobhash(0))
sstore(1, blobhash(1))
sstore(2, blobhash(2)) // should store 0 since EVMHost has only two blob hashes injected in the block the transaction is being executed.
}
// ====
// EVMVersion: >=cancun
// ----
// Trace:
// Memory dump:
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000000: 014916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5
// 0000000000000000000000000000000000000000000000000000000000000001: 0167d3dbed802941483f1afa2a6bc68de5f653128aca9bf1461c5d0a3ad36ed2
15 changes: 15 additions & 0 deletions test/libyul/yulSyntaxTests/blobhash.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
{
let blobhash := 1
}

{
function blobhash() {}
blobhash()
}
}

// ====
// EVMVersion: >=cancun
// ----
// ParserError 5568: (20-28): Cannot use builtin function name "blobhash" as identifier name.
13 changes: 13 additions & 0 deletions test/libyul/yulSyntaxTests/blobhash_pre_cancun.yul
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
{
let blobhash := 1
}

{
function blobhash() {}
blobhash()
}
}
// ====
// EVMVersion: <=shanghai
// ----
16 changes: 16 additions & 0 deletions test/tools/yulInterpreter/EVMInstructionInterpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include <libsolutil/Keccak256.h>
#include <libsolutil/Numeric.h>
#include <libsolutil/picosha2.h>

#include <limits>

Expand Down Expand Up @@ -233,6 +234,8 @@ u256 EVMInstructionInterpreter::eval(
return m_state.chainid;
case Instruction::BASEFEE:
return m_state.basefee;
case Instruction::BLOBHASH:
return blobHash(arg[0]);
case Instruction::BLOBBASEFEE:
return m_state.blobbasefee;
case Instruction::EXTCODESIZE:
Expand Down Expand Up @@ -649,3 +652,16 @@ std::pair<bool, size_t> EVMInstructionInterpreter::isInputMemoryPtrModified(
else
return {false, 0};
}

h256 EVMInstructionInterpreter::blobHash(u256 const& _index)
{
yulAssert(m_evmVersion.hasBlobHash());
if (_index >= m_state.blobCommitments.size())
return util::FixedHash<32>{};

h256 hashedCommitment = h256(picosha2::hash256(toBigEndian(m_state.blobCommitments[static_cast<size_t>(_index)])));
yulAssert(m_state.blobHashVersion.size == 1);
hashedCommitment[0] = *m_state.blobHashVersion.data();
yulAssert(hashedCommitment.size == 32);
return hashedCommitment;
}
4 changes: 4 additions & 0 deletions test/tools/yulInterpreter/EVMInstructionInterpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <libyul/ASTForward.h>

#include <libsolutil/CommonData.h>
#include <libsolutil/FixedHash.h>
#include <libsolutil/Numeric.h>

#include <liblangutil/EVMVersion.h>
Expand Down Expand Up @@ -90,6 +91,9 @@ class EVMInstructionInterpreter
std::vector<u256> const& _evaluatedArguments
);

/// @returns the blob versioned hash
util::h256 blobHash(u256 const& _index);

private:
/// Checks if the memory access is valid and adjusts msize accordingly.
/// @returns true if memory access is valid, false otherwise
Expand Down
5 changes: 5 additions & 0 deletions test/tools/yulInterpreter/Interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ struct InterpreterState
/// Number of the current state instance, used for recursion protection
size_t numInstance = 0;

// Blob commitment hash version
util::FixedHash<1> const blobHashVersion = util::FixedHash<1>(1);
// Blob commitments
std::array<u256, 2> const blobCommitments = {0x01, 0x02};

/// Prints execution trace and non-zero storage to @param _out.
/// Flag @param _disableMemoryTrace, if set, does not produce a memory dump. This
/// avoids false positives reports by the fuzzer when certain optimizer steps are
Expand Down

0 comments on commit 269951e

Please sign in to comment.