diff --git a/crates/generator/src/account_lock_manage/secp256k1.rs b/crates/generator/src/account_lock_manage/secp256k1.rs index 7f43853a6..978bc86ed 100644 --- a/crates/generator/src/account_lock_manage/secp256k1.rs +++ b/crates/generator/src/account_lock_manage/secp256k1.rs @@ -15,6 +15,7 @@ use gw_types::{ bytes::Bytes, packed::{L2Transaction, RawL2Transaction, Script}, }; +use gw_utils::polyjuice_parser::PolyjuiceParser; use lazy_static::lazy_static; use secp256k1::recovery::{RecoverableSignature, RecoveryId}; use sha3::{Digest, Keccak256}; @@ -376,30 +377,14 @@ fn calc_godwoken_signing_message( } fn try_assemble_polyjuice_args(raw_tx: RawL2Transaction, receiver_script: Script) -> Option { - let args: Bytes = raw_tx.args().unpack(); - if args.len() < 52 { - return None; - } - if args[0..7] != b"\xFF\xFF\xFFPOLY"[..] { - return None; - } + let parser = PolyjuiceParser::from_raw_l2_tx(&raw_tx)?; let mut stream = rlp::RlpStream::new(); stream.begin_unbounded_list(); let nonce: u32 = raw_tx.nonce().unpack(); stream.append(&nonce); - let gas_price = { - let mut data = [0u8; 16]; - data.copy_from_slice(&args[16..32]); - u128::from_le_bytes(data) - }; - stream.append(&gas_price); - let gas_limit = { - let mut data = [0u8; 8]; - data.copy_from_slice(&args[8..16]); - u64::from_le_bytes(data) - }; - stream.append(&gas_limit); - let to = if args[7] == 3 { + stream.append(&parser.gas_price()); + stream.append(&parser.gas()); + let to = if parser.is_create() { // 3 for EVMC_CREATE vec![0u8; 0] } else { @@ -418,21 +403,8 @@ fn try_assemble_polyjuice_args(raw_tx: RawL2Transaction, receiver_script: Script to }; stream.append(&to); - let value = { - let mut data = [0u8; 16]; - data.copy_from_slice(&args[32..48]); - u128::from_le_bytes(data) - }; - stream.append(&value); - let payload_length = { - let mut data = [0u8; 4]; - data.copy_from_slice(&args[48..52]); - u32::from_le_bytes(data) - } as usize; - if args.len() != 52 + payload_length { - return None; - } - stream.append(&args[52..52 + payload_length].to_vec()); + stream.append(&parser.value()); + stream.append(&parser.data().to_vec()); stream.append(&raw_tx.chain_id().unpack()); stream.append(&0u8); stream.append(&0u8); diff --git a/crates/generator/src/error.rs b/crates/generator/src/error.rs index d3355aa04..f6cd4b226 100644 --- a/crates/generator/src/error.rs +++ b/crates/generator/src/error.rs @@ -155,6 +155,8 @@ pub enum TransactionError { NoCost, #[error("Nonce Overflow")] NonceOverflow, + #[error("Intrinsic gas")] + IntrinsicGas, } impl From for TransactionError { diff --git a/crates/generator/src/generator.rs b/crates/generator/src/generator.rs index 8ce3dc017..b5f5dedb7 100644 --- a/crates/generator/src/generator.rs +++ b/crates/generator/src/generator.rs @@ -689,17 +689,17 @@ impl Generator { { run_result.write.logs.push(log); } - let args = tx.extract_tx_args().ok_or(TransactionError::NoCost)?; + let parser = tx.parser().ok_or(TransactionError::NoCost)?; let gas_used = match read_polyjuice_gas_used(&run_result) { Some(gas_used) => gas_used, None => { log::warn!( "[gw-generator] failed to parse gas_used, use gas_limit instead" ); - args.gas_limit + parser.gas() } }; - gw_types::U256::from(gas_used).checked_mul(args.gas_price.into()) + gw_types::U256::from(gas_used).checked_mul(parser.gas_price().into()) } } .ok_or(TransactionError::NoCost)?; diff --git a/crates/generator/src/typed_transaction/types.rs b/crates/generator/src/typed_transaction/types.rs index edff2cf2a..7a169f815 100644 --- a/crates/generator/src/typed_transaction/types.rs +++ b/crates/generator/src/typed_transaction/types.rs @@ -8,6 +8,7 @@ use gw_types::{ prelude::*, U256, }; +use gw_utils::polyjuice_parser::PolyjuiceParser; /// Types Transaction pub enum TypedRawTransaction { @@ -123,64 +124,57 @@ impl SimpleUDTTx { } } -pub struct PolyjuiceTxArgs { - pub value: u128, - pub gas_price: u128, - pub gas_limit: u64, -} - pub struct PolyjuiceTx(RawL2Transaction); impl PolyjuiceTx { - pub fn extract_tx_args(&self) -> Option { - let args: Bytes = self.0.args().unpack(); - if args.len() < 52 { - log::error!( - "[gw-generator] parse PolyjuiceTx error, wrong args.len expected: >= 52, actual: {}", - args.len() - ); - return None; - } - if args[0..7] != b"\xFF\xFF\xFFPOLY"[..] { - log::error!("[gw-generator] parse PolyjuiceTx error, invalid args",); - return None; - } - - // parse gas price, gas limit, value - let gas_price = { - let mut data = [0u8; 16]; - data.copy_from_slice(&args[16..32]); - u128::from_le_bytes(data) - }; - let gas_limit = { - let mut data = [0u8; 8]; - data.copy_from_slice(&args[8..16]); - u64::from_le_bytes(data) - }; - - let value = { - let mut data = [0u8; 16]; - data.copy_from_slice(&args[32..48]); - u128::from_le_bytes(data) - }; - Some(PolyjuiceTxArgs { - value, - gas_price, - gas_limit, - }) + pub fn parser(&self) -> Option { + PolyjuiceParser::from_raw_l2_tx(&self.0) } /// Total cost of a tx, sender's balance must sufficient to pay Cost(value + gas_price * gas_limit) pub fn cost(&self) -> Option { - match self.extract_tx_args() { - Some(PolyjuiceTxArgs { - value, - gas_price, - gas_limit, - }) => { - let cost = value.checked_add(gas_price.checked_mul(gas_limit.into())?)?; + match self.parser() { + Some(parser) => { + let cost = parser + .value() + .checked_add(parser.gas_price().checked_mul(parser.gas().into())?)?; cost.try_into().ok() } None => None, } } + + /// Intrinsic gas + pub fn intrinsic_gas(&self) -> Option { + // Minimal gas of a normal transaction + const MIN_TX_GAS: u64 = 21000; + // Minimal gas of a transaction that creates a contract + const MIN_CONTRACT_CREATION_TX_GAS: u64 = 53000; + // Gas per byte of non zero data attached to a transaction + const DATA_NONE_ZERO_GAS: u64 = 16; + // Gas per byte of data attached to a transaction + const DATA_ZERO_GAS: u64 = 4; + + let p = self.parser()?; + + // Set the starting gas for the raw transaction + let mut gas = if p.is_create() { + MIN_CONTRACT_CREATION_TX_GAS + } else { + MIN_TX_GAS + }; + if p.data_size() > 0 { + let mut non_zeros = 0u64; + for &b in p.data() { + if b != 0 { + non_zeros += 1; + } + } + // nonzero bytes gas + gas = gas.checked_add(non_zeros.checked_mul(DATA_NONE_ZERO_GAS)?)?; + let zeros = p.data_size() as u64 - non_zeros; + // zero bytes gas + gas = gas.checked_add(zeros.checked_mul(DATA_ZERO_GAS)?)?; + } + Some(gas) + } } diff --git a/crates/generator/src/verification/transaction.rs b/crates/generator/src/verification/transaction.rs index 78660cf26..bba13d27f 100644 --- a/crates/generator/src/verification/transaction.rs +++ b/crates/generator/src/verification/transaction.rs @@ -65,23 +65,29 @@ impl<'a, S: State + CodeStore> TransactionVerifier<'a, S> { .state .get_registry_address_by_script_hash(ETH_REGISTRY_ACCOUNT_ID, &sender_script_hash)? .ok_or(AccountError::RegistryAddressNotFound)?; + // get balance let balance = self .state .get_sudt_balance(CKB_SUDT_ACCOUNT_ID, &sender_address)?; - // get balance - let tx_cost = { - let tx_type = get_tx_type(self.rollup_context, self.state, &tx.raw())?; - let typed_tx = TypedRawTransaction::from_tx(tx.raw(), tx_type) - .ok_or(AccountError::UnknownScript)?; - // reject txs has no cost, these transaction can only be execute without modify state tree - typed_tx - .cost() - .map(Into::into) - .ok_or(TransactionError::NoCost)? - }; + let tx_type = get_tx_type(self.rollup_context, self.state, &tx.raw())?; + let typed_tx = + TypedRawTransaction::from_tx(tx.raw(), tx_type).ok_or(AccountError::UnknownScript)?; + // reject txs has no cost, these transaction can only be execute without modify state tree + let tx_cost = typed_tx + .cost() + .map(Into::into) + .ok_or(TransactionError::NoCost)?; if balance < tx_cost { return Err(TransactionError::InsufficientBalance.into()); } + // Intrinsic Gas + if let TypedRawTransaction::Polyjuice(tx) = typed_tx { + let p = tx.parser().ok_or(TransactionError::IntrinsicGas)?; + let intrinsic_gas = tx.intrinsic_gas().ok_or(TransactionError::IntrinsicGas)?; + if p.gas() < intrinsic_gas { + return Err(TransactionError::IntrinsicGas.into()); + } + } Ok(()) } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 5776fb6c6..d334fe71e 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -1,6 +1,7 @@ pub mod exponential_backoff; pub mod fee; pub mod genesis_info; +pub mod polyjuice_parser; pub mod script_log; pub mod since; pub mod transaction_skeleton; diff --git a/crates/utils/src/polyjuice_parser.rs b/crates/utils/src/polyjuice_parser.rs new file mode 100644 index 000000000..c30f59c76 --- /dev/null +++ b/crates/utils/src/polyjuice_parser.rs @@ -0,0 +1,58 @@ +use gw_types::{bytes::Bytes, packed::RawL2Transaction, prelude::*}; + +/// The data structure of the Polyjuice transaction arguments +/// +/// see: https://github.com/nervosnetwork/godwoken-polyjuice/blob/main/README.md#polyjuice-arguments +pub struct PolyjuiceParser(Bytes); + +impl PolyjuiceParser { + pub fn from_raw_l2_tx(raw_tx: &RawL2Transaction) -> Option { + let args: Bytes = raw_tx.args().unpack(); + let args_len = args.len(); + if args_len < 52 { + return None; + } + if args[0..7] != b"\xFF\xFF\xFFPOLY"[..] { + return None; + } + let parser = Self(args); + // check data size + if args_len != 52 + parser.data_size() { + return None; + } + Some(parser) + } + + pub fn gas(&self) -> u64 { + let mut data = [0u8; 8]; + data.copy_from_slice(&self.0[8..16]); + u64::from_le_bytes(data) + } + + pub fn gas_price(&self) -> u128 { + let mut data = [0u8; 16]; + data.copy_from_slice(&self.0[16..32]); + u128::from_le_bytes(data) + } + + pub fn is_create(&self) -> bool { + // 3 for EVMC_CREATE + self.0[7] == 3 + } + + pub fn value(&self) -> u128 { + let mut data = [0u8; 16]; + data.copy_from_slice(&self.0[32..48]); + u128::from_le_bytes(data) + } + + pub fn data_size(&self) -> usize { + let mut data = [0u8; 4]; + data.copy_from_slice(&self.0[48..52]); + u32::from_le_bytes(data) as usize + } + + pub fn data(&self) -> &[u8] { + &self.0[52..52 + self.data_size()] + } +}