Skip to content

Commit aae8105

Browse files
Add WASM fetch by hash support (#2225)
1 parent 65c2ba9 commit aae8105

6 files changed

Lines changed: 89 additions & 10 deletions

File tree

FULL_HELP_DOCS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,11 +485,12 @@ Deploy a wasm contract
485485

486486
Fetch a contract's Wasm binary
487487

488-
**Usage:** `stellar contract fetch [OPTIONS] --id <CONTRACT_ID>`
488+
**Usage:** `stellar contract fetch [OPTIONS]`
489489

490490
###### **Options:**
491491

492492
* `--id <CONTRACT_ID>` — Contract ID to fetch
493+
* `--wasm-hash <WASM_HASH>` — Wasm to fetch
493494
* `-o`, `--out-file <OUT_FILE>` — Where to write output otherwise stdout is used
494495
* `--global` — ⚠️ Deprecated: global config is always on
495496
* `--config-dir <CONFIG_DIR>` — Location of config directory. By default, it uses `$XDG_CONFIG_HOME/stellar` if set, falling back to `~/.config/stellar` otherwise. Contains configuration files, aliases, and other persistent settings

cmd/crates/soroban-test/tests/it/integration.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod bindings;
22
mod constructor;
3+
mod contract;
34
mod cookbook;
45
mod custom_types;
56
mod dotenv;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use crate::integration::util::{deploy_contract, DeployOptions, HELLO_WORLD};
2+
3+
use soroban_test::TestEnv;
4+
5+
#[tokio::test]
6+
async fn tx_fetch_with_hash() {
7+
let sandbox = &TestEnv::new();
8+
let test_account_alias = "test";
9+
let wasm_bytes = HELLO_WORLD.bytes();
10+
let wasm_hash = HELLO_WORLD.hash().unwrap();
11+
let _contract_id = deploy_contract(
12+
sandbox,
13+
HELLO_WORLD,
14+
DeployOptions {
15+
deployer: Some(test_account_alias.to_string()),
16+
..Default::default()
17+
},
18+
)
19+
.await;
20+
21+
sandbox
22+
.new_assert_cmd("contract")
23+
.arg("fetch")
24+
.arg("--wasm-hash")
25+
.arg(wasm_hash.to_string())
26+
.assert()
27+
.success()
28+
.stdout(predicates::ord::eq(wasm_bytes));
29+
}
30+
31+
#[tokio::test]
32+
async fn tx_fetch_with_id() {
33+
let sandbox = &TestEnv::new();
34+
let test_account_alias = "test";
35+
let wasm_bytes = HELLO_WORLD.bytes();
36+
let contract_id = deploy_contract(
37+
sandbox,
38+
HELLO_WORLD,
39+
DeployOptions {
40+
deployer: Some(test_account_alias.to_string()),
41+
..Default::default()
42+
},
43+
)
44+
.await;
45+
46+
sandbox
47+
.new_assert_cmd("contract")
48+
.arg("fetch")
49+
.arg("--id")
50+
.arg(contract_id.clone())
51+
.assert()
52+
.success()
53+
.stdout(predicates::ord::eq(wasm_bytes));
54+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod fetch;

cmd/soroban-cli/src/commands/contract/fetch.rs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
self, locator,
1414
network::{self, Network},
1515
},
16-
wasm, Pwd,
16+
wasm, xdr, Pwd,
1717
};
1818

1919
#[derive(Parser, Debug, Default, Clone)]
@@ -22,7 +22,10 @@ use crate::{
2222
pub struct Cmd {
2323
/// Contract ID to fetch
2424
#[arg(long = "id", env = "STELLAR_CONTRACT_ID")]
25-
pub contract_id: config::UnresolvedContract,
25+
pub contract_id: Option<config::UnresolvedContract>,
26+
/// Wasm to fetch
27+
#[arg(long = "wasm-hash", conflicts_with = "contract_id")]
28+
pub wasm_hash: Option<String>,
2629
/// Where to write output otherwise stdout is used
2730
#[arg(long, short = 'o')]
2831
pub out_file: Option<std::path::PathBuf>,
@@ -63,6 +66,10 @@ pub enum Error {
6366
CannotCreateContractDir(PathBuf),
6467
#[error(transparent)]
6568
Wasm(#[from] wasm::Error),
69+
#[error("wasm hash is invalid {0:?}")]
70+
InvalidWasmHash(String),
71+
#[error("must provide one of --wasm-hash, or --id")]
72+
MissingArg,
6673
}
6774

6875
impl From<Infallible> for Error {
@@ -111,12 +118,21 @@ impl NetworkRunnable for Cmd {
111118
config: Option<&config::Args>,
112119
) -> Result<Vec<u8>, Error> {
113120
let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?;
114-
Ok(wasm::fetch_from_contract(
115-
&self
116-
.contract_id
117-
.resolve_contract_id(&self.locator, &network.network_passphrase)?,
118-
&network,
119-
)
120-
.await?)
121+
if let Some(contract_id) = &self.contract_id {
122+
Ok(wasm::fetch_from_contract(
123+
&contract_id.resolve_contract_id(&self.locator, &network.network_passphrase)?,
124+
&network,
125+
)
126+
.await?)
127+
} else if let Some(wasm_hash) = &self.wasm_hash {
128+
let hash = hex::decode(wasm_hash)
129+
.map_err(|_| Error::InvalidWasmHash(wasm_hash.clone()))?
130+
.try_into()
131+
.map_err(|_| Error::InvalidWasmHash(wasm_hash.clone()))?;
132+
let hash = xdr::Hash(hash);
133+
Ok(wasm::fetch_from_wasm_hash(hash, &network).await?)
134+
} else {
135+
Err(Error::MissingArg)
136+
}
121137
}
122138
}

cmd/soroban-cli/src/wasm.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,9 @@ pub async fn fetch_from_contract(
138138
}
139139
Err(UnexpectedContractToken(Box::new(data_entry)))
140140
}
141+
142+
pub async fn fetch_from_wasm_hash(hash: Hash, network: &Network) -> Result<Vec<u8>, Error> {
143+
tracing::trace!(?network);
144+
let client = network.rpc_client()?;
145+
Ok(get_remote_wasm_from_hash(&client, &hash).await?)
146+
}

0 commit comments

Comments
 (0)