Extensions
EraVM extensions are a set of additional instructions that can be expressed in Solidity and Yul, that can only be compiled to EraVM bytecode.
There are two ways of using EraVM extensions with zksolc:
- Call simulations in Solidity.
verbatim
function in Yul mode.
Call simulations
Since zksolc could only operate on Yul received from solc, it was not possible to add EraVM-specific functionality to Solidity and Yul. Instead, zksolc introduced a hack with external call instructions that would be replaced with EraVM-specific instructions during emitting LLVM IR. In such external call instructions, the address argument denotes the instruction type, whereas the rest of the arguments are used as instruction arguments.
Call simulations are the only way to use EraVM extensions in Solidity.
verbatim
In Yul mode, there is a special instruction called verbatim
that allows emitting EraVM-specific instructions directly from Yul. This instruction is more robust than call simulations, as it allows passing more arguments to the instruction, and it is not affected by the solc's optimizer. Unfortunately, verbatim
is only available in Yul mode and cannot be used in Solidity.
It is recommended to only use verbatim
in Yul mode, as it is more robust and less error-prone than call simulations in Solidity.
Call Types
In addition to EVM-like call
, staticcall
and delegatecall
, EraVM introduces a few more call types:
- Mimic call
- System call
- Raw call
Each of the call types above has its by-ref modification, which allows passing pointers to ABI data instead of data itself.
Mimic Call
Mimic call is a call type that allows the caller to execute a call to a contract, but with the ability to specify the address of the contract that will be used as the caller. This is useful for EraVM System Contracts that need to call other contracts on behalf of the user. Essentially, it is a more complete version of DELEGATECALL
.
For a deeper dive into the Mimic Call, visit the EraVM formal specification.
System Call
System call allows passing more arguments to the callee contract using EraVM registers. This is useful for System Contracts that often require auxiliary data that cannot be passed via calldata.
There are also system mimic calls, which are a combination of both, that is auxiliary arguments can be passed via EraVM registers.
Raw Call
Raw calls are similar to EVM's CALL
, STATICCALL
, and DELEGATECALL
, but they do not encode the ABI data. Instead, the ABI data is passed as an argument to the instruction. This is useful for EraVM System Contracts that need to call other contracts with a specific ABI data that cannot be encoded in the calldata.
Active Pointers
Active pointers are a set of calldata and return data pointers stored in global LLVM IR variables. They are not accessible directly from Yul, but they can be used to forward call and return data between contracts.
The number of active pointers is fixed at 10, and they are numbered from 0 to 9. Some instructions can only use the 0th pointer due to the lack of spare arguments to specify the pointer number. In order to use pointers other than the 0th, use the swap instruction.
Instructions that use active pointers have a reference to this section.
Constant Arrays
Constant arrays are a set of global arrays that can be used to store constant values. They are not accessible directly from Yul, but they can be used to store constant values that are used in multiple places in the contract.
Instruction Reference
The sections below have the following structure:
- EraVM instruction name and substituted address.
- Instruction description.
- Pseudo-code illustrating the behavior under the hood.
- Solidity call simulation usage example.
- Yul
verbatim
usage example.
For instance:
Example (0xXXXX)
Executes an EraVM instruction.
Pseudo-code:
return_value = instruction(arg1, arg2, arg3)
Solidity usage:
assembly {
let return_value := call(arg1, 0xXXXX, arg2, arg3, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let return_value := verbatim_3i_1o("instruction", arg1, arg2, arg3)
}
Full list of instructions:
Notes:
- The
input_length
parameter is always set to 0xFFFF or non-zero value. It prevents the solc's optimizer from optimizing the call out. - Instructions that do not modify state are using
staticcall
instead ofcall
. - Instructions such as raw calls preserve the call type, so they act as modifiers of
call
,staticcall
, anddelegatecall
.
To L1 (0xFFFF)
Send a message to L1.
Pseudo-code:
to_l1(is_first, value_1, value_2)
Solidity usage:
assembly {
let _ := call(is_first, 0xFFFF, value_1, value_2, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_3i_0o("to_l1", is_first, value_1, value_2)
}
Precompile (0xFFFD)
Calls an EraVM precompile.
Pseudo-code:
return_value = precompile(input_data, ergs)
Solidity usage:
assembly {
let return_value := staticcall(input_data, 0xFFFD, ergs, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let return_value := verbatim_2i_01("precompile", input_data, ergs)
}
Decommit (0xFFDD)
Calls the EraVM decommit.
Pseudo-code:
return_value = decommit(versioned_hash, ergs)
Solidity usage:
assembly {
let return_value := staticcall(versioned_hash, 0xFFDD, ergs, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let return_value := verbatim_2i_01("decommit", input_data, ergs)
}
Set Context Value (0xFFF3)
Sets the 128-bit context value. Usually the value is used to pass Ether to the callee contract.
Pseudo-code:
set_context_value(value)
Solidity usage:
assembly {
let _ := call(0, 0xFFF3, value, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_1i_0o("set_context_u128", value)
}
Set Pubdata Price (0xFFF2)
Sets the public data price.
Pseudo-code:
set_pubdata_price(value)
Solidity usage:
assembly {
let _ := call(value, 0xFFF2, 0, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_1i_0o("set_pubdata_price", value)
}
Increment TX Counter (0xFFF1)
Increments the EraVM transaction counter.
Pseudo-code:
increment_tx_counter()
Solidity usage:
assembly {
let _ := call(0, 0xFFF1, 0, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_0i_0o("increment_tx_counter")
}
Code Source (0xFFFE)
Returns the address where the contract is actually deployed, even if it is called with a delegate call. Mostly used in EraVM System Contracts.
Pseudo-code:
code_source = code_source()
Solidity usage:
assembly {
let code_source := staticcall(0, 0xFFFE, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let code_source := verbatim_0i_1o("code_source")
}
Meta (0xFFFC)
Returns a part of the internal EraVM state.
Pseudo-code:
meta = meta()
Solidity usage:
assembly {
let meta := staticcall(0, 0xFFFC, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let meta := verbatim_0i_1o("meta")
}
Get Calldata Pointer (0xFFF0)
Returns the ABI-encoded calldata pointer as integer.
Pseudo-code:
pointer = get_calldata_pointer()
Solidity usage:
assembly {
let pointer := staticcall(0, 0xFFF0, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let pointer := verbatim_0i_1o("get_global::ptr_calldata")
}
Get Call Flags (0xFFEF)
Returns the call flags encoded as 256-bit integer.
Pseudo-code:
flags = get_call_flags()
Solidity usage:
assembly {
let flags := staticcall(0, 0xFFEF, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let flags := verbatim_0i_1o("get_global::call_flags")
}
Get Return Data Pointer (0xFFEE)
Returns the ABI-encoded return data pointer as integer.
Pseudo-code:
pointer = get_return_data_pointer()
Solidity usage:
assembly {
let pointer := staticcall(0, 0xFFEE, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let pointer := verbatim_0i_1o("get_global::ptr_return_data")
}
Get Extra ABI Data (0xFFE5)
Returns the N-th extra ABI data value passed via registers r3
-r12
.
Pseudo-code:
value = get_extra_abi_data(index)
Solidity usage:
assembly {
let value := staticcall(index, 0xFFE5, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let value := verbatim_0i_1o("get_global::extra_abi_data_0")
let value := verbatim_0i_1o("get_global::extra_abi_data_1")
...
let value := verbatim_0i_1o("get_global::extra_abi_data_9")
}
Multiplication with Overflow (0xFFE6)
Performs a multiplication with overflow, returning the higher register.
Pseudo-code:
higher_register = mul_high(a, b)
Solidity usage:
assembly {
let higher_register := staticcall(a, 0xFFE6, b, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let higher_register := verbatim_2i_1o("mul_high", a, b)
}
Event Initialize (0xFFED)
Initializes a new EVM-like event.
Pseudo-code:
event_initialize(value_1, value_2)
Solidity usage:
assembly {
let _ := call(value_1, 0xFFED, value_2, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_2i_0o("event_initialize", value_1, value_2)
}
Event Write (0xFFEC)
Writes more data to the previously initialized EVM-like event.
Pseudo-code:
event_write(value_1, value_2)
Solidity usage:
assembly {
let _ := call(value_1, 0xFFEC, value_2, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_2i_0o("event_write", value_1, value_2)
}
Mimic Call (0xFFFB)
Executes an EraVM mimic call.
Pseudo-code:
status = mimic_call(callee_address, mimic_address, abi_data)
Solidity usage:
assembly {
let status := call(callee_address, 0xFFFB, 0, abi_data, mimic_address, 0, 0)
}
Yul usage:
assembly {
let status := verbatim_3i_1o("mimic_call", callee_address, mimic_address, abi_data)
}
Mimic Call by Reference (0xFFF9)
Executes an EraVM mimic call, passing the 0th active pointer instead of ABI data.
Pseudo-code:
status = mimic_call_byref(callee_address, mimic_address)
Solidity usage:
assembly {
let status := call(callee_address, 0xFFF9, 0, 0, mimic_address, 0, 0)
}
Yul usage:
assembly {
let status := verbatim_2i_1o("mimic_call_byref", callee_address, mimic_address)
}
System Mimic Call (0xFFFA)
Executes an EraVM mimic call with additional arguments for System Contracts.
Pseudo-code:
status = system_mimic_call(callee_address, mimic_address, abi_data, r3_value, r4_value, [r5_value, r6_value])
Solidity usage:
assembly {
let status := call(callee_address, 0xFFFA, 0, abi_data, mimic_address, r3_value, r4_value)
}
Yul usage:
assembly {
let status := verbatim_5i_1o("system_mimic_call", callee_address, mimic_address, abi_data, r3_value, r4_value, r5_value, r6_value)
}
Yul's
verbatim
allows passing two more extra arguments as it is no limited by the semantics of thecall
instruction.
System Mimic Call by Reference (0xFFF8)
Executes an EraVM mimic call with additional arguments for System Contracts, passing the 0th active pointer instead of ABI data.
Pseudo-code:
status = system_mimic_call_byref(callee_address, mimic_address, r3_value, r4_value, [r5_value, r6_value])
Solidity usage:
assembly {
let status := call(callee_address, 0xFFF8, 0, 0, mimic_address, r3_value, r4_value)
}
Yul usage:
assembly {
let status := verbatim_4i_1o("system_mimic_call_byref", callee_address, mimic_address, r3_value, r4_value, r5_value, r6_value)
}
Yul's
verbatim
allows passing two more extra arguments as it is no limited by the semantics of thecall
instruction.
Raw Call (0xFFF7)
Executes an EraVM raw call.
Pseudo-code:
status = raw_call(callee_address, abi_data, output_offset, output_length)
Solidity usage:
assembly {
let status := call(callee_address, 0xFFF7, 0, 0, abi_data, output_offset, output_length)
let status := staticcall(callee_address, 0xFFF7, 0, abi_data, output_offset, output_length)
let status := delegatecall(callee_address, 0xFFF7, 0, abi_data, output_offset, output_length)
}
Yul usage:
assembly {
let status := verbatim_4i_1o("raw_call", callee_address, abi_data, output_offset, output_length)
let status := verbatim_4i_1o("raw_static_call", callee_address, abi_data, output_offset, output_length)
let status := verbatim_4i_1o("raw_delegate_call", callee_address, abi_data, output_offset, output_length)
}
Raw Call by Reference (0xFFF6)
Executes an EraVM raw call, passing the 0th active pointer instead of ABI data.
Pseudo-code:
status = raw_call_byref(callee_address, output_offset, output_length)
Solidity usage:
assembly {
let status := call(callee_address, 0xFFF7, 0, 0, 0, output_offset, output_length)
let status := staticcall(callee_address, 0xFFF7, 0, 0, output_offset, output_length)
let status := delegatecall(callee_address, 0xFFF7, 0, 0, output_offset, output_length)
}
Yul usage:
assembly {
let status := verbatim_3i_1o("raw_call_byref", callee_address, output_offset, output_length)
let status := verbatim_3i_1o("raw_static_call_byref", callee_address, output_offset, output_length)
let status := verbatim_3i_1o("raw_delegate_call_byref", callee_address, output_offset, output_length)
}
System Call (0xFFF5)
Executes an EraVM system call.
Pseudo-code:
status = system_call(callee_address, r3_value, r4_value, abi_data, r5_value, r6_value)
Solidity usage:
assembly {
let status := call(callee_address, 0xFFF5, r3_value, r4_value, abi_data, r5_value, r6_value)
}
Yul usage:
assembly {
let status := verbatim_6i_1o("system_call", callee_address, abi_data, r3_value, r4_value, r5_value, r6_value)
let status := verbatim_6i_1o("system_static_call", callee_address, abi_data, r3_value, r4_value, r5_value, r6_value)
let status := verbatim_6i_1o("system_delegate_call", callee_address, abi_data, r3_value, r4_value, r5_value, r6_value)
}
Static and delegate system calls are only available in Yul as
verbatim
.
System Call by Reference (0xFFF4)
Executes an EraVM system call, passing the 0th active pointer instead of ABI data.
Pseudo-code:
status = system_call_byref(callee_address, r3_value, r4_value, r5_value, r6_value)
Solidity usage:
assembly {
let status := call(callee_address, 0xFFF4, r3_value, r4_value, 0xFFFF, r5_value, r6_value)
}
Yul usage:
assembly {
let status := verbatim_5i_1o("system_call_byref", callee_address, r3_value, r4_value, r5_value, r6_value)
let status := verbatim_5i_1o("system_static_call_byref", callee_address, r3_value, r4_value, r5_value, r6_value)
let status := verbatim_5i_1o("system_delegate_call_byref", callee_address, r3_value, r4_value, r5_value, r6_value)
}
Static and delegate system calls are only available in Yul as
verbatim
.
Active Pointer: Load Calldata (0xFFEB)
Loads the calldata pointer to the 0th active pointer.
Pseudo-code:
active_ptr_load_calldata()
Solidity usage:
assembly {
let _ := staticcall(0, 0xFFEB, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_0i_0o("calldata_ptr_to_active")
}
Active Pointer: Load Return Data (0xFFEA)
Loads the return data pointer to the 0th active pointer.
Pseudo-code:
active_ptr_load_return_data()
Solidity usage:
assembly {
let _ := staticcall(0, 0xFFEA, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_0i_0o("return_data_ptr_to_active")
}
Active Pointer: Load Decommit (0xFFDC)
Loads the decommit pointer to the 0th active pointer.
Pseudo-code:
active_ptr_load_decommit()
Solidity usage:
assembly {
let _ := staticcall(0, 0xFFDC, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_0i_0o("decommit_ptr_to_active")
}
Active Pointer: Increment (0xFFE9)
Increments the offset of the 0th active pointer.
Pseudo-code:
active_ptr_add(value)
Solidity usage:
assembly {
let _ := staticcall(value, 0xFFE9, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_1i_0o("active_ptr_add_assign", value)
}
Active Pointer: Shrink (0xFFE8)
Decrements the slice length of the 0th active pointer.
Pseudo-code:
active_ptr_shrink(value)
Solidity usage:
assembly {
let _ := staticcall(value, 0xFFE8, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_1i_0o("active_ptr_shrink_assign", value)
}
Active Pointer: Pack (0xFFE7)
Writes the upper 128 bits to the 0th active pointer.
Pseudo-code:
active_ptr_pack(value)
Solidity usage:
assembly {
let _ := staticcall(value, 0xFFE7, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_1i_0o("active_ptr_pack_assign", value)
}
Active Pointer: Load (0xFFE4)
Loads a value from the 0th active pointer at the specified offset, similarly to EVM's CALLDATALOAD
.
Pseudo-code:
value = active_ptr_load(offset)
Solidity usage:
assembly {
let value := staticcall(offset, 0xFFE4, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let value := verbatim_1i_1o("active_ptr_data_load", offset)
}
Active Pointer: Copy (0xFFE3)
Copies a slice from the the 0th active pointer to the heap, similarly to EVM's CALLDATACOPY
and RETURNDATACOPY
.
Pseudo-code:
active_ptr_copy(destination, source, size)
Solidity usage:
assembly {
let _ := staticcall(destination, 0xFFE3, source, 0xFFFF, size, 0)
}
Yul usage:
assembly {
let _ := verbatim_3i_0o("active_ptr_data_copy", destination, source, size)
}
Active Pointer: Size (0xFFE2)
Returns the length of the slice referenced by the 0th active pointer, similarly to EVM's CALLDATASIZE
and RETURNDATASIZE
.
Pseudo-code:
size = active_ptr_size()
Solidity usage:
assembly {
let size := staticcall(0, 0xFFE2, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let size := verbatim_0i_1o("active_ptr_data_size")
}
Active Pointer: Swap (0xFFD9)
Swaps the Nth and Mth active pointers. Swapping allows the active pointer instructions to use pointers other than the 0th.
Pseudo-code:
active_ptr_swap(N, M)
Solidity usage:
assembly {
let _ := staticcall(N, 0xFFD9, M, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_2i_0o("active_ptr_swap", N, M)
}
Active Pointer: Return (0xFFDB)
Returns from the contract, using the 0th active pointer as the return data.
Pseudo-code:
active_ptr_return()
Solidity usage:
assembly {
let _ := staticcall(0, 0xFFDB, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_0i_0o("active_ptr_return_forward")
}
Active Pointer: Revert (0xFFDA)
Reverts from the contract, using the 0th active pointer as the return data.
Pseudo-code:
active_ptr_revert()
Solidity usage:
assembly {
let _ := staticcall(0, 0xFFDA, 0, 0xFFFF, 0, 0)
}
Yul usage:
assembly {
let _ := verbatim_0i_0o("active_ptr_revert_forward")
}
Constant Array: Declare (0xFFE1)
Declares a new global array of constants. After the array is declared, it must be right away filled with values using the set instruction and declared final using the finalization instruction.
Index must be an 8-bit constant value in the range [0; 255]
.
Size must be a 16-bit constant value in the range [0; 65535]
.
Pseudo-code:
const_array_declare(index, size)
Solidity usage:
assembly {
let _ := staticcall(index, 0xFFE1, size, 0xFFFF, 0, 0)
}
This instruction is not available in Yul as
verbatim
.
Constant Array: Set (0xFFE0)
Sets a value in a global array of constants.
Index must be an 8-bit constant value in the range [0; 255]
.
Size must be a 16-bit constant value in the range [0; 65535]
.
Value must be a 256-bit constant value.
Pseudo-code:
const_array_set(index, size, value)
Solidity usage:
assembly {
let _ := staticcall(index, 0xFFE0, size, 0xFFFF, value, 0)
}
This instruction is not available in Yul as
verbatim
.
Constant Array: Finalize (0xFFDF)
Finalizes a global array of constants.
Index must be an 8-bit constant value in the range [0; 255]
.
Pseudo-code:
const_array_finalize(index)
Solidity usage:
assembly {
let _ := staticcall(index, 0xFFDF, 0, 0xFFFF, 0, 0)
}
This instruction is not available in Yul as
verbatim
.
Constant Array: Get (0xFFDE)
Gets a value from a global array of constants.
Index must be an 8-bit constant value in the range [0; 255]
.
Offset must be a 16-bit constant value in the range [0; 65535]
.
Pseudo-code:
value = const_array_get(index, offset)
Solidity usage:
assembly {
let value := staticcall(index, 0xFFDE, offset, 0xFFFF, 0, 0)
}
This instruction is not available in Yul as
verbatim
.
Return Deployed (verbatim-only)
Returns heap data from the constructor.
Since EraVM constructors always return immutables via auxiliary heap, it is not possible to use them for EVM-like scenarios, such as EVM emulators.
Pseudo-code:
return_deployed(offset, length)
Yul usage:
assembly {
let _ := verbatim_2i_0o("return_deployed", offset, length)
}
Throw (verbatim-only)
Throws a function-level exception.
For a deeper dive into EraVM exceptions, see this page.
Pseudo-code:
throw()
Yul usage:
assembly {
let _ := verbatim_0i_0o("throw")
}