Skip to content

Commit

Permalink
feat(ts-bindings): allow generating from wasm hash
Browse files Browse the repository at this point in the history
Refactor spec-fetching logic from `contract::info::shared` so that it
can be used for `contract::bindings::typescript` as well.

This means that `contract::info` commands now have more logging than
they did before, and support global args for quieting these print
statements.
  • Loading branch information
chadoh committed Dec 11, 2024
1 parent ba4687c commit 22ad32f
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 129 deletions.
35 changes: 18 additions & 17 deletions FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,20 +290,21 @@ Generate Rust bindings

Generate a TypeScript / JavaScript package

**Usage:** `stellar contract bindings typescript [OPTIONS] --output-dir <OUTPUT_DIR>`
**Usage:** `stellar contract bindings typescript [OPTIONS] --output-dir <OUTPUT_DIR> <--wasm <WASM>|--wasm-hash <WASM_HASH>|--contract-id <CONTRACT_ID>>`

###### **Options:**

* `--wasm <WASM>` — Path to wasm file on local filesystem. You must either include this OR `--contract-id`
* `--contract-id <CONTRACT_ID>` — A contract ID/address on a network (if no network settings provided, Testnet will be assumed). You must either include this OR `--wasm`
* `--output-dir <OUTPUT_DIR>` — Where to place generated project
* `--overwrite` — Whether to overwrite output directory if it already exists
* `--wasm <WASM>` — Wasm file path on local filesystem. Provide this OR `--wasm-hash` OR `--contract-id`
* `--wasm-hash <WASM_HASH>` — Hash of Wasm blob on a network. Provide this OR `--wasm` OR `--contract-id`
* `--contract-id <CONTRACT_ID>` — Contract ID/alias on a network. Provide this OR `--wasm-hash` OR `--wasm`
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
* `--network <NETWORK>` — Name of network to use from config
* `--global` — Use global config
* `--config-dir <CONFIG_DIR>` — Location of config directory, default is "."
* `--output-dir <OUTPUT_DIR>` — Where to place generated project
* `--overwrite` — Whether to overwrite output directory if it already exists



Expand Down Expand Up @@ -525,13 +526,13 @@ The data outputted by this command is a stream of `SCSpecEntry` XDR values. See

Outputs no data when no data is present in the contract.

**Usage:** `stellar contract info interface [OPTIONS] <--wasm <WASM>|--wasm-hash <WASM_HASH>|--id <CONTRACT_ID>>`
**Usage:** `stellar contract info interface [OPTIONS] <--wasm <WASM>|--wasm-hash <WASM_HASH>|--contract-id <CONTRACT_ID>>`

###### **Options:**

* `--wasm <WASM>` — Wasm file to extract the data from
* `--wasm-hash <WASM_HASH>` — Wasm hash to get the data for
* `--id <CONTRACT_ID>` — Contract id or contract alias to get the data for
* `--wasm <WASM>` — Wasm file path on local filesystem. Provide this OR `--wasm-hash` OR `--contract-id`
* `--wasm-hash <WASM_HASH>`Hash of Wasm blob on a network. Provide this OR `--wasm` OR `--contract-id`
* `--contract-id <CONTRACT_ID>` — Contract ID/alias on a network. Provide this OR `--wasm-hash` OR `--wasm`
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
Expand Down Expand Up @@ -565,13 +566,13 @@ The data outputted by this command is a stream of `SCMetaEntry` XDR values. See

Outputs no data when no data is present in the contract.

**Usage:** `stellar contract info meta [OPTIONS] <--wasm <WASM>|--wasm-hash <WASM_HASH>|--id <CONTRACT_ID>>`
**Usage:** `stellar contract info meta [OPTIONS] <--wasm <WASM>|--wasm-hash <WASM_HASH>|--contract-id <CONTRACT_ID>>`

###### **Options:**

* `--wasm <WASM>` — Wasm file to extract the data from
* `--wasm-hash <WASM_HASH>` — Wasm hash to get the data for
* `--id <CONTRACT_ID>` — Contract id or contract alias to get the data for
* `--wasm <WASM>` — Wasm file path on local filesystem. Provide this OR `--wasm-hash` OR `--contract-id`
* `--wasm-hash <WASM_HASH>`Hash of Wasm blob on a network. Provide this OR `--wasm` OR `--contract-id`
* `--contract-id <CONTRACT_ID>` — Contract ID/alias on a network. Provide this OR `--wasm-hash` OR `--wasm`
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
Expand Down Expand Up @@ -605,13 +606,13 @@ The data outputted by this command is a stream of `SCEnvMetaEntry` XDR values. S

Outputs no data when no data is present in the contract.

**Usage:** `stellar contract info env-meta [OPTIONS] <--wasm <WASM>|--wasm-hash <WASM_HASH>|--id <CONTRACT_ID>>`
**Usage:** `stellar contract info env-meta [OPTIONS] <--wasm <WASM>|--wasm-hash <WASM_HASH>|--contract-id <CONTRACT_ID>>`

###### **Options:**

* `--wasm <WASM>` — Wasm file to extract the data from
* `--wasm-hash <WASM_HASH>` — Wasm hash to get the data for
* `--id <CONTRACT_ID>` — Contract id or contract alias to get the data for
* `--wasm <WASM>` — Wasm file path on local filesystem. Provide this OR `--wasm-hash` OR `--contract-id`
* `--wasm-hash <WASM_HASH>`Hash of Wasm blob on a network. Provide this OR `--wasm` OR `--contract-id`
* `--contract-id <CONTRACT_ID>` — Contract ID/alias on a network. Provide this OR `--wasm-hash` OR `--wasm`
* `--rpc-url <RPC_URL>` — RPC server endpoint
* `--rpc-header <RPC_HEADERS>` — RPC Header(s) to include in requests to the RPC provider
* `--network-passphrase <NETWORK_PASSPHRASE>` — Network passphrase to sign the transaction sent to the rpc server
Expand Down
2 changes: 1 addition & 1 deletion cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function bind() {
}
function bind_all() {
bind --contract-id $(cat contract-id-custom-types.txt) test-custom-types
bind --wasm ../../../../target/wasm32-unknown-unknown/test-wasms/test_constructor.wasm test-constructor
bind --wasm-hash $(cat contract-wasm-hash-constructor.txt) test-constructor
}

fund_all
Expand Down
2 changes: 1 addition & 1 deletion cmd/soroban-cli/src/commands/contract/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum Cmd {
Rust(rust::Cmd),

/// Generate a TypeScript / JavaScript package
Typescript(typescript::Cmd),
Typescript(Box<typescript::Cmd>),

/// Generate Python bindings
Python(python::Cmd),
Expand Down
97 changes: 19 additions & 78 deletions cmd/soroban-cli/src/commands/contract/bindings/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,30 @@ use std::{ffi::OsString, fmt::Debug, path::PathBuf};

use clap::{command, Parser};
use soroban_spec_tools::contract as contract_spec;
use soroban_spec_typescript::{self as typescript, boilerplate::Project};
use stellar_strkey::DecodeError;
use soroban_spec_typescript::boilerplate::Project;

use crate::print::Print;
use crate::wasm;
use crate::{
commands::{contract::fetch, global, NetworkRunnable},
config::{self, locator, network},
get_spec::{self, get_remote_contract_spec},
xdr::{Hash, ScAddress},
commands::{contract::info::shared as wasm_or_contract, global, NetworkRunnable},
config,
};
use soroban_spec_tools::contract::Spec;

#[derive(Parser, Debug, Clone)]
#[group(skip)]
pub struct Cmd {
/// Path to wasm file on local filesystem. You must either include this OR `--contract-id`.
#[arg(long)]
pub wasm: Option<std::path::PathBuf>,
/// A contract ID/address on a network (if no network settings provided, Testnet will be assumed). You must either include this OR `--wasm`.
#[arg(long, visible_alias = "id")]
pub contract_id: Option<String>,
#[command(flatten)]
pub wasm_or_hash_or_contract_id: wasm_or_contract::Args,
/// Where to place generated project
#[arg(long)]
pub output_dir: PathBuf,
/// Whether to overwrite output directory if it already exists
#[arg(long)]
pub overwrite: bool,
#[command(flatten)]
pub network: network::Args,
#[command(flatten)]
pub locator: locator::Args,
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("failed generate TS from file: {0}")]
GenerateTSFromFile(typescript::GenerateFromFileError),
#[error(transparent)]
Io(#[from] std::io::Error),

Expand All @@ -51,28 +38,15 @@ pub enum Error {
#[error("--output-dir filepath not representable as utf-8: {0:?}")]
NotUtf8(OsString),

#[error("must include either --wasm or --contract-id")]
MissingWasmOrContractId,
#[error("cannot yet generate bindings for Stellar Asset Contract")]
CannotGenerateBindingsForStellarAsset,

#[error(transparent)]
Network(#[from] network::Error),

#[error(transparent)]
Locator(#[from] locator::Error),
#[error(transparent)]
Fetch(#[from] fetch::Error),
#[error(transparent)]
Spec(#[from] contract_spec::Error),
#[error(transparent)]
Wasm(#[from] wasm::Error),
#[error("Failed to get file name from path: {0:?}")]
FailedToGetFileName(PathBuf),
#[error("cannot parse contract ID {0}: {1}")]
CannotParseContractId(String, DecodeError),
#[error(transparent)]
UtilsError(#[from] get_spec::Error),
#[error(transparent)]
Config(#[from] config::Error),
WasmOrContract(#[from] wasm_or_contract::Error),
}

#[async_trait::async_trait]
Expand All @@ -83,51 +57,18 @@ impl NetworkRunnable for Cmd {
async fn run_against_rpc_server(
&self,
global_args: Option<&global::Args>,
config: Option<&config::Args>,
_config: Option<&config::Args>,
) -> Result<(), Error> {
let print = Print::new(global_args.is_some_and(|a| a.quiet));

let (spec, contract_address, rpc_url, network_passphrase) = if let Some(wasm) = &self.wasm {
print.infoln("Loading contract spec from file...");
let wasm: wasm::Args = wasm.into();
(wasm.parse()?.spec, None, None, None)
} else {
let contract_id = self
.contract_id
.as_ref()
.ok_or(Error::MissingWasmOrContractId)?;
let (spec, contract_address, network) =
wasm_or_contract::fetch_wasm(&self.wasm_or_hash_or_contract_id, &print).await?;

let network = self.network.get(&self.locator).ok().unwrap_or_else(|| {
network::DEFAULTS
.get("testnet")
.expect("no network specified and testnet network not found")
.into()
});
print.infoln(format!("Network: {}", network.network_passphrase));

let contract_id = self
.locator
.resolve_contract_id(contract_id, &network.network_passphrase)?
.0;

let contract_address = ScAddress::Contract(Hash(contract_id)).to_string();
print.globeln(format!("Downloading contract spec: {contract_address}"));
if spec.is_none() {
return Err(Error::CannotGenerateBindingsForStellarAsset);
}
let spec = spec.expect("spec is some");

(
get_remote_contract_spec(
&contract_id,
&self.locator,
&self.network,
global_args,
config,
)
.await
.map_err(Error::from)?,
Some(contract_address),
Some(network.rpc_url),
Some(network.network_passphrase),
)
};
if self.output_dir.is_file() {
return Err(Error::IsFile(self.output_dir.clone()));
}
Expand All @@ -153,9 +94,9 @@ impl NetworkRunnable for Cmd {
p.init(
contract_name,
contract_address.as_deref(),
rpc_url.as_deref(),
network_passphrase.as_deref(),
&spec,
network.as_ref().map(|n| n.rpc_url.as_ref()),
network.as_ref().map(|n| n.network_passphrase.as_ref()),
&Spec::new(&spec)?.spec,
)?;
print.checkln("Generated!");
print.infoln(format!(
Expand Down
12 changes: 7 additions & 5 deletions cmd/soroban-cli/src/commands/contract/info.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::fmt::Debug;

use crate::commands::global;

pub mod env_meta;
pub mod interface;
pub mod meta;
mod shared;
pub mod shared;

#[derive(Debug, clap::Subcommand)]
pub enum Cmd {
Expand Down Expand Up @@ -60,11 +62,11 @@ pub enum Error {
}

impl Cmd {
pub async fn run(&self) -> Result<(), Error> {
pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> {
let result = match &self {
Cmd::Interface(interface) => interface.run().await?,
Cmd::Meta(meta) => meta.run().await?,
Cmd::EnvMeta(env_meta) => env_meta.run().await?,
Cmd::Interface(interface) => interface.run(global_args).await?,
Cmd::Meta(meta) => meta.run(global_args).await?,
Cmd::EnvMeta(env_meta) => env_meta.run(global_args).await?,
};
println!("{result}");
Ok(())
Expand Down
15 changes: 10 additions & 5 deletions cmd/soroban-cli/src/commands/contract/info/env_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ use soroban_spec_tools::contract;
use soroban_spec_tools::contract::Spec;

use crate::{
commands::contract::info::{
env_meta::Error::{NoEnvMetaPresent, NoSACEnvMeta},
shared::{self, fetch_wasm, MetasInfoOutput},
commands::{
contract::info::{
env_meta::Error::{NoEnvMetaPresent, NoSACEnvMeta},
shared::{self, fetch_wasm, MetasInfoOutput},
},
global,
},
print::Print,
xdr::{ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion},
};

Expand Down Expand Up @@ -37,8 +41,9 @@ pub enum Error {
}

impl Cmd {
pub async fn run(&self) -> Result<String, Error> {
let bytes = fetch_wasm(&self.common).await?;
pub async fn run(&self, global_args: &global::Args) -> Result<String, Error> {
let print = Print::new(global_args.quiet);
let (bytes, ..) = fetch_wasm(&self.common, &print).await?;

let Some(bytes) = bytes else {
return Err(NoSACEnvMeta());
Expand Down
7 changes: 5 additions & 2 deletions cmd/soroban-cli/src/commands/contract/info/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::fmt::Debug;
use crate::commands::contract::info::interface::Error::NoInterfacePresent;
use crate::commands::contract::info::shared;
use crate::commands::contract::info::shared::fetch_wasm;
use crate::commands::global;
use crate::print::Print;
use clap::{command, Parser};
use soroban_spec_rust::ToFormattedString;
use soroban_spec_tools::contract;
Expand Down Expand Up @@ -43,8 +45,9 @@ pub enum Error {
}

impl Cmd {
pub async fn run(&self) -> Result<String, Error> {
let bytes = fetch_wasm(&self.common).await?;
pub async fn run(&self, global_args: &global::Args) -> Result<String, Error> {
let print = Print::new(global_args.quiet);
let (bytes, ..) = fetch_wasm(&self.common, &print).await?;

let (base64, spec) = if bytes.is_none() {
Spec::spec_to_base64(&soroban_sdk::token::StellarAssetSpec::spec_xdr())?
Expand Down
7 changes: 5 additions & 2 deletions cmd/soroban-cli/src/commands/contract/info/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::fmt::Debug;
use crate::commands::contract::info::meta::Error::{NoMetaPresent, NoSACMeta};
use crate::commands::contract::info::shared;
use crate::commands::contract::info::shared::{fetch_wasm, MetasInfoOutput};
use crate::commands::global;
use crate::print::Print;
use clap::{command, Parser};
use soroban_spec_tools::contract;
use soroban_spec_tools::contract::Spec;
Expand Down Expand Up @@ -32,8 +34,9 @@ pub enum Error {
}

impl Cmd {
pub async fn run(&self) -> Result<String, Error> {
let bytes = fetch_wasm(&self.common).await?;
pub async fn run(&self, global_args: &global::Args) -> Result<String, Error> {
let print = Print::new(global_args.quiet);
let (bytes, ..) = fetch_wasm(&self.common, &print).await?;

let Some(bytes) = bytes else {
return Err(NoSACMeta());
Expand Down
Loading

0 comments on commit 22ad32f

Please sign in to comment.