diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index adccf7d..9cfa5c7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,6 +20,8 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - name: Install libsqlite3 + run: sudo apt-get install -y libsqlite3-dev - name: Build run: cargo build --verbose --workspace @@ -36,5 +38,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true + - name: Install libsqlite3 + run: sudo apt-get install -y libsqlite3-dev - name: Run tests run: cargo test --verbose --workspace diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 6438254..ccb5c44 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -74,7 +74,7 @@ pub async fn run( .await?; let contract_name = "SpamMe"; - let contract_result = db.get_named_tx(contract_name)?; + let contract_result = db.get_named_tx(contract_name, rpc_url.as_str())?; let do_deploy_contracts = if contract_result.is_some() { let input = prompt_cli(format!( "{} deployment already detected. Re-deploy? [y/N]", diff --git a/crates/core/src/db/mock.rs b/crates/core/src/db/mock.rs index dc216b2..5dc2cb8 100644 --- a/crates/core/src/db/mock.rs +++ b/crates/core/src/db/mock.rs @@ -18,11 +18,11 @@ impl DbOps for MockDb { Ok(0) } - fn insert_named_txs(&self, _named_txs: Vec) -> Result<()> { + fn insert_named_txs(&self, _named_txs: Vec, _rpc_url: &str) -> Result<()> { Ok(()) } - fn get_named_tx(&self, _name: &str) -> Result> { + fn get_named_tx(&self, _name: &str, _rpc_url: &str) -> Result> { Ok(Some(NamedTx::new( String::default(), TxHash::default(), diff --git a/crates/core/src/db/mod.rs b/crates/core/src/db/mod.rs index 349a66d..3c3555d 100644 --- a/crates/core/src/db/mod.rs +++ b/crates/core/src/db/mod.rs @@ -50,9 +50,9 @@ pub trait DbOps { fn num_runs(&self) -> Result; - fn insert_named_txs(&self, named_txs: Vec) -> Result<()>; + fn insert_named_txs(&self, named_txs: Vec, rpc_url: &str) -> Result<()>; - fn get_named_tx(&self, name: &str) -> Result>; + fn get_named_tx(&self, name: &str, rpc_url: &str) -> Result>; fn get_named_tx_by_address(&self, address: &Address) -> Result>; diff --git a/crates/core/src/generator/mod.rs b/crates/core/src/generator/mod.rs index 1cf334b..3abf6f0 100644 --- a/crates/core/src/generator/mod.rs +++ b/crates/core/src/generator/mod.rs @@ -102,6 +102,7 @@ where fn get_db(&self) -> &D; fn get_fuzz_seeder(&self) -> &impl Seeder; fn get_agent_store(&self) -> &AgentStore; + fn get_rpc_url(&self) -> String; /// Generates a map of N=`num_values` fuzzed values for each parameter in `fuzz_args`. fn create_fuzz_map( @@ -258,7 +259,12 @@ where let step = self.make_strict_create(step, 0)?; // lookup placeholder values in DB & update map before templating - templater.find_placeholder_values(&step.bytecode, &mut placeholder_map, db)?; + templater.find_placeholder_values( + &step.bytecode, + &mut placeholder_map, + db, + &self.get_rpc_url(), + )?; // create tx with template values let tx = NamedTxRequestBuilder::new( @@ -280,10 +286,11 @@ where let setup_steps = conf.get_setup_steps()?; // txs will be grouped by account [from=1, from=1, from=1, from=2, from=2, from=2, ...] + let rpc_url = self.get_rpc_url(); for step in setup_steps.iter() { // lookup placeholders in DB & update map before templating - templater.find_fncall_placeholders(step, db, &mut placeholder_map)?; + templater.find_fncall_placeholders(step, db, &mut placeholder_map, &rpc_url)?; // setup tx with template values let tx = NamedTxRequest::new( @@ -321,8 +328,10 @@ where }; // finds placeholders in a function call definition and populates `placeholder_map` and `canonical_fuzz_map` with injectable values. + let rpc_url = self.get_rpc_url(); let mut lookup_tx_placeholders = |tx: &FunctionCallDefinition| { - let res = templater.find_fncall_placeholders(tx, db, &mut placeholder_map); + let res = + templater.find_fncall_placeholders(tx, db, &mut placeholder_map, &rpc_url); if let Err(e) = res { eprintln!("error finding placeholders: {}", e); return Err(ContenderError::SpamError( diff --git a/crates/core/src/generator/templater.rs b/crates/core/src/generator/templater.rs index bedf5f8..42475e7 100644 --- a/crates/core/src/generator/templater.rs +++ b/crates/core/src/generator/templater.rs @@ -31,6 +31,7 @@ where arg: &str, placeholder_map: &mut HashMap, db: &impl DbOps, + rpc_url: &str, ) -> Result<()> { // count number of placeholders (by left brace) in arg let num_template_vals = self.num_placeholders(arg); @@ -57,25 +58,27 @@ where } let template_value = db - .get_named_tx(&template_key.to_string()) + .get_named_tx(&template_key.to_string(), rpc_url) .map_err(|e| { ContenderError::SpamError( - "failed to get placeholder value from DB", + "Failed to get named tx from DB. There may be an issue with your database.", Some(format!("value={:?} ({})", template_key, e)), ) - })? - .ok_or(ContenderError::SpamError( - "failed to find placeholder value in DB", + })?; + if let Some(template_value) = template_value { + placeholder_map.insert( + template_key, + template_value + .address + .map(|a| self.encode_contract_address(&a)) + .unwrap_or_default(), + ); + } else { + return Err(ContenderError::SpamError( + "Address for named contract not found in DB. You may need to run setup steps first.", Some(template_key.to_string()), - ))?; - - placeholder_map.insert( - template_key, - template_value - .address - .map(|a| self.encode_contract_address(&a)) - .unwrap_or_default(), - ); + )); + } } Ok(()) } @@ -88,13 +91,14 @@ where fncall: &FunctionCallDefinition, db: &impl DbOps, placeholder_map: &mut HashMap, + rpc_url: &str, ) -> Result<()> { // find templates in fn args & `to` let fn_args = fncall.args.to_owned().unwrap_or_default(); for arg in fn_args.iter() { - self.find_placeholder_values(arg, placeholder_map, db)?; + self.find_placeholder_values(arg, placeholder_map, db, rpc_url)?; } - self.find_placeholder_values(&fncall.to, placeholder_map, db)?; + self.find_placeholder_values(&fncall.to, placeholder_map, db, rpc_url)?; Ok(()) } diff --git a/crates/core/src/test_scenario.rs b/crates/core/src/test_scenario.rs index a1c91d3..c75b656 100644 --- a/crates/core/src/test_scenario.rs +++ b/crates/core/src/test_scenario.rs @@ -171,6 +171,7 @@ where "deploying contract: {:?}", tx_req.name.as_ref().unwrap_or(&"".to_string()) ); + let rpc_url = self.rpc_url.to_owned(); let handle = tokio::task::spawn(async move { // estimate gas limit let gas_limit = wallet @@ -217,6 +218,7 @@ where receipt.contract_address, ) .into(), + rpc_url.as_str(), ) .expect("failed to insert tx into db"); }); @@ -253,7 +255,7 @@ where let wallet = ProviderBuilder::new() .with_simple_nonce_management() .wallet(wallet) - .on_http(rpc_url); + .on_http(rpc_url.to_owned()); let chain_id = wallet.get_chain_id().await.expect("failed to get chain id"); let gas_price = wallet @@ -280,6 +282,7 @@ where db.insert_named_txs( NamedTx::new(name, receipt.transaction_hash, receipt.contract_address) .into(), + rpc_url.as_str(), ) .expect("failed to insert tx into db"); } @@ -595,6 +598,10 @@ where fn get_agent_store(&self) -> &AgentStore { &self.agent_store } + + fn get_rpc_url(&self) -> String { + self.rpc_url.to_string() + } } #[cfg(test)] diff --git a/crates/sqlite_db/src/lib.rs b/crates/sqlite_db/src/lib.rs index 187e53c..3afdda2 100644 --- a/crates/sqlite_db/src/lib.rs +++ b/crates/sqlite_db/src/lib.rs @@ -136,12 +136,21 @@ impl DbOps for SqliteDb { )", params![], )?; + self.execute( + "CREATE TABLE rpc_urls ( + id INTEGER PRIMARY KEY, + url TEXT NOT NULL UNIQUE + )", + params![], + )?; self.execute( "CREATE TABLE named_txs ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, tx_hash TEXT NOT NULL, - contract_address TEXT + contract_address TEXT, + rpc_url_id INTEGER NOT NULL, + FOREIGN KEY (rpc_url_id) REFERENCES rpc_urls(id) )", params![], )?; @@ -196,14 +205,30 @@ impl DbOps for SqliteDb { Ok(res) } - fn insert_named_txs(&self, named_txs: Vec) -> Result<()> { + fn insert_named_txs(&self, named_txs: Vec, rpc_url: &str) -> Result<()> { let pool = self.get_pool()?; + + // first check the rpc_urls table; insert if not present + pool.execute( + "INSERT OR IGNORE INTO rpc_urls (url) VALUES (?)", + params![rpc_url], + ) + .map_err(|e| ContenderError::with_err(e, "failed to insert rpc_url into DB"))?; + + // then get the rpc_url ID + let rpc_url_id: i64 = self.query_row( + "SELECT id FROM rpc_urls WHERE url = ?1", + params![rpc_url], + |row| row.get(0), + )?; + let stmts = named_txs.iter().map(|tx| { format!( - "INSERT INTO named_txs (name, tx_hash, contract_address) VALUES ('{}', '{}', '{}');", + "INSERT INTO named_txs (name, tx_hash, contract_address, rpc_url_id) VALUES ('{}', '{}', '{}', {});", tx.name, tx.tx_hash.encode_hex(), - tx.address.map(|a| a.encode_hex()).unwrap_or_default() + tx.address.map(|a| a.encode_hex()).unwrap_or_default(), + rpc_url_id, ) }); pool.execute_batch(&format!( @@ -218,16 +243,18 @@ impl DbOps for SqliteDb { Ok(()) } - fn get_named_tx(&self, name: &str) -> Result> { + fn get_named_tx(&self, name: &str, rpc_url: &str) -> Result> { let pool = self.get_pool()?; let mut stmt = pool .prepare( - "SELECT name, tx_hash, contract_address FROM named_txs WHERE name = ?1 ORDER BY id DESC LIMIT 1", + "SELECT name, tx_hash, contract_address, rpc_url_id FROM named_txs WHERE name = ?1 AND rpc_url_id = ( + SELECT id FROM rpc_urls WHERE url = ?2 + ) ORDER BY id DESC LIMIT 1", ) .map_err(|e| ContenderError::with_err(e, "failed to prepare statement"))?; let row = stmt - .query_map(params![name], NamedTxRow::from_row) + .query_map(params![name, rpc_url], NamedTxRow::from_row) .map_err(|e| ContenderError::with_err(e, "failed to map row"))?; let res = row .last() @@ -328,10 +355,14 @@ mod tests { let contract_address = Some(Address::from_slice(&[4u8; 20])); let name1 = "test_tx".to_string(); let name2 = "test_tx2"; - db.insert_named_txs(vec![ - NamedTx::new(name1.to_owned(), tx_hash, contract_address), - NamedTx::new(name2.to_string(), tx_hash, contract_address), - ]) + let rpc_url = "http://test.url:8545"; + db.insert_named_txs( + vec![ + NamedTx::new(name1.to_owned(), tx_hash, contract_address), + NamedTx::new(name2.to_string(), tx_hash, contract_address), + ], + rpc_url, + ) .unwrap(); let count: i64 = db .get_pool() @@ -342,10 +373,12 @@ mod tests { .unwrap(); assert_eq!(count, 2); - let res1 = db.get_named_tx(&name1).unwrap().unwrap(); + let res1 = db.get_named_tx(&name1, rpc_url).unwrap().unwrap(); assert_eq!(res1.name, name1); assert_eq!(res1.tx_hash, tx_hash); assert_eq!(res1.address, contract_address); + let res2 = db.get_named_tx(&name1, "http://wrong.url:8545").unwrap(); + assert!(res2.is_none()); } #[test] diff --git a/crates/testfile/src/types.rs b/crates/testfile/src/types.rs index becd3de..b9d471d 100644 --- a/crates/testfile/src/types.rs +++ b/crates/testfile/src/types.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Configuration to run a test scenario; used to generate PlanConfigs. +/// Defines TOML schema for scenario files. #[derive(Clone, Deserialize, Debug, Serialize)] pub struct TestConfig { /// Template variables diff --git a/scenarios/stress.toml b/scenarios/stress.toml index 08e5c54..d76a98a 100644 --- a/scenarios/stress.toml +++ b/scenarios/stress.toml @@ -1,7 +1,7 @@ [[create]] bytecode = "0x608060405234801561001057600080fd5b5060408051808201909152600d81526c48656c6c6f2c20576f726c642160981b602082015260009061004290826100e7565b506101a5565b634e487b7160e01b600052604160045260246000fd5b600181811c9082168061007257607f821691505b60208210810361009257634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156100e257806000526020600020601f840160051c810160208510156100bf5750805b601f840160051c820191505b818110156100df57600081556001016100cb565b50505b505050565b81516001600160401b0381111561010057610100610048565b6101148161010e845461005e565b84610098565b6020601f82116001811461014857600083156101305750848201515b600019600385901b1c1916600184901b1784556100df565b600084815260208120601f198516915b828110156101785787850151825560209485019460019092019101610158565b50848210156101965786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b610a72806101b46000396000f3fe6080604052600436106100555760003560e01c806369f86ec81461005a5780638199ba20146100715780639402c00414610091578063a329e8de146100b1578063c5eeaf17146100d1578063fb0e722b146100d9575b600080fd5b34801561006657600080fd5b5061006f610104565b005b34801561007d57600080fd5b5061006f61008c3660046106f6565b61010f565b34801561009d57600080fd5b5061006f6100ac36600461074f565b610413565b3480156100bd57600080fd5b5061006f6100cc3660046107a0565b610444565b61006f6104d7565b3480156100e557600080fd5b506100ee610506565b6040516100fb91906107dd565b60405180910390f35b5b60325a1161010557565b6040805180820190915260068152657373746f726560d01b6020820152610137908390610594565b1561015d5760005b818110156101585761015060008055565b60010161013f565b505050565b6040805180820190915260058152641cdb1bd85960da1b6020820152610184908390610594565b1561019c5760005b818110156101585760010161018c565b6040805180820190915260068152656d73746f726560d01b60208201526101c4908390610594565b156101e55760005b81811015610158576101dd60008052565b6001016101cc565b6040805180820190915260058152641b5b1bd85960da1b602082015261020c908390610594565b1561022157600081156101585760010161018c565b60408051808201909152600381526218591960ea1b6020820152610246908390610594565b1561025b57600081156101585760010161018c565b60408051808201909152600381526239bab160e91b6020820152610280908390610594565b1561029557600081156101585760010161018c565b6040805180820190915260038152621b5d5b60ea1b60208201526102ba908390610594565b156102cf57600081156101585760010161018c565b6040805180820190915260038152623234bb60e91b60208201526102f4908390610594565b1561030957600081156101585760010161018c565b60408051808201909152600981526832b1b932b1b7bb32b960b91b6020820152610334908390610594565b156103545760005b818110156101585761034c6105ee565b60010161033c565b60408051808201909152600981526835b2b1b1b0b5991a9b60b91b602082015261037f908390610594565b1561039457600081156101585760010161018c565b60408051808201909152600781526662616c616e636560c81b60208201526103bd908390610594565b156103d257600081156101585760010161018c565b60408051808201909152600681526531b0b63632b960d11b60208201526103fa908390610594565b1561040f57600081156101585760010161018c565b5050565b60008160405160200161042792919061084a565b6040516020818303038152906040526000908161040f919061091e565b600081116104985760405162461bcd60e51b815260206004820152601a60248201527f476173206d7573742062652067726561746572207468616e2030000000000000604482015260640160405180910390fd5b600060956104a8610a28846109dd565b6104b291906109fe565b9050806000036104c0575060015b60005b8181101561015857600080556001016104c3565b60405141903480156108fc02916000818181858888f19350505050158015610503573d6000803e3d6000fd5b50565b6000805461051390610810565b80601f016020809104026020016040519081016040528092919081815260200182805461053f90610810565b801561058c5780601f106105615761010080835404028352916020019161058c565b820191906000526020600020905b81548152906001019060200180831161056f57829003601f168201915b505050505081565b6000816040516020016105a79190610a20565b60405160208183030381529060405280519060200120836040516020016105ce9190610a20565b604051602081830303815290604052805190602001201490505b92915050565b604080516000808252602082018084527f7b05e003631381b3ecd0222e748a7900c262a008c4b7f002ce4a9f0a190619539052604292820183905260608201839052608082019290925260019060a0016020604051602081039080840390855afa158015610660573d6000803e3d6000fd5b50505050565b634e487b7160e01b600052604160045260246000fd5b60008067ffffffffffffffff84111561069757610697610666565b50604051601f19601f85018116603f0116810181811067ffffffffffffffff821117156106c6576106c6610666565b6040528381529050808284018510156106de57600080fd5b83836020830137600060208583010152509392505050565b6000806040838503121561070957600080fd5b823567ffffffffffffffff81111561072057600080fd5b8301601f8101851361073157600080fd5b6107408582356020840161067c565b95602094909401359450505050565b60006020828403121561076157600080fd5b813567ffffffffffffffff81111561077857600080fd5b8201601f8101841361078957600080fd5b6107988482356020840161067c565b949350505050565b6000602082840312156107b257600080fd5b5035919050565b60005b838110156107d45781810151838201526020016107bc565b50506000910152565b60208152600082518060208401526107fc8160408501602087016107b9565b601f01601f19169190910160400192915050565b600181811c9082168061082457607f821691505b60208210810361084457634e487b7160e01b600052602260045260246000fd5b50919050565b600080845461085881610810565b60018216801561086f5760018114610884576108b4565b60ff19831686528115158202860193506108b4565b87600052602060002060005b838110156108ac57815488820152600190910190602001610890565b505081860193505b50505083516108c78183602088016107b9565b01949350505050565b601f82111561015857806000526020600020601f840160051c810160208510156108f75750805b601f840160051c820191505b818110156109175760008155600101610903565b5050505050565b815167ffffffffffffffff81111561093857610938610666565b61094c816109468454610810565b846108d0565b6020601f82116001811461098057600083156109685750848201515b600019600385901b1c1916600184901b178455610917565b600084815260208120601f198516915b828110156109b05787850151825560209485019460019092019101610990565b50848210156109ce5786840151600019600387901b60f8161c191681555b50505050600190811b01905550565b818103818111156105e857634e487b7160e01b600052601160045260246000fd5b600082610a1b57634e487b7160e01b600052601260045260246000fd5b500490565b60008251610a328184602087016107b9565b919091019291505056fea264697066735822122040db52b9a7c8a77f16a18198a6085a3ff5f3e5c378e4a9cd497037d20f775eb864736f6c634300081b0033" name = "SpamMe3" -from = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +from_pool = "admin" [[spam]]