Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/cli/src/default_scenarios/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
10 changes: 6 additions & 4 deletions crates/cli/src/server/static/openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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},
Expand All @@ -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}
}
Expand Down Expand Up @@ -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"}}}},
Expand All @@ -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"]}
}
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/generator/constants.rs
Original file line number Diff line number Diff line change
@@ -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";

Expand Down
71 changes: 71 additions & 0 deletions crates/core/src/generator/function_def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
}

/// User-facing definition of a function call to be executed.
Expand All @@ -70,6 +75,7 @@ impl FunctionCallDefinition {
blob_data: None,
authorization_address: None,
for_all_accounts: false,
max_priority_fee_per_gas: None,
}
}

Expand Down Expand Up @@ -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<str>) -> Self {
self.max_priority_fee_per_gas = Some(fee_wei.as_ref().to_owned());
self
}

pub fn sidecar_data(&self) -> Result<Option<BlobTransactionSidecar>, GeneratorError> {
let sidecar_data = if let Some(data) = self.blob_data.as_ref() {
Expand Down Expand Up @@ -154,16 +164,29 @@ pub struct FunctionCallDefinitionStrict {
pub fuzz: Vec<FuzzParam>,
pub kind: Option<String>,
pub gas_limit: Option<u64>,
pub max_priority_fee_per_gas: Option<String>, // may be a placeholder, so we can't use u128
pub sidecar: Option<BlobTransactionSidecar>,
pub authorization: Option<Vec<SignedAuthorization>>,
}

/// 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<String>,
/// Fuzz the `value` field of the tx (ETH sent with the tx).
pub value: Option<bool>,
/// Fuzz a tx-level field (e.g. `max_priority_fee_per_gas`).
/// Mutually exclusive with `param` and `value`.
pub tx_field: Option<TxField>,
/// Minimum value fuzzer will use.
pub min: Option<U256>,
/// Maximum value fuzzer will use.
Expand Down Expand Up @@ -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());
}
}
6 changes: 6 additions & 0 deletions crates/core/src/generator/templater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,19 @@ where
.as_ref()
.map(|x| self.replace_placeholders(x, placeholder_map))
.and_then(|s| s.parse::<U256>().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::<u128>().ok());

Ok(TransactionRequest {
to: Some(TxKind::Call(to)),
input: alloy::rpc::types::TransactionInput::both(input.into()),
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()
Expand Down
Loading