changed 3 years ago
Linked with GitHub

ZKEVM - Random Thoughts

EVM Caveat

Memory Expansion

There are several opcodes that expand memory:

opcode off1 len1 off2 len2 Pure Memory Gas Cost
32 SHA3 stack[sp] stack[sp+1]
55 CALLDATACOPY stack[sp] stack[sp+2]
57 CODECOPY stack[sp] stack[sp+2]
60 EXTCODECOPY stack[sp+1] stack[sp+3]
62 RETURNDATACOPY stack[sp] stack[sp+2]
81 MLOAD stack[sp] 32 Yes
82 MSTORE stack[sp] 32 Yes
83 MSTORE8 stack[sp] 1 Yes
160 LOG0 stack[sp] stack[sp+1]
161 LOG1 stack[sp] stack[sp+1]
162 LOG2 stack[sp] stack[sp+1]
163 LOG3 stack[sp] stack[sp+1]
164 LOG4 stack[sp] stack[sp+1]
240 CREATE stack[sp+1] stack[sp+2] Yes
241 CALL stack[sp+3] stack[sp+4] stack[sp+5] stack[sp+6]
242 CALLCODE stack[sp+3] stack[sp+4] stack[sp+5] stack[sp+6]
243 RETURN stack[sp] stack[sp+1] Yes
244 DELEGATECALL stack[sp+2] stack[sp+3] stack[sp+4] stack[sp+5]
245 CREATE2 stack[sp+1] stack[sp+2]
250 STATICCALL stack[sp+2] stack[sp+3] stack[sp+4] stack[sp+5]
253 REVERT stack[sp] stack[sp+1] Yes

The psuedo code of memory expansion:

from math import ceil

MEMORY_SIZE_MAX = 32 * (2**32 - 1) # 0x1FFFFFFFE0

# returns (new_memory_size, is_overflow)
def expand_memory(memory_size: int, off1: int, len1: int, off2: int, len2: int) -> int, bool:
    def m(off: int, len: int) -> int, bool:
        if len is None or len == 0:
            return 0, False

        new_memory_size = 32 * ceil(off + len / 32)

        if new_memory_size > MEMORY_SIZE_MAX:
            return 0, True

        return new_memory_size, False

    new_memory_size1, is_overflow1 = m(off1, len1)
    new_memory_size2, is_overflow2 = m(off2, len2)

    if is_overflow1 or is_overflow2:
        return 0, True

    return max(memory_size, new_memory_size1, new_memory_size2), False

Ideally we can verify the OOG caused only by memory expansion in a single branch, by lookup the offset and length in stack position with the opcode, and verify the new memory size indeed exceeds the max value.

Access List (EIP2929)

To support EIP2929, we add 2 extra revertable targets tx_access_list_account and tx_access_list_storage_slot in State circuit to maintain, their value is constraint to only be 0 or 1, and lazily initialied to be 0. Then in EVM circuit, we will always write it to 1, and see the different between current and previous value to see if it's a warm or cold access.

Precompiles

address name execution result gas cost
0x01 ECRecover
  • ECRECOVER
  • ERROR_OOG_CONSTANT
3000
0x02 Sha256
  • SHA256
  • ERROR_OOG_SHA256
60 + 12*ceil(len(input)/32)
0x03 Ripemd160
  • RIPEMD160
  • ERROR_OOG_RIPEMD160
600 + 120*ceil(len(input)/32)
0x04 DataCopy
  • DATA_COPY
  • ERROR_OOG_DATA_COPY
15 + 3*ceil(len(input)/32)
0x05 BigModExp
  • BIG_MOD_EXP
  • ERROR_OOG_BIG_MOD_EXP
see EIP2565
0x06 BN254Add
  • BN254_ADD
  • ERROR_OOG_CONSTANT
  • ERROR_INVALID_INPUT_BN254_ADD (Invalid G1)
150
0x07 BN254Mul
  • BN254_MUL
  • ERROR_OOG_CONSTANT
  • ERROR_INVALID_INPUT_BN254_MUL (Invalid G1)
6000
0x08 BN254Pairing
  • BN254_PAIRING
  • ERROR_OOG_BN254_PAIRING
  • ERROR_INVALID_INPUT_BN254_PAIRING (Invalid length, G1, G2)
45000 + 34000*(len(input)/192)
0x09 Blake2F
  • BLAKE2F
  • ERROR_OOG_BLAKE2F
  • ERROR_INVALID_INPUT_BLAKE2F (Invalid length)
be_to_int(input[:4])

Note: If we are reading returndata directly from last callee's memory, we need to be careful when handling precompile 0x04 DataCopy since the last callee would be caller itself. We should explicitly copy calldata to precompile's memory and treat it as a call.

Possible Upgrade of EVM

SELFDESTRUCT Neutralization

The SELFDESTRUCT opcode is renamed to SENDALL, and now only immediately moves all ETH in the account to the target; it no longer destroys code or storage or alters the nonce
quotes from <a href="https://notes.ethereum.org/-fJSOrnYQl-mqoWKpaTIsQ#Miscellaneous">Statelessness gas cost changes EIP draft #Miscellaneous</a>

If so, we can get rid of a bunch of consideration of the destroy of code and storage.

Reference

EOF - EVM Object Format

After EIP3670 - EOF - Code Validation:

  • Create transaction, CREATE* becomes more complicated, we need to verify creation code and also returned runtime code.
  • Opcode lookup becomes easier since no any implict behavior (implict PUSH* data padding, implicit STOP in the end).

After EIP4200 - Static relative jumps:

  • JUMP* becomes easier since no any invalid jump at runtime.

Reference

Statelessness

  • Merkle (Hexary) Patricia Trie becomes Verkle Trie
  • Trie-access-aware gas cost calculation
  • Chunkified contract bytecode

Reference

EIP4488 - Transaction calldata gas cost reduction with total calldata limit

EIP4488 specification:

Parameter Value
NEW_CALLDATA_GAS_COST 3
BASE_MAX_CALLDATA_PER_BLOCK 1,048,576
CALLDATA_PER_TX_STIPEND 300

Reduce the gas cost of transaction calldata to NEW_CALLDATA_GAS_COST per byte, regardless of whether the byte is zero or nonzero.

Add a rule that a block is only valid if sum(len(tx.calldata) for tx in block.txs) <= BASE_MAX_CALLDATA_PER_BLOCK + len(block.txs) * CALLDATA_PER_TX_STIPEND.

Misc

Select a repo