在 Ethereum 虛擬機(EVM) 中,JUMPI 是一個條件跳躍指令。它從堆疊(Stack)中彈出兩個元素:目的地(dest)和條件(cond)。如果 cond 不是 0,則程序計數器(pc) 被設置為 dest,代碼跳轉到 dest 位置並開始執行;如果 cond 是 0,則繼續執行下一個指令。在跳轉目的地,JUMPDEST 操作碼通常會被用來標記一個有效的跳轉位置。
(op, dest, cond) = (87, 4, cond): 這是一個操作的示例,其中 87 是 JUMPI 操作的操作碼,4 是跳躍的目的地,cond 是壓縮形式的條件值。q, a_1 到 a_32 表示的是約束和操作的不同變數和狀態。JUMPI (op - 87 為 0) 時,自定義約束才被激活。bus_mapping_lookup 函數讀取堆疊中的 dest 和 cond 值。pc_next 的值取決於 cond 是否為 0,並可能設置為 dest 或 pc + 1。這部分定義了在不同的執行情況(例如成功、out_of_gas錯誤、stack_underflow錯誤、無效或超出範圍的跳轉目的地)下應該應用的約束和操作。
JUMPI並且操作成功時,進行兩個讀取操作(一個用於目的地,另一個用於條件)。如果條件為非0,程式計數器跳到指定的目的地,否則增加1。堆疊指針增加2,因為兩個值已從堆疊中彈出。TODO標記),但它們通常會包括檢查某些條件(例如:gas用量、堆疊大小和跳轉目的地的有效性)並可能進行一些清理或回滾操作。JUMPI 指令確實涉及到複雜的檢查和條件分支,並且在處理合約時需要仔細處理和驗證,以確保跳轉的合法性和安全性。// switch on custom constraint only when the opcode is JUMPI
if is_zero(op - 87) {
    if case_success {
        // one stack read for destination
        bus_mapping_lookup(
            gc,
            Stack,
            sp,
            compress(dest),
            Read
        )
        // one stack read for condition
        bus_mapping_lookup(
            gc+1,
            Stack,
            sp+1,
            cond,
            Read
        )
        // we don't jump when condition is zero
        if is_zero(cond) {
            pc_next === pc + 1 // pc should increase by 1
        } else {
            pc_next === dest // pc should change to dest
            op_next === 91   // destination should be JUMPDEST
        }
        gc_next === gc + 1 // gc should increase by 1
        addr_next === addr // addr remains the same
        sp_next === sp + 2 // sp should increase by 2 (JUMPI = 2 POP)
    }
    if case_err_out_of_gas {
        // TODO:
        // - gas_left > gas_cost
        // - consume all give gas
        // - context switch to parent
        // - ...
    }
    if case_err_stack_underflow {
        // TODO:
        // - sp + 1 === 1023 + surfeit
        // - consume all give gas
        // - context switch to parent
        // - ...
    }}
    if case_err_jump_dest_invalid {
        // TODO:
        // - op_lookup(addr, dest, dest_op) && dest_op !== JUMPDEST
        // - consume all give gas
        // - context switch to parent
        // - ...
    }
    if case_err_jump_dest_out_of_range {
        // TODO:
        // - dest >= len(code)
        // - consume all give gas
        // - context switch to parent
        // - ...
    }
}
Python
def jumpi(instruction: Instruction):
    opcode = instruction.opcode_lookup(True)
    instruction.constrain_equal(opcode, Opcode.JUMPI)
    # Do not check 'dest' is within MaxCodeSize(24576) range in successful case
    # as byte code lookup can ensure it.
    dest_word = instruction.stack_pop()
    instruction.constrain_zero(dest_word.hi.expr())
    dest = dest_word.lo.expr()
    cond = instruction.stack_pop()
    # check `cond` is zero or not
    if instruction.is_zero_word(cond):
        pc_diff = FQ(1)
    else:
        pc_diff = dest - instruction.curr.program_counter
        # assert Opcode.JUMPDEST == instruction.opcode_lookup_at(dest_value, True)
        instruction.constrain_equal(Opcode.JUMPDEST, instruction.opcode_lookup_at(dest, True))
    instruction.step_state_transition_in_same_context(
        opcode,
        rw_counter=Transition.delta(2),
        program_counter=Transition.delta(pc_diff),
        stack_pointer=Transition.delta(2),
    )