1use std::sync::Arc;
2
3use blockifier::state::state_api::StateReader;
4use starknet_api::core::calculate_contract_address;
5use starknet_api::transaction::fields::{Calldata, ContractAddressSalt};
6use starknet_api::{felt, patricia_key};
7use starknet_rs_core::types::Felt;
8use starknet_types::contract_address::ContractAddress;
9use starknet_types::contract_class::ContractClass;
10use starknet_types::error::Error;
11use starknet_types::felt::{ClassHash, Key, felt_from_prefixed_hex, join_felts, split_biguint};
12use starknet_types::rpc::state::Balance;
13
14use crate::constants::{
15 CHARGEABLE_ACCOUNT_ADDRESS, CHARGEABLE_ACCOUNT_PRIVATE_KEY, CHARGEABLE_ACCOUNT_PUBLIC_KEY,
16 ISRC6_ID_HEX, chargeable_account_initial_balance,
17};
18use crate::contract_class_choice::{AccountClassWrapper, AccountContractClassChoice};
19use crate::error::DevnetResult;
20use crate::state::state_readers::DictState;
21use crate::state::{CustomState, StarknetState};
22use crate::traits::{Accounted, Deployed};
23use crate::utils::get_storage_var_address;
24
25const ACCOUNT_CLASS_HASH_HEX_FOR_ADDRESS_COMPUTATION: &str =
27 "0x3FCBF77B28C96F4F2FB5BD2D176AB083A12A5E123ADEB0DE955D7EE228C9854";
28
29pub enum FeeToken {
30 ETH,
31 STRK,
32}
33
34#[derive(Clone)]
35pub struct KeyPair {
36 pub public_key: Key,
37 pub private_key: Key,
38}
39
40#[derive(Clone)]
41pub struct Account {
42 pub keys: KeyPair,
43 pub account_address: ContractAddress,
44 pub initial_balance: Balance,
45 pub class_hash: ClassHash,
46 pub class_metadata: &'static str,
47 pub(crate) contract_class: ContractClass,
48 pub(crate) eth_fee_token_address: ContractAddress,
49 pub(crate) strk_fee_token_address: ContractAddress,
50}
51
52impl Account {
53 pub(crate) fn new_chargeable(
54 eth_fee_token_address: ContractAddress,
55 strk_fee_token_address: ContractAddress,
56 ) -> DevnetResult<Self> {
57 let AccountClassWrapper { contract_class, class_hash, class_metadata } =
58 AccountContractClassChoice::Cairo1.get_class_wrapper()?;
59
60 Ok(Self {
61 keys: KeyPair {
62 public_key: Key::from_hex(CHARGEABLE_ACCOUNT_PUBLIC_KEY)?,
63 private_key: Key::from_hex(CHARGEABLE_ACCOUNT_PRIVATE_KEY)?,
64 },
65 account_address: ContractAddress::new(felt_from_prefixed_hex(
66 CHARGEABLE_ACCOUNT_ADDRESS,
67 )?)?,
68 initial_balance: chargeable_account_initial_balance(),
69 class_hash,
70 class_metadata,
71 contract_class,
72 eth_fee_token_address,
73 strk_fee_token_address,
74 })
75 }
76
77 pub(crate) fn new(
78 initial_balance: Balance,
79 keys: KeyPair,
80 class_hash: ClassHash,
81 class_metadata: &'static str,
82 contract_class: ContractClass,
83 eth_fee_token_address: ContractAddress,
84 strk_fee_token_address: ContractAddress,
85 ) -> DevnetResult<Self> {
86 let account_address = Account::compute_account_address(&keys.public_key)?;
87 Ok(Self {
88 initial_balance,
89 keys,
90 class_hash,
91 class_metadata,
92 contract_class,
93 account_address,
94 eth_fee_token_address,
95 strk_fee_token_address,
96 })
97 }
98
99 fn compute_account_address(public_key: &Key) -> DevnetResult<ContractAddress> {
100 let account_address = calculate_contract_address(
101 ContractAddressSalt(felt!(20u32)),
102 starknet_api::core::ClassHash(felt_from_prefixed_hex(
103 ACCOUNT_CLASS_HASH_HEX_FOR_ADDRESS_COMPUTATION,
104 )?),
105 &Calldata(Arc::new(vec![*public_key])),
106 starknet_api::core::ContractAddress(patricia_key!(0u32)),
107 )
108 .map_err(Error::StarknetApiError)?;
109
110 Ok(ContractAddress::from(account_address))
111 }
112
113 fn simulate_constructor(&self, state: &mut StarknetState) -> DevnetResult<()> {
116 let core_address = self.account_address.into();
117
118 let interface_storage_var = get_storage_var_address(
119 "SRC5_supported_interfaces",
120 &[felt_from_prefixed_hex(ISRC6_ID_HEX)?],
121 )?;
122 state.state.state.set_storage_at(core_address, interface_storage_var.into(), Felt::ONE)?;
123
124 let public_key_storage_var = get_storage_var_address("Account_public_key", &[])?;
125 state.state.state.set_storage_at(
126 core_address,
127 public_key_storage_var.into(),
128 self.keys.public_key,
129 )?;
130
131 Ok(())
132 }
133}
134
135impl Deployed for Account {
136 fn deploy(&self, state: &mut StarknetState) -> DevnetResult<()> {
137 self.declare_if_undeclared(state, self.class_hash, &self.contract_class)?;
138
139 state.predeploy_contract(self.account_address, self.class_hash)?;
140 self.set_initial_balance(&mut state.state.state)?;
141 self.simulate_constructor(state)?;
142
143 Ok(())
144 }
145
146 fn get_address(&self) -> ContractAddress {
147 self.account_address
148 }
149}
150
151impl Accounted for Account {
152 fn set_initial_balance(&self, state: &mut DictState) -> DevnetResult<()> {
154 let storage_var_address_low: starknet_api::state::StorageKey =
155 get_storage_var_address("ERC20_balances", &[Felt::from(self.account_address)])?.into();
156
157 let storage_var_address_high = storage_var_address_low.next_storage_key()?;
158
159 let total_supply_storage_address_low: starknet_api::state::StorageKey =
160 get_storage_var_address("ERC20_total_supply", &[])?.into();
161 let total_supply_storage_address_high =
162 total_supply_storage_address_low.next_storage_key()?;
163
164 let (high, low) = split_biguint(self.initial_balance.clone());
165
166 for fee_token_address in [self.eth_fee_token_address, self.strk_fee_token_address] {
167 let token_address = fee_token_address.into();
168
169 let total_supply_low =
170 state.get_storage_at(token_address, total_supply_storage_address_low)?;
171 let total_supply_high =
172 state.get_storage_at(token_address, total_supply_storage_address_high)?;
173
174 let new_total_supply =
175 join_felts(&total_supply_high, &total_supply_low) + self.initial_balance.clone();
176
177 let (new_total_supply_high, new_total_supply_low) = split_biguint(new_total_supply);
178
179 state.set_storage_at(token_address, storage_var_address_low, low)?;
181 state.set_storage_at(token_address, storage_var_address_high, high)?;
182
183 state.set_storage_at(
185 token_address,
186 total_supply_storage_address_low,
187 new_total_supply_low,
188 )?;
189
190 state.set_storage_at(
191 token_address,
192 total_supply_storage_address_high,
193 new_total_supply_high,
194 )?;
195 }
196
197 Ok(())
198 }
199
200 fn get_balance(&self, state: &mut impl StateReader, token: FeeToken) -> DevnetResult<Balance> {
201 let fee_token_address = match token {
202 FeeToken::ETH => self.eth_fee_token_address,
203 FeeToken::STRK => self.strk_fee_token_address,
204 };
205 let (low, high) =
206 state.get_fee_token_balance(self.account_address.into(), fee_token_address.into())?;
207 Ok(join_felts(&high, &low))
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use starknet_rs_core::types::Felt;
214 use starknet_types::contract_address::ContractAddress;
215 use starknet_types::felt::felt_from_prefixed_hex;
216 use starknet_types::rpc::state::Balance;
217
218 use super::{Account, KeyPair};
219 use crate::account::FeeToken;
220 use crate::constants::STRK_ERC20_CONTRACT_CLASS_HASH;
221 use crate::state::{CustomState, StarknetState};
222 use crate::traits::{Accounted, Deployed};
223 use crate::utils::test_utils::{
224 dummy_cairo_1_contract_class, dummy_contract_address, dummy_felt,
225 };
226
227 #[test]
230 fn account_address_should_be_equal() {
231 let expected_result = ContractAddress::new(
232 felt_from_prefixed_hex(
233 "0x6e3205f9b7c4328f00f718fdecf56ab31acfb3cd6ffeb999dcbac41236ea502",
234 )
235 .unwrap(),
236 )
237 .unwrap();
238 let generated_result = Account::compute_account_address(
239 &felt_from_prefixed_hex(
240 "0x60dea6c1228f1db4ca1f9db11c01b6e9cce5e627f7181dcaa27d69cbdbe57b5",
241 )
242 .unwrap(),
243 )
244 .unwrap();
245
246 assert_eq!(expected_result, generated_result);
247 }
248
249 #[test]
250 fn account_address_should_not_be_equal() {
251 let expected_result = ContractAddress::new(
252 felt_from_prefixed_hex(
253 "0x6e3205f9b7c4328f00f718fdecf56ab31acfb3cd6ffeb999dcbac41236ea502",
254 )
255 .unwrap(),
256 )
257 .unwrap();
258 let generated_result = Account::compute_account_address(
259 &felt_from_prefixed_hex(
260 "0x60dea6c1228f1db4ca1f9db11c01b6e9cce5e627f7181dcaa27d69cbdbe57b6",
261 )
262 .unwrap(),
263 )
264 .unwrap();
265
266 assert_ne!(expected_result, generated_result);
267 }
268
269 #[test]
270 fn account_deployed_successfully() {
271 let (account, mut state) = setup();
272 assert!(account.deploy(&mut state).is_ok());
273 }
274
275 #[test]
276 fn account_get_balance_should_return_correct_value() {
277 let (mut account, mut state) = setup();
278 let expected_balance = Balance::from(100_u8);
279 account.initial_balance = expected_balance.clone();
280 account.deploy(&mut state).unwrap();
281
282 let generated_balance = account.get_balance(&mut state, FeeToken::ETH).unwrap();
283 assert_eq!(expected_balance, generated_balance);
284
285 let generated_balance = account.get_balance(&mut state, FeeToken::STRK).unwrap();
286 assert_eq!(expected_balance, generated_balance);
287 }
288
289 #[test]
290 fn account_changed_balance_successfully_without_deployment() {
291 let (account, mut state) = setup();
292 assert!(account.set_initial_balance(&mut state.state.state).is_ok());
293 }
294
295 #[test]
296 fn account_get_address_correct() {
297 let (mut account, _) = setup();
298 let expected_address = ContractAddress::new(Felt::from(11111)).unwrap();
299 account.account_address = expected_address;
300 assert_eq!(expected_address, account.get_address());
301 }
302
303 fn setup() -> (Account, StarknetState) {
304 let mut state = StarknetState::default();
305 let fee_token_address = dummy_contract_address();
306
307 state.predeploy_contract(fee_token_address, STRK_ERC20_CONTRACT_CLASS_HASH).unwrap();
309
310 (
311 Account::new(
312 Balance::from(10_u8),
313 KeyPair { public_key: Felt::from(13431515), private_key: Felt::from(11) },
314 dummy_felt(),
315 "Dummy account",
316 dummy_cairo_1_contract_class().into(),
317 fee_token_address,
318 fee_token_address,
319 )
320 .unwrap(),
321 state,
322 )
323 }
324}