Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
111 changes: 80 additions & 31 deletions crates/stellar-scaffold-cli/src/commands/build/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ pub enum Error {
Json(#[from] serde_json::Error),
#[error("⛔ ️Failed to run npm command in {0:?}: {1:?}")]
NpmCommandFailure(std::path::PathBuf, String),
#[error(transparent)]
AccountFund(#[from] cli::keys::fund::Error),
}

impl Args {
Expand All @@ -109,7 +111,9 @@ impl Args {
// Create the '.stellar' directory if it doesn't exist
std::fs::create_dir_all(workspace_root.join(".stellar"))
.map_err(stellar_cli::config::locator::Error::Io)?;
Self::handle_accounts(current_env.accounts.as_deref()).await?;
self.clone()
.handle_accounts(current_env.accounts.as_deref())
.await?;
self.clone()
.handle_contracts(
current_env.contracts.as_ref(),
Expand Down Expand Up @@ -338,7 +342,7 @@ export default new Client.Client({{
Ok(())
}

async fn handle_accounts(accounts: Option<&[env_toml::Account]>) -> Result<(), Error> {
async fn handle_accounts(self, accounts: Option<&[env_toml::Account]>) -> Result<(), Error> {
let Some(accounts) = accounts else {
return Err(Error::NeedAtLeastOneAccount);
};
Expand All @@ -358,18 +362,22 @@ export default new Client.Client({{

for account in accounts {
eprintln!("🔐 creating keys for {:?}", account.name);
cli::keys::generate::Cmd::parse_arg_vec(&[&account.name, "--fund"])?
.run(&stellar_cli::commands::global::Args::default())
let args = stellar_cli::commands::global::Args {
locator: self.clone().get_config_locator(),
..Default::default()
};

match cli::keys::generate::Cmd::parse_arg_vec(&[&account.name])?
.run(&args)
.await
.or_else(|e| {
if e.to_string().contains("already exists") {
// ignore "already exists" errors
eprintln!("{e}");
Ok(())
} else {
Err(e)
}
})?;
{
Ok(()) => {}
Comment thread
BlaineHeffron marked this conversation as resolved.
Outdated
Err(e) if e.to_string().contains("already exists") => {
Comment thread
BlaineHeffron marked this conversation as resolved.
eprintln!("{e}");
// Just log that the identity already exists, funding will happen on-demand
}
Err(e) => return Err(e.into()),
Comment thread
BlaineHeffron marked this conversation as resolved.
Outdated
}
Comment thread
BlaineHeffron marked this conversation as resolved.
}

std::env::set_var("STELLAR_ACCOUNT", &default_account);
Expand Down Expand Up @@ -525,21 +533,20 @@ export default new Client.Client({{

// Deploy new contract if we got here
let contract_id = self.deploy_contract(name, &hash, &settings).await?;
// Run after_deploy script if in development or test environment
if (env == "development" || env == "testing") && settings.after_deploy.is_some() {
eprintln!("🚀 Running after_deploy script for {name:?}");
self.run_after_deploy_script(
name,
&contract_id,
settings.after_deploy.as_ref().unwrap(),
)
.await?;
}
self.save_contract_alias(name, &contract_id, network)?;
contract_id
};

// Run after_deploy script if in development or test environment
if (env == "development" || env == "testing") && settings.after_deploy.is_some() {
eprintln!("🚀 Running after_deploy script for {name:?}");
self.run_after_deploy_script(
name,
&contract_id,
settings.after_deploy.as_ref().unwrap(),
)
.await?;
}

self.clone()
.generate_contract_bindings(name, &contract_id.to_string())
.await?;
Expand All @@ -553,19 +560,61 @@ export default new Client.Client({{
wasm_path: &std::path::Path,
) -> Result<String, Error> {
eprintln!("📲 installing {name:?} wasm bytecode on-chain...");
let hash = cli::contract::upload::Cmd::parse_arg_vec(&[

let upload_result = cli::contract::upload::Cmd::parse_arg_vec(&[
"--wasm",
wasm_path
.to_str()
.expect("we do not support non-utf8 paths"),
])?
.run_against_rpc_server(None, None)
.await?
.into_result()
.expect("no hash returned by 'contract upload'")
.to_string();
eprintln!(" ↳ hash: {hash}");
Ok(hash)
.await;

match upload_result {
Ok(hash) => {
let hash_str = hash
.into_result()
.expect("no hash returned by 'contract upload'")
.to_string();
eprintln!(" ↳ hash: {hash_str}");
Ok(hash_str)
}
Err(e) if e.to_string().contains("Account not found") => {
eprintln!("Account not found, attempting to fund...");

// Get the default account name from environment
Comment thread
BlaineHeffron marked this conversation as resolved.
Outdated
let account_name = std::env::var("STELLAR_ACCOUNT")
.expect("STELLAR_ACCOUNT should be set earlier in handle_accounts");

// Fund the account
let args = stellar_cli::commands::global::Args {
locator: self.clone().get_config_locator(),
..Default::default()
};

eprintln!("💸 funding {account_name:?}");
cli::keys::fund::Cmd::parse_arg_vec(&[&account_name])?
.run(&args)
.await?;

// Retry the upload
eprintln!("🔄 retrying wasm upload...");
let hash = cli::contract::upload::Cmd::parse_arg_vec(&[
"--wasm",
wasm_path
.to_str()
.expect("we do not support non-utf8 paths"),
])?
.run_against_rpc_server(None, None)
.await?
.into_result()
.expect("no hash returned by 'contract upload'")
.to_string();
eprintln!(" ↳ hash: {hash}");
Ok(hash)
}
Err(e) => Err(e.into()),
}
}

fn parse_script_line(line: &str) -> Result<(Option<String>, Vec<String>), Error> {
Expand Down
36 changes: 36 additions & 0 deletions crates/stellar-scaffold-cli/tests/it/build_clients/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,39 @@ soroban_token_contract.client = false
assert!(stderr.contains("Account AliasOrSecret(\"alice\") funded"));
});
}

#[test]
fn funding_existing_account_toml() {
use std::fs;

TestEnv::from("soroban-init-boilerplate", |env| {
env.set_environments_toml(r#"
[development]
network = { rpc-url = "http://localhost:8000/rpc", network-passphrase = "Standalone Network ; February 2017"}

accounts = [
"alice",
]
[development.contracts]
soroban_hello_world_contract.client = true
soroban_increment_contract.client = false
soroban_custom_types_contract.client = false
soroban_auth_contract.client = false
soroban_token_contract.client = false
"#);

// Create alice.toml manually, simulating a pre-existing identity
let alice_toml_path = env.cwd.join(".stellar/identity/alice.toml");
let parent = alice_toml_path.parent().unwrap();
fs::create_dir_all(parent).unwrap();
fs::write(&alice_toml_path, r#"
seed_phrase = "own social that glimpse hurry lion arrange spot vault clip leisure innocent borrow peanut invest scrub network enter enemy digital uncover ivory expire peace"
"#).unwrap();

// Run scaffold_build and assert success
env.scaffold_build("development", true)
.assert()
.success()
.stderr_as_str();
});
}