diff --git a/bus-mapping/src/evm/opcodes/extcodesize.rs b/bus-mapping/src/evm/opcodes/extcodesize.rs index e7219ae71c..a1caf2b1a1 100644 --- a/bus-mapping/src/evm/opcodes/extcodesize.rs +++ b/bus-mapping/src/evm/opcodes/extcodesize.rs @@ -1,8 +1,8 @@ use crate::circuit_input_builder::{CircuitInputStateRef, ExecStep}; use crate::evm::Opcode; -use crate::operation::{TxAccessListAccountOp, RW}; +use crate::operation::{AccountField, CallContextField, TxAccessListAccountOp, RW}; use crate::Error; -use eth_types::{GethExecStep, ToAddress, ToWord}; +use eth_types::{GethExecStep, ToAddress, ToWord, Word}; #[derive(Debug, Copy, Clone)] pub(crate) struct Extcodesize; @@ -12,34 +12,264 @@ impl Opcode for Extcodesize { state: &mut CircuitInputStateRef, geth_steps: &[GethExecStep], ) -> Result, Error> { - // TODO: finish this, only access list part is done let geth_step = &geth_steps[0]; let mut exec_step = state.new_step(geth_step)?; - let external_address = geth_steps[0].stack.last()?.to_address(); + // Read account address from stack. + let address = geth_step.stack.last()?.to_address(); state.stack_read( &mut exec_step, geth_step.stack.last_filled(), - external_address.to_word(), + address.to_word(), )?; - let is_warm = state.sdb.check_account_in_access_list(&external_address); + + // Read transaction ID, rw_counter_end_of_reversion, and is_persistent from call + // context. + for (field, value) in [ + (CallContextField::TxId, state.tx_ctx.id().to_word()), + ( + CallContextField::RwCounterEndOfReversion, + state.call()?.rw_counter_end_of_reversion.to_word(), + ), + ( + CallContextField::IsPersistent, + state.call()?.is_persistent.to_word(), + ), + ] { + state.call_context_read(&mut exec_step, state.call()?.call_id, field, value); + } + + // Update transaction access list for account address. + let is_warm = state.sdb.check_account_in_access_list(&address); state.push_op_reversible( &mut exec_step, RW::WRITE, TxAccessListAccountOp { tx_id: state.tx_ctx.id(), - address: external_address, + address, is_warm: true, is_warm_prev: is_warm, }, )?; + // Read account code hash and get code length. + let account = state.sdb.get_account(&address).1; + let code_size = if !account.is_empty() { + let code_hash = account.code_hash; + let code_hash_word = code_hash.to_word(); + state.account_read( + &mut exec_step, + address, + AccountField::CodeHash, + code_hash_word, + code_hash_word, + )?; + state.code(code_hash)?.len() + } else { + state.account_read( + &mut exec_step, + address, + AccountField::NonExisting, + Word::zero(), + Word::zero(), + )?; + 0 + }; + + // Write the EXTCODESIZE result to stack. + debug_assert_eq!(code_size, geth_steps[1].stack.last()?.as_usize()); state.stack_write( &mut exec_step, geth_steps[1].stack.nth_last_filled(0), - geth_steps[1].stack.nth_last(0)?, + code_size.into(), )?; Ok(vec![exec_step]) } } + +#[cfg(test)] +mod extcodesize_tests { + use super::*; + use crate::circuit_input_builder::ExecState; + use crate::mock::BlockData; + use crate::operation::{AccountOp, CallContextOp, StackOp}; + use eth_types::evm_types::{OpcodeId, StackAddress}; + use eth_types::geth_types::{Account, GethData}; + use eth_types::{bytecode, Bytecode, Word, U256}; + use ethers_core::utils::keccak256; + use mock::{TestContext, MOCK_1_ETH, MOCK_ACCOUNTS, MOCK_CODES}; + use pretty_assertions::assert_eq; + + #[test] + fn test_extcodesize_opcode() { + let account = Account { + address: MOCK_ACCOUNTS[4], + code: MOCK_CODES[4].clone(), + ..Default::default() + }; + + // Test for empty account. + test_ok(&Account::default(), false); + // Test for cold account. + test_ok(&account, false); + // Test for warm account. + test_ok(&account, true); + } + + fn test_ok(account: &Account, is_warm: bool) { + let account_exists = !account.is_empty(); + + let mut bytecode = Bytecode::default(); + if is_warm { + bytecode.append(&bytecode! { + PUSH20(account.address.to_word()) + EXTCODESIZE + POP + }); + } + bytecode.append(&bytecode! { + PUSH20(account.address.to_word()) + EXTCODESIZE + STOP + }); + + // Get the execution steps from the external tracer. + let block: GethData = TestContext::<3, 1>::new( + None, + |accs| { + accs[0] + .address(MOCK_ACCOUNTS[0]) + .balance(*MOCK_1_ETH) + .code(bytecode); + if account_exists { + accs[1].address(account.address).code(account.code.clone()); + } else { + accs[1].address(MOCK_ACCOUNTS[1]).balance(*MOCK_1_ETH); + } + accs[2].address(MOCK_ACCOUNTS[2]).balance(*MOCK_1_ETH); + }, + |mut txs, accs| { + txs[0].to(accs[0].address).from(accs[2].address); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + + let mut builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + builder + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + + // Check if account address is in access list as a result of bus mapping. + assert!(builder.sdb.add_account_to_access_list(account.address)); + + let tx_id = 1; + let transaction = &builder.block.txs()[tx_id - 1]; + let call_id = transaction.calls()[0].call_id; + + let indices = transaction + .steps() + .iter() + .filter(|step| step.exec_state == ExecState::Op(OpcodeId::EXTCODESIZE)) + .last() + .unwrap() + .bus_mapping_instance + .clone(); + let container = builder.block.container; + let operation = &container.stack[indices[0].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &StackOp { + call_id, + address: StackAddress::from(1023u32), + value: account.address.to_word() + } + ); + + let operation = &container.call_context[indices[1].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::TxId, + value: tx_id.into() + } + ); + + let operation = &container.call_context[indices[2].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::RwCounterEndOfReversion, + value: U256::zero() + } + ); + + let operation = &container.call_context[indices[3].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &CallContextOp { + call_id, + field: CallContextField::IsPersistent, + value: U256::one() + } + ); + + let operation = &container.tx_access_list_account[indices[4].as_usize()]; + assert_eq!(operation.rw(), RW::WRITE); + assert_eq!( + operation.op(), + &TxAccessListAccountOp { + tx_id, + address: account.address, + is_warm: true, + is_warm_prev: is_warm + } + ); + + let operation = &container.account[indices[5].as_usize()]; + assert_eq!(operation.rw(), RW::READ); + assert_eq!( + operation.op(), + &(if account_exists { + let code_hash = Word::from(keccak256(account.code.clone())); + AccountOp { + address: account.address, + field: AccountField::CodeHash, + value: code_hash, + value_prev: code_hash, + } + } else { + AccountOp { + address: account.address, + field: AccountField::NonExisting, + value: Word::zero(), + value_prev: Word::zero(), + } + }) + ); + + let operation = &container.stack[indices[6].as_usize()]; + assert_eq!(operation.rw(), RW::WRITE); + assert_eq!( + operation.op(), + &StackOp { + call_id, + address: 1023u32.into(), + value: (if account_exists { + account.code.len() + } else { + 0 + }) + .into(), + } + ); + } +} diff --git a/eth-types/src/geth_types.rs b/eth-types/src/geth_types.rs index 397699a00a..5dac0b8bcd 100644 --- a/eth-types/src/geth_types.rs +++ b/eth-types/src/geth_types.rs @@ -32,6 +32,16 @@ pub struct Account { pub storage: HashMap, } +impl Account { + /// Return if account is empty or not. + pub fn is_empty(&self) -> bool { + self.nonce.is_zero() + && self.balance.is_zero() + && self.code.is_empty() + && self.storage.is_empty() + } +} + fn serde_account_storage( to_serialize: &HashMap, serializer: S, diff --git a/mock/src/lib.rs b/mock/src/lib.rs index a34bfbd25e..c556446374 100644 --- a/mock/src/lib.rs +++ b/mock/src/lib.rs @@ -1,6 +1,6 @@ //! Mock types and functions to generate GethData used for tests -use eth_types::{address, Address, Word}; +use eth_types::{address, Address, Bytes, Word}; use ethers_signers::LocalWallet; use lazy_static::lazy_static; use rand::SeedableRng; @@ -16,6 +16,8 @@ pub use test_ctx::TestContext; pub use transaction::{AddrOrWallet, MockTransaction, CORRECT_MOCK_TXS}; lazy_static! { + /// Mock 1 ETH + pub static ref MOCK_1_ETH: Word = eth(1); /// Mock coinbase value pub static ref MOCK_COINBASE: Address = address!("0x00000000000000000000000000000000c014ba5e"); @@ -37,6 +39,14 @@ lazy_static! { address!("0x000000000000000000000000000000000cafe444"), address!("0x000000000000000000000000000000000cafe555"), ]; + /// Mock EVM codes to use for test cases. + pub static ref MOCK_CODES: Vec = vec![ + Bytes::from([0x60, 0x10, 0x00]), // PUSH1(0x10), STOP + Bytes::from([0x60, 0x01, 0x60, 0x02, 0x01, 0x00]), // PUSH1(1), PUSH1(2), ADD, STOP + Bytes::from([0x60, 0x01, 0x60, 0x02, 0x02, 0x00]), // PUSH1(1), PUSH1(2), MUL, STOP + Bytes::from([0x60, 0x02, 0x60, 0x01, 0x03, 0x00]), // PUSH1(2), PUSH1(1), SUB, STOP + Bytes::from([0x60, 0x09, 0x60, 0x03, 0x04, 0x00]), // PUSH1(9), PUSH1(3), DIV, STOP + ]; /// Mock wallets used to generate correctly signed and hashed Transactions. pub static ref MOCK_WALLETS: Vec = { let mut rng = ChaCha20Rng::seed_from_u64(2u64); diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 577bb5e0dc..b4fae38687 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -57,6 +57,7 @@ mod error_oog_static_memory; mod error_stack; mod exp; mod extcodehash; +mod extcodesize; mod gas; mod gasprice; mod is_zero; @@ -118,6 +119,7 @@ use error_oog_constant::ErrorOOGConstantGadget; use error_stack::ErrorStackGadget; use exp::ExponentiationGadget; use extcodehash::ExtcodehashGadget; +use extcodesize::ExtcodesizeGadget; use gas::GasGadget; use gasprice::GasPriceGadget; use is_zero::IsZeroGadget; @@ -210,6 +212,7 @@ pub(crate) struct ExecutionConfig { dup_gadget: DupGadget, exp_gadget: ExponentiationGadget, extcodehash_gadget: ExtcodehashGadget, + extcodesize_gadget: ExtcodesizeGadget, gas_gadget: GasGadget, gasprice_gadget: GasPriceGadget, iszero_gadget: IsZeroGadget, @@ -232,7 +235,6 @@ pub(crate) struct ExecutionConfig { sha3_gadget: Sha3Gadget, shl_shr_gadget: ShlShrGadget, sar_gadget: DummyGadget, - extcodesize_gadget: DummyGadget, extcodecopy_gadget: DummyGadget, returndatasize_gadget: ReturnDataSizeGadget, returndatacopy_gadget: ReturnDataCopyGadget, @@ -444,6 +446,7 @@ impl ExecutionConfig { comparator_gadget: configure_gadget!(), dup_gadget: configure_gadget!(), extcodehash_gadget: configure_gadget!(), + extcodesize_gadget: configure_gadget!(), gas_gadget: configure_gadget!(), gasprice_gadget: configure_gadget!(), iszero_gadget: configure_gadget!(), @@ -469,7 +472,6 @@ impl ExecutionConfig { blockhash_gadget: configure_gadget!(), exp_gadget: configure_gadget!(), sar_gadget: configure_gadget!(), - extcodesize_gadget: configure_gadget!(), extcodecopy_gadget: configure_gadget!(), returndatasize_gadget: configure_gadget!(), returndatacopy_gadget: configure_gadget!(), @@ -990,6 +992,7 @@ impl ExecutionConfig { ExecutionState::DUP => assign_exec_step!(self.dup_gadget), ExecutionState::EXP => assign_exec_step!(self.exp_gadget), ExecutionState::EXTCODEHASH => assign_exec_step!(self.extcodehash_gadget), + ExecutionState::EXTCODESIZE => assign_exec_step!(self.extcodesize_gadget), ExecutionState::GAS => assign_exec_step!(self.gas_gadget), ExecutionState::GASPRICE => assign_exec_step!(self.gasprice_gadget), ExecutionState::ISZERO => assign_exec_step!(self.iszero_gadget), @@ -1018,7 +1021,6 @@ impl ExecutionConfig { ExecutionState::SELFBALANCE => assign_exec_step!(self.selfbalance_gadget), // dummy gadgets ExecutionState::SAR => assign_exec_step!(self.sar_gadget), - ExecutionState::EXTCODESIZE => assign_exec_step!(self.extcodesize_gadget), ExecutionState::EXTCODECOPY => assign_exec_step!(self.extcodecopy_gadget), ExecutionState::CREATE => assign_exec_step!(self.create_gadget), ExecutionState::CREATE2 => assign_exec_step!(self.create2_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs b/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs new file mode 100644 index 0000000000..86896672c1 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs @@ -0,0 +1,247 @@ +use crate::evm_circuit::execution::ExecutionGadget; +use crate::evm_circuit::param::N_BYTES_ACCOUNT_ADDRESS; +use crate::evm_circuit::step::ExecutionState; +use crate::evm_circuit::util::common_gadget::SameContextGadget; +use crate::evm_circuit::util::constraint_builder::Transition::Delta; +use crate::evm_circuit::util::constraint_builder::{ + ConstraintBuilder, ReversionInfo, StepStateTransition, +}; +use crate::evm_circuit::util::{from_bytes, select, CachedRegion, Cell, RandomLinearCombination}; +use crate::evm_circuit::witness::{Block, Call, ExecStep, Rw, Transaction}; +use crate::table::{AccountFieldTag, CallContextFieldTag}; +use crate::util::Expr; +use eth_types::evm_types::GasCost; +use eth_types::{Field, ToAddress, ToLittleEndian}; +use halo2_proofs::circuit::Value; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct ExtcodesizeGadget { + same_context: SameContextGadget, + address: RandomLinearCombination, + reversion_info: ReversionInfo, + tx_id: Cell, + is_warm: Cell, + exists: Cell, + code_hash: Cell, + code_size: Cell, +} + +impl ExecutionGadget for ExtcodesizeGadget { + const NAME: &'static str = "EXTCODESIZE"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::EXTCODESIZE; + + fn configure(cb: &mut ConstraintBuilder) -> Self { + let address = cb.query_rlc(); + cb.stack_pop(address.expr()); + + let tx_id = cb.call_context(None, CallContextFieldTag::TxId); + let mut reversion_info = cb.reversion_info_read(None); + let is_warm = cb.query_bool(); + cb.account_access_list_write( + tx_id.expr(), + from_bytes::expr(&address.cells), + 1.expr(), + is_warm.expr(), + Some(&mut reversion_info), + ); + + let exists = cb.query_bool(); + let code_hash = cb.query_cell(); + let code_size = cb.condition(exists.expr(), |cb| { + cb.account_read( + from_bytes::expr(&address.cells), + AccountFieldTag::CodeHash, + code_hash.expr(), + ); + cb.bytecode_length(code_hash.expr()) + }); + cb.condition(1.expr() - exists.expr(), |cb| { + cb.account_read( + from_bytes::expr(&address.cells), + AccountFieldTag::NonExisting, + 0.expr(), + ); + }); + + cb.stack_push(select::expr(exists.expr(), code_size.expr(), 0.expr())); + + let gas_cost = select::expr( + is_warm.expr(), + GasCost::WARM_ACCESS.expr(), + GasCost::COLD_ACCOUNT_ACCESS.expr(), + ); + + let step_state_transition = StepStateTransition { + rw_counter: Delta(7.expr()), + program_counter: Delta(1.expr()), + stack_pointer: Delta(0.expr()), + gas_left: Delta(-gas_cost), + reversible_write_counter: Delta(1.expr()), + ..Default::default() + }; + + let opcode = cb.query_cell(); + let same_context = SameContextGadget::construct(cb, opcode, step_state_transition); + + Self { + same_context, + address, + tx_id, + reversion_info, + is_warm, + exists, + code_hash, + code_size, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + tx: &Transaction, + call: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + self.same_context.assign_exec_step(region, offset, step)?; + + let address = block.rws[step.rw_indices[0]].stack_value().to_address(); + let mut address_bytes = address.0; + address_bytes.reverse(); + self.address.assign(region, offset, Some(address_bytes))?; + + self.tx_id + .assign(region, offset, Value::known(F::from(tx.id as u64)))?; + + self.reversion_info.assign( + region, + offset, + call.rw_counter_end_of_reversion, + call.is_persistent, + )?; + + let (_, is_warm) = block.rws[step.rw_indices[4]].tx_access_list_value_pair(); + self.is_warm + .assign(region, offset, Value::known(F::from(is_warm)))?; + + let (exists, code_hash) = match block.rws[step.rw_indices[5]] { + Rw::Account { + field_tag: AccountFieldTag::CodeHash, + value, + .. + } => (true, value), + Rw::Account { + field_tag: AccountFieldTag::NonExisting, + .. + } => (false, 0.into()), + _ => unreachable!(), + }; + + let code_size = block.rws[step.rw_indices[6]].stack_value().as_u64(); + + self.exists + .assign(region, offset, Value::known(F::from(exists)))?; + self.code_hash.assign( + region, + offset, + Value::known(RandomLinearCombination::random_linear_combine( + code_hash.to_le_bytes(), + block.randomness, + )), + )?; + self.code_size + .assign(region, offset, Value::known(F::from(code_size)))?; + + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::evm_circuit::test::rand_bytes; + use crate::test_util::run_test_circuits; + use eth_types::geth_types::Account; + use eth_types::{bytecode, Bytecode, ToWord, Word}; + use mock::{TestContext, MOCK_1_ETH, MOCK_ACCOUNTS, MOCK_CODES}; + + #[test] + fn test_extcodesize_gadget() { + let account = Account { + address: MOCK_ACCOUNTS[4], + code: MOCK_CODES[4].clone(), + ..Default::default() + }; + + // Test for empty account. + test_ok(&Account::default(), false); + // Test for cold account. + test_ok(&account, false); + // Test for warm account. + test_ok(&account, true); + } + + fn test_ok(account: &Account, is_warm: bool) { + let account_exists = !account.is_empty(); + + let (addr_a, addr_b) = (mock::MOCK_ACCOUNTS[0], mock::MOCK_ACCOUNTS[1]); + + // code B gets called by code A, so the call is an internal call. + let mut bytecode_b = Bytecode::default(); + if is_warm { + bytecode_b.append(&bytecode! { + PUSH20(account.address.to_word()) + EXTCODESIZE + POP + }); + } + bytecode_b.append(&bytecode! { + PUSH20(account.address.to_word()) + EXTCODESIZE + POP + }); + + // code A calls code B. + let pushdata = rand_bytes(8); + let bytecode_a = bytecode! { + // populate memory in A's context. + PUSH8(Word::from_big_endian(&pushdata)) + PUSH1(0x00) // offset + MSTORE + // call ADDR_B. + PUSH1(0x00) // retLength + PUSH1(0x00) // retOffset + PUSH32(0xff) // argsLength + PUSH32(0x1010) // argsOffset + PUSH1(0x00) // value + PUSH32(addr_b.to_word()) // addr + PUSH32(0x1_0000) // gas + CALL + STOP + }; + + let ctx = TestContext::<4, 1>::new( + None, + |accs| { + accs[0].address(addr_b).code(bytecode_b); + accs[1].address(addr_a).code(bytecode_a); + // Set code if account exists. + if account_exists { + accs[2].address(account.address).code(account.code.clone()); + } else { + accs[2].address(mock::MOCK_ACCOUNTS[2]).balance(*MOCK_1_ETH); + } + accs[3].address(mock::MOCK_ACCOUNTS[3]).balance(*MOCK_1_ETH); + }, + |mut txs, accs| { + txs[0].to(accs[1].address).from(accs[3].address); + }, + |block, _tx| block, + ) + .unwrap(); + + assert_eq!(run_test_circuits(ctx, None), Ok(())); + } +} diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 013a24a2cf..ce4f1c4b2d 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -7,7 +7,6 @@ use bus_mapping::{ }; use eth_types::{Address, Field, ToLittleEndian, ToScalar, Word}; use halo2_proofs::halo2curves::bn256::Fr; -use itertools::Itertools; use super::{step::step_convert, tx::tx_convert, Bytecode, ExecStep, RwMap, Transaction}; @@ -180,20 +179,12 @@ pub fn block_convert( .collect(), end_block_not_last: step_convert(&block.block_steps.end_block_not_last), end_block_last: step_convert(&block.block_steps.end_block_last), - bytecodes: block - .txs() - .iter() - .flat_map(|tx| { - tx.calls() - .iter() - .map(|call| call.code_hash) - .unique() - .into_iter() - .map(|code_hash| { - let bytecode = - Bytecode::new(code_db.0.get(&code_hash).cloned().unwrap_or_default()); - (bytecode.hash, bytecode) - }) + bytecodes: code_db + .0 + .values() + .map(|v| { + let bytecode = Bytecode::new(v.clone()); + (bytecode.hash, bytecode) }) .collect(), copy_events: block.copy_events.clone(), diff --git a/zkevm-circuits/src/witness/step.rs b/zkevm-circuits/src/witness/step.rs index f342ed0ecf..479aedb589 100644 --- a/zkevm-circuits/src/witness/step.rs +++ b/zkevm-circuits/src/witness/step.rs @@ -154,6 +154,7 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::CALLER => ExecutionState::CALLER, OpcodeId::CALLVALUE => ExecutionState::CALLVALUE, OpcodeId::EXTCODEHASH => ExecutionState::EXTCODEHASH, + OpcodeId::EXTCODESIZE => ExecutionState::EXTCODESIZE, OpcodeId::BLOCKHASH => ExecutionState::BLOCKHASH, OpcodeId::TIMESTAMP | OpcodeId::NUMBER | OpcodeId::GASLIMIT => { ExecutionState::BLOCKCTXU64 @@ -183,7 +184,6 @@ impl From<&circuit_input_builder::ExecStep> for ExecutionState { OpcodeId::RETURNDATACOPY => ExecutionState::RETURNDATACOPY, // dummy ops OpcodeId::SAR => dummy!(ExecutionState::SAR), - OpcodeId::EXTCODESIZE => dummy!(ExecutionState::EXTCODESIZE), OpcodeId::EXTCODECOPY => dummy!(ExecutionState::EXTCODECOPY), OpcodeId::CREATE => dummy!(ExecutionState::CREATE), OpcodeId::CREATE2 => dummy!(ExecutionState::CREATE2),