From 8fa51159fdbabe8eddbf09f80d0687ed5f35a8a4 Mon Sep 17 00:00:00 2001 From: op-will <232669456+op-will@users.noreply.github.com> Date: Wed, 6 May 2026 17:25:49 -0500 Subject: [PATCH] Adding the ability to fuzz max_priority_fee_per_gas --- crates/cli/src/default_scenarios/erc20.rs | 1 + crates/cli/src/server/static/openrpc.json | 10 +- crates/core/src/generator/constants.rs | 1 + crates/core/src/generator/function_def.rs | 71 ++++++++++++ crates/core/src/generator/templater.rs | 6 + crates/core/src/generator/trait.rs | 131 ++++++++++++++++++++-- crates/core/src/generator/util.rs | 6 +- crates/core/src/test_scenario.rs | 11 +- crates/testfile/src/lib.rs | 1 + 9 files changed, 221 insertions(+), 17 deletions(-) diff --git a/crates/cli/src/default_scenarios/erc20.rs b/crates/cli/src/default_scenarios/erc20.rs index 630cfca0..39504cfd 100644 --- a/crates/cli/src/default_scenarios/erc20.rs +++ b/crates/cli/src/default_scenarios/erc20.rs @@ -119,6 +119,7 @@ impl ToTestConfig for Erc20Args { func_def = func_def.with_fuzz(&[FuzzParam { param: Some("guy".to_string()), value: None, + tx_field: None, min: Some(U256::from(1)), max: Some( U256::from_str("0x0000000000ffffffffffffffffffffffffffffffff").unwrap(), diff --git a/crates/cli/src/server/static/openrpc.json b/crates/cli/src/server/static/openrpc.json index 20c0d09b..34afea47 100644 --- a/crates/cli/src/server/static/openrpc.json +++ b/crates/cli/src/server/static/openrpc.json @@ -15,7 +15,7 @@ "BlobsCliArgs": {"file":"crates/cli/src/default_scenarios/blobs.rs","line":10}, "BuilderParams": {"file":"crates/cli/src/server/rpc_server/types.rs","line":227}, "BuiltinScenarioCli": {"file":"crates/cli/src/default_scenarios/builtin.rs","line":33}, - "BundleCallDefinition": {"file":"crates/core/src/generator/function_def.rs","line":53}, + "BundleCallDefinition": {"file":"crates/core/src/generator/function_def.rs","line":58}, "BundleTypeCli": {"file":"crates/cli/src/commands/common.rs","line":426}, "CompiledContract": {"file":"crates/core/src/generator/create_def.rs","line":8}, "ContenderSessionInfo": {"file":"crates/cli/src/server/sessions.rs","line":127}, @@ -29,7 +29,7 @@ "FillBlockCliArgs": {"file":"crates/cli/src/default_scenarios/fill_block.rs","line":17}, "FunctionCallDefinition": {"file":"crates/core/src/generator/function_def.rs","line":12}, "FundAccountsParams": {"file":"crates/cli/src/server/rpc_server/types.rs","line":273}, - "FuzzParam": {"file":"crates/core/src/generator/function_def.rs","line":162}, + "FuzzParam": {"file":"crates/core/src/generator/function_def.rs","line":182}, "RevertCliArgs": {"file":"crates/cli/src/default_scenarios/revert.rs","line":8}, "ServerStatus": {"file":"crates/cli/src/server/rpc_server/types.rs","line":33}, "SessionOptions": {"file":"crates/cli/src/server/rpc_server/types.rs","line":234}, @@ -45,6 +45,7 @@ "TestConfig": {"file":"crates/cli/src/server/rpc_server/types.rs","line":83}, "TestConfigSource": {"file":"crates/cli/src/server/rpc_server/types.rs","line":83}, "TransferStressCliArgs": {"file":"crates/cli/src/default_scenarios/transfers.rs","line":12}, + "TxField": {"file":"crates/core/src/generator/function_def.rs","line":176}, "TxTypeCli": {"file":"crates/cli/src/commands/common.rs","line":395}, "UniV2CliArgs": {"file":"crates/cli/src/default_scenarios/uni_v2.rs","line":15} } @@ -188,9 +189,9 @@ "EthereumOpcode": {"type":"string","enum":["Stop","Add","Mul","Sub","Div","Sdiv","Mod","Smod","Addmod","Mulmod","Exp","Signextend","Lt","Gt","Slt","Sgt","Eq","Iszero","And","Or","Xor","Not","Byte","Shl","Shr","Sar","Sha3","Keccak256","Address","Balance","Origin","Caller","Callvalue","Calldataload","Calldatasize","Calldatacopy","Codesize","Codecopy","Gasprice","Extcodesize","Extcodecopy","Returndatasize","Returndatacopy","Extcodehash","Blockhash","Coinbase","Timestamp","Number","Prevrandao","Gaslimit","Chainid","Selfbalance","Basefee","Pop","Mload","Mstore","Mstore8","Sload","Sstore","Msize","Gas","Log0","Log1","Log2","Log3","Log4","Create","Call","Callcode","Return","Delegatecall","Create2","Staticcall","Revert","Invalid","Selfdestruct"]}, "EthereumPrecompile": {"type":"string","enum":["HashSha256","HashRipemd160","Identity","ModExp","EcAdd","EcMul","EcPairing","Blake2f"]}, "FillBlockCliArgs": {"description":"Taken from the CLI, this is used to fill a block with transactions.","type":"object","properties":{"max_gas_per_block":{"type":"integer"}}}, - "FunctionCallDefinition": {"description":"User-facing definition of a function call to be executed.","type":"object","properties":{"to":{"description":"Address of the contract to call.","type":"string"},"from":{"description":"Address of the tx sender.","type":"string"},"from_pool":{"description":"Get a `from` address from the pool of signers specified here.","type":"string"},"signature":{"description":"Name of the function to call.","type":"string"},"args":{"description":"Parameters to pass to the function.","type":"array","items":{"type":"string"}},"value":{"description":"Value in wei to send with the tx.","type":"string"},"fuzz":{"description":"Parameters to fuzz during the test.","type":"array","items":{"$ref":"#/components/schemas/FuzzParam"}},"kind":{"description":"Optional type of the spam transaction for categorization.","type":"string"},"gas_limit":{"description":"Optional gas limit, which will skip gas estimation. This allows reverting txs to be sent.","type":"integer"},"blob_data":{"description":"Optional blob data; tx type must be set to EIP4844 by spammer","type":"string"},"authorization_address":{"description":"Optional setCode data; tx type must be set to EIP7702 by spammer","type":"string"},"for_all_accounts":{"description":"If true and `from_pool` is set, run this setup transaction for all accounts in the pool. Defaults to false (only runs for the first account).","type":"boolean"}},"required":["to","for_all_accounts"]}, + "FunctionCallDefinition": {"description":"User-facing definition of a function call to be executed.","type":"object","properties":{"to":{"description":"Address of the contract to call.","type":"string"},"from":{"description":"Address of the tx sender.","type":"string"},"from_pool":{"description":"Get a `from` address from the pool of signers specified here.","type":"string"},"signature":{"description":"Name of the function to call.","type":"string"},"args":{"description":"Parameters to pass to the function.","type":"array","items":{"type":"string"}},"value":{"description":"Value in wei to send with the tx.","type":"string"},"fuzz":{"description":"Parameters to fuzz during the test.","type":"array","items":{"$ref":"#/components/schemas/FuzzParam"}},"kind":{"description":"Optional type of the spam transaction for categorization.","type":"string"},"gas_limit":{"description":"Optional gas limit, which will skip gas estimation. This allows reverting txs to be sent.","type":"integer"},"blob_data":{"description":"Optional blob data; tx type must be set to EIP4844 by spammer","type":"string"},"authorization_address":{"description":"Optional setCode data; tx type must be set to EIP7702 by spammer","type":"string"},"for_all_accounts":{"description":"If true and `from_pool` is set, run this setup transaction for all accounts in the pool. Defaults to false (only runs for the first account).","type":"boolean"},"max_priority_fee_per_gas":{"description":"Optional EIP-1559 priority fee (wei) for this tx. May be a `{placeholder}`. If unset, the spammer falls back to its default (`gas_price / 10`). This field is also fuzzable via `FuzzParam::tx_field = \"max_priority_fee_per_gas\"`.","type":"string"}},"required":["to","for_all_accounts"]}, "FundAccountsParams": {"type":"object","properties":{"sessionId":{"type":"integer"},"agentClass":{"$ref":"#/components/schemas/AgentClass"},"amount":{"type":"string"}},"required":["sessionId","amount"]}, - "FuzzParam": {"type":"object","properties":{"param":{"description":"Name of the parameter to fuzz.","type":"string"},"value":{"description":"Fuzz the `value` field of the tx (ETH sent with the tx).","type":"boolean"},"min":{"description":"Minimum value fuzzer will use.","type":"string"},"max":{"description":"Maximum value fuzzer will use.","type":"string"}}}, + "FuzzParam": {"type":"object","properties":{"param":{"description":"Name of the parameter to fuzz.","type":"string"},"value":{"description":"Fuzz the `value` field of the tx (ETH sent with the tx).","type":"boolean"},"tx_field":{"description":"Fuzz a tx-level field (e.g. `max_priority_fee_per_gas`). Mutually exclusive with `param` and `value`.","$ref":"#/components/schemas/TxField"},"min":{"description":"Minimum value fuzzer will use.","type":"string"},"max":{"description":"Maximum value fuzzer will use.","type":"string"}}}, "RevertCliArgs": {"type":"object","properties":{"gas_use":{"description":"Amount of gas to use before reverting.","type":"integer"}},"required":["gas_use"]}, "ServerStatus": {"description":"Data returned from the `status` endpoint, containing general info about the server.","type":"object","properties":{"numSessions":{"type":"integer"}},"required":["numSessions"]}, "SessionOptions": {"type":"object","properties":{"auth":{"$ref":"#/components/schemas/AuthParams"},"builder":{"$ref":"#/components/schemas/BuilderParams"},"minBalance":{"type":"string"},"timeoutSecs":{"type":"object","properties":{"secs":{"type":"integer"},"nanos":{"type":"integer"}}},"txType":{"$ref":"#/components/schemas/TxTypeCli"},"privateKeys":{"type":"array","items":{"type":"string"}},"agents":{"$ref":"#/components/schemas/AgentParams"},"env":{"type":"object","additionalProperties":{"type":"string"}}}}, @@ -206,6 +207,7 @@ "TestConfig": {"description":"Configuration to run a test scenario; used to generate PlanConfigs. Defines TOML schema for scenario files.","type":"object","properties":{"env":{"description":"Template variables","type":"object","additionalProperties":{"type":"string"}},"create":{"description":"Contract deployments; array of hex-encoded bytecode strings.","type":"array","items":{"$ref":"#/components/schemas/CreateDefinition"}},"setup":{"description":"Setup steps to run before spamming.","type":"array","items":{"$ref":"#/components/schemas/FunctionCallDefinition"}},"spam":{"description":"Function to call in spam txs.","type":"array","items":{"$ref":"#/components/schemas/SpamRequest"}}}}, "TestConfigSource": {"oneOf":[{"type":"object","properties":{"TomlBase64": {"type":"string"}},"required":["TomlBase64"]},{"type":"object","properties":{"Json": {"$ref":"#/components/schemas/TestConfig"}},"required":["Json"]},{"type":"object","properties":{"Builtin": {"$ref":"#/components/schemas/BuiltinScenarioCli"}},"required":["Builtin"]}]}, "TransferStressCliArgs": {"type":"object","properties":{"amount":{"type":"string"},"recipient":{"type":"string"}},"required":["amount"]}, + "TxField": {"description":"Routes a `FuzzParam` to a specific tx-level field (as opposed to a function-call argument or the `value` field).","type":"string","enum":["MaxPriorityFeePerGas"]}, "TxTypeCli": {"type":"string","enum":["legacy","eip1559","eip4844","eip7702"]}, "UniV2CliArgs": {"type":"object","properties":{"num_tokens":{"type":"integer"},"weth_per_token":{"type":"string"},"initial_token_supply":{"type":"string"},"weth_trade_amount":{"type":"string"},"token_trade_amount":{"type":"string"}},"required":["num_tokens","weth_per_token","initial_token_supply"]} } diff --git a/crates/core/src/generator/constants.rs b/crates/core/src/generator/constants.rs index 4c3ca867..ad4aab8f 100644 --- a/crates/core/src/generator/constants.rs +++ b/crates/core/src/generator/constants.rs @@ -1,4 +1,5 @@ pub const VALUE_KEY: &str = "__tx_value_contender__"; +pub const MAX_PRIORITY_FEE_KEY: &str = "__tx_max_priority_fee_contender__"; pub const SENDER_KEY: &str = "_sender"; pub const SETCODE_KEY: &str = "_setCodeSender"; diff --git a/crates/core/src/generator/function_def.rs b/crates/core/src/generator/function_def.rs index 3cc307cd..d4172f47 100644 --- a/crates/core/src/generator/function_def.rs +++ b/crates/core/src/generator/function_def.rs @@ -46,6 +46,11 @@ pub struct FunctionCallDefinition { /// Defaults to false (only runs for the first account). #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub for_all_accounts: bool, + /// Optional EIP-1559 priority fee (wei) for this tx. May be a `{placeholder}`. + /// If unset, the spammer falls back to its default (`gas_price / 10`). + /// This field is also fuzzable via `FuzzParam::tx_field = "max_priority_fee_per_gas"`. + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, } /// User-facing definition of a function call to be executed. @@ -70,6 +75,7 @@ impl FunctionCallDefinition { blob_data: None, authorization_address: None, for_all_accounts: false, + max_priority_fee_per_gas: None, } } @@ -122,6 +128,10 @@ impl FunctionCallDefinition { self.for_all_accounts = for_all_accounts; self } + pub fn with_max_priority_fee_per_gas(mut self, fee_wei: impl AsRef) -> Self { + self.max_priority_fee_per_gas = Some(fee_wei.as_ref().to_owned()); + self + } pub fn sidecar_data(&self) -> Result, GeneratorError> { let sidecar_data = if let Some(data) = self.blob_data.as_ref() { @@ -154,16 +164,29 @@ pub struct FunctionCallDefinitionStrict { pub fuzz: Vec, pub kind: Option, pub gas_limit: Option, + pub max_priority_fee_per_gas: Option, // may be a placeholder, so we can't use u128 pub sidecar: Option, pub authorization: Option>, } +/// Routes a `FuzzParam` to a specific tx-level field (as opposed to a +/// function-call argument or the `value` field). +#[derive(Clone, Copy, Deserialize, Debug, Serialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum TxField { + /// EIP-1559 priority fee (wei). + MaxPriorityFeePerGas, +} + #[derive(Clone, Deserialize, Debug, Serialize)] pub struct FuzzParam { /// Name of the parameter to fuzz. pub param: Option, /// Fuzz the `value` field of the tx (ETH sent with the tx). pub value: Option, + /// Fuzz a tx-level field (e.g. `max_priority_fee_per_gas`). + /// Mutually exclusive with `param` and `value`. + pub tx_field: Option, /// Minimum value fuzzer will use. pub min: Option, /// Maximum value fuzzer will use. @@ -221,4 +244,52 @@ mod tests { .with_for_all_accounts(false); assert!(!def.for_all_accounts); } + + #[test] + fn parses_max_priority_fee_per_gas_field() { + let toml = r#" + to = "0x1234567890123456789012345678901234567890" + from_pool = "p" + signature = "burn(uint256)" + max_priority_fee_per_gas = "10000000000" + "#; + let def: FunctionCallDefinition = toml::from_str(toml).unwrap(); + assert_eq!( + def.max_priority_fee_per_gas.as_deref(), + Some("10000000000") + ); + } + + #[test] + fn parses_fuzz_with_tx_field_routing() { + let toml = r#" + to = "0x1234567890123456789012345678901234567890" + from_pool = "p" + signature = "burn(uint256)" + fuzz = [{ tx_field = "max_priority_fee_per_gas", min = "0x2540be400", max = "0x4a817c800" }] + "#; + let def: FunctionCallDefinition = toml::from_str(toml).unwrap(); + let fuzz = def.fuzz.expect("fuzz must parse"); + assert_eq!(fuzz.len(), 1); + assert_eq!(fuzz[0].tx_field, Some(TxField::MaxPriorityFeePerGas)); + assert!(fuzz[0].param.is_none()); + assert!(fuzz[0].value.is_none()); + } + + #[test] + fn rejects_fuzz_with_tx_field_and_param_both_set() { + let toml = r#" + to = "0x1234567890123456789012345678901234567890" + from_pool = "p" + signature = "burn(uint256 n)" + fuzz = [{ param = "n", tx_field = "max_priority_fee_per_gas", min = "0x1", max = "0x2" }] + "#; + // Deserialization itself succeeds; conflict is caught later by + // `parse_map_key` when the fuzz map is built. + let def: FunctionCallDefinition = + toml::from_str(toml).expect("toml parses; conflict caught at runtime"); + let fuzz = def.fuzz.expect("fuzz must parse"); + assert!(fuzz[0].param.is_some()); + assert!(fuzz[0].tx_field.is_some()); + } } diff --git a/crates/core/src/generator/templater.rs b/crates/core/src/generator/templater.rs index a929c3ea..da4274a2 100644 --- a/crates/core/src/generator/templater.rs +++ b/crates/core/src/generator/templater.rs @@ -225,6 +225,11 @@ where .as_ref() .map(|x| self.replace_placeholders(x, placeholder_map)) .and_then(|s| s.parse::().ok()); + let max_priority_fee_per_gas = funcdef + .max_priority_fee_per_gas + .as_ref() + .map(|x| self.replace_placeholders(x, placeholder_map)) + .and_then(|s| s.parse::().ok()); Ok(TransactionRequest { to: Some(TxKind::Call(to)), @@ -232,6 +237,7 @@ where from: Some(funcdef.from), value, gas: funcdef.gas_limit, + max_priority_fee_per_gas, sidecar: funcdef.sidecar.to_owned(), authorization_list: funcdef.authorization.to_owned(), ..Default::default() diff --git a/crates/core/src/generator/trait.rs b/crates/core/src/generator/trait.rs index 0992ae95..52e56835 100644 --- a/crates/core/src/generator/trait.rs +++ b/crates/core/src/generator/trait.rs @@ -4,7 +4,7 @@ use crate::{ generator::{ constants::*, error::GeneratorError, - function_def::{FunctionCallDefinition, FunctionCallDefinitionStrict, FuzzParam}, + function_def::{FunctionCallDefinition, FunctionCallDefinitionStrict, FuzzParam, TxField}, named_txs::{ExecutionRequest, NamedTxRequest, NamedTxRequestBuilder}, seeder::{SeedValue, Seeder}, templater::Templater, @@ -325,6 +325,7 @@ where fuzz: funcdef.fuzz.to_owned().unwrap_or_default(), kind: funcdef.kind.to_owned(), gas_limit: funcdef.gas_limit.to_owned(), + max_priority_fee_per_gas: funcdef.max_priority_fee_per_gas.to_owned(), sidecar: funcdef.sidecar_data()?, authorization: signed_auth.map(|a| vec![a]), }) @@ -563,6 +564,11 @@ where let prepare_tx = |req| { let mut args = get_fuzzed_args(req, &canonical_fuzz_map, i)?; let fuzz_tx_value = get_fuzzed_tx_value(req, &canonical_fuzz_map, i)?; + let fuzz_priority_fee = get_fuzzed_max_priority_fee_per_gas( + req, + &canonical_fuzz_map, + i, + )?; // Special handling for WorldID proof generation if req .kind @@ -593,6 +599,9 @@ where if fuzz_tx_value.is_some() { req.value = fuzz_tx_value; } + if fuzz_priority_fee.is_some() { + req.max_priority_fee_per_gas = fuzz_priority_fee; + } let tx = NamedTxRequest::new( templater.template_function_call( @@ -711,22 +720,56 @@ fn get_fuzzed_tx_value( Ok(None) } +/// If a `FuzzParam` with `tx_field = "max_priority_fee_per_gas"` is present, +/// return the fuzzed wei value (as a decimal string) for `fuzz_idx`. +fn get_fuzzed_max_priority_fee_per_gas( + tx: &FunctionCallDefinition, + fuzz_map: &HashMap>, + fuzz_idx: usize, +) -> Result> { + if let Some(fuzz) = &tx.fuzz { + for fuzz_param in fuzz { + if matches!(fuzz_param.tx_field, Some(TxField::MaxPriorityFeePerGas)) { + let values = fuzz_map + .get(MAX_PRIORITY_FEE_KEY) + .ok_or(GeneratorError::ValueFuzzerNotInitialized)?; + return Ok(Some(values[fuzz_idx].to_string())); + } + } + } + Ok(None) +} + fn parse_map_key(fuzz: FuzzParam) -> Result { - if fuzz.param.is_none() && fuzz.value.is_none() { + // exactly one of `param`, `value`, or `tx_field` must be set + let set_count = [ + fuzz.param.is_some(), + fuzz.value.is_some(), + fuzz.tx_field.is_some(), + ] + .iter() + .filter(|&&b| b) + .count(); + if set_count == 0 { return Err(GeneratorError::FuzzMissingParams); } - if fuzz.param.is_some() && fuzz.value.is_some() { + if set_count > 1 { return Err(GeneratorError::FuzzConflictingParams); } - let key = match (fuzz.param.as_ref(), fuzz.value) { - (Some(param), _) => param.to_owned(), - (None, Some(true)) => VALUE_KEY.to_owned(), - (None, Some(false)) => return Err(GeneratorError::FuzzValueNeedsParam), - _ => return Err(GeneratorError::FuzzInvalid), - }; - - Ok(key) + if let Some(param) = fuzz.param { + return Ok(param); + } + if let Some(field) = fuzz.tx_field { + return Ok(match field { + TxField::MaxPriorityFeePerGas => MAX_PRIORITY_FEE_KEY.to_owned(), + }); + } + match fuzz.value { + Some(true) => Ok(VALUE_KEY.to_owned()), + Some(false) => Err(GeneratorError::FuzzValueNeedsParam), + None => Err(GeneratorError::FuzzInvalid), + } } pub fn sign_auth(signer: &PrivateKeySigner, auth: Authorization) -> Result { @@ -735,3 +778,69 @@ pub fn sign_auth(signer: &PrivateKeySigner, auth: Authorization) -> Result FuzzParam { + FuzzParam { + param: None, + value: None, + tx_field: None, + min: None, + max: None, + } + } + + #[test] + fn parse_map_key_routes_param_by_name() { + let mut p = fuzz_param(); + p.param = Some("guy".to_string()); + assert_eq!(parse_map_key(p).unwrap(), "guy"); + } + + #[test] + fn parse_map_key_routes_value_to_value_key() { + let mut p = fuzz_param(); + p.value = Some(true); + assert_eq!(parse_map_key(p).unwrap(), VALUE_KEY); + } + + #[test] + fn parse_map_key_routes_tx_field_to_priority_fee_key() { + let mut p = fuzz_param(); + p.tx_field = Some(TxField::MaxPriorityFeePerGas); + assert_eq!(parse_map_key(p).unwrap(), MAX_PRIORITY_FEE_KEY); + } + + #[test] + fn parse_map_key_rejects_param_and_tx_field() { + let mut p = fuzz_param(); + p.param = Some("x".to_string()); + p.tx_field = Some(TxField::MaxPriorityFeePerGas); + assert!(matches!( + parse_map_key(p), + Err(GeneratorError::FuzzConflictingParams) + )); + } + + #[test] + fn parse_map_key_rejects_value_and_tx_field() { + let mut p = fuzz_param(); + p.value = Some(true); + p.tx_field = Some(TxField::MaxPriorityFeePerGas); + assert!(matches!( + parse_map_key(p), + Err(GeneratorError::FuzzConflictingParams) + )); + } + + #[test] + fn parse_map_key_rejects_empty() { + assert!(matches!( + parse_map_key(fuzz_param()), + Err(GeneratorError::FuzzMissingParams) + )); + } +} diff --git a/crates/core/src/generator/util.rs b/crates/core/src/generator/util.rs index f69ac1d5..1a737413 100644 --- a/crates/core/src/generator/util.rs +++ b/crates/core/src/generator/util.rs @@ -118,7 +118,11 @@ pub fn complete_tx_request( tx_req.gas_price = Some(gas_price); } TxType::Eip1559 => { - tx_req.max_fee_per_gas = Some(gas_price); + // EIP-1559 requires max_fee_per_gas >= max_priority_fee_per_gas. + // If a fuzzed/explicit priority fee is higher than the sampled + // base+tip cap, raise the cap to keep the tx well-formed. + let max_fee = gas_price.max(priority_fee); + tx_req.max_fee_per_gas = Some(max_fee); tx_req.max_priority_fee_per_gas = Some(priority_fee); tx_req.chain_id = Some(chain_id); } diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index 55c56642..f0c49468 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -940,11 +940,17 @@ where .to_owned(); let mut full_tx = tx_req.to_owned().with_nonce(nonce); + // honor a per-tx priority fee if the scenario set one (e.g., via + // FuzzParam::tx_field = "max_priority_fee_per_gas"); fall back to the + // gas_price/10 default otherwise. + let priority_fee = tx_req + .max_priority_fee_per_gas + .unwrap_or(gas_price / 10); complete_tx_request( &mut full_tx, self.tx_type, gas_price, - gas_price / 10, + priority_fee, gas_limit, self.chain_id, blob_gas_price, @@ -2234,6 +2240,7 @@ pub mod tests { .with_fuzz(&[FuzzParam { param: Some("x".to_string()), value: None, + tx_field: None, min: None, max: None, }]) @@ -2253,6 +2260,7 @@ pub mod tests { .with_fuzz(&[FuzzParam { param: Some("x".to_string()), value: None, + tx_field: None, min: None, max: None, }]) @@ -2272,6 +2280,7 @@ pub mod tests { .with_fuzz(&[FuzzParam { param: Some("x".to_string()), value: None, + tx_field: None, min: None, max: None, }]) diff --git a/crates/testfile/src/lib.rs b/crates/testfile/src/lib.rs index 4b7a472a..294ebb58 100644 --- a/crates/testfile/src/lib.rs +++ b/crates/testfile/src/lib.rs @@ -91,6 +91,7 @@ pub mod tests { .with_fuzz(&[FuzzParam { param: Some("x".to_string()), value: None, + tx_field: None, min: None, max: None, }])