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

Add script init command #1298

Merged
merged 51 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3cc29e6
Move `script.rs` to separate dir
ddoktorski Nov 30, 2023
cbeecc8
Add `script init` command
ddoktorski Dec 4, 2023
bf7642d
Add basic tests for `script init` command
ddoktorski Dec 5, 2023
c8296c3
Move logic for running script command out of main fn
ddoktorski Dec 5, 2023
f33f6b1
Add `ScriptInitResponse`
ddoktorski Dec 5, 2023
b921ae7
Replace scripts literal with constant
ddoktorski Dec 5, 2023
7d30963
Move `get_cairo_version` to `scarb_utils`
ddoktorski Dec 5, 2023
26a75e1
Pass script root dir as `Utf8PathBuf` instead of `&str`
ddoktorski Dec 5, 2023
1f515f7
Update docs
ddoktorski Dec 5, 2023
496c2ba
Update changelog
ddoktorski Dec 5, 2023
14b9ddf
Fix lint
ddoktorski Dec 5, 2023
83cc3ee
Update script command info
ddoktorski Dec 5, 2023
dbdf81d
Fix typo
ddoktorski Dec 5, 2023
315dfa7
Update cast_version variable to have it in one line
ddoktorski Dec 6, 2023
8a8e5e3
Rename script module name argument in docs
ddoktorski Dec 6, 2023
44b5c64
Enhance error handling
ddoktorski Dec 7, 2023
79244b8
Fix rebasing
ddoktorski Dec 15, 2023
1b743b6
Change logic for scripts dir in cwd
ddoktorski Dec 18, 2023
e84bd7e
Resolve merge conflicts
ddoktorski Jan 18, 2024
b4fb8d5
Change logic to always create `scripts` dir
ddoktorski Jan 18, 2024
08faf72
Update changelog
ddoktorski Jan 18, 2024
462d184
Fix merge
ddoktorski Jan 18, 2024
2e0d824
Remove empty file
ddoktorski Jan 18, 2024
8e8343c
Update test name
ddoktorski Jan 19, 2024
e32cd51
Update structure in `init` docs
ddoktorski Jan 19, 2024
11448fa
Remove `init_files_created` test
ddoktorski Jan 19, 2024
043930c
Use `ScarbCommand` from `scarb_api`
ddoktorski Jan 19, 2024
8293035
Resolve merge conflicts
ddoktorski Jan 31, 2024
88ee0c2
Add basic script as a template
ddoktorski Jan 31, 2024
ed00431
Merge branch 'master' into 1196-script-init
ddoktorski Jan 31, 2024
f1a0b07
Fix lint
ddoktorski Jan 31, 2024
83a4995
Resolve merge conflicts
ddoktorski Feb 7, 2024
4e4fdc1
Replace println with assert
ddoktorski Feb 7, 2024
0dd4715
Move doc files to appropriate directory
ddoktorski Feb 7, 2024
74b6f34
Fix lint
ddoktorski Feb 7, 2024
ddfa35d
Merge branch 'master' into 1196-script-init
ddoktorski Feb 9, 2024
8e77a43
Rename `SCRIPTS_DIR` to `INIT_SCRIPTS_DIR`
ddoktorski Feb 9, 2024
c55c475
Print call results in the template script
ddoktorski Feb 9, 2024
e62d065
Add init example to documentation
ddoktorski Feb 9, 2024
02b449c
Print `CallResult` struct instead of single felt
ddoktorski Feb 9, 2024
6847b75
Add test to check that init script compiles
ddoktorski Feb 12, 2024
a20905b
Resolve merge conflicts
ddoktorski Feb 12, 2024
a49266b
Merge branch 'master' into 1196-script-init
ddoktorski Feb 13, 2024
57f806f
Apply suggestions from code review
ddoktorski Feb 15, 2024
8102d8f
Merge branch 'master' into 1196-script-init
ddoktorski Feb 15, 2024
0a7438c
Merge branch 'master' into 1196-script-init
ddoktorski Feb 16, 2024
53b9bd0
Add warning message
ddoktorski Feb 16, 2024
15511d0
Resolve merge conflicts
ddoktorski Feb 16, 2024
6778d3c
Update warning and docs
ddoktorski Feb 19, 2024
605c87c
Resolve merge conflicts
ddoktorski Feb 19, 2024
ec45e25
Fix note format
ddoktorski Feb 19, 2024
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Cast

#### Added

- `script init` command to generate a template file structure for deployment scripts

## [0.17.1] - 2024-02-12

### Cast
Expand Down
2 changes: 2 additions & 0 deletions crates/sncast/src/helpers/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ pub const CREATE_KEYSTORE_PASSWORD_ENV_VAR: &str = "CREATE_KEYSTORE_PASSWORD";
pub const SCRIPT_LIB_ARTIFACT_NAME: &str = "__sncast_script_lib";
pub const CONFIG_FILENAME: &str = "snfoundry.toml";
pub const STATE_FILE_VERSION: u8 = 1;

pub const INIT_SCRIPTS_DIR: &str = "scripts";
6 changes: 6 additions & 0 deletions crates/sncast/src/helpers/scarb_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ pub fn get_scarb_metadata_with_deps(manifest_path: &Utf8PathBuf) -> Result<Metad
execute_scarb_metadata_command(&command)
}

pub fn get_cairo_version(manifest_path: &Utf8PathBuf) -> Result<String> {
let scarb_metadata = get_scarb_metadata(manifest_path)?;

Ok(scarb_metadata.app_version_info.cairo.version.to_string())
}

pub fn assert_manifest_path_exists(
path_to_scarb_toml: &Option<Utf8PathBuf>,
) -> Result<Utf8PathBuf> {
Expand Down
82 changes: 51 additions & 31 deletions crates/sncast/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::starknet_commands::{
account, call::Call, declare::Declare, deploy::Deploy, invoke::Invoke, multicall::Multicall,
script::Script,
};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use sncast::response::print::{print_command_result, OutputFormat};

use camino::Utf8PathBuf;
Expand Down Expand Up @@ -108,7 +108,7 @@ enum Commands {
/// Show current configuration being used
ShowConfig(ShowConfig),

/// Run a deployment script
/// Run or initialize a deployment script
Script(Script),
}

Expand All @@ -121,35 +121,7 @@ fn main() -> Result<()> {
let runtime = Runtime::new().expect("Failed to instantiate Runtime");

if let Commands::Script(script) = &cli.command {
let manifest_path = assert_manifest_path_exists(&cli.path_to_scarb_toml)?;
let package_metadata = get_package_metadata(&manifest_path, &script.package)?;

let mut config = load_config(&cli.profile, &Some(package_metadata.root.clone()))?;
update_cast_config(&mut config, &cli);
let provider = get_provider(&config.rpc_url)?;

let mut artifacts = build_and_load_artifacts(
&package_metadata,
&BuildConfig {
scarb_toml_path: manifest_path.clone(),
json: cli.json,
profile: cli.profile.unwrap_or("dev".to_string()),
},
)
.expect("Failed to build script");
let metadata_with_deps = get_scarb_metadata_with_deps(&manifest_path)?;
let mut result = starknet_commands::script::run(
&script.script_module_name,
&metadata_with_deps,
&package_metadata,
&mut artifacts,
&provider,
runtime,
&config,
);

print_command_result("script", &mut result, numbers_format, &output_format)?;
Ok(())
run_script_command(&cli, runtime, script, numbers_format, &output_format)
} else {
let mut config = load_config(&cli.profile, &None)?;
update_cast_config(&mut config, &cli);
Expand Down Expand Up @@ -421,6 +393,54 @@ async fn run_async_command(
}
}

fn run_script_command(
cli: &Cli,
runtime: Runtime,
script: &Script,
numbers_format: NumbersFormat,
output_format: &OutputFormat,
) -> Result<()> {
if let Some(starknet_commands::script::Commands::Init(init)) = &script.command {
let mut result = starknet_commands::script::init::init(init);
print_command_result("script init", &mut result, numbers_format, output_format)?;
} else {
let manifest_path = assert_manifest_path_exists(&cli.path_to_scarb_toml)?;
let package_metadata = get_package_metadata(&manifest_path, &script.package)?;

let mut config = load_config(&cli.profile, &Some(package_metadata.root.clone()))?;
update_cast_config(&mut config, cli);
let provider = get_provider(&config.rpc_url)?;

let mut artifacts = build_and_load_artifacts(
&package_metadata,
&BuildConfig {
scarb_toml_path: manifest_path.clone(),
json: cli.json,
profile: cli.profile.clone().unwrap_or("dev".to_string()),
},
)
.expect("Failed to build script");
let metadata_with_deps = get_scarb_metadata_with_deps(&manifest_path)?;

let script_module_name = script.module_name.as_ref().ok_or_else(|| {
anyhow!("Required positional argument SCRIPT_MODULE_NAME not provided")
})?;

let mut result = starknet_commands::script::run::run(
script_module_name,
&metadata_with_deps,
&package_metadata,
&mut artifacts,
&provider,
runtime,
&config,
);

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

fn update_cast_config(config: &mut CastConfig, cli: &Cli) {
macro_rules! clone_or_else {
($field:expr, $config_field:expr) => {
Expand Down
7 changes: 7 additions & 0 deletions crates/sncast/src/response/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,10 @@ pub struct ScriptResponse {
}

impl CommandResponse for ScriptResponse {}

#[derive(Serialize)]
pub struct ScriptInitResponse {
pub status: String,
}

impl CommandResponse for ScriptInitResponse {}
162 changes: 162 additions & 0 deletions crates/sncast/src/starknet_commands/script/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use anyhow::{anyhow, ensure, Context, Ok, Result};
use camino::Utf8PathBuf;
use std::fs;

use clap::Args;
use indoc::{formatdoc, indoc};
use scarb_api::ScarbCommand;
use sncast::helpers::constants::INIT_SCRIPTS_DIR;
use sncast::helpers::scarb_utils::get_cairo_version;
use sncast::response::print::print_as_warning;
use sncast::response::structs::ScriptInitResponse;

#[derive(Args, Debug)]
pub struct Init {
/// Name of a script to create
pub script_name: String,
}

pub fn init(init_args: &Init) -> Result<ScriptInitResponse> {
let script_root_dir_path = get_script_root_dir_path(&init_args.script_name)?;

init_scarb_project(&init_args.script_name, &script_root_dir_path)?;

let modify_files_result = add_dependencies(&script_root_dir_path)
.and_then(|()| modify_files_in_src_dir(&init_args.script_name, &script_root_dir_path));

print_as_warning(
&anyhow!("The newly created script isn't auto-added to the workspace. You may need to manually add it to your workspace configuration in Scarb.toml")
ddoktorski marked this conversation as resolved.
Show resolved Hide resolved
);

match modify_files_result {
Result::Ok(()) => Ok(ScriptInitResponse {
status: format!(
"Successfully initialized `{}` at {}",
init_args.script_name, script_root_dir_path
),
}),
Err(err) => {
clean_created_dir_and_files(&script_root_dir_path);
Err(err)
}
}
}

fn get_script_root_dir_path(script_name: &str) -> Result<Utf8PathBuf> {
let current_dir = Utf8PathBuf::from_path_buf(std::env::current_dir()?)
.expect("Failed to create Utf8PathBuf for the current directory");

let scripts_dir = current_dir.join(INIT_SCRIPTS_DIR);

ensure!(
!scripts_dir.exists(),
"Scripts directory already exists at `{scripts_dir}`"
);

Ok(scripts_dir.join(script_name))
}

fn init_scarb_project(script_name: &str, script_root_dir: &Utf8PathBuf) -> Result<()> {
ScarbCommand::new()
.args([
"new",
"--name",
&script_name,
"--no-vcs",
"--quiet",
script_root_dir.as_str(),
])
.run()
.context("Failed to init Scarb project")?;

Ok(())
}

fn add_dependencies(script_root_dir: &Utf8PathBuf) -> Result<()> {
add_sncast_std_dependency(script_root_dir)
.context("Failed to add sncast_std dependency to Scarb.toml")?;
add_starknet_dependency(script_root_dir)
.context("Failed to add starknet dependency to Scarb.toml")?;

Ok(())
}

fn add_sncast_std_dependency(script_root_dir: &Utf8PathBuf) -> Result<()> {
let cast_version = format!("v{}", env!("CARGO_PKG_VERSION"));

ScarbCommand::new()
.current_dir(script_root_dir)
.args([
"--offline",
"add",
"sncast_std",
"--git",
"https://github.com/foundry-rs/starknet-foundry.git",
"--tag",
&cast_version,
])
.run()?;

Ok(())
}

fn add_starknet_dependency(script_root_dir: &Utf8PathBuf) -> Result<()> {
let scarb_manifest_path = script_root_dir.join("Scarb.toml");
let cairo_version =
get_cairo_version(&scarb_manifest_path).context("Failed to get cairo version")?;
let starknet_dependency = format!("starknet@>={cairo_version}");

ScarbCommand::new()
.current_dir(script_root_dir)
.args(["--offline", "add", &starknet_dependency])
.run()?;

Ok(())
}

fn modify_files_in_src_dir(script_name: &str, script_root_dir: &Utf8PathBuf) -> Result<()> {
create_script_main_file(script_name, script_root_dir)
.context(format!("Failed to create {script_name}.cairo file"))?;
overwrite_lib_file(script_name, script_root_dir).context("Failed to overwrite lib.cairo file")
}

fn create_script_main_file(script_name: &str, script_root_dir: &Utf8PathBuf) -> Result<()> {
let script_main_file_name = format!("{script_name}.cairo");
let script_main_file_path = script_root_dir.join("src").join(script_main_file_name);

fs::write(
script_main_file_path,
indoc! {r#"
use sncast_std::{call, CallResult};

// The example below uses a contract deployed to the Goerli testnet
fn main() {
let contract_address = 0x7ad10abd2cc24c2e066a2fee1e435cd5fa60a37f9268bfbaf2e98ce5ca3c436;
let call_result = call(contract_address.try_into().unwrap(), 'get_greeting', array![]);
assert(*call_result.data[0]=='Hello, Starknet!', *call_result.data[0]);
println!("{:?}", call_result);
}
"#},
)?;

Ok(())
}

fn overwrite_lib_file(script_name: &str, script_root_dir: &Utf8PathBuf) -> Result<()> {
let lib_file_path = script_root_dir.join("src/lib.cairo");

fs::write(
lib_file_path,
formatdoc! {r#"
mod {script_name};
"#},
)?;

Ok(())
}

fn clean_created_dir_and_files(script_root_dir: &Utf8PathBuf) {
if fs::remove_dir_all(script_root_dir).is_err() {
eprintln!("Failed to clean created files by init command at {script_root_dir}");
}
}
23 changes: 23 additions & 0 deletions crates/sncast/src/starknet_commands/script/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::starknet_commands::script::init::Init;
use clap::{Args, Subcommand};

pub mod init;
pub mod run;

#[derive(Args)]
pub struct Script {
/// Module name that contains the `main` function, which will be executed
pub module_name: Option<String>,

/// Specifies scarb package to be used
#[clap(long)]
pub package: Option<String>,
THenry14 marked this conversation as resolved.
Show resolved Hide resolved

#[clap(subcommand)]
pub command: Option<Commands>,
}

#[derive(Debug, Subcommand)]
pub enum Commands {
Init(Init),
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_vm::types::relocatable::Relocatable;
use cairo_vm::vm::errors::hint_errors::HintError;
use cairo_vm::vm::vm_core::VirtualMachine;
use clap::command;
use clap::Args;
use conversions::{FromConv, IntoConv};
use itertools::chain;
use runtime::starknet::context::{build_context, BlockInfo};
Expand All @@ -45,17 +43,6 @@ use tokio::runtime::Runtime;

type ScriptStarknetContractArtifacts = StarknetContractArtifacts;

#[derive(Args)]
#[command(about = "Execute a deployment script")]
pub struct Script {
/// Module name that contains the `main` function, which will be executed
pub script_module_name: String,

/// Specifies scarb package to be used
#[clap(long)]
pub package: Option<String>,
}

pub struct CastScriptExtension<'a> {
pub hints: &'a HashMap<String, Hint>,
pub provider: &'a JsonRpcClient<HttpTransport>,
Expand Down
Loading
Loading