Skip to content
Draft
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
158 changes: 118 additions & 40 deletions crates/starknet-devnet-core/src/account.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::fmt::Display;
use std::sync::Arc;

use blockifier::context::BlockContext;
use blockifier::state::state_api::StateReader;
use blockifier::transaction::account_transaction::ExecutionFlags;
use blockifier::transaction::transactions::ExecutableTransaction;
use starknet_api::core::calculate_contract_address;
use starknet_api::transaction::fields::{Calldata, ContractAddressSalt};
use starknet_api::{felt, patricia_key};
Expand All @@ -11,15 +15,18 @@ use starknet_types::error::Error;
use starknet_types::felt::{ClassHash, Key, felt_from_prefixed_hex, join_felts, split_biguint};
use starknet_types::num_bigint::BigUint;
use starknet_types::rpc::state::Balance;
use starknet_types::rpc::transactions::broadcasted_deploy_account_transaction_v3::BroadcastedDeployAccountTransactionV3;
use starknet_types::rpc::transactions::{
BroadcastedDeployAccountTransaction, BroadcastedTransactionCommonV3, ResourceBoundsWrapper,
};

use crate::constants::{
CHARGEABLE_ACCOUNT_ADDRESS, CHARGEABLE_ACCOUNT_PRIVATE_KEY, CHARGEABLE_ACCOUNT_PUBLIC_KEY,
ISRC6_ID_HEX,
};
use crate::contract_class_choice::{AccountClassWrapper, AccountContractClassChoice};
use crate::error::DevnetResult;
use crate::state::state_readers::DictState;
use crate::state::{CustomState, StarknetState};
use crate::state::StarknetState;
use crate::traits::{Accounted, Deployed};
use crate::utils::get_storage_var_address;

Expand All @@ -32,6 +39,28 @@ pub enum FeeToken {
STRK,
}

#[derive(Clone, Debug, Default, Copy)]
pub enum AccountType {
OpenZeppelin0_5_1,
#[default]
OpenZeppelin0_20_0,
Argent0_4_0,
Custom,
}

impl Display for AccountType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let account_version = match self {
AccountType::OpenZeppelin0_5_1 => "OpenZeppelin 0.5.1",
AccountType::OpenZeppelin0_20_0 => "OpenZeppelin 0.20.0",
AccountType::Argent0_4_0 => "Argent 0.4.0",
AccountType::Custom => "Custom",
};

f.write_str(account_version)
}
}

#[derive(Clone)]
pub struct KeyPair {
pub public_key: Key,
Expand All @@ -44,18 +73,23 @@ pub struct Account {
pub account_address: ContractAddress,
pub initial_balance: Balance,
pub class_hash: ClassHash,
pub class_metadata: &'static str,
pub class_metadata: String,
pub(crate) contract_class: ContractClass,
pub(crate) eth_fee_token_address: ContractAddress,
pub(crate) strk_fee_token_address: ContractAddress,
block_context: BlockContext,
account_type: AccountType,
chain_id: Felt,
}

impl Account {
pub(crate) fn new_chargeable(
eth_fee_token_address: ContractAddress,
strk_fee_token_address: ContractAddress,
block_context: BlockContext,
chain_id: Felt,
) -> DevnetResult<Self> {
let AccountClassWrapper { contract_class, class_hash, class_metadata } =
let AccountClassWrapper { contract_class, class_hash, account_type } =
AccountContractClassChoice::Cairo1.get_class_wrapper()?;

// very big number
Expand All @@ -71,32 +105,41 @@ impl Account {
)?)?,
initial_balance,
class_hash,
class_metadata,
class_metadata: account_type.to_string(),
contract_class,
eth_fee_token_address,
strk_fee_token_address,
block_context,
account_type,
chain_id,
})
}

#[allow(clippy::too_many_arguments)]
pub(crate) fn new(
initial_balance: Balance,
keys: KeyPair,
class_hash: ClassHash,
class_metadata: &'static str,
contract_class: ContractClass,
eth_fee_token_address: ContractAddress,
strk_fee_token_address: ContractAddress,
block_context: BlockContext,
account_type: AccountType,
chain_id: Felt,
) -> DevnetResult<Self> {
let account_address = Account::compute_account_address(&keys.public_key)?;
Ok(Self {
initial_balance,
keys,
class_hash,
class_metadata,
class_metadata: account_type.to_string(),
contract_class,
account_address,
eth_fee_token_address,
strk_fee_token_address,
block_context,
account_type,
chain_id,
})
}

Expand All @@ -114,27 +157,52 @@ impl Account {
Ok(ContractAddress::from(account_address))
}

// simulate constructor logic (register interfaces and set public key), as done in
// https://github.com/OpenZeppelin/cairo-contracts/blob/89a450a88628ec3b86273f261b2d8d1ca9b1522b/src/account/account.cairo#L207-L211
fn simulate_constructor(&self, state: &mut StarknetState) -> DevnetResult<()> {
let core_address = self.account_address.try_into()?;

let interface_storage_var = get_storage_var_address(
"SRC5_supported_interfaces",
&[felt_from_prefixed_hex(ISRC6_ID_HEX)?],
)?;
state.state.state.set_storage_at(
core_address,
interface_storage_var.try_into()?,
Felt::ONE,
)?;
fn calldata(&self) -> Vec<Felt> {
match self.account_type {
AccountType::OpenZeppelin0_5_1 | AccountType::OpenZeppelin0_20_0 => {
vec![self.keys.public_key]
}
AccountType::Argent0_4_0 => todo!(),
AccountType::Custom => vec![],
}
}

let public_key_storage_var = get_storage_var_address("Account_public_key", &[])?;
state.state.state.set_storage_at(
core_address,
public_key_storage_var.try_into()?,
self.keys.public_key,
)?;
/// Invoke constructor by executing BroadcastedDeployAccount transaction and change the state
fn deploy_via_transaction(&self, state: &mut StarknetState) -> DevnetResult<()> {
let core_address = self.account_address.try_into()?;
let mut deploy_account_txn =
BroadcastedDeployAccountTransaction::V3(BroadcastedDeployAccountTransactionV3 {
common: BroadcastedTransactionCommonV3 {
version: Felt::THREE,
signature: vec![],
nonce: Felt::ZERO,
resource_bounds: ResourceBoundsWrapper::new(0, 0, 0, 0, 0, 0),
tip: Default::default(),
paymaster_data: vec![],
nonce_data_availability_mode:
starknet_api::data_availability::DataAvailabilityMode::L1,
fee_data_availability_mode:
starknet_api::data_availability::DataAvailabilityMode::L1,
},
contract_address_salt: Felt::ZERO,
constructor_calldata: self.calldata(),
class_hash: self.class_hash,
})
.create_sn_api_deploy_account(&self.chain_id)?;

deploy_account_txn.contract_address = core_address;

blockifier::transaction::account_transaction::AccountTransaction {
tx: starknet_api::executable_transaction::AccountTransaction::DeployAccount(
deploy_account_txn,
),
execution_flags: ExecutionFlags {
only_query: false,
charge_fee: false,
validate: false,
},
}
.execute(&mut state.state, &self.block_context)?;

Ok(())
}
Expand All @@ -144,19 +212,13 @@ impl Deployed for Account {
fn deploy(&self, state: &mut StarknetState) -> DevnetResult<()> {
self.declare_if_undeclared(state, self.class_hash, &self.contract_class)?;

state.predeploy_contract(self.account_address, self.class_hash)?;

// set balance directly in the most underlying state
self.set_initial_balance(&mut state.state.state)?;

self.simulate_constructor(state)?;
self.deploy_via_transaction(state)?;

Ok(())
}

fn get_address(&self) -> ContractAddress {
self.account_address
}
}

impl Accounted for Account {
Expand Down Expand Up @@ -223,19 +285,26 @@ impl Accounted for Account {

#[cfg(test)]
mod tests {
use blockifier::context::{BlockContext, ChainInfo};

use starknet_api::block::BlockInfo;
use starknet_rs_core::types::Felt;
use starknet_types::chain_id::ChainId;
use starknet_types::contract_address::ContractAddress;
use starknet_types::felt::felt_from_prefixed_hex;
use starknet_types::rpc::state::Balance;

use super::{Account, KeyPair};
use crate::account::FeeToken;
use crate::constants::CAIRO_1_ERC20_CONTRACT_CLASS_HASH;
use crate::constants::{CAIRO_1_ERC20_CONTRACT_CLASS_HASH, USE_KZG_DA};

use crate::state::{CustomState, StarknetState};
use crate::traits::{Accounted, Deployed};
use crate::utils::test_utils::{
dummy_cairo_1_contract_class, dummy_contract_address, dummy_felt,
cairo_0_account_without_validations, dummy_contract_address,
dummy_felt,
};
use crate::utils::{custom_bouncer_config, get_versioned_constants};

/// Testing if generated account address has the same value as the first account in
/// https://github.com/0xSpaceShard/starknet-devnet-deprecated/blob/9d867e38e6d465e568e82a47e82e40608f6d220f/test/support/schemas/predeployed_accounts_fixed_seed.json
Expand Down Expand Up @@ -311,13 +380,20 @@ mod tests {
let (mut account, _) = setup();
let expected_address = ContractAddress::new(Felt::from(11111)).unwrap();
account.account_address = expected_address;
assert_eq!(expected_address, account.get_address());
assert_eq!(expected_address, account.account_address);
}

fn setup() -> (Account, StarknetState) {
let mut state = StarknetState::default();
let fee_token_address = dummy_contract_address();

let block_context = BlockContext::new(
BlockInfo::create_for_testing_with_kzg(USE_KZG_DA),
ChainInfo::default(),
get_versioned_constants(),
custom_bouncer_config(),
);

let account_contract_class = cairo_0_account_without_validations();
// deploy the erc20 contract
state.predeploy_contract(fee_token_address, CAIRO_1_ERC20_CONTRACT_CLASS_HASH).unwrap();

Expand All @@ -326,10 +402,12 @@ mod tests {
Balance::from(10_u8),
KeyPair { public_key: Felt::from(13431515), private_key: Felt::from(11) },
dummy_felt(),
"Dummy account",
dummy_cairo_1_contract_class().into(),
account_contract_class.into(),
fee_token_address,
fee_token_address,
block_context,
super::AccountType::Custom,
ChainId::Testnet.to_felt(),
)
.unwrap(),
state,
Expand Down
27 changes: 18 additions & 9 deletions crates/starknet-devnet-core/src/contract_class_choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use starknet_types::contract_class::deprecated::json_contract_class::Cairo0Json;
use starknet_types::contract_class::{Cairo0ContractClass, ContractClass};
use starknet_types::traits::HashProducer;

use crate::account::AccountType;
use crate::constants::{CAIRO_0_ACCOUNT_CONTRACT, CAIRO_1_ACCOUNT_CONTRACT_SIERRA};
use crate::error::DevnetResult;

Expand All @@ -25,7 +26,7 @@ impl AccountContractClassChoice {
AccountClassWrapper {
class_hash: contract_class.generate_hash()?,
contract_class: ContractClass::Cairo0(contract_class),
class_metadata: "OpenZeppelin 0.5.1",
account_type: AccountType::OpenZeppelin0_5_1,
}
}
AccountContractClassChoice::Cairo1 => {
Expand All @@ -35,7 +36,7 @@ impl AccountContractClassChoice {
AccountClassWrapper {
class_hash: contract_class.generate_hash()?,
contract_class,
class_metadata: "OpenZeppelin 0.20.0",
account_type: AccountType::OpenZeppelin0_20_0,
}
}
})
Expand All @@ -45,7 +46,7 @@ impl AccountContractClassChoice {
pub struct AccountClassWrapper {
pub contract_class: ContractClass,
pub class_hash: Felt,
pub class_metadata: &'static str,
pub account_type: AccountType,
}

impl FromStr for AccountClassWrapper {
Expand Down Expand Up @@ -81,7 +82,7 @@ impl FromStr for AccountClassWrapper {
// generate the hash and return
let contract_class = ContractClass::Cairo1(contract_class);
let class_hash = contract_class.generate_hash()?;
Ok(Self { contract_class, class_hash, class_metadata: "Custom" })
Ok(Self { contract_class, class_hash, account_type: AccountType::Custom })
}
}

Expand All @@ -103,11 +104,11 @@ mod tests {
#[test]
fn all_methods_work_with_all_options() {
for implementation in AccountContractClassChoice::value_variants().iter() {
let AccountClassWrapper { contract_class, class_hash, class_metadata } =
let AccountClassWrapper { contract_class, class_hash, account_type } =
implementation.get_class_wrapper().unwrap();
let generated_hash = contract_class.generate_hash().unwrap();
assert_eq!(generated_hash, class_hash);
assert!(class_metadata.starts_with("OpenZeppelin"));
assert!(account_type.to_string().starts_with("OpenZeppelin"));
}
}

Expand All @@ -127,16 +128,24 @@ mod tests {
#[test]
fn correct_metadata() {
assert_eq!(
AccountContractClassChoice::Cairo0.get_class_wrapper().unwrap().class_metadata,
AccountContractClassChoice::Cairo0
.get_class_wrapper()
.unwrap()
.account_type
.to_string(),
"OpenZeppelin 0.5.1"
);
assert_eq!(
AccountContractClassChoice::Cairo1.get_class_wrapper().unwrap().class_metadata,
AccountContractClassChoice::Cairo1
.get_class_wrapper()
.unwrap()
.account_type
.to_string(),
"OpenZeppelin 0.20.0"
);

let custom_class =
AccountClassWrapper::from_str(CAIRO_1_ACCOUNT_CONTRACT_SIERRA_PATH).unwrap();
assert_eq!(custom_class.class_metadata, "Custom");
assert_eq!(custom_class.account_type.to_string(), "Custom");
}
}
Loading