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

Introduced by-name deployment and declare-deploy command #2331

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
eecc2e2
Added a compiled contract representation
integraledelebesgue Jul 24, 2024
c4dbf57
Redesigned `declare` handler
integraledelebesgue Jul 24, 2024
69cf938
Introduced deploy by name
integraledelebesgue Jul 24, 2024
c449de5
Improved deploy-by-name
integraledelebesgue Jul 24, 2024
1f74c9c
Added deploy-by-name tests
integraledelebesgue Jul 24, 2024
b87ef03
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Jul 24, 2024
436dd28
Added deploy-by-name to changelog and docs
integraledelebesgue Jul 24, 2024
48ab4a9
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Jul 29, 2024
c84afe1
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Jul 29, 2024
f691a22
Moved deploy-by-name example higher in the docs
integraledelebesgue Jul 31, 2024
40b8ad7
Improved descriptivity of the `build_artifacts` method
integraledelebesgue Jul 31, 2024
d6b13d3
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Jul 31, 2024
873f524
Removed auto-declaration from `deploy` and introduced `declare-deploy…
integraledelebesgue Aug 5, 2024
9739534
Fixed `clippy` warnings
integraledelebesgue Aug 5, 2024
41880aa
Merged `master`
integraledelebesgue Aug 5, 2024
ae24960
Updated `declare-deploy` in docs and changelog
integraledelebesgue Aug 5, 2024
c29673b
Updated doc summary
integraledelebesgue Aug 5, 2024
9af83f9
Updated tests
integraledelebesgue Aug 7, 2024
8f9502a
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Aug 8, 2024
6350ac6
Simplified `declare_compiled` flow
integraledelebesgue Aug 8, 2024
67b2b96
Updated tests
integraledelebesgue Aug 9, 2024
79f23e9
Updated docs
integraledelebesgue Aug 9, 2024
fa5052f
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Aug 9, 2024
9a60f27
Optimized errors
integraledelebesgue Aug 9, 2024
c272f71
Merge branch 'master' into integraledelebesgue/feature-2279
integraledelebesgue Aug 27, 2024
1d0341e
Removed duplicated test
integraledelebesgue Aug 27, 2024
15d26df
Changed salt name to more professional :woozy_face:
integraledelebesgue Aug 27, 2024
7578221
Changed notes to warnings
integraledelebesgue Aug 27, 2024
f085c3e
Changed `From` to `TryFrom`
integraledelebesgue Aug 27, 2024
7b77cd3
Added block explorer output to `declare-deploy`
integraledelebesgue Aug 27, 2024
85f478e
Removed unnecessary `test-runner` argument
integraledelebesgue Aug 27, 2024
b11b1bc
Fixed logic related to class hashes
integraledelebesgue Aug 27, 2024
8c5bd8d
Merge branch 'master' of https://github.com/foundry-rs/starknet-found…
franciszekjob Dec 20, 2024
1fab090
Resolve other conflicts
franciszekjob Dec 20, 2024
28319d2
Use `HelloSncast` contract in snippet
franciszekjob Dec 20, 2024
b2a7f16
Fix declare-deploy about text
franciszekjob Dec 20, 2024
e3cbebe
Update declare-deploy docs
franciszekjob Dec 20, 2024
c6b1d55
Trigger CI
franciszekjob Dec 20, 2024
05113b7
Merge branch 'master' of https://github.com/foundry-rs/starknet-found…
franciszekjob Dec 20, 2024
363e4cf
Merge branch 'master' of https://github.com/foundry-rs/starknet-found…
franciszekjob Jan 14, 2025
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Cast

### Added
- `sncast deploy` can now deploy contracts by name instead of class hash. [Read more here](https://foundry-rs.github.io/starknet-foundry/appendix/sncast/deploy.html)

#### Changed
- `account create` outputs hint about the type of the tokens required to prefund a newly created account with before deployment

Expand Down
54 changes: 54 additions & 0 deletions crates/sncast/src/helpers/scarb_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ use scarb_api::{
};
use scarb_ui::args::PackagesFilter;
use shared::{command::CommandExt, print::print_as_warning};
use starknet::{
core::types::{
contract::{CompiledClass, SierraClass},
BlockId, FlattenedSierraClass,
},
providers::{jsonrpc::HttpTransport, JsonRpcClient, Provider},
};
use starknet_crypto::FieldElement;
use std::collections::HashMap;
use std::env;
use std::str::FromStr;
Expand Down Expand Up @@ -176,6 +184,52 @@ pub fn build_and_load_artifacts(
}
}

pub fn read_manifest_and_build_artifacts(
package: &Option<String>,
json: bool,
profile: &Option<String>,
) -> Result<HashMap<String, StarknetContractArtifacts>> {
let manifest_path = assert_manifest_path_exists()?;
let package_metadata = get_package_metadata(&manifest_path, package)?;

let profile = profile.to_owned().unwrap_or("dev".to_string());

let build_config = BuildConfig {
scarb_toml_path: manifest_path,
json,
profile,
};

build_and_load_artifacts(&package_metadata, &build_config).context("Failed to build contract")
}

pub struct CompiledContract {
pub class: FlattenedSierraClass,
pub hash: FieldElement,
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved
}

impl CompiledContract {
pub fn from(artifacts: &StarknetContractArtifacts) -> Result<Self> {
let class: SierraClass =
serde_json::from_str(&artifacts.sierra).context("Failed to parse sierra artifact")?;

let class = class.flatten().map_err(anyhow::Error::from)?;
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved

let compiled_class: CompiledClass =
serde_json::from_str(&artifacts.casm).context("Failed to parse casm artifact")?;

let hash = compiled_class.class_hash().map_err(anyhow::Error::from)?;

Ok(Self { class, hash })
}
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved

pub async fn is_declared(&self, provider: &JsonRpcClient<HttpTransport>) -> bool {
let block_id = BlockId::Tag(starknet::core::types::BlockTag::Pending);
let class_hash = self.hash;
provider.get_class(block_id, class_hash).await.is_ok()
}
}

#[cfg(test)]
mod tests {
use crate::helpers::scarb_utils::{get_package_metadata, get_scarb_metadata};
Expand Down
64 changes: 43 additions & 21 deletions crates/sncast/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ use sncast::helpers::constants::{DEFAULT_ACCOUNTS_FILE, DEFAULT_MULTICALL_CONTEN
use sncast::helpers::fee::PayableTransaction;
use sncast::helpers::scarb_utils::{
assert_manifest_path_exists, build, build_and_load_artifacts, get_package_metadata,
get_scarb_metadata_with_deps, BuildConfig,
get_scarb_metadata_with_deps, read_manifest_and_build_artifacts, BuildConfig,
};
use sncast::response::errors::handle_starknet_command_error;
use sncast::{
chain_id_to_network_name, get_account, get_block_id, get_chain_id, get_default_state_file_name,
get_nonce, get_provider, NumbersFormat, ValidatedWaitParams, WaitForTx,
get_provider, NumbersFormat, ValidatedWaitParams, WaitForTx,
};
use starknet::core::utils::get_selector_from_name;
use starknet::providers::jsonrpc::HttpTransport;
use starknet::providers::JsonRpcClient;
use starknet_commands::account::list::print_account_list;
use starknet_commands::deploy::DeployResolved;
use starknet_commands::verify::Verify;
use tokio::runtime::Runtime;

Expand Down Expand Up @@ -198,28 +199,23 @@ async fn run_async_command(
config.keystore,
)
.await?;
let manifest_path = assert_manifest_path_exists()?;
let package_metadata = get_package_metadata(&manifest_path, &declare.package)?;
let artifacts = build_and_load_artifacts(
&package_metadata,
&BuildConfig {
scarb_toml_path: manifest_path,
json: cli.json,
profile: cli.profile.unwrap_or("dev".to_string()),
},

let artifacts =
read_manifest_and_build_artifacts(&declare.package, cli.json, &cli.profile)?;

let mut result = crate::starknet_commands::declare::declare(
declare,
&account,
&artifacts,
wait_config,
)
.expect("Failed to build contract");
let mut result =
starknet_commands::declare::declare(declare, &account, &artifacts, wait_config)
.await
.map_err(handle_starknet_command_error);
.await
.map_err(handle_starknet_command_error);

print_command_result("declare", &mut result, numbers_format, &output_format)?;
Ok(())
print_command_result("declare", &mut result, numbers_format, &output_format)
}

Commands::Deploy(deploy) => {
deploy.validate()?;
let account = get_account(
&config.account,
&config.accounts_file,
Expand All @@ -228,12 +224,38 @@ async fn run_async_command(
)
.await?;

let deploy: DeployResolved = if deploy.class_hash.is_some() {
deploy.try_into().unwrap()
} else {
let contract =
deploy.build_artifacts_and_get_compiled_contract(cli.json, &cli.profile)?;
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved
let class_hash = contract.hash;

if !contract.is_declared(&provider).await {
let declare = deploy.declare_data()?;

let mut result = crate::starknet_commands::declare::declare_compiled(
declare,
&account,
contract,
wait_config,
)
.await
.map_err(handle_starknet_command_error);

print_command_result("declare", &mut result, numbers_format, &output_format)?;
}

deploy.resolve_class_hash(class_hash)
};

deploy.validate()?;

let mut result = starknet_commands::deploy::deploy(deploy, &account, wait_config)
.await
.map_err(handle_starknet_command_error);

print_command_result("deploy", &mut result, numbers_format, &output_format)?;
Ok(())
print_command_result("deploy", &mut result, numbers_format, &output_format)
}

Commands::Call(call) => {
Expand Down
145 changes: 86 additions & 59 deletions crates/sncast/src/starknet_commands/declare.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, Result};
use clap::{Args, ValueEnum};
use scarb_api::StarknetContractArtifacts;
use sncast::helpers::error::token_not_supported_for_declaration;
use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken, PayableTransaction};
use sncast::helpers::scarb_utils::CompiledContract;
use sncast::response::errors::StarknetCommandError;
use sncast::response::structs::DeclareResponse;
use sncast::response::structs::Felt;
use sncast::{apply_optional, handle_wait_for_tx, impl_payable_transaction, ErrorData, WaitForTx};
use starknet::accounts::AccountError;
use starknet::accounts::AccountError::Provider;
use starknet::accounts::{ConnectedAccount, DeclarationV2, DeclarationV3};

use sncast::helpers::error::token_not_supported_for_declaration;
use sncast::helpers::fee::{FeeArgs, FeeSettings, FeeToken, PayableTransaction};
use sncast::response::errors::StarknetCommandError;
use starknet::core::types::DeclareTransactionResult;
use starknet::core::types::FieldElement;
use starknet::{
accounts::{Account, SingleOwnerAccount},
core::types::contract::{CompiledClass, SierraClass},
providers::jsonrpc::{HttpTransport, JsonRpcClient},
signers::LocalWallet,
};
Expand Down Expand Up @@ -54,79 +55,105 @@ impl_payable_transaction!(Declare, token_not_supported_for_declaration,
DeclareVersion::V3 => FeeToken::Strk
);

#[allow(clippy::too_many_lines)]
pub async fn declare(
declare: Declare,
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
artifacts: &HashMap<String, StarknetContractArtifacts>,
wait_config: WaitForTx,
) -> Result<DeclareResponse, StarknetCommandError> {
let fee_settings = declare
.fee_args
.clone()
.fee_token(declare.token_from_version())
.try_into_fee_settings(account.provider(), account.block_id())
.await?;

let contract_artifacts =
artifacts
.get(&declare.contract)
.ok_or(StarknetCommandError::ContractArtifactsNotFound(
ErrorData::new(declare.contract),
))?;

let contract_definition: SierraClass = serde_json::from_str(&contract_artifacts.sierra)
.context("Failed to parse sierra artifact")?;
let casm_contract_definition: CompiledClass =
serde_json::from_str(&contract_artifacts.casm).context("Failed to parse casm artifact")?;
type SendDeclarationError<'client> = AccountError<<SingleOwnerAccount<&'client JsonRpcClient<HttpTransport>, LocalWallet> as starknet::accounts::Account>::SignError>;

let casm_class_hash = casm_contract_definition
.class_hash()
.map_err(anyhow::Error::from)?;
async fn send_declaration<'client>(
contract: CompiledContract,
account: &'client SingleOwnerAccount<&'client JsonRpcClient<HttpTransport>, LocalWallet>,
nonce: Option<FieldElement>,
fee_settings: FeeSettings,
) -> Result<DeclareTransactionResult, SendDeclarationError<'client>> {
let CompiledContract { class, hash } = contract;

let declared = match fee_settings {
match fee_settings {
FeeSettings::Eth { max_fee } => {
let declaration = account.declare_v2(
Arc::new(contract_definition.flatten().map_err(anyhow::Error::from)?),
casm_class_hash,
);
let declaration = account.declare_v2(Arc::new(class), hash);

let declaration = apply_optional(declaration, max_fee, DeclarationV2::max_fee);
let declaration = apply_optional(declaration, declare.nonce, DeclarationV2::nonce);
let declaration = apply_optional(declaration, nonce, DeclarationV2::nonce);

declaration.send().await
}

FeeSettings::Strk {
max_gas,
max_gas_unit_price,
} => {
let declaration = account.declare_v3(
Arc::new(contract_definition.flatten().map_err(anyhow::Error::from)?),
casm_class_hash,
);
let declaration = account.declare_v3(Arc::new(class), hash);

let declaration = apply_optional(declaration, max_gas, DeclarationV3::gas);
let declaration =
apply_optional(declaration, max_gas_unit_price, DeclarationV3::gas_price);
let declaration = apply_optional(declaration, declare.nonce, DeclarationV3::nonce);
let declaration = apply_optional(declaration, nonce, DeclarationV3::nonce);

declaration.send().await
}
};

match declared {
Ok(result) => handle_wait_for_tx(
account.provider(),
result.transaction_hash,
DeclareResponse {
class_hash: Felt(result.class_hash),
transaction_hash: Felt(result.transaction_hash),
},
wait_config,
)
.await
.map_err(StarknetCommandError::from),
}
}

async fn handle_declaration<'client>(
result: Result<DeclareTransactionResult, SendDeclarationError<'client>>,
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved
account: &'client SingleOwnerAccount<&'client JsonRpcClient<HttpTransport>, LocalWallet>,
wait_config: WaitForTx,
) -> Result<DeclareResponse, StarknetCommandError> {
match result {
Ok(result) => {
let wait = handle_wait_for_tx(
account.provider(),
result.transaction_hash,
DeclareResponse {
class_hash: Felt(result.class_hash),
transaction_hash: Felt(result.transaction_hash),
},
wait_config,
);

wait.await.map_err(StarknetCommandError::from)
}
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved

Err(Provider(error)) => Err(StarknetCommandError::ProviderError(error.into())),

_ => Err(anyhow!("Unknown RPC error").into()),
}
}

async fn get_fee_settings(
declare: &Declare,
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
) -> Result<FeeSettings> {
declare
.fee_args
.clone()
.fee_token(declare.token_from_version())
.try_into_fee_settings(account.provider(), account.block_id())
.await
integraledelebesgue marked this conversation as resolved.
Show resolved Hide resolved
}

pub async fn declare_compiled(
declare: Declare,
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
contract: CompiledContract,
wait_config: WaitForTx,
) -> Result<DeclareResponse, StarknetCommandError> {
let fee_settings = get_fee_settings(&declare, account).await?;
let declared = send_declaration(contract, account, declare.nonce, fee_settings).await;
handle_declaration(declared, account, wait_config).await
}

pub async fn declare(
declare: Declare,
account: &SingleOwnerAccount<&JsonRpcClient<HttpTransport>, LocalWallet>,
artifacts: &HashMap<String, StarknetContractArtifacts>,
wait_config: WaitForTx,
) -> Result<DeclareResponse, StarknetCommandError> {
let contract_artifacts =
artifacts
.get(&declare.contract)
.ok_or(StarknetCommandError::ContractArtifactsNotFound(
ErrorData::new(declare.contract.clone()),
))?;

let contract = CompiledContract::from(contract_artifacts)?;

declare_compiled(declare, account, contract, wait_config).await
}
Loading
Loading