Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip #2807

Open
wants to merge 8 commits into
base: yair/flow_test_fund_address
Choose a base branch
from
164 changes: 140 additions & 24 deletions crates/mempool_test_utils/src/starknet_api_test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use blockifier::test_utils::contracts::FeatureContract;
use blockifier::test_utils::{create_trivial_calldata, CairoVersion, RunnableCairo1};
use infra_utils::path::resolve_project_relative_path;
use pretty_assertions::assert_ne;
use starknet_api::abi::abi_utils::selector_from_name;
use starknet_api::block::GasPrice;
use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce};
use starknet_api::executable_transaction::AccountTransaction;
Expand All @@ -19,7 +20,8 @@ use starknet_api::state::SierraContractClass;
use starknet_api::test_utils::declare::rpc_declare_tx;
use starknet_api::test_utils::deploy_account::rpc_deploy_account_tx;
use starknet_api::test_utils::invoke::{rpc_invoke_tx, InvokeTxArgs};
use starknet_api::test_utils::NonceManager;
use starknet_api::test_utils::{NonceManager, TEST_ERC20_CONTRACT_ADDRESS2};
use starknet_api::transaction::constants::TRANSFER_ENTRY_POINT_NAME;
use starknet_api::transaction::fields::{
AllResourceBounds,
ContractAddressSalt,
Expand All @@ -28,7 +30,14 @@ use starknet_api::transaction::fields::{
TransactionSignature,
ValidResourceBounds,
};
use starknet_api::{declare_tx_args, deploy_account_tx_args, felt, invoke_tx_args, nonce};
use starknet_api::{
calldata,
declare_tx_args,
deploy_account_tx_args,
felt,
invoke_tx_args,
nonce,
};
use starknet_types_core::felt::Felt;

use crate::{COMPILED_CLASS_HASH_OF_CONTRACT_CLASS, CONTRACT_CLASS_FILE, TEST_FILES_FOLDER};
Expand Down Expand Up @@ -118,8 +127,8 @@ pub fn executable_invoke_tx(cairo_version: CairoVersion) -> AccountTransaction {
let default_account = FeatureContract::AccountWithoutValidations(cairo_version);

let mut tx_generator = MultiAccountTransactionGenerator::new();
tx_generator.register_account(default_account);
tx_generator.account_with_id(0).generate_executable_invoke()
tx_generator.register_deployed_account(default_account);
tx_generator.account_with_id_mut(0).generate_executable_invoke()
}

pub fn generate_deploy_account_with_salt(
Expand All @@ -141,6 +150,13 @@ pub type AccountId = usize;

type SharedNonceManager = Rc<RefCell<NonceManager>>;

// TODO: Separate MultiAccountTransactionGenerator to phases:
// 1. Setup phase - register erc20 contract and initialy deployed account with some balance (produce
// the state diff that represents the initial state so it can be used in the test).
// 2. Execution phase - generate transactions.

// TODO: Add optional StateReader and assert that the state supports each operation (e.g. nonce).

/// Manages transaction generation for multiple pre-funded accounts, internally bumping nonces
/// as needed.
///
Expand All @@ -154,17 +170,29 @@ type SharedNonceManager = Rc<RefCell<NonceManager>>;
/// use blockifier::test_utils::contracts::FeatureContract;
/// use blockifier::test_utils::{CairoVersion, RunnableCairo1};
/// use mempool_test_utils::starknet_api_test_utils::MultiAccountTransactionGenerator;
/// use starknet_api::transaction::fields::ContractAddressSalt;
///
/// let mut tx_generator = MultiAccountTransactionGenerator::new();
/// let some_account_type =
/// FeatureContract::AccountWithoutValidations(CairoVersion::Cairo1(RunnableCairo1::Casm));
/// // Initialize multiple accounts, these can be any account type in `FeatureContract`.
/// tx_generator.register_account_for_flow_test(some_account_type.clone());
/// tx_generator.register_account_for_flow_test(some_account_type);
/// tx_generator.register_deployed_account(some_account_type.clone());
/// tx_generator.register_deployed_account(some_account_type.clone());
///
/// let account_0_tx_with_nonce_0 = tx_generator.account_with_id(0).generate_invoke_with_tip(1);
/// let account_1_tx_with_nonce_0 = tx_generator.account_with_id(1).generate_invoke_with_tip(3);
/// let account_0_tx_with_nonce_1 = tx_generator.account_with_id(0).generate_invoke_with_tip(1);
/// let account_0_tx_with_nonce_0 = tx_generator.account_with_id_mut(0).generate_invoke_with_tip(1);
/// let account_1_tx_with_nonce_0 = tx_generator.account_with_id_mut(1).generate_invoke_with_tip(3);
/// let account_0_tx_with_nonce_1 = tx_generator.account_with_id_mut(0).generate_invoke_with_tip(1);
///
/// // Initialize an undeployed account.
/// let salt = ContractAddressSalt(123_u64.into());
/// tx_generator.register_undeployed_account(some_account_type, salt);
/// let undeployed_account = tx_generator.account_with_id(2).account;
/// // Generate a transfer to fund the undeployed account.
/// let transfer_tx = tx_generator.account_with_id_mut(0).generate_transfer(&undeployed_account);
/// // Generate a deploy account transaction for the undeployed account.
/// let deploy_account_tx = tx_generator.account_with_id_mut(2).generate_deploy_account();
/// // Mark the undeployed account as deployed.
/// tx_generator.account_with_id_mut(2).mark_deployed();
/// ```
// Note: when moving this to starknet api crate, see if blockifier's
// [blockifier::transaction::test_utils::FaultyAccountTxCreatorArgs] can be made to use this.
Expand All @@ -182,19 +210,42 @@ impl MultiAccountTransactionGenerator {
Self::default()
}

pub fn register_account(&mut self, account_contract: FeatureContract) -> RpcTransaction {
/// Registers a new account with the given contract, assuming it is already deployed.
/// Note: the state should reflect it if the account is already deployed.
pub fn register_deployed_account(&mut self, account_contract: FeatureContract) -> AccountId {
let new_account_id = self.account_tx_generators.len();
let (account_tx_generator, default_deploy_account_tx) = AccountTransactionGenerator::new(
new_account_id,
let salt = ContractAddressSalt(new_account_id.into());
let (account_tx_generator, _default_deploy_account_tx) = AccountTransactionGenerator::new(
account_contract,
self.nonce_manager.clone(),
salt,
true,
);
self.account_tx_generators.push(account_tx_generator);
new_account_id
}

default_deploy_account_tx
/// Registers a new undeployed account with the given contract.
pub fn register_undeployed_account(
&mut self,
account_contract: FeatureContract,
contract_address_salt: ContractAddressSalt,
) -> AccountId {
let new_account_id = self.account_tx_generators.len();
let (account_tx_generator, _default_deploy_account_tx) = AccountTransactionGenerator::new(
account_contract,
self.nonce_manager.clone(),
contract_address_salt,
false,
);
self.account_tx_generators.push(account_tx_generator);
new_account_id
}

pub fn account_with_id(&mut self, account_id: AccountId) -> &mut AccountTransactionGenerator {
pub fn account_with_id_mut(
&mut self,
account_id: AccountId,
) -> &mut AccountTransactionGenerator {
self.account_tx_generators.get_mut(account_id).unwrap_or_else(|| {
panic!(
"{account_id:?} not found! This number should be an index of an account in the \
Expand All @@ -203,16 +254,22 @@ impl MultiAccountTransactionGenerator {
})
}

// TODO(deploy_account_support): once we support deploy account in tests, remove this method and
// only use new_account_default in tests. In particular, deploy account txs must be then sent to
// the GW via the add tx endpoint just like other txs.
pub fn register_account_for_flow_test(&mut self, account_contract: FeatureContract) {
self.register_account(account_contract);
pub fn account_with_id(&self, account_id: AccountId) -> &AccountTransactionGenerator {
self.account_tx_generators.get(account_id).unwrap_or_else(|| {
panic!(
"{account_id:?} not found! This number should be an index of an account in the \
initialization array. "
)
})
}

pub fn accounts(&self) -> Vec<Contract> {
self.account_tx_generators.iter().map(|tx_gen| &tx_gen.account).copied().collect()
}

pub fn account_tx_generators(&mut self) -> &mut Vec<AccountTransactionGenerator> {
&mut self.account_tx_generators
}
}

/// Manages transaction generation for a single account.
Expand All @@ -224,13 +281,20 @@ impl MultiAccountTransactionGenerator {
/// TODO: add more transaction generation methods as needed.
#[derive(Debug)]
pub struct AccountTransactionGenerator {
account: Contract,
pub account: Contract,
pub is_deployed: bool,
nonce_manager: SharedNonceManager,
contract_address_salt: ContractAddressSalt,
}

impl AccountTransactionGenerator {
/// Generate a valid `RpcTransaction` with default parameters.
pub fn generate_invoke_with_tip(&mut self, tip: u64) -> RpcTransaction {
assert!(
self.is_deployed,
"Cannot invoke on behalf of an undeployed account: the first transaction of every \
account must be a deploy account transaction."
);
let nonce = self.next_nonce();
assert_ne!(
nonce,
Expand All @@ -249,6 +313,11 @@ impl AccountTransactionGenerator {
}

pub fn generate_executable_invoke(&mut self) -> AccountTransaction {
assert!(
self.is_deployed,
"Cannot invoke on behalf of an undeployed account: the first transaction of every \
account must be a deploy account transaction."
);
let nonce = self.next_nonce();
assert_ne!(
nonce,
Expand Down Expand Up @@ -280,12 +349,57 @@ impl AccountTransactionGenerator {
rpc_invoke_tx(invoke_tx_args)
}

pub fn sender_address(&mut self) -> ContractAddress {
pub fn generate_transfer(&mut self, to: &Contract) -> RpcTransaction {
let nonce = self.next_nonce();
let entry_point_selector = selector_from_name(TRANSFER_ENTRY_POINT_NAME);
let erc20_address = felt!(TEST_ERC20_CONTRACT_ADDRESS2);

let calldata = calldata![
erc20_address, // Contract address.
entry_point_selector.0, // EP selector.
felt!(3_u8), // Calldata length.
*to.sender_address.key(), // Calldata: recipient.
felt!(1_u8), // Calldata: lsb amount.
felt!(0_u8) // Calldata: msb amount.
];

let invoke_args = invoke_tx_args!(
sender_address: self.sender_address(),
resource_bounds: test_valid_resource_bounds(),
nonce,
calldata
);

rpc_invoke_tx(invoke_args)
}

pub fn generate_deploy_account(&mut self) -> RpcTransaction {
assert!(
!self.is_deployed,
"Cannot deploy an already deployed account: the first transaction of every account \
must be a deploy account transaction."
);
let nonce = self.next_nonce();
assert_ne!(nonce, nonce!(0), "The deploy account tx should have nonce 0.");
let deploy_account_args = deploy_account_tx_args!(
class_hash: self.account.class_hash(),
resource_bounds: test_valid_resource_bounds(),
contract_address_salt: ContractAddressSalt(self.contract_address_salt.0)
);
rpc_deploy_account_tx(deploy_account_args)
}

pub fn mark_deployed(&mut self) {
// TODO: Assert that the account is actually deployed once we have a StateReader.
self.is_deployed = true;
}

pub fn sender_address(&self) -> ContractAddress {
self.account.sender_address
}

/// Retrieves the nonce for the current account, and __increments__ it internally.
pub fn next_nonce(&mut self) -> Nonce {
fn next_nonce(&mut self) -> Nonce {
let sender_address = self.sender_address();
self.nonce_manager.borrow_mut().next(sender_address)
}
Expand All @@ -295,11 +409,11 @@ impl AccountTransactionGenerator {
// TODO: add a version that doesn't rely on the default deploy account constructor, but takes
// deploy account args.
fn new(
account_id: usize,
account: FeatureContract,
nonce_manager: SharedNonceManager,
contract_address_salt: ContractAddressSalt,
is_deployed: bool,
) -> (Self, RpcTransaction) {
let contract_address_salt = ContractAddressSalt(account_id.into());
// A deploy account transaction must be created now in order to affix an address to it.
// If this doesn't happen now it'll be difficult to fund the account during test setup.
let default_deploy_account_tx =
Expand All @@ -308,6 +422,8 @@ impl AccountTransactionGenerator {
let mut account_tx_generator = Self {
account: Contract::new_for_account(account, &default_deploy_account_tx),
nonce_manager,
is_deployed,
contract_address_salt,
};
// Bump the account nonce after transaction creation.
account_tx_generator.next_nonce();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async fn await_block(
.ok_or(())
}

pub async fn end_to_end_integration(mut tx_generator: MultiAccountTransactionGenerator) {
pub async fn end_to_end_integration(tx_generator: MultiAccountTransactionGenerator) {
const EXPECTED_BLOCK_NUMBER: BlockNumber = BlockNumber(15);

info!("Checking that the sequencer node executable is present.");
Expand Down
25 changes: 15 additions & 10 deletions crates/starknet_integration_tests/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ use starknet_sequencer_node::config::test_utils::RequiredParams;
use starknet_state_sync::config::StateSyncConfig;
use starknet_types_core::felt::Felt;

pub const ACCOUNT_ID_0: AccountId = 0;
pub const ACCOUNT_ID_1: AccountId = 1;

pub fn create_chain_info() -> ChainInfo {
let mut chain_info = ChainInfo::create_for_testing();
// Note that the chain_id affects hashes of transactions and blocks, therefore affecting the
Expand Down Expand Up @@ -148,24 +151,21 @@ pub fn create_integration_test_tx_generator() -> MultiAccountTransactionGenerato
FeatureContract::AccountWithoutValidations(CairoVersion::Cairo1(RunnableCairo1::Casm)),
FeatureContract::AccountWithoutValidations(CairoVersion::Cairo0),
] {
tx_generator.register_account_for_flow_test(account);
tx_generator.register_deployed_account(account);
}
tx_generator
}

fn create_txs_for_integration_test(
pub fn create_txs_for_integration_test(
tx_generator: &mut MultiAccountTransactionGenerator,
) -> Vec<RpcTransaction> {
const ACCOUNT_ID_0: AccountId = 0;
const ACCOUNT_ID_1: AccountId = 1;

// Create RPC transactions.
let account0_invoke_nonce1 =
tx_generator.account_with_id(ACCOUNT_ID_0).generate_invoke_with_tip(2);
tx_generator.account_with_id_mut(ACCOUNT_ID_0).generate_invoke_with_tip(2);
let account0_invoke_nonce2 =
tx_generator.account_with_id(ACCOUNT_ID_0).generate_invoke_with_tip(3);
tx_generator.account_with_id_mut(ACCOUNT_ID_0).generate_invoke_with_tip(3);
let account1_invoke_nonce1 =
tx_generator.account_with_id(ACCOUNT_ID_1).generate_invoke_with_tip(4);
tx_generator.account_with_id_mut(ACCOUNT_ID_1).generate_invoke_with_tip(4);

vec![account0_invoke_nonce1, account0_invoke_nonce2, account1_invoke_nonce1]
}
Expand All @@ -176,7 +176,7 @@ fn create_account_txs(
n_txs: usize,
) -> Vec<RpcTransaction> {
(0..n_txs)
.map(|_| tx_generator.account_with_id(account_id).generate_invoke_with_tip(1))
.map(|_| tx_generator.account_with_id_mut(account_id).generate_invoke_with_tip(1))
.collect()
}

Expand All @@ -198,14 +198,19 @@ where
/// list of transaction hashes, in the order they are expected to be in the mempool.
pub async fn run_integration_test_scenario<'a, Fut>(
tx_generator: &mut MultiAccountTransactionGenerator,
create_rpc_txs_fn: impl Fn(&mut MultiAccountTransactionGenerator) -> Vec<RpcTransaction>,
send_rpc_tx_fn: &'a mut dyn FnMut(RpcTransaction) -> Fut,
test_tx_hashes_fn: impl Fn(&[TransactionHash]) -> Vec<TransactionHash>,
) -> Vec<TransactionHash>
where
Fut: Future<Output = TransactionHash> + 'a,
{
let rpc_txs = create_txs_for_integration_test(tx_generator);
let rpc_txs = create_rpc_txs_fn(tx_generator);
let tx_hashes = send_rpc_txs(rpc_txs, send_rpc_tx_fn).await;
test_tx_hashes_fn(&tx_hashes)
}

pub fn test_tx_hashes_for_integration_test(tx_hashes: &[TransactionHash]) -> Vec<TransactionHash> {
// Return the transaction hashes in the order they should be given by the mempool:
// Transactions from the same account are ordered by nonce; otherwise, higher tips are given
// priority.
Expand Down
Loading
Loading