Skip to content

Commit

Permalink
dev: work on comments from fred
Browse files Browse the repository at this point in the history
  • Loading branch information
Harsh Bajpai authored and Harsh Bajpai committed Sep 13, 2023
1 parent 423cb6f commit f0a1c75
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 7 deletions.
8 changes: 4 additions & 4 deletions crates/hive-utils/src/hive/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ pub struct HiveGenesisConfig {

impl HiveGenesisConfig {
pub fn from_file(path: &str) -> Result<Self> {
let hive_genesis_file = File::open(path).unwrap();
Ok(serde_json::from_reader(hive_genesis_file).unwrap())
let hive_genesis_file = File::open(path)?;
Ok(serde_json::from_reader(hive_genesis_file)?)
}
}

Expand Down Expand Up @@ -226,8 +226,8 @@ pub async fn serialize_hive_to_madara_genesis_config(
});

let combined_genesis_file =
File::options().create_new(true).write(true).append(true).open(combined_genesis_path).unwrap();
// Serialize the loader to a string and then write to a file
File::options().create_new(true).write(true).append(false).open(combined_genesis_path).unwrap();
// Serialize the loader to a file
serde_json::to_writer_pretty(combined_genesis_file, &madara_loader)?;

Ok(())
Expand Down
15 changes: 12 additions & 3 deletions crates/test-utils/src/bin/dump-katana.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;

Check failure on line 3 in crates/test-utils/src/bin/dump-katana.rs

View workflow job for this annotation

GitHub Actions / Linters / clippy

unused import: `std::path::PathBuf`

use ethers::abi::Token;
use git2::{Repository, SubmoduleIgnore};
Expand Down Expand Up @@ -38,9 +39,17 @@ async fn main() {

// Dump the state
std::fs::create_dir_all(".katana/").expect("Failed to create Kakata dump dir");
let katana_dump_file =
File::options().create_new(true).read(true).write(true).append(true).open(".katana/dump.json").unwrap();
serde_json::to_writer(katana_dump_file, &dump_state);

let katana_dump_path = String::from(".katana/dump.json");
let katana_dump_file = File::options()
.create_new(true)
.read(true)
.write(true)
.append(false)
.open(katana_dump_path)
.expect(format!("Failed to open file {}", katana_dump_path));

Check failure on line 50 in crates/test-utils/src/bin/dump-katana.rs

View workflow job for this annotation

GitHub Actions / Linters / clippy

mismatched types
serde_json::to_writer_pretty(katana_dump_file, &dump_state)
.expect(format!("Failed to write to the file {}", katana_dump_path));

Check failure on line 52 in crates/test-utils/src/bin/dump-katana.rs

View workflow job for this annotation

GitHub Actions / Linters / clippy

mismatched types

let deployer_account = DeployerAccount {
address: test_context.client().deployer_account().address(),
Expand Down
323 changes: 323 additions & 0 deletions crates/test-utils/src/hive_utils/hive/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
use std::collections::HashMap;
use std::fs;
use std::io::Error as IoError;
use std::path::Path;

use eyre::Result;
use kakarot_rpc_core::client::constants::STARKNET_NATIVE_TOKEN;
use kakarot_rpc_core::models::felt::Felt252Wrapper;
use lazy_static::lazy_static;
use reth_primitives::{Address, Bytes, H256, U256, U64};
use serde::{Deserialize, Serialize};
use starknet::core::types::FieldElement;

use crate::deploy_helpers::compute_kakarot_contracts_class_hash;
use crate::hive_utils::kakarot::compute_starknet_address;
use crate::hive_utils::madara::utils::{
genesis_approve_kakarot, genesis_fund_starknet_address, genesis_set_bytecode,
genesis_set_storage_kakarot_contract_account, genesis_set_storage_starknet_contract,
};
use crate::hive_utils::types::{ClassHash, ContractAddress, ContractStorageKey, Felt, StorageValue};

#[derive(Deserialize, Serialize)]
pub struct GenesisLoader {
pub madara_path: Option<String>,
pub contract_classes: Vec<(ClassHash, ContractClassPath)>,
pub contracts: Vec<(ContractAddress, ClassHash)>,
pub storage: Vec<(ContractStorageKey, StorageValue)>,
pub fee_token_address: ContractAddress,
pub seq_addr_updated: bool,
}

#[derive(Deserialize, Serialize)]
pub struct ContractClassPath {
pub path: String,
pub version: u8,
}

/// Types from https://github.com/ethereum/go-ethereum/blob/master/core/genesis.go#L49C1-L58
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HiveGenesisConfig {
pub config: Config,
pub coinbase: Address,
pub difficulty: U64,
pub extra_data: Bytes,
pub gas_limit: U64,
pub nonce: U64,
pub timestamp: U64,
pub alloc: HashMap<Address, AccountInfo>,
}

impl HiveGenesisConfig {
pub fn from_file(path: &str) -> Result<Self> {
let hive_genesis_file = File::open(path)?;
Ok(serde_json::from_reader(&hive_genesis_file)?)
}
}

// Define constant addresses for Kakarot contracts
lazy_static! {
pub static ref KAKAROT_ADDRESS: FieldElement = FieldElement::from_hex_be("0x9001").unwrap(); // Safe unwrap, 0x9001
pub static ref BLOCKHASH_REGISTRY_ADDRESS: FieldElement = FieldElement::from_hex_be("0x9002").unwrap(); // Safe unwrap, 0x9002
pub static ref DEPLOYER_ACCOUNT_ADDRESS: FieldElement = FieldElement::from_hex_be("0x9003").unwrap(); // Safe unwrap, 0x9003
}

/// Convert Hive Genesis Config to Madara Genesis Config
///
/// This function will:
/// 1. Load the Madara genesis file
/// 2. Compute the class hash of Kakarot contracts
/// 3. Add Kakarot contracts to Loader
/// 4. Add Hive accounts to Loader (fund, storage, bytecode, proxy implementation)
/// 5. Serialize Loader to Madara genesis file
pub async fn serialize_hive_to_madara_genesis_config(
hive_genesis: HiveGenesisConfig,
mut madara_loader: GenesisLoader,
combined_genesis: &Path,
compiled_path: &Path,
) -> Result<(), IoError> {
// Compute the class hash of Kakarot contracts
let class_hashes = compute_kakarot_contracts_class_hash();

// { contract : class_hash }
let mut kakarot_contracts = HashMap::<String, FieldElement>::new();

// Add Kakarot contracts Contract Classes to loader
// Vec so no need to sort
class_hashes.iter().for_each(|(filename, class_hash)| {
madara_loader.contract_classes.push((
Felt(*class_hash),
ContractClassPath {
// Add the compiled path to the Kakarot contract filename
path: compiled_path.join(filename).with_extension("json").into_os_string().into_string().unwrap(), /* safe unwrap,
* valid path */
version: 0,
},
));

// Add Kakarot contracts {contract : class_hash} to Kakarot Contracts HashMap
// Remove .json from filename to get contract name
kakarot_contracts.insert(filename.to_string(), *class_hash);
});

// Set the Kakarot contracts address and proxy class hash
let account_proxy_class_hash = *kakarot_contracts.get("proxy").expect("Failed to get proxy class hash");
let contract_account_class_hash =
*kakarot_contracts.get("contract_account").expect("Failed to get contract_account class hash");
let eoa_class_hash = *kakarot_contracts.get("externally_owned_account").expect("Failed to get eoa class hash");

// Add Kakarot contracts to Loader
madara_loader.contracts.push((
Felt(*KAKAROT_ADDRESS),
Felt(*kakarot_contracts.get("kakarot").expect("Failed to get kakarot class hash")),
));
madara_loader.contracts.push((
Felt(*BLOCKHASH_REGISTRY_ADDRESS),
Felt(*kakarot_contracts.get("blockhash_registry").expect("Failed to get blockhash_registry class hash")),
));
madara_loader.contracts.push((
Felt(*DEPLOYER_ACCOUNT_ADDRESS),
Felt(*kakarot_contracts.get("OpenzeppelinAccount").expect("Failed to get deployer account class hash")),
));

// Set storage keys of Kakarot contract
// https://github.com/kkrt-labs/kakarot/blob/main/src/kakarot/constants.cairo
let storage_keys = [
("native_token_address", FieldElement::from_hex_be(STARKNET_NATIVE_TOKEN).unwrap()),
("contract_account_class_hash", contract_account_class_hash),
("externally_owned_account", eoa_class_hash),
("account_proxy_class_hash", account_proxy_class_hash),
("blockhash_registry_address", *BLOCKHASH_REGISTRY_ADDRESS),
// TODO: Use DEPLOY_FEE constant https://github.com/kkrt-labs/kakarot-rpc/pull/431/files#diff-88f745498d0aaf0b185085d99a74f0feaf253f047babc85770847931e7f726c3R125
("deploy_fee", FieldElement::from(100000_u64)),
];

storage_keys.into_iter().for_each(|(key, value)| {
let storage = genesis_set_storage_starknet_contract(*KAKAROT_ADDRESS, key, &[], value, 0);
madara_loader.storage.push(storage);
});

// Add Hive accounts to loader
// Convert the EVM accounts to Starknet accounts using compute_starknet_address
// Sort by key to ensure deterministic order
let mut hive_accounts: Vec<(reth_primitives::H160, AccountInfo)> = hive_genesis.alloc.into_iter().collect();
hive_accounts.sort_by_key(|(address, _)| *address);
hive_accounts.into_iter().for_each(|(evm_address, account_info)| {
// Use the given Kakarot contract address and declared proxy class hash for compute_starknet_address
let starknet_address = compute_starknet_address(
*KAKAROT_ADDRESS,
account_proxy_class_hash,
FieldElement::from_byte_slice_be(evm_address.as_bytes()).unwrap(), /* safe unwrap since evm_address
* is 20 bytes */
);
// Push to contracts
madara_loader.contracts.push((Felt(starknet_address), Felt(account_proxy_class_hash)));

// Set the balance of the account and approve Kakarot for infinite allowance
// Call genesis_fund_starknet_address util to get the storage tuples
let balances = genesis_fund_starknet_address(starknet_address, account_info.balance);
let allowance = genesis_approve_kakarot(starknet_address, *KAKAROT_ADDRESS, U256::MAX);
balances.into_iter().zip(allowance.into_iter()).for_each(|(balance, allowance)| {
madara_loader.storage.push(balance);
madara_loader.storage.push(allowance);
});

// Set the storage of the account, if any
if let Some(storage) = account_info.storage {
let mut storage: Vec<(U256, U256)> = storage.into_iter().collect();
storage.sort_by_key(|(key, _)| *key);
storage.into_iter().for_each(|(key, value)| {
// Call genesis_set_storage_kakarot_contract_account util to get the storage tuples
let storages = genesis_set_storage_kakarot_contract_account(starknet_address, key, value);
storages.into_iter().for_each(|storage| {
madara_loader.storage.push(storage);
});
});
}

// Determine the proxy implementation class hash based on whether bytecode is present
// Set the bytecode to the storage of the account, if any
let proxy_implementation_class_hash = if let Some(bytecode) = account_info.code {
let bytecode_len =
genesis_set_storage_starknet_contract(starknet_address, "bytecode_len_", &[], bytecode.len().into(), 0);
let bytecode = genesis_set_bytecode(&bytecode, starknet_address);

// Set the bytecode of the account
madara_loader.storage.extend(bytecode);
// Set the bytecode length of the account
madara_loader.storage.push(bytecode_len);

// Set the Owner
let owner =
genesis_set_storage_starknet_contract(starknet_address, "Ownable_owner", &[], *KAKAROT_ADDRESS, 0);
madara_loader.storage.push(owner);

// Since it has bytecode, it's a contract account
contract_account_class_hash
} else {
// Set kakarot address
let kakarot_address =
genesis_set_storage_starknet_contract(starknet_address, "kakarot_address", &[], *KAKAROT_ADDRESS, 0);
madara_loader.storage.push(kakarot_address);

// Since it has no bytecode, it's an externally owned account
eoa_class_hash
};

// Set the proxy implementation of the account to the determined class hash
let proxy_implementation_storage = genesis_set_storage_starknet_contract(
starknet_address,
"_implementation",
&[],
proxy_implementation_class_hash,
0, // 0 since it's storage value is felt
);
madara_loader.storage.push(proxy_implementation_storage);

// Set the evm address of the account and the "is_initialized" flag
let evm_address: Felt252Wrapper = evm_address.into();
let evm_address =
genesis_set_storage_starknet_contract(starknet_address, "evm_address", &[], evm_address.into(), 0);
let is_initialized =
genesis_set_storage_starknet_contract(starknet_address, "is_initialized_", &[], FieldElement::ONE, 0);
madara_loader.storage.push(evm_address);
madara_loader.storage.push(is_initialized);
});

// Serilaize the loader to a string and then to a file
let combined_genesis_file= File::new(combined_genesis);
fs::write(combined_genesis, serde_json::to_string_pretty(&madara_loader)?)?;

Ok(())
}

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Config {
pub chain_id: i128,
pub homestead_block: i128,
pub eip150_block: i128,
pub eip150_hash: H256,
pub eip155_block: i128,
pub eip158_block: i128,
}

#[derive(Serialize, Deserialize)]
pub struct AccountInfo {
pub balance: U256,
pub code: Option<Bytes>,
pub storage: Option<HashMap<U256, U256>>,
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use reth_primitives::U256;

use super::*;

#[test]
fn test_read_hive_genesis() {
// Read the hive genesis file
let genesis = HiveGenesisConfig::from_file("./src/hive_utils/test_data/hive_genesis.json").unwrap();

// Verify the genesis file has the expected number of accounts
assert_eq!(genesis.alloc.len(), 7);

// Verify balance of each account is not empty
assert!(genesis.alloc.values().all(|account_info| account_info.balance >= U256::from(0)));

// Verify the storage field for each account
// Since there is only one account with non-empty storage, we can hardcode the expected values
assert!(genesis.alloc.values().all(|account_info| {
account_info.storage.as_ref().map_or(true, |storage| {
storage.len() == 2
&& *storage
.get(
&U256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")
.unwrap(),
)
.unwrap()
== U256::from_str("0x1234").unwrap()
&& *storage
.get(
&U256::from_str("0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9")
.unwrap(),
)
.unwrap()
== U256::from_str("0x01").unwrap()
})
}));

// Verify the code field for each account, if exists, is not empty
assert!(
genesis.alloc.values().all(|account_info| account_info.code.as_ref().map_or(true, |code| !code.is_empty()))
);
}

#[tokio::test]
async fn test_madara_genesis() {
// Given
let hive_genesis = HiveGenesisConfig::from_file("./src/hive_utils/test_data/hive_genesis.json").unwrap();
let madara_loader =
serde_json::from_str::<GenesisLoader>(std::include_str!("../test_data/madara_genesis.json")).unwrap();
let combined_genesis_path = Path::new("./src/hive_utils/test_data/combined_genesis.json");
let compiled_path = Path::new("./cairo-contracts/build");

// When
serialize_hive_to_madara_genesis_config(hive_genesis, madara_loader, combined_genesis_path, compiled_path)
.await
.unwrap();

// Then
let combined_genesis_file = File::open(combined_genesis_path).unwrap();
let loader: GenesisLoader =
serde_json::from_reader(&combined_genesis_file).expect("Failed to read combined_genesis.json");
assert_eq!(9 + 3 + 7, loader.contracts.len()); // 9 original + 3 Kakarot contracts + 7 hive

// After
fs::remove_file(combined_genesis_path).unwrap();
}
}

0 comments on commit f0a1c75

Please sign in to comment.