diff --git a/bus-mapping/src/error.rs b/bus-mapping/src/error.rs
index 8eb668b2fb..4bec3d713b 100644
--- a/bus-mapping/src/error.rs
+++ b/bus-mapping/src/error.rs
@@ -92,8 +92,8 @@ pub enum OogError {
     SloadSstore,
     /// Out of Gas for CALL, CALLCODE, DELEGATECALL and STATICCALL
     Call,
-    /// Out of Gas for CREATE2
-    Create2,
+    /// Out of Gas for CREATE and CREATE2
+    Create,
     /// Out of Gas for SELFDESTRUCT
     SelfDestruct,
 }
@@ -105,9 +105,7 @@ impl From<&OpcodeId> for OogError {
             OpcodeId::MLOAD | OpcodeId::MSTORE | OpcodeId::MSTORE8 => {
                 OogError::StaticMemoryExpansion
             }
-            OpcodeId::CREATE | OpcodeId::RETURN | OpcodeId::REVERT => {
-                OogError::DynamicMemoryExpansion
-            }
+            OpcodeId::RETURN | OpcodeId::REVERT => OogError::DynamicMemoryExpansion,
             OpcodeId::CALLDATACOPY
             | OpcodeId::CODECOPY
             | OpcodeId::EXTCODECOPY
@@ -124,7 +122,7 @@ impl From<&OpcodeId> for OogError {
                 OogError::Call
             }
             OpcodeId::SLOAD | OpcodeId::SSTORE => OogError::SloadSstore,
-            OpcodeId::CREATE2 => OogError::Create2,
+            OpcodeId::CREATE | OpcodeId::CREATE2 => OogError::Create,
             OpcodeId::SELFDESTRUCT => OogError::SelfDestruct,
             _ => OogError::Constant,
         }
diff --git a/bus-mapping/src/evm/opcodes.rs b/bus-mapping/src/evm/opcodes.rs
index 5a1e477bc5..3e0a49941c 100644
--- a/bus-mapping/src/evm/opcodes.rs
+++ b/bus-mapping/src/evm/opcodes.rs
@@ -270,12 +270,20 @@ fn fn_gen_associated_ops(opcode_id: &OpcodeId) -> FnGenAssociatedOps {
     }
 }
 
-fn fn_gen_error_state_associated_ops(error: &ExecError) -> Option<FnGenAssociatedOps> {
+fn fn_gen_error_state_associated_ops(
+    geth_step: &GethExecStep,
+    error: &ExecError,
+) -> Option<FnGenAssociatedOps> {
     match error {
         ExecError::InvalidJump => Some(InvalidJump::gen_associated_ops),
         ExecError::InvalidOpcode => Some(ErrorSimple::gen_associated_ops),
         ExecError::OutOfGas(OogError::Call) => Some(OOGCall::gen_associated_ops),
         ExecError::OutOfGas(OogError::Constant) => Some(ErrorSimple::gen_associated_ops),
+        ExecError::OutOfGas(OogError::Create) => match geth_step.op {
+            OpcodeId::CREATE => Some(StackOnlyOpcode::<3, 0, true>::gen_associated_ops),
+            OpcodeId::CREATE2 => Some(StackOnlyOpcode::<4, 0, true>::gen_associated_ops),
+            op => unreachable!("OOG Create cannot occur in {op}"),
+        },
         ExecError::OutOfGas(OogError::Exp) => Some(OOGExp::gen_associated_ops),
         ExecError::OutOfGas(OogError::Log) => Some(ErrorOOGLog::gen_associated_ops),
         ExecError::OutOfGas(OogError::MemoryCopy) => Some(OOGMemoryCopy::gen_associated_ops),
@@ -370,7 +378,7 @@ pub fn gen_associated_ops(
         // TODO: after more error state handled, refactor all error handling in
         // fn_gen_error_state_associated_ops method
         // For exceptions that have been implemented
-        if let Some(fn_gen_error_ops) = fn_gen_error_state_associated_ops(&exec_error) {
+        if let Some(fn_gen_error_ops) = fn_gen_error_state_associated_ops(geth_step, &exec_error) {
             return fn_gen_error_ops(state, geth_steps);
         } else {
             // For exceptions that fail to enter next call context, we need
diff --git a/eth-types/src/evm_types.rs b/eth-types/src/evm_types.rs
index f59c186824..44b1befedf 100644
--- a/eth-types/src/evm_types.rs
+++ b/eth-types/src/evm_types.rs
@@ -28,6 +28,39 @@ pub const GAS_STIPEND_CALL_WITH_VALUE: u64 = 2300;
 /// <https://github.com/ethereum/go-ethereum/blob/e6b6a8b738069ad0579f6798ee59fde93ed13b43/core/vm/gas_table.go#L38>
 pub const MAX_EXPANDED_MEMORY_ADDRESS: u64 = 0x1FFFFFFFE0;
 
+#[cfg(feature = "shanghai")]
+mod gas_create {
+    // For EIP-3860, there are 2 special gas cost constraints in geth
+    // [gasCreate2Eip3860](https://github.com/ethereum/go-ethereum/blob/eb83e7c54021573eaceb14236af3a7a8c64f6027/core/vm/gas_table.go#L321)
+    // (similar for CREATE).
+    // 1. size <= 49152 (MaxInitCodeSize)
+    // 2. gasCost = memoryGasCost + (2 + 6) * ((size + 31) / 32) should not
+    //    overflow for Uint64.
+    // No need to constrain the second condition, since the maximum gas cost
+    // cannot overflow for Uint64 (36028809887100925 calculated by
+    // `memorySize = 0x1FFFFFFFE0` and `size = 49152`) if the first condition is
+    // satisfied.
+
+    /// Maximum init code size to permit in a creation transaction and create instructions.
+    pub const MAX_INIT_CODE_SIZE: u64 = 2 * super::MAX_CODE_SIZE;
+    /// Once per word of the init code when creating a contract.
+    pub const INIT_CODE_WORD_GAS: u64 = 2;
+    /// Gas per code word for CREATE.
+    pub const CREATE_GAS_PER_CODE_WORD: u64 = INIT_CODE_WORD_GAS;
+    /// Gas per code word for CREATE2.
+    pub const CREATE2_GAS_PER_CODE_WORD: u64 = INIT_CODE_WORD_GAS + super::GasCost::COPY_SHA3.0;
+}
+#[cfg(not(feature = "shanghai"))]
+mod gas_create {
+    /// Maximum init code size (0x1FFFFFFFE0) if not EIP-3860.
+    pub use super::MAX_EXPANDED_MEMORY_ADDRESS as MAX_INIT_CODE_SIZE;
+    /// Gas per code word for CREATE if not EIP-3860.
+    pub const CREATE_GAS_PER_CODE_WORD: u64 = 0;
+    /// Gas per code word for CREATE2 if not EIP-3860.
+    pub const CREATE2_GAS_PER_CODE_WORD: u64 = super::GasCost::COPY_SHA3;
+}
+pub use gas_create::*;
+
 /// Defines the gas consumption.
 pub struct GasCost;
 
diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs
index 89e13690e4..4261c76b9a 100644
--- a/zkevm-circuits/src/evm_circuit/execution.rs
+++ b/zkevm-circuits/src/evm_circuit/execution.rs
@@ -74,6 +74,7 @@ mod error_invalid_opcode;
 mod error_oog_account_access;
 mod error_oog_call;
 mod error_oog_constant;
+mod error_oog_create;
 mod error_oog_dynamic_memory;
 mod error_oog_exp;
 mod error_oog_log;
@@ -151,6 +152,7 @@ use error_invalid_opcode::ErrorInvalidOpcodeGadget;
 use error_oog_account_access::ErrorOOGAccountAccessGadget;
 use error_oog_call::ErrorOOGCallGadget;
 use error_oog_constant::ErrorOOGConstantGadget;
+use error_oog_create::ErrorOOGCreateGadget;
 use error_oog_dynamic_memory::ErrorOOGDynamicMemoryGadget;
 use error_oog_exp::ErrorOOGExpGadget;
 use error_oog_log::ErrorOOGLogGadget;
@@ -311,7 +313,7 @@ pub struct ExecutionConfig<F> {
     error_oog_sha3: Box<ErrorOOGSha3Gadget<F>>,
     error_oog_account_access: Box<ErrorOOGAccountAccessGadget<F>>,
     error_oog_ext_codecopy: Box<DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasEXTCODECOPY }>>,
-    error_oog_create2: Box<DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasCREATE2 }>>,
+    error_oog_create: Box<ErrorOOGCreateGadget<F>>,
     error_oog_self_destruct:
         Box<DummyGadget<F, 0, 0, { ExecutionState::ErrorOutOfGasSELFDESTRUCT }>>,
     error_oog_code_store: Box<ErrorCodeStoreGadget<F>>,
@@ -576,7 +578,7 @@ impl<F: Field> ExecutionConfig<F> {
             error_oog_sha3: configure_gadget!(),
             error_oog_ext_codecopy: configure_gadget!(),
             error_oog_exp: configure_gadget!(),
-            error_oog_create2: configure_gadget!(),
+            error_oog_create: configure_gadget!(),
             error_oog_self_destruct: configure_gadget!(),
             error_oog_code_store: configure_gadget!(),
             error_invalid_jump: configure_gadget!(),
@@ -1316,8 +1318,8 @@ impl<F: Field> ExecutionConfig<F> {
             ExecutionState::ErrorOutOfGasEXP => {
                 assign_exec_step!(self.error_oog_exp)
             }
-            ExecutionState::ErrorOutOfGasCREATE2 => {
-                assign_exec_step!(self.error_oog_create2)
+            ExecutionState::ErrorOutOfGasCREATE => {
+                assign_exec_step!(self.error_oog_create)
             }
             ExecutionState::ErrorOutOfGasSELFDESTRUCT => {
                 assign_exec_step!(self.error_oog_self_destruct)
diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs
new file mode 100644
index 0000000000..b22619ae01
--- /dev/null
+++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs
@@ -0,0 +1,379 @@
+use crate::{
+    evm_circuit::{
+        execution::ExecutionGadget,
+        param::{N_BYTES_GAS, N_BYTES_MEMORY_ADDRESS, N_BYTES_MEMORY_WORD_SIZE},
+        step::ExecutionState,
+        util::{
+            common_gadget::CommonErrorGadget,
+            constraint_builder::{ConstrainBuilderCommon, EVMConstraintBuilder},
+            math_gadget::{LtGadget, PairSelectGadget},
+            memory_gadget::{
+                CommonMemoryAddressGadget, MemoryExpandedAddressGadget, MemoryExpansionGadget,
+                MemoryWordSizeGadget,
+            },
+            or, select, CachedRegion, Cell, WordExpr,
+        },
+    },
+    util::{word::Word32Cell, Expr},
+    witness::{Block, Call, ExecStep, Transaction},
+};
+use eth_types::{
+    evm_types::{
+        GasCost, OpcodeId, CREATE2_GAS_PER_CODE_WORD, CREATE_GAS_PER_CODE_WORD, MAX_INIT_CODE_SIZE,
+    },
+    Field, U256,
+};
+use halo2_proofs::{circuit::Value, plonk::Error};
+
+#[derive(Clone, Debug)]
+pub(crate) struct ErrorOOGCreateGadget<F> {
+    opcode: Cell<F>,
+    value: Word32Cell<F>,
+    salt: Word32Cell<F>,
+    is_create2: PairSelectGadget<F>,
+    minimum_word_size: MemoryWordSizeGadget<F>,
+    memory_address: MemoryExpandedAddressGadget<F>,
+    memory_expansion: MemoryExpansionGadget<F, 1, N_BYTES_MEMORY_WORD_SIZE>,
+    // Init code size is overflow when it is greater than 49152
+    // (maximum init code size) if Shanghai, otherwise when it is greater than
+    // 0x1FFFFFFFE0 (maximum value of offset + size).
+    // Uint64 overflow is checked in `memory_address` (offset + length).
+    init_code_size_overflow: LtGadget<F, { N_BYTES_MEMORY_ADDRESS }>,
+    insufficient_gas: LtGadget<F, N_BYTES_GAS>,
+    common_error_gadget: CommonErrorGadget<F>,
+}
+
+impl<F: Field> ExecutionGadget<F> for ErrorOOGCreateGadget<F> {
+    const NAME: &'static str = "ErrorOutOfGasCREATE";
+
+    const EXECUTION_STATE: ExecutionState = ExecutionState::ErrorOutOfGasCREATE;
+
+    fn configure(cb: &mut EVMConstraintBuilder<F>) -> Self {
+        let opcode = cb.query_cell();
+
+        let is_create2 = PairSelectGadget::construct(
+            cb,
+            opcode.expr(),
+            OpcodeId::CREATE2.expr(),
+            OpcodeId::CREATE.expr(),
+        );
+
+        let value = cb.query_word32();
+        let salt = cb.query_word32();
+
+        let memory_address = MemoryExpandedAddressGadget::construct_self(cb);
+
+        cb.stack_pop(value.to_word());
+        cb.stack_pop(memory_address.offset_word());
+        cb.stack_pop(memory_address.length_word());
+        cb.condition(is_create2.expr().0, |cb| cb.stack_pop(salt.to_word()));
+
+        let init_code_size_overflow =
+            LtGadget::construct(cb, MAX_INIT_CODE_SIZE.expr(), memory_address.length());
+
+        let minimum_word_size = MemoryWordSizeGadget::construct(cb, memory_address.length());
+        let memory_expansion = MemoryExpansionGadget::construct(cb, [memory_address.address()]);
+
+        let code_store_gas_cost = minimum_word_size.expr()
+            * select::expr(
+                is_create2.expr().0,
+                CREATE2_GAS_PER_CODE_WORD.expr(),
+                CREATE_GAS_PER_CODE_WORD.expr(),
+            );
+        let gas_cost = GasCost::CREATE.expr() + memory_expansion.gas_cost() + code_store_gas_cost;
+        let insufficient_gas = LtGadget::construct(cb, cb.curr.state.gas_left.expr(), gas_cost);
+
+        cb.require_equal(
+            "Memory address is overflow, init code size is overflow, or gas left is less than cost",
+            or::expr([
+                memory_address.overflow(),
+                init_code_size_overflow.expr(),
+                insufficient_gas.expr(),
+            ]),
+            1.expr(),
+        );
+
+        let common_error_gadget = CommonErrorGadget::construct(
+            cb,
+            opcode.expr(),
+            select::expr(is_create2.expr().0, 4.expr(), 3.expr()),
+        );
+
+        Self {
+            opcode,
+            value,
+            salt,
+            is_create2,
+            minimum_word_size,
+            memory_address,
+            memory_expansion,
+            init_code_size_overflow,
+            insufficient_gas,
+            common_error_gadget,
+        }
+    }
+
+    fn assign_exec_step(
+        &self,
+        region: &mut CachedRegion<'_, '_, F>,
+        offset: usize,
+        block: &Block<F>,
+        _tx: &Transaction,
+        call: &Call,
+        step: &ExecStep,
+    ) -> Result<(), Error> {
+        log::debug!(
+            "ErrorOutOfGasCREATE: gas_cost = {}, gas_left = {}",
+            step.gas_cost,
+            step.gas_left,
+        );
+
+        let opcode = step.opcode().unwrap();
+        let is_create2 = opcode == OpcodeId::CREATE2;
+        self.opcode
+            .assign(region, offset, Value::known(F::from(opcode.as_u64())))?;
+        self.is_create2.assign(
+            region,
+            offset,
+            F::from(opcode.as_u64()),
+            F::from(OpcodeId::CREATE2.as_u64()),
+            F::from(OpcodeId::CREATE.as_u64()),
+        )?;
+
+        let [value, memory_offset, memory_length] =
+            [0, 1, 2].map(|i| block.get_rws(step, i).stack_value());
+        let salt = if is_create2 {
+            block.get_rws(step, 3).stack_value()
+        } else {
+            U256::zero()
+        };
+
+        self.value.assign_u256(region, offset, value)?;
+        self.salt.assign_u256(region, offset, salt)?;
+
+        let memory_address =
+            self.memory_address
+                .assign(region, offset, memory_offset, memory_length)?;
+
+        let init_code_size =
+            MemoryExpandedAddressGadget::<F>::length_value(memory_offset, memory_length);
+        let minimum_word_size = self
+            .minimum_word_size
+            .assign(region, offset, init_code_size)?;
+        let memory_expansion_gas = self
+            .memory_expansion
+            .assign(region, offset, step.memory_word_size(), [memory_address])?
+            .1;
+
+        self.init_code_size_overflow.assign(
+            region,
+            offset,
+            F::from(MAX_INIT_CODE_SIZE),
+            F::from(init_code_size),
+        )?;
+
+        let code_store_gas_cost = minimum_word_size
+            * if is_create2 {
+                CREATE2_GAS_PER_CODE_WORD
+            } else {
+                CREATE_GAS_PER_CODE_WORD
+            };
+        self.insufficient_gas.assign(
+            region,
+            offset,
+            F::from(step.gas_left),
+            F::from(GasCost::CREATE + memory_expansion_gas + code_store_gas_cost),
+        )?;
+
+        self.common_error_gadget.assign(
+            region,
+            offset,
+            block,
+            call,
+            step,
+            if is_create2 { 6 } else { 5 },
+        )?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test_util::CircuitTestBuilder;
+    use eth_types::{bytecode, word, Bytecode, ToWord};
+    use mock::{
+        eth,
+        test_ctx::{helpers::account_0_code_account_1_no_code, LoggerConfig},
+        TestContext, MOCK_ACCOUNTS, MOCK_BLOCK_GAS_LIMIT,
+    };
+
+    struct TestCase {
+        bytecode: Bytecode,
+        gas: u64,
+    }
+
+    impl TestCase {
+        pub fn new(is_create2: bool, offset: U256, size: U256, gas: u64) -> Self {
+            let mut bytecode = Bytecode::default();
+            if is_create2 {
+                bytecode.append(&bytecode! {PUSH1(0)}); // salt;
+            }
+            bytecode.append(&bytecode! {
+                PUSH32(size) // size
+                PUSH32(offset) // offset
+                PUSH1(0) // value
+            });
+            bytecode.write_op(if is_create2 {
+                OpcodeId::CREATE2
+            } else {
+                OpcodeId::CREATE
+            });
+
+            Self { bytecode, gas }
+        }
+    }
+
+    #[test]
+    fn test_oog_create_simple() {
+        let mut cases = vec![];
+        for is_create2 in [true, false] {
+            cases.push(TestCase::new(
+                is_create2,
+                0xffffffff_u64.into(),
+                0xff.into(),
+                0xffff,
+            ));
+
+            cases.push(TestCase::new(is_create2, U256::zero(), 4.into(), 0x7d08));
+        }
+
+        for case in cases.iter() {
+            test_root(case);
+            test_internal(case);
+        }
+    }
+
+    #[test]
+    fn test_oog_create_max_expanded_address() {
+        for is_create2 in [true, false] {
+            // 0xffffffff1 + 0xffffffff0 = 0x1fffffffe1
+            // > MAX_EXPANDED_MEMORY_ADDRESS (0x1fffffffe0)
+            let case = TestCase::new(
+                is_create2,
+                0xffffffff1_u64.into(),
+                0xffffffff0_u64.into(),
+                MOCK_BLOCK_GAS_LIMIT,
+            );
+
+            test_root(&case);
+            test_internal(&case);
+        }
+    }
+
+    #[test]
+    fn test_oog_create_max_u64_address() {
+        for is_create2 in [true, false] {
+            let case = TestCase::new(
+                is_create2,
+                u64::MAX.into(),
+                u64::MAX.into(),
+                MOCK_BLOCK_GAS_LIMIT,
+            );
+
+            test_root(&case);
+            test_internal(&case);
+        }
+    }
+
+    #[test]
+    fn test_oog_create_max_word_address() {
+        for is_create2 in [true, false] {
+            let case = TestCase::new(is_create2, U256::MAX, U256::MAX, MOCK_BLOCK_GAS_LIMIT);
+
+            test_root(&case);
+            test_internal(&case);
+        }
+    }
+
+    #[test]
+    fn test_oog_create_max_init_code_size() {
+        for is_create2 in [true, false] {
+            // For Shanghai, MAX_INIT_CODE_SIZE is 49152, it is constrained by
+            // `init_code_size_overflow`.
+            // For not Shanghai, MAX_INIT_CODE_SIZE is 0x1FFFFFFFE0, it is
+            // constrained by `memory_address.overflow()`
+            // (and `init_code_size_overflow`).
+            let case = TestCase::new(
+                is_create2,
+                U256::zero(),
+                (MAX_INIT_CODE_SIZE + 1).into(),
+                MOCK_BLOCK_GAS_LIMIT,
+            );
+
+            test_root(&case);
+            test_internal(&case);
+        }
+    }
+
+    fn test_root(case: &TestCase) {
+        let ctx = TestContext::<2, 1>::new_with_logger_config(
+            None,
+            account_0_code_account_1_no_code(case.bytecode.clone()),
+            |mut txs, accs| {
+                txs[0]
+                    .from(accs[1].address)
+                    .to(accs[0].address)
+                    .gas(case.gas.into());
+            },
+            |block, _tx| block,
+            LoggerConfig {
+                enable_memory: true,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+
+        CircuitTestBuilder::new_from_test_ctx(ctx).run();
+    }
+
+    fn test_internal(case: &TestCase) {
+        let code_a = bytecode! {
+            PUSH1(0x00) // retLength
+            PUSH1(0x00) // retOffset
+            PUSH32(0x00) // argsLength
+            PUSH32(0x00) // argsOffset
+            PUSH1(0x00) // value
+            PUSH32(MOCK_ACCOUNTS[1].to_word()) // addr
+            PUSH32(case.gas) // gas
+            CALL
+            STOP
+        };
+
+        let ctx = TestContext::<3, 1>::new_with_logger_config(
+            None,
+            |accs| {
+                accs[0].address(MOCK_ACCOUNTS[0]).code(code_a);
+                accs[1]
+                    .address(MOCK_ACCOUNTS[1])
+                    .code(case.bytecode.clone());
+                accs[2].address(MOCK_ACCOUNTS[2]).balance(eth(1));
+            },
+            |mut txs, accs| {
+                txs[0]
+                    .from(accs[2].address)
+                    .to(accs[0].address)
+                    .gas(word!("0xFFFFF"));
+            },
+            |block, _tx| block,
+            LoggerConfig {
+                enable_memory: true,
+                ..Default::default()
+            },
+        )
+        .unwrap();
+
+        CircuitTestBuilder::new_from_test_ctx(ctx).run();
+    }
+}
diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs
index 14e297bab3..5f549e25e8 100644
--- a/zkevm-circuits/src/evm_circuit/step.rs
+++ b/zkevm-circuits/src/evm_circuit/step.rs
@@ -136,7 +136,7 @@ pub enum ExecutionState {
     ErrorOutOfGasEXTCODECOPY,
     ErrorOutOfGasCall,
     ErrorOutOfGasSloadSstore,
-    ErrorOutOfGasCREATE2,
+    ErrorOutOfGasCREATE,
     ErrorOutOfGasSELFDESTRUCT,
 }
 
@@ -196,7 +196,7 @@ impl From<&ExecError> for ExecutionState {
                 OogError::Sha3 => ExecutionState::ErrorOutOfGasSHA3,
                 OogError::Call => ExecutionState::ErrorOutOfGasCall,
                 OogError::SloadSstore => ExecutionState::ErrorOutOfGasSloadSstore,
-                OogError::Create2 => ExecutionState::ErrorOutOfGasCREATE2,
+                OogError::Create => ExecutionState::ErrorOutOfGasCREATE,
                 OogError::SelfDestruct => ExecutionState::ErrorOutOfGasSELFDESTRUCT,
             },
         }
@@ -345,7 +345,7 @@ impl ExecutionState {
                 | Self::ErrorOutOfGasEXTCODECOPY
                 | Self::ErrorOutOfGasCall
                 | Self::ErrorOutOfGasSloadSstore
-                | Self::ErrorOutOfGasCREATE2
+                | Self::ErrorOutOfGasCREATE
                 | Self::ErrorOutOfGasSELFDESTRUCT
         )
     }