diff --git a/.github/workflows/bindings-ts.yml b/.github/workflows/bindings-ts.yml new file mode 100644 index 000000000..30858aa4a --- /dev/null +++ b/.github/workflows/bindings-ts.yml @@ -0,0 +1,42 @@ +name: bindings typescript + +on: + push: + branches: [main, release/**] + pull_request: + +jobs: + test: + name: test generated libraries + runs-on: ubuntu-22.04 + services: + rpc: + image: stellar/quickstart:testing + ports: + - 8000:8000 + env: + ENABLE_LOGS: true + NETWORK: local + ENABLE_SOROBAN_RPC: true + options: >- + --health-cmd "curl --no-progress-meter --fail-with-body -X POST \"http://localhost:8000/soroban/rpc\" -H 'Content-Type: application/json' -d '{\"jsonrpc\":\"2.0\",\"id\":8675309,\"method\":\"getNetwork\"}' && curl --no-progress-meter \"http://localhost:8000/friendbot\" | grep '\"invalid_field\": \"addr\"'" + --health-interval 10s + --health-timeout 5s + --health-retries 50 + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: rustup update + - run: cargo build + - run: rustup target add wasm32-unknown-unknown + - run: make build-test-wasms + - run: npm ci && npm run test + working-directory: cmd/crates/soroban-spec-typescript/ts-tests diff --git a/Cargo.lock b/Cargo.lock index 9bb3cc694..7a54f07a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4417,9 +4417,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" dependencies = [ "base64 0.22.1", "chrono", @@ -4435,9 +4435,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.3" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" dependencies = [ "darling", "proc-macro2", diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json index ed1fe73e7..8e620a1a3 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json @@ -7,7 +7,7 @@ "hasInstallScript": true, "devDependencies": { "@ava/typescript": "^4.1.0", - "@stellar/stellar-sdk": "12.1.0", + "@stellar/stellar-sdk": "12.2.0", "@types/node": "^20.4.9", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", @@ -199,18 +199,18 @@ } }, "node_modules/@stellar/js-xdr": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.1.tgz", - "integrity": "sha512-3gnPjAz78htgqsNEDkEsKHKosV2BF2iZkoHCNxpmZwUxiPsw+2VaXMed8RRMe0rGk3d5GZe7RrSba8zV80J3Ag==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", + "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", "dev": true }, "node_modules/@stellar/stellar-base": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.0.1.tgz", - "integrity": "sha512-g6c27MNsDgEdUmoNQJn7zCWoCY50WHt0OIIOq3PhWaJRtUaT++qs1Jpb8+1bny2GmhtfRGOfPUFSyQBuHT9Mvg==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.1.0.tgz", + "integrity": "sha512-pWwn+XWP5NotmIteZNuJzHeNn9DYSqH3lsYbtFUoSYy1QegzZdi9D8dK6fJ2fpBAnf/rcDjHgHOw3gtHaQFVbg==", "dev": true, "dependencies": { - "@stellar/js-xdr": "^3.1.1", + "@stellar/js-xdr": "^3.1.2", "base32.js": "^0.1.0", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", @@ -222,12 +222,12 @@ } }, "node_modules/@stellar/stellar-sdk": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.1.0.tgz", - "integrity": "sha512-Va0hu9SaPezmMbO5eMwL5D15Wrx1AGWRtxayUDRWV2Fr3ynY58mvCZS1vsgNQ4kE8MZe3nBVKv6T9Kzqwgx1PQ==", + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.2.0.tgz", + "integrity": "sha512-Wy5sDOqb5JvAC76f4sQIV6Pe3JNyZb0PuyVNjwt3/uWsjtxRkFk6s2yTHTefBLWoR+mKxDjO7QfzhycF1v8FXQ==", "dev": true, "dependencies": { - "@stellar/stellar-base": "^12.0.1", + "@stellar/stellar-base": "^12.1.0", "axios": "^1.7.2", "bignumber.js": "^9.1.2", "eventsource": "^2.0.2", diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package.json b/cmd/crates/soroban-spec-typescript/ts-tests/package.json index 2f0e08b0d..734b26bba 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package.json +++ b/cmd/crates/soroban-spec-typescript/ts-tests/package.json @@ -14,7 +14,7 @@ "ava": "^5.3.1", "dotenv": "^16.3.1", "eslint": "^8.53.0", - "@stellar/stellar-sdk": "12.1.0", + "@stellar/stellar-sdk": "12.2.0", "typescript": "^5.3.3" }, "ava": { diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts b/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts index 8dc4c899a..a5315a643 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts +++ b/cmd/crates/soroban-spec-typescript/ts-tests/src/util.ts @@ -1,15 +1,22 @@ import { spawnSync } from "node:child_process"; import { Address, Keypair } from "@stellar/stellar-sdk"; -import { basicNodeSigner } from "@stellar/stellar-sdk/ContractClient"; +import { basicNodeSigner } from "@stellar/stellar-sdk/contract"; -const rootKeypair = Keypair.fromSecret(spawnSync("./soroban", ["keys", "show", "root"], { shell: true, encoding: "utf8" }).stdout.trim()); +const rootKeypair = Keypair.fromSecret( + spawnSync("./soroban", ["keys", "show", "root"], { + shell: true, + encoding: "utf8", + }).stdout.trim(), +); export const root = { keypair: rootKeypair, address: Address.fromString(rootKeypair.publicKey()), -} +}; export const rpcUrl = process.env.SOROBAN_RPC_URL ?? "http://localhost:8000/"; -export const networkPassphrase = process.env.SOROBAN_NETWORK_PASSPHRASE ?? "Standalone Network ; February 2017"; +export const networkPassphrase = + process.env.SOROBAN_NETWORK_PASSPHRASE ?? + "Standalone Network ; February 2017"; export const signer = basicNodeSigner(root.keypair, networkPassphrase); diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs index ec002c424..b25d47313 100644 --- a/cmd/crates/soroban-test/tests/it/integration.rs +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -1,6 +1,7 @@ mod bindings; mod custom_types; mod dotenv; +mod fund; mod hello_world; mod tx; mod util; diff --git a/cmd/crates/soroban-test/tests/it/integration/fund.rs b/cmd/crates/soroban-test/tests/it/integration/fund.rs new file mode 100644 index 000000000..b9a99e783 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/fund.rs @@ -0,0 +1,19 @@ +use soroban_test::TestEnv; + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn fund() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test") + .assert() + .success(); + sandbox + .new_assert_cmd("keys") + .arg("fund") + .arg("test") + .assert() + .stderr(predicates::str::contains("funding failed")); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index 1c766095f..25e00f093 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -32,12 +32,6 @@ async fn invoke() { let sandbox = &TestEnv::new(); let c = soroban_rpc::Client::new(&sandbox.rpc_url).unwrap(); let GetLatestLedgerResponse { sequence, .. } = c.get_latest_ledger().await.unwrap(); - sandbox - .new_assert_cmd("keys") - .arg("fund") - .arg("test") - .assert() - .stderr(predicates::str::contains("Account already exists")); sandbox .new_assert_cmd("keys") .arg("fund") diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index 9fc66e84e..830db5cd4 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -6,12 +6,15 @@ use std::str::FromStr; use std::{fmt::Debug, fs, io}; use clap::{arg, command, Parser}; +use stellar_xdr::curr::{ContractDataEntry, ContractExecutable, ScVal}; +use crate::commands::contract::fetch::Error::{ContractIsStellarAsset, UnexpectedContractToken}; use crate::commands::{global, NetworkRunnable}; use crate::config::{ self, locator, network::{self, Network}, }; +use crate::utils::rpc::get_remote_wasm_from_hash; use crate::{ rpc::{self, Client}, Pwd, @@ -64,6 +67,13 @@ pub enum Error { Network(#[from] network::Error), #[error("cannot create contract directory for {0:?}")] CannotCreateContractDir(PathBuf), + #[error("unexpected contract data {0:?}")] + UnexpectedContractToken(ContractDataEntry), + #[error( + "cannot fetch wasm for contract because the contract is \ + a network built-in asset contract that does not have a downloadable code binary" + )] + ContractIsStellarAsset(), } impl From for Error { @@ -126,6 +136,15 @@ impl NetworkRunnable for Cmd { client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; - Ok(client.get_remote_wasm(&contract_id).await?) + let data_entry = client.get_contract_data(&contract_id).await?; + if let ScVal::ContractInstance(contract) = &data_entry.val { + return match &contract.executable { + ContractExecutable::Wasm(hash) => { + Ok(get_remote_wasm_from_hash(&client, hash).await?) + } + ContractExecutable::StellarAsset => Err(ContractIsStellarAsset()), + }; + } + return Err(UnexpectedContractToken(data_entry)); } } diff --git a/cmd/soroban-cli/src/config/network.rs b/cmd/soroban-cli/src/config/network.rs index 958150cd7..d332fa904 100644 --- a/cmd/soroban-cli/src/config/network.rs +++ b/cmd/soroban-cli/src/config/network.rs @@ -31,8 +31,8 @@ pub enum Error { FailedToParseJSON(String, serde_json::Error), #[error("Invalid URL {0}")] InvalidUrl(String), - #[error("Inproper response {0}")] - InproperResponse(String), + #[error("funding failed: {0}")] + FundingFailed(String), } #[derive(Debug, clap::Args, Clone, Default)] @@ -149,16 +149,16 @@ impl Network { return Err(Error::InvalidUrl(uri.to_string())); } }; + let request_successful = response.status().is_success(); let body = hyper::body::to_bytes(response.into_body()).await?; let res = serde_json::from_slice::(&body) .map_err(|e| Error::FailedToParseJSON(uri.to_string(), e))?; tracing::debug!("{res:#?}"); - if let Some(detail) = res.get("detail").and_then(Value::as_str) { - if detail.contains("createAccountAlreadyExist") { - eprintln!("Account already exists"); + if !request_successful { + if let Some(detail) = res.get("detail").and_then(Value::as_str) { + return Err(Error::FundingFailed(detail.to_string())); } - } else if res.get("successful").is_none() { - return Err(Error::InproperResponse(res.to_string())); + return Err(Error::FundingFailed("unknown cause".to_string())); } Ok(()) } diff --git a/cmd/soroban-cli/src/get_spec.rs b/cmd/soroban-cli/src/get_spec.rs index cd9477de2..125fa984c 100644 --- a/cmd/soroban-cli/src/get_spec.rs +++ b/cmd/soroban-cli/src/get_spec.rs @@ -10,6 +10,7 @@ pub use soroban_spec_tools::contract as contract_spec; use crate::commands::global; use crate::config::{self, data, locator, network}; use crate::rpc; +use crate::utils::rpc::get_remote_wasm_from_hash; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -65,7 +66,7 @@ pub async fn get_remote_contract_spec( if let Ok(entries) = data::read_spec(&hash_str) { entries } else { - let raw_wasm = client.get_remote_wasm_from_hash(hash).await?; + let raw_wasm = get_remote_wasm_from_hash(&client, &hash).await?; let res = contract_spec::Spec::new(&raw_wasm)?; let res = res.spec; if global_args.map_or(true, |a| !a.no_cache) { diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index e74222c2c..88daa9a80 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -129,6 +129,29 @@ pub fn contract_id_hash_from_asset( Ok(Hash(Sha256::digest(preimage_xdr).into())) } +pub mod rpc { + use soroban_env_host::xdr; + use soroban_rpc::{Client, Error}; + use stellar_xdr::curr::{Hash, LedgerEntryData, LedgerKey, Limits, ReadXdr}; + + pub async fn get_remote_wasm_from_hash(client: &Client, hash: &Hash) -> Result, Error> { + let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() }); + let contract_data = client.get_ledger_entries(&[code_key]).await?; + let entries = contract_data.entries.unwrap_or_default(); + if entries.is_empty() { + return Err(Error::NotFound( + "Contract Code".to_string(), + hex::encode(hash), + )); + } + let contract_data_entry = &entries[0]; + match LedgerEntryData::from_xdr_base64(&contract_data_entry.xdr, Limits::none())? { + LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()), + scval => Err(Error::UnexpectedContractCodeDataType(scval)), + } + } +} + pub mod parsing { use regex::Regex;