Skip to content

Commit

Permalink
Merge branch 'main' into fix/ledger-tests-org-update
Browse files Browse the repository at this point in the history
  • Loading branch information
elizabethengelman authored May 30, 2024
2 parents 63953b2 + 8c17d74 commit 02a3615
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 54 deletions.
1 change: 1 addition & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[alias] # command aliases
f = "fmt"
md-gen = "run --bin doc-gen --features clap-markdown"
# b = "build"
# c = "check"
# t = "test"
Expand Down
2 changes: 1 addition & 1 deletion FULL_HELP_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ Initialize a Soroban project with an example contract

* `-w`, `--with-example <WITH_EXAMPLE>`

Possible values: `account`, `alloc`, `atomic_multiswap`, `atomic_swap`, `auth`, `cross_contract`, `custom_types`, `deep_contract_auth`, `deployer`, `errors`, `eth_abi`, `events`, `fuzzing`, `increment`, `liquidity_pool`, `logging`, `mint-lock`, `simple_account`, `single_offer`, `timelock`, `token`, `upgradeable_contract`, `workspace`
Possible values: `account`, `alloc`, `atomic_multiswap`, `atomic_swap`, `auth`, `cross_contract`, `custom_types`, `deep_contract_auth`, `deployer`, `errors`, `eth_abi`, `events`, `fuzzing`, `increment`, `liquidity_pool`, `logging`, `mint-lock`, `simple_account`, `single_offer`, `timelock`, `token`, `ttl`, `upgradeable_contract`, `workspace`

* `-f`, `--frontend-template <FRONTEND_TEMPLATE>`

Expand Down
1 change: 1 addition & 0 deletions cmd/crates/soroban-test/tests/it/integration.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod bindings;
mod custom_types;
mod dotenv;
mod hello_world;
Expand Down
34 changes: 34 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use super::util::deploy_swap;
use soroban_test::{TestEnv, LOCAL_NETWORK_PASSPHRASE};

const OUTPUT_DIR: &str = "./bindings-output";

#[tokio::test]
async fn invoke_test_generate_typescript_bindings() {
let sandbox = &TestEnv::new();
let contract_id = deploy_swap(sandbox).await;
let outdir = sandbox.dir().join(OUTPUT_DIR);
let cmd = sandbox.cmd_arr::<soroban_cli::commands::contract::bindings::typescript::Cmd>(&[
"--network-passphrase",
LOCAL_NETWORK_PASSPHRASE,
"--rpc-url",
&sandbox.rpc_url,
"--output-dir",
&outdir.display().to_string(),
"--overwrite",
"--contract-id",
&contract_id.to_string(),
]);

let result = sandbox.run_cmd_with(cmd, "test").await;

assert!(result.is_ok(), "Failed to generate TypeScript bindings");

assert!(outdir.exists(), "Output directory does not exist");

let files = std::fs::read_dir(outdir).expect("Failed to read output directory");
assert!(
files.count() > 0,
"No files generated in the output directory"
);
}
5 changes: 5 additions & 0 deletions cmd/crates/soroban-test/tests/it/integration/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::fmt::Display;

pub const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world");
pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types");
pub const SWAP: &Wasm = &Wasm::Custom("test-wasms", "test_swap");

pub async fn invoke_with_roundtrip<D>(e: &TestEnv, id: &str, func: &str, data: D)
where
Expand All @@ -28,6 +29,10 @@ pub async fn deploy_custom(sandbox: &TestEnv) -> String {
deploy_contract(sandbox, CUSTOM_TYPES).await
}

pub async fn deploy_swap(sandbox: &TestEnv) -> String {
deploy_contract(sandbox, SWAP).await
}

pub async fn deploy_contract(sandbox: &TestEnv, wasm: &Wasm<'static>) -> String {
let cmd = sandbox.cmd_with_config::<_, commands::contract::deploy::wasm::Cmd>(&[
"--fee",
Expand Down
1 change: 1 addition & 0 deletions cmd/soroban-cli/example_contracts.list
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ simple_account
single_offer
timelock
token
ttl
upgradeable_contract
workspace
57 changes: 42 additions & 15 deletions cmd/soroban-cli/src/commands/contract/bindings/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@ 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 crate::commands::{
config::locator,
contract::{self, fetch},
network::{self, Network},
};
use crate::wasm;
use crate::{
commands::{
config::{self, locator},
contract::fetch,
global,
network::{self, Network},
NetworkRunnable,
},
get_spec::{self, get_remote_contract_spec},
};

#[derive(Parser, Debug, Clone)]
#[group(skip)]
Expand Down Expand Up @@ -61,22 +67,37 @@ pub enum Error {
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),
}

impl Cmd {
pub async fn run(&self) -> Result<(), Error> {
#[async_trait::async_trait]
impl NetworkRunnable for Cmd {
type Error = Error;
type Result = ();

async fn run_against_rpc_server(
&self,
global_args: Option<&global::Args>,
config: Option<&config::Args>,
) -> Result<(), Error> {
let spec = if let Some(wasm) = &self.wasm {
let wasm: wasm::Args = wasm.into();
wasm.parse()?.spec
} else {
let fetch = contract::fetch::Cmd {
contract_id: self.contract_id.clone(),
out_file: None,
locator: self.locator.clone(),
network: self.network.clone(),
};
let bytes = fetch.get_bytes().await?;
contract_spec::Spec::new(&bytes)?.spec
let contract_id = soroban_spec_tools::utils::contract_id_from_str(&self.contract_id)
.map_err(|e| Error::CannotParseContractId(self.contract_id.clone(), e))?;
get_remote_contract_spec(
&contract_id,
&self.locator,
&self.network,
global_args,
config,
)
.await
.map_err(Error::from)?
};
if self.output_dir.is_file() {
return Err(Error::IsFile(self.output_dir.clone()));
Expand Down Expand Up @@ -127,3 +148,9 @@ impl Cmd {
Ok(())
}
}

impl Cmd {
pub async fn run(&self) -> Result<(), Error> {
self.run_against_rpc_server(None, None).await
}
}
59 changes: 21 additions & 38 deletions cmd/soroban-cli/src/commands/contract/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use soroban_env_host::{
};

use soroban_env_host::xdr::{
AccountEntry, AccountEntryExt, AccountId, ContractDataEntry, DiagnosticEvent, Thresholds,
AccountEntry, AccountEntryExt, AccountId, DiagnosticEvent, Thresholds,
};
use soroban_spec::read::FromWasmError;
use stellar_strkey::DecodeError;
Expand All @@ -33,6 +33,7 @@ use super::super::{
};
use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult};
use crate::commands::NetworkRunnable;
use crate::get_spec::{self, get_remote_contract_spec};
use crate::{
commands::{config::data, global, network},
rpc, Pwd,
Expand Down Expand Up @@ -102,7 +103,6 @@ pub enum Error {
FunctionNotFoundInContractSpec(String),
#[error("parsing contract spec: {0}")]
CannotParseContractSpec(FromWasmError),
// },
#[error("function name {0} is too long")]
FunctionNameTooLong(String),
#[error("argument count ({current}) surpasses maximum allowed count ({maximum})")]
Expand All @@ -122,8 +122,6 @@ pub enum Error {
UnexpectedContractCodeDataType(LedgerEntryData),
#[error("missing operation result")]
MissingOperationResult,
#[error("missing result")]
MissingResult,
#[error(transparent)]
StrVal(#[from] soroban_spec_tools::Error),
#[error("error loading signing key: {0}")]
Expand Down Expand Up @@ -152,6 +150,8 @@ pub enum Error {
Data(#[from] data::Error),
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
GetSpecError(#[from] get_spec::Error),
}

impl From<Infallible> for Error {
Expand Down Expand Up @@ -320,14 +320,15 @@ impl NetworkRunnable for Cmd {
global_args: Option<&global::Args>,
config: Option<&config::Args>,
) -> Result<TxnResult<String>, Error> {
let config = config.unwrap_or(&self.config);
let network = config.get_network()?;
let unwrap_config = config.unwrap_or(&self.config);
let network = unwrap_config.get_network()?;
tracing::trace!(?network);
let contract_id = self.contract_id()?;
let spec_entries = self.spec_entries()?;
if let Some(spec_entries) = &spec_entries {
// For testing wasm arg parsing
let _ = self.build_host_function_parameters(contract_id, spec_entries, config)?;
let _ =
self.build_host_function_parameters(contract_id, spec_entries, unwrap_config)?;
}
let client = rpc::Client::new(&network.rpc_url)?;
let account_details = if self.is_view {
Expand All @@ -336,7 +337,7 @@ impl NetworkRunnable for Cmd {
client
.verify_network_passphrase(Some(&network.network_passphrase))
.await?;
let key = config.key_pair()?;
let key = unwrap_config.key_pair()?;

// Get the account sequence number
let public_strkey =
Expand All @@ -346,37 +347,19 @@ impl NetworkRunnable for Cmd {
let sequence: i64 = account_details.seq_num.into();
let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id;

let r = client.get_contract_data(&contract_id).await?;
tracing::trace!("{r:?}");
let ContractDataEntry {
val: xdr::ScVal::ContractInstance(xdr::ScContractInstance { executable, .. }),
..
} = r
else {
return Err(Error::MissingResult);
};
// Get the contract
let spec_entries = match executable {
xdr::ContractExecutable::Wasm(hash) => {
let hash = hash.to_string();
if let Ok(entries) = data::read_spec(&hash) {
entries
} else {
let res = client.get_remote_contract_spec(&contract_id).await?;
if global_args.map_or(true, |a| !a.no_cache) {
data::write_spec(&hash, &res)?;
}
res
}
}
xdr::ContractExecutable::StellarAsset => {
soroban_spec::read::parse_raw(&soroban_sdk::token::StellarAssetSpec::spec_xdr())?
}
};
let spec_entries = get_remote_contract_spec(
&contract_id,
&unwrap_config.locator,
&unwrap_config.network,
global_args,
config,
)
.await
.map_err(Error::from)?;

// Get the ledger footprint
let (function, spec, host_function_params, signers) =
self.build_host_function_parameters(contract_id, &spec_entries, config)?;
self.build_host_function_parameters(contract_id, &spec_entries, unwrap_config)?;
let tx = build_invoke_contract_tx(
host_function_params.clone(),
sequence + 1,
Expand All @@ -403,11 +386,11 @@ impl NetworkRunnable for Cmd {
very_verbose,
no_cache,
..
} = global_args.map(Clone::clone).unwrap_or_default();
} = global_args.cloned().unwrap_or_default();
let res = client
.send_assembled_transaction(
txn,
&config.key_pair()?,
&unwrap_config.key_pair()?,
&signers,
&network.network_passphrase,
Some(log_events),
Expand Down
78 changes: 78 additions & 0 deletions cmd/soroban-cli/src/get_spec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use soroban_env_host::xdr;

use soroban_env_host::xdr::{
ContractDataEntry, ContractExecutable, ScContractInstance, ScSpecEntry, ScVal,
};

use soroban_spec::read::FromWasmError;
pub use soroban_spec_tools::contract as contract_spec;

use crate::commands::config::{self, locator};
use crate::commands::network;
use crate::commands::{config::data, global};
use crate::rpc;

#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("parsing contract spec: {0}")]
CannotParseContractSpec(FromWasmError),
#[error(transparent)]
Rpc(#[from] rpc::Error),
#[error("missing result")]
MissingResult,
#[error(transparent)]
Data(#[from] data::Error),
#[error(transparent)]
Xdr(#[from] xdr::Error),
#[error(transparent)]
Network(#[from] network::Error),
#[error(transparent)]
Config(#[from] config::Error),
}

///
/// # Errors
pub async fn get_remote_contract_spec(
contract_id: &[u8; 32],
locator: &locator::Args,
network: &network::Args,
global_args: Option<&global::Args>,
config: Option<&config::Args>,
) -> Result<Vec<ScSpecEntry>, Error> {
let network = config.map_or_else(
|| network.get(locator).map_err(Error::from),
|c| c.get_network().map_err(Error::from),
)?;
tracing::trace!(?network);
let client = rpc::Client::new(&network.rpc_url)?;
// Get contract data
let r = client.get_contract_data(contract_id).await?;
tracing::trace!("{r:?}");

let ContractDataEntry {
val: ScVal::ContractInstance(ScContractInstance { executable, .. }),
..
} = r
else {
return Err(Error::MissingResult);
};

// Get the contract spec entries based on the executable type
Ok(match executable {
ContractExecutable::Wasm(hash) => {
let hash = hash.to_string();
if let Ok(entries) = data::read_spec(&hash) {
entries
} else {
let res = client.get_remote_contract_spec(contract_id).await?;
if global_args.map_or(true, |a| !a.no_cache) {
data::write_spec(&hash, &res)?;
}
res
}
}
ContractExecutable::StellarAsset => {
soroban_spec::read::parse_raw(&soroban_sdk::token::StellarAssetSpec::spec_xdr())?
}
})
}
1 change: 1 addition & 0 deletions cmd/soroban-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub use cli::main;

pub mod commands;
pub mod fee;
pub mod get_spec;
pub mod key;
pub mod log;
pub mod toid;
Expand Down
Empty file.

0 comments on commit 02a3615

Please sign in to comment.