1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::{
    evm_circuit::{
        step::ExecutionState,
        util::{
            common_gadget::SameContextGadget,
            constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta},
            CachedRegion,
        },
        witness::{Block, Call, Chunk, ExecStep, Transaction},
    },
    table::BlockContextFieldTag,
    util::{
        word::{WordExpr, WordLoHiCell},
        Expr,
    },
};
use bus_mapping::evm::OpcodeId;
use eth_types::Field;
use halo2_proofs::plonk::Error;

use super::ExecutionGadget;

#[derive(Clone, Debug)]
pub(crate) struct BlockCtxGadget<F> {
    same_context: SameContextGadget<F>,
    value: WordLoHiCell<F>,
}

impl<F: Field> ExecutionGadget<F> for BlockCtxGadget<F> {
    const NAME: &'static str = "BlockCTX";

    const EXECUTION_STATE: ExecutionState = ExecutionState::BLOCKCTX;

    fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
        let value = cb.query_word_unchecked(); // block table lookup below

        cb.stack_push(value.to_word());

        // Get op's FieldTag
        let opcode = cb.query_cell();
        let blockctx_tag = BlockContextFieldTag::Coinbase.expr()
            + (opcode.expr() - OpcodeId::COINBASE.as_u64().expr());

        // Lookup block table with block context ops
        // TIMESTAMP/NUMBER/GASLIMIT, COINBASE and DIFFICULTY/BASEFEE
        cb.block_lookup(blockctx_tag, None, value.to_word());

        // State transition
        let step_state_transition = StepStateTransition {
            rw_counter: Delta(1.expr()),
            program_counter: Delta(1.expr()),
            stack_pointer: Delta((-1).expr()),
            gas_left: Delta(-OpcodeId::TIMESTAMP.constant_gas_cost().expr()),
            ..Default::default()
        };
        let same_context = SameContextGadget::construct(cb, opcode, step_state_transition);

        Self {
            same_context,
            value,
        }
    }

    fn assign_exec_step(
        &self,
        region: &mut CachedRegion<'_, '_, F>,
        offset: usize,
        block: &Block<F>,
        _chunk: &Chunk<F>,
        _: &Transaction,
        _: &Call,
        step: &ExecStep,
    ) -> Result<(), Error> {
        self.same_context.assign_exec_step(region, offset, step)?;

        let value = block.get_rws(step, 0).stack_value();

        self.value.assign_u256(region, offset, value)?;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use crate::test_util::CircuitTestBuilder;
    use eth_types::bytecode;
    use mock::TestContext;

    fn test_ok(bytecode: bytecode::Bytecode) {
        CircuitTestBuilder::new_from_test_ctx(
            TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(),
        )
        .run()
    }

    #[test]
    fn blockcxt_u64_gadget_test() {
        let bytecode = bytecode! {
            TIMESTAMP
            POP
            NUMBER
            POP
            GASLIMIT
            STOP
        };
        test_ok(bytecode);
    }
    #[test]
    fn blockcxt_u160_gadget_test() {
        let bytecode = bytecode! {
            COINBASE
            STOP
        };
        test_ok(bytecode);
    }

    #[test]
    fn blockcxt_u256_gadget_test() {
        let bytecode = bytecode! {
            DIFFICULTY
            POP
            BASEFEE
            STOP
        };
        test_ok(bytecode);
    }
}