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
use crate::{
    evm_circuit::{
        execution::ExecutionGadget,
        step::ExecutionState,
        table::{FixedTableTag, Lookup},
        util::{
            common_gadget::SameContextGadget,
            constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta},
            CachedRegion,
        },
        witness::{Block, Call, Chunk, ExecStep, Transaction},
    },
    util::{
        word::{Word32Cell, WordExpr},
        Expr,
    },
};
use eth_types::{evm_types::OpcodeId, Field};
use halo2_proofs::plonk::Error;

#[derive(Clone, Debug)]
pub(crate) struct NotGadget<F> {
    same_context: SameContextGadget<F>,
    input: Word32Cell<F>,
    output: Word32Cell<F>,
}

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

    const EXECUTION_STATE: ExecutionState = ExecutionState::NOT;

    fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
        let opcode = cb.query_cell();

        let input = cb.query_word32();
        let output = cb.query_word32();

        cb.stack_pop(input.to_word());
        cb.stack_push(output.to_word());

        for (i, o) in input.limbs.iter().zip(output.limbs.iter()) {
            cb.add_lookup(
                "input XOR output is all 1's",
                Lookup::Fixed {
                    tag: FixedTableTag::BitwiseXor.expr(),
                    values: [i.expr(), o.expr(), 255.expr()],
                },
            );
        }

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

        Self {
            same_context,
            input,
            output,
        }
    }

    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 [input, output] = [0, 1].map(|index| block.get_rws(step, index).stack_value());
        self.input.assign_u256(region, offset, input)?;
        self.output.assign_u256(region, offset, output)?;

        Ok(())
    }
}

#[cfg(test)]
mod test {
    use crate::{evm_circuit::test::rand_word, test_util::CircuitTestBuilder};
    use eth_types::{bytecode, Word};
    use mock::TestContext;

    fn test_ok(a: Word) {
        let bytecode = bytecode! {
            PUSH32(a)
            NOT
            STOP
        };

        CircuitTestBuilder::new_from_test_ctx(
            TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(),
        )
        .run();
    }

    #[test]
    fn not_gadget_simple() {
        test_ok(0.into());
        test_ok(1.into());
        test_ok(255.into());
        test_ok(256.into());
        test_ok(Word::MAX);
    }

    #[test]
    fn not_gadget_rand() {
        let a = rand_word();
        // the debug statement is useful for random tests so in case it fails, the
        // failing example shows up in the logs.
        dbg!(a);
        test_ok(a);
    }
}