Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(katana): settlement layer initialization flow improvement #2926

Merged
merged 1 commit into from
Jan 18, 2025
Merged
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
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions bin/katana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ repository.workspace = true
version.workspace = true

[dependencies]
katana-cairo.workspace = true
katana-cli.workspace = true
katana-db.workspace = true
katana-node.workspace = true
katana-primitives.workspace = true
katana-rpc-types.workspace = true

anyhow.workspace = true
byte-unit = "5.1.4"
Expand All @@ -22,16 +22,19 @@ comfy-table = "7.1.1"
dirs = "5.0.1"
dojo-utils.workspace = true
inquire = "0.7.5"
rand.workspace = true
serde.workspace = true
serde_json.workspace = true
shellexpand = "3.1.0"
spinoff.workspace = true
starknet.workspace = true
thiserror.workspace = true
tokio.workspace = true
toml.workspace = true
tracing.workspace = true

[dev-dependencies]
assert_matches.workspace = true
rstest.workspace = true
starknet.workspace = true

[features]
Expand Down
327 changes: 327 additions & 0 deletions bin/katana/src/cli/init/deployment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
use std::str::FromStr;
use std::sync::Arc;

use anyhow::{anyhow, Result};
use cainome::cairo_serde;
use cainome::rs::abigen;
use dojo_utils::{TransactionWaiter, TransactionWaitingError};
use katana_primitives::class::{
CompiledClassHash, ComputeClassHashError, ContractClass, ContractClassCompilationError,
ContractClassFromStrError,
};
use katana_primitives::{felt, ContractAddress, Felt};
use katana_rpc_types::class::RpcContractClass;
use spinoff::{spinners, Color, Spinner};
use starknet::accounts::{Account, AccountError, ConnectedAccount, SingleOwnerAccount};
use starknet::contract::ContractFactory;
use starknet::core::crypto::compute_hash_on_elements;
use starknet::core::types::{BlockId, BlockTag, FlattenedSierraClass, StarknetError};
use starknet::macros::short_string;
use starknet::providers::jsonrpc::HttpTransport;
use starknet::providers::{JsonRpcClient, Provider, ProviderError};
use starknet::signers::LocalWallet;
use thiserror::Error;
use tracing::trace;

type InitializerAccount = SingleOwnerAccount<Arc<JsonRpcClient<HttpTransport>>, LocalWallet>;

const PROGRAM_HASH: Felt =
felt!("0x5ab580b04e3532b6b18f81cfa654a05e29dd8e2352d88df1e765a84072db07");

/// The contract address that handles fact verification.
///
/// This address points to Herodotus' Atlantic Fact Registry contract on Starknet Sepolia as we rely
/// on their services to generates and verifies proofs.
const ATLANTIC_FACT_REGISTRY_SEPOLIA: Felt =
felt!("0x4ce7851f00b6c3289674841fd7a1b96b6fd41ed1edc248faccd672c26371b8c");

/// Deploys the settlement contract in the settlement layer and initializes it with the right
/// necessary states.
pub async fn deploy_settlement_contract(
mut account: InitializerAccount,
chain_id: Felt,
) -> Result<ContractAddress, ContractInitError> {
// This is important! Otherwise all the estimate fees after a transaction will be executed
// against invalid state.
account.set_block_id(BlockId::Tag(BlockTag::Pending));

let mut sp = Spinner::new(spinners::Dots, "", Color::Blue);

Check warning on line 48 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L40-L48

Added lines #L40 - L48 were not covered by tests

let result = async {
// -----------------------------------------------------------------------
// CONTRACT DEPLOYMENT
// -----------------------------------------------------------------------

let class = include_str!(
"../../../../../crates/katana/contracts/build/appchain_core_contract.json"
);

Check warning on line 57 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L50-L57

Added lines #L50 - L57 were not covered by tests

abigen!(
AppchainContract,
"[{\"type\":\"function\",\"name\":\"set_program_info\",\"inputs\":[{\"name\":\"\
program_hash\",\"type\":\"core::Felt\"},{\"name\":\"config_hash\",\"type\":\"\
core::Felt\"}],\"outputs\":[],\"state_mutability\":\"external\"},{\"type\":\"\
function\",\"name\":\"set_facts_registry\",\"inputs\":[{\"name\":\"address\",\"type\"\
:\"core::starknet::contract_address::ContractAddress\"}],\"outputs\":[],\"\
state_mutability\":\"external\"},{\"type\":\"function\",\"name\":\"\
get_facts_registry\",\"inputs\":[],\"outputs\":[{\"type\":\"\
core::starknet::contract_address::ContractAddress\"}],\"state_mutability\":\"view\"},\
{\"type\":\"function\",\"name\":\"get_program_info\",\"inputs\":[],\"outputs\":[{\"\
type\":\"(core::Felt, core::Felt)\"}],\"state_mutability\":\"view\"}]"
);

Check warning on line 71 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L59-L71

Added lines #L59 - L71 were not covered by tests

let class = ContractClass::from_str(class)?;
let class_hash = class.class_hash()?;

Check warning on line 74 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L73-L74

Added lines #L73 - L74 were not covered by tests

// Check if the class has already been declared,
match account.provider().get_class(BlockId::Tag(BlockTag::Pending), class_hash).await {
Ok(..) => {
// Class has already been declared, no need to do anything...
}

Check warning on line 80 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L77-L80

Added lines #L77 - L80 were not covered by tests

Err(ProviderError::StarknetError(StarknetError::ClassHashNotFound)) => {
sp.update_text("Declaring contract...");
let (rpc_class, casm_hash) = prepare_contract_declaration_params(class)?;

Check warning on line 84 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L83-L84

Added lines #L83 - L84 were not covered by tests

let res = account
.declare_v2(rpc_class.into(), casm_hash)
.send()
.await
.inspect(|res| {
let tx = format!("{:#x}", res.transaction_hash);
trace!(target: "init", %tx, "Transaction sent");
})
.map_err(ContractInitError::DeclarationError)?;

Check warning on line 94 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L86-L94

Added lines #L86 - L94 were not covered by tests

TransactionWaiter::new(res.transaction_hash, account.provider()).await?;

Check warning on line 96 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L96

Added line #L96 was not covered by tests
}

Err(err) => return Err(ContractInitError::Provider(err)),

Check warning on line 99 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L99

Added line #L99 was not covered by tests
}

sp.update_text("Deploying contract...");

let salt = Felt::from(rand::random::<u64>());
let factory = ContractFactory::new(class_hash, &account);

// appchain::constructor() https://github.com/cartridge-gg/piltover/blob/d373a844c3428383a48518adf468bf83249dec3a/src/appchain.cairo#L119-L125
let request = factory.deploy_v1(
vec![
account.address(), // owner
Felt::ZERO, // state_root
Felt::ZERO, // block_number
Felt::ZERO, // block_hash
],
salt,
false,
);

Check warning on line 117 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L102-L117

Added lines #L102 - L117 were not covered by tests

let res = request
.send()
.await
.inspect(|res| {
let tx = format!("{:#x}", res.transaction_hash);
trace!(target: "init", %tx, "Transaction sent");
})
.map_err(ContractInitError::DeploymentError)?;

Check warning on line 126 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L119-L126

Added lines #L119 - L126 were not covered by tests

TransactionWaiter::new(res.transaction_hash, account.provider()).await?;

Check warning on line 128 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L128

Added line #L128 was not covered by tests

// -----------------------------------------------------------------------
// CONTRACT INITIALIZATIONS
// -----------------------------------------------------------------------

let deployed_appchain_contract = request.deployed_address();
let appchain = AppchainContract::new(deployed_appchain_contract, &account);

// Compute the chain's config hash
let config_hash = compute_config_hash(
chain_id,
felt!("0x2e7442625bab778683501c0eadbc1ea17b3535da040a12ac7d281066e915eea"),
);

// 1. Program Info

sp.update_text("Setting program info...");

Check warning on line 145 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L134-L145

Added lines #L134 - L145 were not covered by tests

let res = appchain
.set_program_info(&PROGRAM_HASH, &config_hash)
.send()
.await
.inspect(|res| {
let tx = format!("{:#x}", res.transaction_hash);
trace!(target: "init", %tx, "Transaction sent");
})
.map_err(ContractInitError::Initialization)?;

Check warning on line 155 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L147-L155

Added lines #L147 - L155 were not covered by tests

TransactionWaiter::new(res.transaction_hash, account.provider()).await?;

Check warning on line 157 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L157

Added line #L157 was not covered by tests

// 2. Fact Registry

sp.update_text("Setting fact registry...");

Check warning on line 161 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L161

Added line #L161 was not covered by tests

let res = appchain
.set_facts_registry(&ATLANTIC_FACT_REGISTRY_SEPOLIA.into())
.send()
.await
.inspect(|res| {
let tx = format!("{:#x}", res.transaction_hash);
trace!(target: "init", %tx, "Transaction sent");
})
.map_err(ContractInitError::Initialization)?;

Check warning on line 171 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L163-L171

Added lines #L163 - L171 were not covered by tests

TransactionWaiter::new(res.transaction_hash, account.provider()).await?;

Check warning on line 173 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L173

Added line #L173 was not covered by tests

// -----------------------------------------------------------------------
// FINAL CHECKS
// -----------------------------------------------------------------------

// Assert that the values are correctly set
let (program_info_res, facts_registry_res) =
tokio::join!(appchain.get_program_info().call(), appchain.get_facts_registry().call());

Check warning on line 181 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L180-L181

Added lines #L180 - L181 were not covered by tests

let (actual_program_hash, actual_config_hash) = program_info_res?;
let facts_registry = facts_registry_res?;

Check warning on line 184 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L183-L184

Added lines #L183 - L184 were not covered by tests

if actual_program_hash != PROGRAM_HASH {
return Err(ContractInitError::InvalidProgramHash {
actual: actual_program_hash,
expected: PROGRAM_HASH,
});
}

if actual_config_hash != config_hash {
return Err(ContractInitError::InvalidConfigHash {
actual: actual_config_hash,
expected: config_hash,
});
}

if facts_registry != ATLANTIC_FACT_REGISTRY_SEPOLIA.into() {
return Err(ContractInitError::InvalidFactRegistry {
actual: facts_registry.into(),
expected: ATLANTIC_FACT_REGISTRY_SEPOLIA,
});
}

Ok(deployed_appchain_contract.into())
}
.await;

Check warning on line 209 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L186-L209

Added lines #L186 - L209 were not covered by tests

match result {
Ok(addr) => sp.success(&format!("Deployment successful ({addr})")),
Err(..) => sp.fail("Deployment failed"),

Check warning on line 213 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L211-L213

Added lines #L211 - L213 were not covered by tests
}
result
}

Check warning on line 216 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L215-L216

Added lines #L215 - L216 were not covered by tests

/// Error that can happen during the initialization of the core contract.
#[derive(Error, Debug)]

Check warning on line 219 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L219

Added line #L219 was not covered by tests
pub enum ContractInitError {
#[error("failed to declare contract: {0:#?}")]
DeclarationError(AccountError<<InitializerAccount as Account>::SignError>),

#[error("failed to deploy contract: {0:#?}")]
DeploymentError(AccountError<<InitializerAccount as Account>::SignError>),

#[error("failed to initialize contract: {0:#?}")]
Initialization(AccountError<<InitializerAccount as Account>::SignError>),

#[error(
"invalid program info: program hash mismatch - expected {expected:#x}, got {actual:#x}"
)]
InvalidProgramHash { expected: Felt, actual: Felt },

#[error("invalid program info: config hash mismatch - expected {expected:#x}, got {actual:#x}")]
InvalidConfigHash { expected: Felt, actual: Felt },

#[error("invalid program state: fact registry mismatch - expected {expected:}, got {actual}")]
InvalidFactRegistry { expected: Felt, actual: Felt },

#[error(transparent)]
TxWaitingError(#[from] TransactionWaitingError),

#[error("failed parsing contract class: {0}")]
ContractParsing(#[from] ContractClassFromStrError),

#[error(transparent)]
ContractClassCompilation(#[from] ContractClassCompilationError),

#[error(transparent)]
ComputeClassHash(#[from] ComputeClassHashError),

#[error(transparent)]
Provider(#[from] ProviderError),

#[error(transparent)]
Other(#[from] anyhow::Error),
}

impl From<cairo_serde::Error> for ContractInitError {
fn from(value: cairo_serde::Error) -> Self {
match value {
cairo_serde::Error::Provider(e) => Self::Provider(e),
_ => Self::Other(anyhow!(value)),

Check warning on line 264 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L261-L264

Added lines #L261 - L264 were not covered by tests
}
}

Check warning on line 266 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L266

Added line #L266 was not covered by tests
}

fn prepare_contract_declaration_params(
class: ContractClass,
) -> Result<(FlattenedSierraClass, CompiledClassHash)> {
let casm_hash = class.clone().compile()?.class_hash()?;

Check warning on line 272 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L269-L272

Added lines #L269 - L272 were not covered by tests

let rpc_class = RpcContractClass::try_from(class).expect("should be valid");
let RpcContractClass::Class(class) = rpc_class else { unreachable!("unexpected legacy class") };
let flattened: FlattenedSierraClass = class.try_into()?;

Check warning on line 276 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L274-L276

Added lines #L274 - L276 were not covered by tests

Ok((flattened, casm_hash))
}

Check warning on line 279 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L278-L279

Added lines #L278 - L279 were not covered by tests

// NOTE: The reason why we're using the same address for both fee tokens is because we don't yet
// support having native fee token on the chain.
fn compute_config_hash(chain_id: Felt, fee_token: Felt) -> Felt {
compute_starknet_os_config_hash(chain_id, fee_token, fee_token)
}

Check warning on line 285 in bin/katana/src/cli/init/deployment.rs

View check run for this annotation

Codecov / codecov/patch

bin/katana/src/cli/init/deployment.rs#L283-L285

Added lines #L283 - L285 were not covered by tests

// https://github.com/starkware-libs/cairo-lang/blob/a86e92bfde9c171c0856d7b46580c66e004922f3/src/starkware/starknet/core/os/os_config/os_config.cairo#L1-L39
fn compute_starknet_os_config_hash(
chain_id: Felt,
deprecated_fee_token: Felt,
fee_token: Felt,
) -> Felt {
// A constant representing the StarkNet OS config version.
const STARKNET_OS_CONFIG_VERSION: Felt = short_string!("StarknetOsConfig2");

compute_hash_on_elements(&[
STARKNET_OS_CONFIG_VERSION,
chain_id,
deprecated_fee_token,
fee_token,
])
}

#[cfg(test)]
mod tests {
use katana_primitives::{felt, Felt};
use starknet::core::chain_id::{MAINNET, SEPOLIA};

use super::compute_starknet_os_config_hash;

const ETH_FEE_TOKEN: Felt =
felt!("0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7");
const STRK_FEE_TOKEN: Felt =
felt!("0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d");

// Source:
//
// - https://github.com/starkware-libs/cairo-lang/blob/8e11b8cc65ae1d0959328b1b4a40b92df8b58595/src/starkware/starknet/core/os/os_config/os_config_hash.json#L4
// - https://docs.starknet.io/tools/important-addresses/#fee_tokens
#[rstest::rstest]
#[case::mainnet(felt!("0x5ba2078240f1585f96424c2d1ee48211da3b3f9177bf2b9880b4fc91d59e9a2"), MAINNET)]
#[case::testnet(felt!("0x504fa6e5eb930c0d8329d4a77d98391f2730dab8516600aeaf733a6123432"), SEPOLIA)]
fn calculate_config_hash(#[case] config_hash: Felt, #[case] chain: Felt) {
let computed = compute_starknet_os_config_hash(chain, ETH_FEE_TOKEN, STRK_FEE_TOKEN);
assert_eq!(computed, config_hash);
}
}
Loading
Loading