diff --git a/bin/katana/Cargo.toml b/bin/katana/Cargo.toml index 6f1a659af6..a9e8ec4442 100644 --- a/bin/katana/Cargo.toml +++ b/bin/katana/Cargo.toml @@ -42,5 +42,5 @@ blockifier = [ "katana-executor/blockifier" ] jemalloc = [ "dojo-metrics/jemalloc" ] messaging = [ "katana-core/messaging" ] -slot = [ "dep:katana-slot-controller" ] +slot = [ "dep:katana-slot-controller", "katana-primitives/slot" ] starknet-messaging = [ "katana-core/starknet-messaging", "messaging" ] diff --git a/crates/katana/controller/src/lib.rs b/crates/katana/controller/src/lib.rs index b187bcf52c..ec4716d891 100644 --- a/crates/katana/controller/src/lib.rs +++ b/crates/katana/controller/src/lib.rs @@ -1,5 +1,4 @@ use std::collections::HashMap; -use std::sync::Arc; use account_sdk::abigen::controller::{Signer, SignerType}; use account_sdk::signers::webauthn::{DeviceSigner, WebauthnAccountSigner}; @@ -8,11 +7,10 @@ use account_sdk::wasm_webauthn::CredentialID; use alloy_primitives::U256; use anyhow::Result; use coset::CoseKey; -use katana_primitives::class::{ClassHash, CompiledClass, SierraCompiledClass}; use katana_primitives::contract::{ContractAddress, StorageKey, StorageValue}; use katana_primitives::genesis::allocation::{GenesisAllocation, GenesisContractAlloc}; -use katana_primitives::genesis::{Genesis, GenesisClass}; -use katana_primitives::utils::class::{parse_compiled_class_v1, parse_sierra_class}; +use katana_primitives::genesis::constant::CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH; +use katana_primitives::genesis::Genesis; use katana_primitives::FieldElement; use slot::credential::Credentials; use starknet::core::utils::get_storage_var_address; @@ -22,9 +20,6 @@ mod webauthn; const LOG_TARGET: &str = "katana::controller"; -const CONTROLLER_SIERRA_ARTIFACT: &str = - include_str!("../../contracts/compiled/controller_CartridgeAccount.contract_class.json"); - const WEBAUTHN_RP_ID: &str = "cartridge.gg"; const WEBAUTHN_ORIGIN: &str = "https://x.cartridge.gg"; @@ -34,33 +29,6 @@ pub fn add_controller_account(genesis: &mut Genesis) -> Result<()> { add_controller_account_inner(genesis, credentials.account) } -fn add_controller_class(genesis: &mut Genesis) -> Result { - let sierra = parse_sierra_class(CONTROLLER_SIERRA_ARTIFACT)?; - let casm = read_compiled_class_artifact(CONTROLLER_SIERRA_ARTIFACT)?; - - let class_hash = sierra.class_hash()?; - let flattened_sierra = sierra.flatten()?; - let casm_hash = FieldElement::from_bytes_be(&casm.casm.compiled_class_hash().to_be_bytes())?; - - trace!( - target: LOG_TARGET, - class_hash = format!("{class_hash:#x}"), - casm_hash = format!("{casm_hash:#x}"), - "Adding Cartridge Controller account class to genesis." - ); - - genesis.classes.insert( - class_hash, - GenesisClass { - sierra: Some(Arc::new(flattened_sierra)), - compiled_class_hash: casm_hash, - casm: Arc::new(CompiledClass::Class(casm)), - }, - ); - - Ok(class_hash) -} - fn add_controller_account_inner(genesis: &mut Genesis, user: slot::account::Account) -> Result<()> { let cred = user.credentials.webauthn.first().unwrap(); @@ -71,16 +39,14 @@ fn add_controller_account_inner(genesis: &mut Genesis, user: slot::account::Acco "Adding Cartridge Controller account to genesis." ); - let class_hash = add_controller_class(genesis)?; - let credential_id = webauthn::credential::from_base64(&cred.id)?; let public_key = webauthn::cose_key::from_base64(&cred.public_key)?; let (address, contract) = { let account = GenesisContractAlloc { nonce: None, - class_hash: Some(class_hash), balance: Some(U256::from(0xfffffffffffffffu128)), + class_hash: Some(CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH), storage: Some(get_contract_storage(credential_id, public_key, SignerType::Webauthn)?), }; @@ -108,6 +74,8 @@ pub mod json { use super::*; + const CONTROLLER_SIERRA_ARTIFACT: &str = + include_str!("../../contracts/compiled/controller_CartridgeAccount.contract_class.json"); const CONTROLLER_CLASS_NAME: &str = "controller"; // TODO(kariy): should accept the whole account struct instead of individual fields @@ -188,11 +156,6 @@ fn get_contract_storage( Ok(HashMap::from([(storage, guid)])) } -fn read_compiled_class_artifact(artifact: &str) -> Result { - let value = serde_json::from_str(artifact)?; - parse_compiled_class_v1(value) -} - #[cfg(test)] mod tests { use slot::account::WebAuthnCredential; @@ -230,15 +193,14 @@ mod tests { }, }; - let controller_class_hash = add_controller_class(&mut Genesis::default()).unwrap(); add_controller_account_inner(&mut genesis, account.clone()).unwrap(); let address = ContractAddress::from(account.contract_address); let allocation = genesis.allocations.get(&address).unwrap(); assert!(genesis.allocations.contains_key(&address)); - assert_eq!(allocation.class_hash(), Some(controller_class_hash)); assert_eq!(allocation.balance(), Some(U256::from(0xfffffffffffffffu128))); + assert_eq!(allocation.class_hash(), Some(CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH)); } #[test] diff --git a/crates/katana/primitives/Cargo.toml b/crates/katana/primitives/Cargo.toml index 1d01902fa9..90b721a4a2 100644 --- a/crates/katana/primitives/Cargo.toml +++ b/crates/katana/primitives/Cargo.toml @@ -32,5 +32,8 @@ similar-asserts.workspace = true [features] default = [ "serde" ] + +controller = [ ] rpc = [ ] serde = [ "alloy-primitives/serde" ] +slot = [ "controller" ] diff --git a/crates/katana/primitives/src/genesis/constant.rs b/crates/katana/primitives/src/genesis/constant.rs index b5e8c555a3..affd6890ce 100644 --- a/crates/katana/primitives/src/genesis/constant.rs +++ b/crates/katana/primitives/src/genesis/constant.rs @@ -1,5 +1,6 @@ use lazy_static::lazy_static; use starknet::core::utils::get_storage_var_address; +use starknet::macros::felt; use crate::class::{ClassHash, CompiledClass, CompiledClassHash, SierraClass}; use crate::contract::{ContractAddress, StorageKey}; @@ -121,6 +122,8 @@ pub const DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH: CompiledClassHash = 190499602541245794, ]); +pub const CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH: ClassHash = felt!("0xCC"); + // Pre-compiled contract classes lazy_static! { @@ -134,7 +137,13 @@ lazy_static! { // Default account contract pub static ref DEFAULT_OZ_ACCOUNT_CONTRACT: SierraClass = parse_sierra_class(include_str!("../../../contracts/compiled/oz_account_080.json")).unwrap(); pub static ref DEFAULT_OZ_ACCOUNT_CONTRACT_CASM: CompiledClass = read_compiled_class_artifact(include_str!("../../../contracts/compiled/oz_account_080.json")); +} +#[cfg(feature = "controller")] +lazy_static! { + // Cartridge Controller account + pub static ref CONTROLLER_ACCOUNT_CONTRACT: SierraClass = parse_sierra_class(include_str!("../../../contracts/compiled/controller_CartridgeAccount.contract_class.json")).unwrap(); + pub static ref CONTROLLER_ACCOUNT_CONTRACT_CASM: CompiledClass = read_compiled_class_artifact(include_str!("../../../contracts/compiled/oz_account_080.json")); } /// A helper function to get the base storage address for the fee token balance of a given account. diff --git a/crates/katana/primitives/src/genesis/json.rs b/crates/katana/primitives/src/genesis/json.rs index 4372a5a424..d1fa24bfd6 100644 --- a/crates/katana/primitives/src/genesis/json.rs +++ b/crates/katana/primitives/src/genesis/json.rs @@ -25,6 +25,11 @@ use starknet::core::types::FromByteArrayError; use super::allocation::{ DevGenesisAccount, GenesisAccount, GenesisAccountAlloc, GenesisContractAlloc, }; +#[cfg(feature = "slot")] +use super::constant::{ + CONTROLLER_ACCOUNT_CONTRACT, CONTROLLER_ACCOUNT_CONTRACT_CASM, + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, +}; use super::constant::{ DEFAULT_FEE_TOKEN_ADDRESS, DEFAULT_LEGACY_ERC20_CONTRACT_CASM, DEFAULT_LEGACY_ERC20_CONTRACT_CLASS_HASH, DEFAULT_LEGACY_ERC20_CONTRACT_COMPILED_CLASS_HASH, @@ -320,6 +325,19 @@ impl TryFrom for Genesis { let mut class_names: HashMap = HashMap::new(); let mut classes: HashMap = HashMap::new(); + #[cfg(feature = "slot")] + // Merely a band aid fix for now. + // Adding this by default so that we can support mounting the genesis file from k8s + // ConfigMap when we embed the Controller class, and its capacity is only limited to 1MiB. + classes.insert( + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + GenesisClass { + casm: Arc::new(CONTROLLER_ACCOUNT_CONTRACT_CASM.clone()), + compiled_class_hash: CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + sierra: Some(Arc::new(CONTROLLER_ACCOUNT_CONTRACT.clone().flatten()?)), + }, + ); + for entry in value.classes { let GenesisClassJson { class, class_hash, name } = entry; @@ -513,9 +531,7 @@ impl TryFrom for Genesis { // insert default account class to the classes map e.insert(GenesisClass { casm: Arc::new(DEFAULT_OZ_ACCOUNT_CONTRACT_CASM.clone()), - sierra: Some(Arc::new( - DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten().unwrap(), - )), + sierra: Some(Arc::new(DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten()?)), compiled_class_hash: DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, }); } @@ -662,34 +678,9 @@ fn class_artifact_at_path( #[cfg(test)] mod tests { - use std::collections::{BTreeMap, HashMap}; - use std::fs::File; - use std::io::BufReader; - use std::path::PathBuf; - use std::str::FromStr; - - use alloy_primitives::U256; use starknet::macros::felt; - use super::{from_base64, GenesisAccountJson, GenesisClassJson, GenesisJson}; - use crate::block::GasPrices; - use crate::genesis::allocation::{ - DevGenesisAccount, GenesisAccount, GenesisAccountAlloc, GenesisContractAlloc, - }; - use crate::genesis::constant::{ - DEFAULT_FEE_TOKEN_ADDRESS, DEFAULT_LEGACY_ERC20_CONTRACT_CASM, - DEFAULT_LEGACY_ERC20_CONTRACT_CLASS_HASH, - DEFAULT_LEGACY_ERC20_CONTRACT_COMPILED_CLASS_HASH, DEFAULT_LEGACY_UDC_CASM, - DEFAULT_LEGACY_UDC_CLASS_HASH, DEFAULT_LEGACY_UDC_COMPILED_CLASS_HASH, - DEFAULT_OZ_ACCOUNT_CONTRACT, DEFAULT_OZ_ACCOUNT_CONTRACT_CASM, - DEFAULT_OZ_ACCOUNT_CONTRACT_CLASS_HASH, DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, - DEFAULT_UDC_ADDRESS, - }; - use crate::genesis::json::{to_base64, ClassNameOrHash}; - use crate::genesis::{ - ContractAddress, FeeTokenConfig, Genesis, GenesisAllocation, GenesisClass, - UniversalDeployerConfig, - }; + use super::*; #[test] fn deserialize_from_json() { @@ -925,6 +916,15 @@ mod tests { sierra: Some(DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), }, ), + #[cfg(feature = "slot")] + ( + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + GenesisClass { + casm: Arc::new(CONTROLLER_ACCOUNT_CONTRACT_CASM.clone()), + compiled_class_hash: CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + sierra: Some(Arc::new(CONTROLLER_ACCOUNT_CONTRACT.clone().flatten().unwrap())), + }, + ), ]); let expected_fee_token = FeeTokenConfig { @@ -1145,6 +1145,15 @@ mod tests { sierra: Some(DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), }, ), + #[cfg(feature = "slot")] + ( + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + GenesisClass { + casm: Arc::new(CONTROLLER_ACCOUNT_CONTRACT_CASM.clone()), + compiled_class_hash: CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + sierra: Some(Arc::new(CONTROLLER_ACCOUNT_CONTRACT.clone().flatten().unwrap())), + }, + ), ]); let fee_token = FeeTokenConfig { diff --git a/crates/katana/primitives/src/genesis/mod.rs b/crates/katana/primitives/src/genesis/mod.rs index 5774e3407c..d8c46b5170 100644 --- a/crates/katana/primitives/src/genesis/mod.rs +++ b/crates/katana/primitives/src/genesis/mod.rs @@ -7,6 +7,11 @@ use std::fmt::Debug; use std::sync::Arc; use alloy_primitives::U256; +#[cfg(feature = "slot")] +use constant::{ + CONTROLLER_ACCOUNT_CONTRACT, CONTROLLER_ACCOUNT_CONTRACT_CASM, + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, +}; use serde::{Deserialize, Serialize}; use starknet::core::serde::unsigned_field_element::UfeHex; use starknet::core::utils::cairo_short_string_to_felt; @@ -282,6 +287,15 @@ impl Default for Genesis { compiled_class_hash: DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, }, ), + #[cfg(feature = "slot")] + ( + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + GenesisClass { + casm: CONTROLLER_ACCOUNT_CONTRACT_CASM.clone().into(), + compiled_class_hash: CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + sierra: Some(CONTROLLER_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), + }, + ), ]); Self { @@ -303,16 +317,8 @@ impl Default for Genesis { mod tests { use std::str::FromStr; + use allocation::GenesisAccount; use starknet::macros::felt; - use tests::allocation::GenesisAccount; - use tests::constant::{ - DEFAULT_FEE_TOKEN_ADDRESS, DEFAULT_LEGACY_ERC20_CONTRACT_CASM, - DEFAULT_LEGACY_ERC20_CONTRACT_CLASS_HASH, - DEFAULT_LEGACY_ERC20_CONTRACT_COMPILED_CLASS_HASH, DEFAULT_LEGACY_UDC_CASM, - DEFAULT_LEGACY_UDC_CLASS_HASH, DEFAULT_LEGACY_UDC_COMPILED_CLASS_HASH, - DEFAULT_OZ_ACCOUNT_CONTRACT, DEFAULT_OZ_ACCOUNT_CONTRACT_CASM, - DEFAULT_OZ_ACCOUNT_CONTRACT_CLASS_HASH, DEFAULT_OZ_ACCOUNT_CONTRACT_COMPILED_CLASS_HASH, - }; use super::*; @@ -345,6 +351,15 @@ mod tests { sierra: Some(DEFAULT_OZ_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), }, ), + #[cfg(feature = "slot")] + ( + CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + GenesisClass { + casm: CONTROLLER_ACCOUNT_CONTRACT_CASM.clone().into(), + compiled_class_hash: CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH, + sierra: Some(CONTROLLER_ACCOUNT_CONTRACT.clone().flatten().unwrap().into()), + }, + ), ]); let fee_token = FeeTokenConfig { @@ -480,14 +495,27 @@ mod tests { assert_eq!(actual_block.header.version, expected_block.header.version); assert_eq!(actual_block.body, expected_block.body); - assert!( - actual_state_updates.declared_compiled_classes.len() == 3, - "should be 3 casm classes: udc, erc20, oz account" - ); - assert!( - actual_state_updates.declared_sierra_classes.len() == 1, - "should be only 1 sierra class: oz account" - ); + if cfg!(feature = "slot") { + assert!( + actual_state_updates.declared_compiled_classes.len() == 4, + "should be 4 casm classes: udc, erc20, oz account, controller account" + ); + + assert!( + actual_state_updates.declared_sierra_classes.len() == 2, + "should be 2 sierra classes: oz account, controller account" + ); + } else { + assert!( + actual_state_updates.declared_compiled_classes.len() == 3, + "should be 3 casm classes: udc, erc20, oz account" + ); + + assert!( + actual_state_updates.declared_sierra_classes.len() == 1, + "should be only 1 sierra class: oz account" + ); + } assert_eq!( actual_state_updates.state_updates.declared_classes.get(&fee_token.class_hash), @@ -561,6 +589,34 @@ mod tests { "The default oz account contract sierra class should be declared" ); + #[cfg(feature = "slot")] + { + assert_eq!( + actual_state_updates + .state_updates + .declared_classes + .get(&CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH), + Some(&CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH), + "The controller account class should be declared" + ); + + assert_eq!( + actual_state_updates + .declared_compiled_classes + .get(&CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH), + Some(&CONTROLLER_ACCOUNT_CONTRACT_CASM.clone()), + "The controller account contract casm class should be declared" + ); + + assert_eq!( + actual_state_updates + .declared_sierra_classes + .get(&CONTROLLER_ACCOUNT_CONTRACT_CLASS_HASH), + Some(&CONTROLLER_ACCOUNT_CONTRACT.clone().flatten().unwrap()), + "The controller account contract sierra class should be declared" + ); + } + // check that all contract allocations exist in the state updates assert_eq!(