diff --git a/Cargo.lock b/Cargo.lock index 4ac904da11..605c18402a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6957,6 +6957,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "similar-asserts", "starknet", "starknet-crypto 0.6.2", "starknet_api", diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index f46ee91f99..7c2e3f0cc7 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -30,6 +30,9 @@ cairo-lang-starknet.workspace = true flate2.workspace = true starknet_api.workspace = true +[dev-dependencies] +similar-asserts.workspace = true + [features] default = [ "serde" ] rpc = [ ] diff --git a/crates/katana/primitives/src/genesis/json.rs b/crates/katana/primitives/src/genesis/json.rs index 102020e7c9..2a312baa40 100644 --- a/crates/katana/primitives/src/genesis/json.rs +++ b/crates/katana/primitives/src/genesis/json.rs @@ -14,7 +14,6 @@ use alloy_primitives::U256; use base64::prelude::*; use cairo_lang_starknet_classes::casm_contract_class::StarknetSierraCompilationError; use cairo_vm::types::errors::program_errors::ProgramError; -use rayon::prelude::*; use serde::de::value::MapAccessDeserializer; use serde::de::Visitor; use serde::{Deserialize, Serialize}; @@ -97,6 +96,54 @@ pub struct GenesisClassJson { /// The class hash of the contract. If not provided, the class hash is computed from the /// class at `path`. pub class_hash: Option, + // Allows class identification by a unique name rather than by hash when specifying the class. + pub name: Option, +} + +/// Class identifier. +/// +/// When deploying a contract through the genesis file, the class implementation of the contract +/// can be specified either by the class hash or by the class name. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ClassNameOrHash { + Name(String), + Hash(ClassHash), +} + +// We implement the `Serialize` manually because we need to serialize the class hash as hex string +// with the `0x` prefix (if it's the hash variant). Otherwise, it'd be a decimal string, and +// deserializing it back would fail as it'd be interpreted as a name instead. +impl Serialize for ClassNameOrHash { + fn serialize(&self, serializer: S) -> Result { + match self { + ClassNameOrHash::Name(name) => serializer.serialize_str(name), + ClassNameOrHash::Hash(hash) => serializer.serialize_str(&format!("{hash:#x}")), + } + } +} + +impl<'de> Deserialize<'de> for ClassNameOrHash { + fn deserialize>(deserializer: D) -> Result { + struct _Visitor; + + impl<'de> Visitor<'de> for _Visitor { + type Value = ClassNameOrHash; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a name or a class hash prefixed with 0x") + } + + fn visit_str(self, v: &str) -> Result { + if v.starts_with("0x") { + Ok(ClassNameOrHash::Hash(ClassHash::from_str(v).map_err(E::custom)?)) + } else { + Ok(ClassNameOrHash::Name(v.to_string())) + } + } + } + + deserializer.deserialize_any(_Visitor) + } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] @@ -108,7 +155,7 @@ pub struct FeeTokenConfigJson { pub decimals: u8, /// The class hash of the fee token contract. /// If not provided, the default fee token class is used. - pub class: Option, + pub class: Option, /// To initialize the fee token contract storage pub storage: Option>, } @@ -120,7 +167,7 @@ pub struct UniversalDeployerConfigJson { pub address: Option, /// The class hash of the universal deployer contract. /// If not provided, the default UD class is used. - pub class: Option, + pub class: Option, /// To initialize the UD contract storage pub storage: Option>, } @@ -128,7 +175,7 @@ pub struct UniversalDeployerConfigJson { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct GenesisContractJson { - pub class: Option, + pub class: Option, pub balance: Option, pub nonce: Option, pub storage: Option>, @@ -142,7 +189,7 @@ pub struct GenesisAccountJson { pub balance: Option, pub nonce: Option, /// The class hash of the account contract. If not provided, the default account class is used. - pub class: Option, + pub class: Option, pub storage: Option>, pub private_key: Option, } @@ -182,6 +229,12 @@ pub enum GenesisJsonError { #[error(transparent)] Decode(#[from] base64::DecodeError), + #[error("Class name '{0}' already exists in the genesis classes")] + DuplicateClassName(String), + + #[error("Class name '{0}' not found in the genesis classes")] + UnknownClassName(String), + #[error(transparent)] Other(#[from] anyhow::Error), } @@ -263,74 +316,106 @@ impl TryFrom for Genesis { type Error = GenesisJsonError; fn try_from(value: GenesisJson) -> Result { - let mut classes: HashMap = value - .classes - .into_par_iter() - .map(|entry| { - let GenesisClassJson { class, class_hash } = entry; - - let artifact = match class { - PathOrFullArtifact::Artifact(artifact) => artifact, - PathOrFullArtifact::Path(path) => { - return Err(GenesisJsonError::UnresolvedClassPath(path)); - } - }; + // a lookup table for classes that is assigned a name + let mut class_names: HashMap = HashMap::new(); + let mut classes: HashMap = HashMap::new(); + + for entry in value.classes { + let GenesisClassJson { class, class_hash, name } = entry; + + // at this point, it is assumed that any class paths should have been resolved to an + // artifact, otherwise it is an error + let artifact = match class { + PathOrFullArtifact::Artifact(artifact) => artifact, + PathOrFullArtifact::Path(path) => { + return Err(GenesisJsonError::UnresolvedClassPath(path)); + } + }; - let sierra = serde_json::from_value::(artifact.clone()); + let sierra = serde_json::from_value::(artifact.clone()); - let (class_hash, compiled_class_hash, sierra, casm) = match sierra { - Ok(sierra) => { - let class = parse_compiled_class_v1(artifact)?; + let (class_hash, compiled_class_hash, sierra, casm) = match sierra { + Ok(sierra) => { + let class = parse_compiled_class_v1(artifact)?; - // check if the class hash is provided, otherwise compute it from the - // artifacts - let class_hash = class_hash.unwrap_or(sierra.class_hash()?); - let compiled_hash = class.casm.compiled_class_hash().to_be_bytes(); + // check if the class hash is provided, otherwise compute it from the + // artifacts + let class_hash = class_hash.unwrap_or(sierra.class_hash()?); + let compiled_hash = class.casm.compiled_class_hash().to_be_bytes(); - ( - class_hash, - FieldElement::from_bytes_be(&compiled_hash)?, - Some(Arc::new(sierra.flatten()?)), - Arc::new(CompiledClass::Class(class)), - ) - } + ( + class_hash, + FieldElement::from_bytes_be(&compiled_hash)?, + Some(Arc::new(sierra.flatten()?)), + Arc::new(CompiledClass::Class(class)), + ) + } - // if the artifact is not a sierra contract, we check if it's a legacy contract - Err(_) => { - let casm = parse_deprecated_compiled_class(artifact.clone())?; + // if the artifact is not a sierra contract, we check if it's a legacy contract + Err(_) => { + let casm = parse_deprecated_compiled_class(artifact.clone())?; - let class_hash = if let Some(class_hash) = class_hash { - class_hash - } else { - let casm: LegacyContractClass = - serde_json::from_value(artifact.clone())?; - casm.class_hash()? - }; + let class_hash = if let Some(class_hash) = class_hash { + class_hash + } else { + let casm: LegacyContractClass = serde_json::from_value(artifact.clone())?; + casm.class_hash()? + }; - (class_hash, class_hash, None, Arc::new(CompiledClass::Deprecated(casm))) + (class_hash, class_hash, None, Arc::new(CompiledClass::Deprecated(casm))) + } + }; + + // if the class has a name, we add it to the lookup table to use later when we're + // parsing the contracts + if let Some(name) = name { + // if there's a duplicate class name, we return an error + match class_names.entry(name.clone()) { + hash_map::Entry::Occupied(_) => { + return Err(GenesisJsonError::DuplicateClassName(name)); } - }; - Ok((class_hash, GenesisClass { compiled_class_hash, sierra, casm })) - }) - .collect::>()?; + hash_map::Entry::Vacant(e) => { + e.insert(class_hash); + } + } + } + + classes.insert(class_hash, GenesisClass { compiled_class_hash, sierra, casm }); + } let fee_token = FeeTokenConfig { name: value.fee_token.name, symbol: value.fee_token.symbol, decimals: value.fee_token.decimals, address: value.fee_token.address.unwrap_or(DEFAULT_FEE_TOKEN_ADDRESS), - class_hash: value.fee_token.class.unwrap_or(DEFAULT_LEGACY_ERC20_CONTRACT_CLASS_HASH), + class_hash: match value.fee_token.class { + Some(ClassNameOrHash::Hash(class_hash)) => class_hash, + Some(ClassNameOrHash::Name(ref class_name)) => *class_names + .get(class_name) + .ok_or_else(|| GenesisJsonError::UnknownClassName(class_name.clone()))?, + None => DEFAULT_LEGACY_ERC20_CONTRACT_CLASS_HASH, + }, storage: value.fee_token.storage, }; match value.fee_token.class { - Some(hash) => { + Some(ClassNameOrHash::Hash(hash)) => { if !classes.contains_key(&hash) { return Err(GenesisJsonError::MissingClass(hash)); } } + Some(ClassNameOrHash::Name(name)) => { + let hash = class_names + .get(&name) + .ok_or_else(|| GenesisJsonError::UnknownClassName(name.clone()))?; + + if !classes.contains_key(hash) { + return Err(GenesisJsonError::MissingClass(*hash)); + } + } + // if no class hash is provided, use the default fee token class None => { let _ = classes.insert( @@ -346,7 +431,7 @@ impl TryFrom for Genesis { let universal_deployer = if let Some(config) = value.universal_deployer { match config.class { - Some(hash) => { + Some(ClassNameOrHash::Hash(hash)) => { if !classes.contains_key(&hash) { return Err(GenesisJsonError::MissingClass(hash)); } @@ -358,6 +443,22 @@ impl TryFrom for Genesis { }) } + Some(ClassNameOrHash::Name(name)) => { + let hash = class_names + .get(&name) + .ok_or_else(|| GenesisJsonError::UnknownClassName(name))?; + + if !classes.contains_key(hash) { + return Err(GenesisJsonError::MissingClass(*hash)); + } + + Some(UniversalDeployerConfig { + class_hash: *hash, + address: config.address.unwrap_or(DEFAULT_UDC_ADDRESS), + storage: config.storage, + }) + } + // if no class hash is provided, use the default UD contract parameters None => { let class_hash = DEFAULT_LEGACY_UDC_CLASS_HASH; @@ -385,7 +486,17 @@ impl TryFrom for Genesis { for (address, account) in value.accounts { // check that the class hash exists in the classes field let class_hash = match account.class { - Some(hash) => { + Some(class) => { + let hash = match class { + ClassNameOrHash::Hash(hash) => hash, + ClassNameOrHash::Name(name) => { + // Handle the case when the class is specified by name. + *class_names + .get(&name) + .ok_or_else(|| GenesisJsonError::UnknownClassName(name))? + } + }; + if !classes.contains_key(&hash) { return Err(GenesisJsonError::MissingClass(hash)); } else { @@ -444,7 +555,23 @@ impl TryFrom for Genesis { for (address, contract) in value.contracts { // check that the class hash exists in the classes field - if let Some(hash) = contract.class { + let class_hash = if let Some(class) = contract.class { + let hash = match class { + ClassNameOrHash::Hash(hash) => hash, + ClassNameOrHash::Name(name) => { + // Handle the case when the class is specified by name. + *class_names + .get(&name) + .ok_or_else(|| GenesisJsonError::UnknownClassName(name))? + } + }; + + Some(hash) + } else { + None + }; + + if let Some(hash) = class_hash { if !classes.contains_key(&hash) { return Err(GenesisJsonError::MissingClass(hash)); } @@ -454,7 +581,7 @@ impl TryFrom for Genesis { address, GenesisAllocation::Contract(GenesisContractAlloc { balance: contract.balance, - class_hash: contract.class, + class_hash, nonce: contract.nonce, storage: contract.storage, }), @@ -544,7 +671,7 @@ mod tests { use alloy_primitives::U256; use starknet::macros::felt; - use super::{from_base64, GenesisClassJson, GenesisJson}; + use super::{from_base64, GenesisAccountJson, GenesisClassJson, GenesisJson}; use crate::block::GasPrices; use crate::genesis::allocation::{ DevGenesisAccount, GenesisAccount, GenesisAccountAlloc, GenesisContractAlloc, @@ -558,7 +685,7 @@ mod tests { DEFAULT_OZ_ACCOUNT_CONTRACT_CLASS_HASH, DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, DEFAULT_UDC_ADDRESS, }; - use crate::genesis::json::to_base64; + use crate::genesis::json::{to_base64, ClassNameOrHash}; use crate::genesis::{ ContractAddress, FeeTokenConfig, Genesis, GenesisAllocation, GenesisClass, UniversalDeployerConfig, @@ -579,7 +706,7 @@ mod tests { assert_eq!(json.fee_token.address, Some(ContractAddress::from(felt!("0x55")))); assert_eq!(json.fee_token.name, String::from("ETHER")); assert_eq!(json.fee_token.symbol, String::from("ETH")); - assert_eq!(json.fee_token.class, Some(felt!("0x8"))); + assert_eq!(json.fee_token.class, Some(ClassNameOrHash::Name(String::from("MyErc20")))); assert_eq!(json.fee_token.decimals, 18); assert_eq!( json.fee_token.storage, @@ -619,7 +746,7 @@ mod tests { Some(U256::from_str("0xD3C21BCECCEDA1000000").unwrap()) ); assert_eq!(json.accounts[&acc_1].nonce, Some(felt!("0x1"))); - assert_eq!(json.accounts[&acc_1].class, Some(felt!("0x80085"))); + assert_eq!(json.accounts[&acc_1].class, Some(ClassNameOrHash::Hash(felt!("0x80085")))); assert_eq!( json.accounts[&acc_1].storage, Some(HashMap::from([(felt!("0x1"), felt!("0x1")), (felt!("0x2"), felt!("0x2")),])) @@ -631,7 +758,7 @@ mod tests { Some(U256::from_str("0xD3C21BCECCEDA1000000").unwrap()) ); assert_eq!(json.accounts[&acc_2].nonce, None); - assert_eq!(json.accounts[&acc_2].class, None); + assert_eq!(json.accounts[&acc_2].class, Some(ClassNameOrHash::Name("MyClass".to_string()))); assert_eq!(json.accounts[&acc_2].storage, None); assert_eq!(json.accounts[&acc_3].public_key, felt!("0x3")); @@ -667,7 +794,10 @@ mod tests { Some(U256::from_str("0xD3C21BCECCEDA1000000").unwrap()) ); assert_eq!(json.contracts[&contract_1].nonce, None); - assert_eq!(json.contracts[&contract_1].class, Some(felt!("0x8"))); + assert_eq!( + json.contracts[&contract_1].class, + Some(ClassNameOrHash::Name(String::from("MyErc20"))) + ); assert_eq!( json.contracts[&contract_1].storage, Some(HashMap::from([(felt!("0x1"), felt!("0x1")), (felt!("0x2"), felt!("0x2"))])) @@ -683,27 +813,33 @@ mod tests { assert_eq!(json.contracts[&contract_3].balance, None); assert_eq!(json.contracts[&contract_3].nonce, None); - assert_eq!(json.contracts[&contract_3].class, None); + assert_eq!( + json.contracts[&contract_3].class, + Some(ClassNameOrHash::Hash(felt!("0x80085"))) + ); assert_eq!( json.contracts[&contract_3].storage, Some(HashMap::from([(felt!("0x1"), felt!("0x1"))])) ); - assert_eq!( + similar_asserts::assert_eq!( json.classes, vec![ GenesisClassJson { class_hash: Some(felt!("0x8")), class: PathBuf::from("../../../contracts/compiled/erc20.json").into(), + name: Some("MyErc20".to_string()), }, GenesisClassJson { class_hash: Some(felt!("0x80085")), class: PathBuf::from("../../../contracts/compiled/universal_deployer.json") .into(), + name: None, }, GenesisClassJson { - class_hash: Some(felt!("0xa55")), + class_hash: None, class: PathBuf::from("../../../contracts/compiled/oz_account_080.json").into(), + name: Some("MyClass".to_string()), }, ] ); @@ -712,28 +848,39 @@ mod tests { #[test] fn deserialize_from_json_with_class() { let file = File::open("./src/genesis/test-genesis-with-class.json").unwrap(); - let genesis: GenesisJson = serde_json::from_reader(BufReader::new(file)).unwrap(); - - assert_eq!( - genesis.classes, - vec![ - GenesisClassJson { - class_hash: Some(felt!("0x8")), - class: PathBuf::from("../../../contracts/compiled/erc20.json").into(), - }, - GenesisClassJson { - class_hash: Some(felt!("0x80085")), - class: PathBuf::from("../../../contracts/compiled/universal_deployer.json") - .into(), - }, - GenesisClassJson { - class_hash: Some(felt!("0xa55")), - class: serde_json::to_value(DEFAULT_OZ_ACCOUNT_CONTRACT.clone()) - .unwrap() - .into(), - }, - ] - ); + let genesis_result: Result = serde_json::from_reader(BufReader::new(file)); + match genesis_result { + Ok(genesis) => { + assert_eq!( + genesis.classes, + vec![ + GenesisClassJson { + class_hash: None, + class: PathBuf::from("../../../contracts/compiled/erc20.json").into(), + name: Some("MyErc20".to_string()), + }, + GenesisClassJson { + class_hash: Some(felt!("0x80085")), + class: PathBuf::from( + "../../../contracts/compiled/universal_deployer.json" + ) + .into(), + name: None, + }, + GenesisClassJson { + class_hash: Some(felt!("0xa55")), + class: serde_json::to_value(DEFAULT_OZ_ACCOUNT_CONTRACT.clone()) + .unwrap() + .into(), + name: None, + }, + ] + ); + } + Err(e) => { + println!("Error parsing JSON: {:?}", e); + } + } } #[test] @@ -778,14 +925,6 @@ mod tests { sierra: Some(DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), }, ), - ( - felt!("0xa55"), - GenesisClass { - compiled_class_hash: DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, - casm: DEFAULT_OZ_ACCOUNT_CONTRACT_CASM.clone().into(), - sierra: Some(DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), - }, - ), ]); let expected_fee_token = FeeTokenConfig { @@ -895,7 +1034,7 @@ mod tests { GenesisAllocation::Contract(GenesisContractAlloc { balance: None, nonce: None, - class_hash: None, + class_hash: Some(felt!("0x80085")), storage: Some(HashMap::from([(felt!("0x1"), felt!("0x1"))])), }), ), @@ -1089,6 +1228,39 @@ mod tests { let encoded = to_base64(genesis_clone).unwrap(); let decoded = from_base64(encoded.as_slice()).unwrap(); - assert_eq!(genesis, decoded); + similar_asserts::assert_eq!(genesis, decoded); + } + + #[test] + fn account_with_unknown_class() { + let name = "MyClass"; + + let account = GenesisAccountJson { + nonce: None, + storage: None, + balance: None, + private_key: None, + public_key: Default::default(), + class: Some(ClassNameOrHash::Name(name.to_string())), + }; + + let mut json = GenesisJson::default(); + json.accounts.insert(felt!("1").into(), account); + + let res = Genesis::try_from(json); + assert!(res.unwrap_err().to_string().contains(&format!("Class name '{name}' not found"))) + } + + #[test] + fn classes_with_duplicate_names() { + let name = "MyClass"; + + let json = GenesisJson::load("./src/genesis/test-genesis-with-duplicate-name.json") + .expect("failed to load genesis file"); + + let res = Genesis::try_from(json); + assert!( + res.unwrap_err().to_string().contains(&format!("Class name '{name}' already exists")) + ) } } diff --git a/crates/katana/primitives/src/genesis/test-genesis-with-class.json b/crates/katana/primitives/src/genesis/test-genesis-with-class.json index 177ff094f0..42088d7d0c 100644 --- a/crates/katana/primitives/src/genesis/test-genesis-with-class.json +++ b/crates/katana/primitives/src/genesis/test-genesis-with-class.json @@ -58,7 +58,7 @@ "classes": [ { "class": "../../../contracts/compiled/erc20.json", - "classHash": "0x8" + "classHash": "MyErc20" }, { "class": "../../../contracts/compiled/universal_deployer.json", diff --git a/crates/katana/primitives/src/genesis/test-genesis-with-duplicate-name.json b/crates/katana/primitives/src/genesis/test-genesis-with-duplicate-name.json new file mode 100644 index 0000000000..cc6d462760 --- /dev/null +++ b/crates/katana/primitives/src/genesis/test-genesis-with-duplicate-name.json @@ -0,0 +1,38 @@ +{ + "number": 0, + "parentHash": "0x999", + "timestamp": 5123512314, + "stateRoot": "0x99", + "sequencerAddress": "0x100", + "gasPrices": { + "ETH": 1111, + "STRK": 2222 + }, + "feeToken": { + "name": "ETHER", + "symbol": "ETH", + "decimals": 18 + }, + "universalDeployer": {}, + "accounts": { + "0x66efb28ac62686966ae85095ff3a772e014e7fbf56d4c5f6fac5606d4dde23a": { + "publicKey": "0x1", + "balance": "0xD3C21BCECCEDA1000000" + } + }, + "contracts": {}, + "classes": [ + { + "class": "../../../contracts/compiled/erc20.json", + "classHash": "0x8", + "name": "MyClass" + }, + { + "class": "../../../contracts/compiled/universal_deployer.json" + }, + { + "class": "../../../contracts/compiled/oz_account_080.json", + "name": "MyClass" + } + ] +} diff --git a/crates/katana/primitives/src/genesis/test-genesis.json b/crates/katana/primitives/src/genesis/test-genesis.json index b122261e2f..bda1ecdcbd 100644 --- a/crates/katana/primitives/src/genesis/test-genesis.json +++ b/crates/katana/primitives/src/genesis/test-genesis.json @@ -1,84 +1,87 @@ { - "number": 0, - "parentHash": "0x999", - "timestamp": 5123512314, - "stateRoot": "0x99", - "sequencerAddress": "0x100", - "gasPrices": { - "ETH": 1111, - "STRK": 2222 - }, - "feeToken": { - "address": "0x55", - "name": "ETHER", - "symbol": "ETH", - "decimals": 18, - "class": "0x8", - "storage": { - "0x111": "0x1", - "0x222": "0x2" - } - }, - "universalDeployer": { - "address": "0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf", - "storage": { - "0x10": "0x100" - } - }, - "accounts": { - "0x66efb28ac62686966ae85095ff3a772e014e7fbf56d4c5f6fac5606d4dde23a": { - "publicKey": "0x1", - "balance": "0xD3C21BCECCEDA1000000", - "nonce": "0x1", - "class": "0x80085", - "storage": { - "0x1": "0x1", - "0x2": "0x2" - } - }, - "0x6b86e40118f29ebe393a75469b4d926c7a44c2e2681b6d319520b7c1156d114": { - "publicKey": "0x2", - "balance": "0xD3C21BCECCEDA1000000" - }, - "0x79156ecb3d8f084001bb498c95e37fa1c4b40dbb35a3ae47b77b1ad535edcb9": { - "publicKey": "0x3" - }, - "0x053a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf": { - "publicKey": "0x4", - "balance": "0xD3C21BCECCEDA1000000", - "privateKey": "0x115" - } - }, - "contracts": { - "0x29873c310fbefde666dc32a1554fea6bb45eecc84f680f8a2b0a8fbb8cb89af": { - "balance": "0xD3C21BCECCEDA1000000", - "class": "0x8", - "storage": { - "0x1": "0x1", - "0x2": "0x2" - } - }, - "0xe29882a1fcba1e7e10cad46212257fea5c752a4f9b1b1ec683c503a2cf5c8a": { - "balance": "0xD3C21BCECCEDA1000000" - }, - "0x05400e90f7e0ae78bd02c77cd75527280470e2fe19c54970dd79dc37a9d3645c": { - "storage": { - "0x1": "0x1" - } - } - }, - "classes": [ - { - "class": "../../../contracts/compiled/erc20.json", - "classHash": "0x8" - }, - { - "class": "../../../contracts/compiled/universal_deployer.json", - "classHash": "0x80085" - }, - { - "class": "../../../contracts/compiled/oz_account_080.json", - "classHash": "0xa55" - } - ] + "number": 0, + "parentHash": "0x999", + "timestamp": 5123512314, + "stateRoot": "0x99", + "sequencerAddress": "0x100", + "gasPrices": { + "ETH": 1111, + "STRK": 2222 + }, + "feeToken": { + "address": "0x55", + "name": "ETHER", + "symbol": "ETH", + "decimals": 18, + "class": "MyErc20", + "storage": { + "0x111": "0x1", + "0x222": "0x2" + } + }, + "universalDeployer": { + "address": "0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf", + "storage": { + "0x10": "0x100" + } + }, + "accounts": { + "0x66efb28ac62686966ae85095ff3a772e014e7fbf56d4c5f6fac5606d4dde23a": { + "publicKey": "0x1", + "balance": "0xD3C21BCECCEDA1000000", + "nonce": "0x1", + "class": "0x80085", + "storage": { + "0x1": "0x1", + "0x2": "0x2" + } + }, + "0x6b86e40118f29ebe393a75469b4d926c7a44c2e2681b6d319520b7c1156d114": { + "publicKey": "0x2", + "balance": "0xD3C21BCECCEDA1000000", + "class": "MyClass" + }, + "0x79156ecb3d8f084001bb498c95e37fa1c4b40dbb35a3ae47b77b1ad535edcb9": { + "publicKey": "0x3" + }, + "0x053a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf": { + "publicKey": "0x4", + "balance": "0xD3C21BCECCEDA1000000", + "privateKey": "0x115" + } + }, + "contracts": { + "0x29873c310fbefde666dc32a1554fea6bb45eecc84f680f8a2b0a8fbb8cb89af": { + "balance": "0xD3C21BCECCEDA1000000", + "class": "MyErc20", + "storage": { + "0x1": "0x1", + "0x2": "0x2" + } + }, + "0xe29882a1fcba1e7e10cad46212257fea5c752a4f9b1b1ec683c503a2cf5c8a": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x05400e90f7e0ae78bd02c77cd75527280470e2fe19c54970dd79dc37a9d3645c": { + "class": "0x80085", + "storage": { + "0x1": "0x1" + } + } + }, + "classes": [ + { + "class": "../../../contracts/compiled/erc20.json", + "classHash": "0x8", + "name": "MyErc20" + }, + { + "class": "../../../contracts/compiled/universal_deployer.json", + "classHash": "0x80085" + }, + { + "class": "../../../contracts/compiled/oz_account_080.json", + "name": "MyClass" + } + ] }