diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 42ddb786a..c008cc7b4 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -13,6 +13,11 @@ defaults: run: shell: bash +permissions: + id-token: write + contents: write + attestations: write + jobs: build: @@ -22,17 +27,15 @@ jobs: crate: - name: stellar-cli binary: stellar - - name: soroban-cli - binary: soroban sys: - os: ubuntu-20.04 # Use 20.04 to get an older version of glibc for increased compat target: x86_64-unknown-linux-gnu - os: ubuntu-20.04 # Use 20.04 to get an older version of glibc for increased compat target: aarch64-unknown-linux-gnu - os: macos-14 - target: x86_64-apple-darwin - - os: macos-12 target: aarch64-apple-darwin + - os: macos-14 + target: x86_64-apple-darwin - os: windows-latest target: x86_64-pc-windows-msvc ext: .exe @@ -41,36 +44,50 @@ jobs: - uses: actions/checkout@v4 - run: rustup update - run: rustup target add ${{ matrix.sys.target }} + - if: matrix.sys.target == 'aarch64-unknown-linux-gnu' run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libudev-dev + - name: Setup vars run: | version="$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "stellar-cli") | .version')" echo "VERSION=${version}" >> $GITHUB_ENV echo "NAME=${{ matrix.crate.name }}-${version}-${{ matrix.sys.target }}" >> $GITHUB_ENV + - name: Package (release only) if: github.event_name == 'release' run: cargo package --no-verify --package ${{ matrix.crate.name }} + - name: Package Extract (release only) if: github.event_name == 'release' run: | cd target/package tar xvfz ${{ matrix.crate.name }}-$VERSION.crate echo "BUILD_WORKING_DIR=target/package/${{ matrix.crate.name }}-$VERSION" >> $GITHUB_ENV + - name: Build env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc working-directory: ${{ env.BUILD_WORKING_DIR }} run: cargo build --target-dir="$GITHUB_WORKSPACE/target" --package ${{ matrix.crate.name }} --features opt --release --target ${{ matrix.sys.target }} + + - name: Build provenance for attestation (release only) + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v1 + with: + subject-path: target/${{ matrix.sys.target }}/release/${{ matrix.crate.binary }}${{ matrix.sys.ext }} + - name: Compress run: | cd target/${{ matrix.sys.target }}/release tar czvf $NAME.tar.gz ${{ matrix.crate.binary }}${{ matrix.sys.ext }} + - name: Upload to Artifacts uses: actions/upload-artifact@v4 with: - name: ${{ env.NAME }} + name: ${{ env.NAME }}.tar.gz path: 'target/${{ matrix.sys.target }}/release/${{ env.NAME }}.tar.gz' + - name: Upload to Release (release only) if: github.event_name == 'release' uses: actions/github-script@v7 @@ -84,3 +101,61 @@ jobs: name: '${{ env.NAME }}.tar.gz', data: fs.readFileSync('target/${{ matrix.sys.target }}/release/${{ env.NAME }}.tar.gz'), }); + + installer: + needs: build + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup vars + run: | + version="$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[] | select(.name == "stellar-cli") | .version')" + installer_basename="stellar-cli-installer-${version}-x86_64-pc-windows-msvc" + echo "VERSION=${version}" >> $GITHUB_ENV + echo "STELLAR_CLI_INSTALLER_BASENAME=${installer_basename}" >> $GITHUB_ENV + echo "STELLAR_CLI_INSTALLER=${installer_basename}.exe" >> $GITHUB_ENV + echo "ARTIFACT_NAME=stellar-cli-${version}-x86_64-pc-windows-msvc.tar.gz" >> $GITHUB_ENV + + - name: Download Artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.ARTIFACT_NAME }} + + - name: Uncompress Artifact + run: tar xvf ${{ env.ARTIFACT_NAME }} + + - name: Build Installer + shell: powershell + run: | + $Env:Path += ";C:\Users\$Env:UserName\AppData\Local\Programs\Inno Setup 6" + $Env:STELLAR_CLI_VERSION = "${{ env.VERSION }}" + ISCC.exe installer.iss + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ env.STELLAR_CLI_INSTALLER }} + path: Output/stellar-installer.exe + + - name: Build provenance for attestation (release only) + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v1 + with: + subject-path: ${{ env.STELLAR_CLI_INSTALLER }} + + - name: Upload to Release (release only) + if: github.event_name == 'release' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + await github.rest.repos.uploadReleaseAsset({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: ${{ github.event.release.id }}, + name: '${{ env.STELLAR_CLI_INSTALLER }}', + data: fs.readFileSync('Output/stellar-installer.exe'), + }); + diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index cef838a7c..fe8c0e8bc 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -14,12 +14,6 @@ defaults: shell: bash jobs: - dependency-sanity-checker: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: rustup update - - run: scripts/check-dependencies.bash validate-rust-git-rev-deps: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/full-help-docs.yml b/.github/workflows/full-help-docs.yml deleted file mode 100644 index f564ae9ae..000000000 --- a/.github/workflows/full-help-docs.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: CLI Help Doc - -on: - push: - branches: [main, release/**] - pull_request: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref_protected == 'true' && github.sha || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - pull-requests: read - -jobs: - complete: - if: always() - needs: [doc-check] - runs-on: ubuntu-latest - steps: - - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') - run: exit 1 - - doc-check: - runs-on: ubuntu-latest-16-cores - steps: - - uses: actions/checkout@v4 - - uses: stellar/actions/rust-cache@main - - run: rustup update - - name: Generate help doc - # this looks goofy to get GITHUB_OUTPUT to work with multi-line return values; - # see https://stackoverflow.com/a/74266196/249801 - run: | - make generate-full-help-doc - raw_diff=$(git diff FULL_HELP_DOCS.md) - if [ "$raw_diff" != "" ]; then echo ""; echo "Unexpected docs change:"; echo "======================="; echo ""; echo "$raw_diff"; echo ""; echo "======================="; echo ""; fi - echo diff=$raw_diff >> $GITHUB_OUTPUT - id: doc-gen - - - name: Check diff - if: steps.doc-gen.outputs.diff != '' - uses: actions/github-script@v7 - with: - script: | - core.setFailed('Expected `doc-gen` to generate no diffs, but got diff shown in previous step.\n\nUpdate the full help docs:\n\n cargo md-gen\n\nDo this automatically on every commit by installing the pre-commit hook as explained in the README.') diff --git a/.github/workflows/rpc-tests.yml b/.github/workflows/rpc-tests.yml index 769cd21f7..75b6d7760 100644 --- a/.github/workflows/rpc-tests.yml +++ b/.github/workflows/rpc-tests.yml @@ -1,4 +1,3 @@ - name: RPC Tests on: push: @@ -15,32 +14,32 @@ jobs: runs-on: ubuntu-22.04 services: rpc: - image: stellar/quickstart:v438-testing + image: stellar/quickstart:testing ports: - 8000:8000 env: ENABLE_LOGS: true ENABLE_SOROBAN_DIAGNOSTIC_EVENTS: true NETWORK: local + PROTOCOL_VERSION: 22 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@v4 - - uses: actions/cache@v4 - 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: SOROBAN_PORT=8000 cargo test --features it --package soroban-test --test it -- integration - + - uses: actions/checkout@v4 + - uses: actions/cache@v4 + 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: SOROBAN_PORT=8000 cargo test --features it --package soroban-test --test it -- integration diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index eb4e4c414..595e6e817 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,7 +17,7 @@ jobs: complete: if: always() - needs: [fmt, build-and-test, publish-dry-run] + needs: [fmt, cargo-deny, check-generated-full-help-docs, build-and-test, publish-dry-run] runs-on: ubuntu-latest steps: - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') @@ -30,23 +30,37 @@ jobs: - run: rustup update - run: cargo fmt --all --check - check-generated-examples-list: + cargo-deny: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + check: [advisories, bans, licenses, sources] + continue-on-error: ${{ matrix.check == 'advisories' || matrix.check == 'bans' || matrix.check == 'licenses' }} + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@b01e7a8cfb1f496c52d77361e84c1840d8246393 + with: + command: check ${{ matrix.check }} + + check-generated-full-help-docs: + runs-on: ubuntu-latest-16-cores steps: - uses: actions/checkout@v4 - - run: make generate-examples-list - - name: Check no diffs exist - run: git add -N . && git diff HEAD --exit-code + - uses: stellar/actions/rust-cache@main + - run: rustup update + - run: make generate-full-help-doc + - run: git add -N . && git diff HEAD --exit-code build-and-test: strategy: fail-fast: false matrix: rust: [msrv, latest] - include: + sys: - os: ubuntu-latest-16-cores target: x86_64-unknown-linux-gnu - - os: ubuntu-latest-16-cores + - os: ubuntu-jammy-16-cores-arm64 target: aarch64-unknown-linux-gnu - os: macos-latest target: x86_64-apple-darwin @@ -54,7 +68,7 @@ jobs: target: aarch64-apple-darwin - os: windows-latest-8-cores target: x86_64-pc-windows-msvc - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.sys.os }} env: CI_TESTS: true steps: @@ -73,18 +87,15 @@ jobs: run: echo RUSTFLAGS='-Dwarnings -Dclippy::all -Dclippy::pedantic' >> $GITHUB_ENV - run: rustup update - run: cargo version - - run: rustup target add ${{ matrix.target }} + - run: rustup target add ${{ matrix.sys.target }} - run: rustup target add wasm32-unknown-unknown - - if: matrix.target == 'aarch64-unknown-linux-gnu' - run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu - - run: cargo clippy --all-targets --target ${{ matrix.target }} - - run: make build-test - - if: startsWith(matrix.target, 'x86_64') - # specify directories explicitly (otherwise it will fail with missing symbols) - run: | - for I in cmd/soroban-cli cmd/crates/* cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world ; do - cargo test --target ${{ matrix.target }} --manifest-path $I/Cargo.toml - done + - if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libudev-dev + - run: cargo clippy --all-targets --target ${{ matrix.sys.target }} + - run: make test + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + CARGO_BUILD_TARGET: ${{ matrix.sys.target }} publish-dry-run: if: github.event_name == 'push' || startsWith(github.head_ref, 'release/') @@ -96,10 +107,11 @@ jobs: target: x86_64-unknown-linux-gnu cargo-hack-feature-options: --feature-powerset additional-deb-packages: libudev-dev - # - os: ubuntu-latest-16-cores - # target: aarch64-unknown-linux-gnu - # cargo-hack-feature-options: --feature-powerset - # additional-deb-packages: libudev-dev libssl-dev + # TODO: add back ARM support + #- os: ubuntu-latest-16-cores + # target: aarch64-unknown-linux-gnu + # cargo-hack-feature-options: --feature-powerset + # additional-deb-packages: libudev-dev libssl-dev - os: macos-latest target: x86_64-apple-darwin cargo-hack-feature-options: --feature-powerset diff --git a/.gitignore b/.gitignore index 7723bb84b..b7150ae5d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test_snapshots .vscode/settings.json .idea local.sh +.stellar diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21b38392b..7c54767c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ changes quickly. ## Setting up development environment -There are 2 ways to being developing stellar-cli: +There are 2 ways to begin developing stellar-cli: ### Installing all required dependencies diff --git a/Cargo.lock b/Cargo.lock index 7ce514d54..89cb82cdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -106,10 +118,122 @@ dependencies = [ ] [[package]] -name = "arc-swap" -version = "1.7.1" +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] [[package]] name = "arrayref" @@ -146,7 +270,7 @@ dependencies = [ "bstr", "doc-comment", "libc", - "predicates 3.1.2", + "predicates", "predicates-core", "predicates-tree", "wait-timeout", @@ -161,7 +285,7 @@ dependencies = [ "anstyle", "doc-comment", "globwalk", - "predicates 3.1.2", + "predicates", "predicates-core", "predicates-tree", "tempfile", @@ -591,21 +715,18 @@ dependencies = [ "serde", ] -[[package]] -name = "btoi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" -dependencies = [ - "num-traits", -] - [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855" + [[package]] name = "byteorder" version = "1.5.0" @@ -727,7 +848,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] @@ -757,12 +878,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "clru" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd0f76e066e64fdc5631e3bb46381254deab9ef1158292f27c8c57e3bf3fe59" - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -829,17 +944,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crate-git-revision" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f998aef136a4e7833b0e4f0fc0939a59c40140b28e0ffbf524ad84fb2cc568c8" -dependencies = [ - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "crate-git-revision" version = "0.0.6" @@ -907,7 +1011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core 0.6.4", + "rand_core", "subtle", "zeroize", ] @@ -963,19 +1067,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -1067,7 +1158,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", + "strsim", "syn 2.0.77", ] @@ -1108,6 +1199,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -1158,16 +1260,7 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", + "dirs-sys", ] [[package]] @@ -1176,7 +1269,7 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys 0.4.1", + "dirs-sys", ] [[package]] @@ -1189,17 +1282,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dirs-sys" version = "0.4.1" @@ -1252,12 +1334,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clone" version = "1.0.17" @@ -1266,24 +1342,15 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ecdsa" -version = "0.16.7" +version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature 2.1.0", -] - -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", + "signature", ] [[package]] @@ -1293,19 +1360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature 2.1.0", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek 3.2.0", - "ed25519 1.5.3", - "sha2 0.9.9", - "zeroize", + "signature", ] [[package]] @@ -1314,9 +1369,9 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", - "rand_core 0.6.4", + "curve25519-dalek", + "ed25519", + "rand_core", "serde", "sha2 0.10.8", "subtle", @@ -1341,7 +1396,7 @@ dependencies = [ "ff", "generic-array", "group", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -1466,12 +1521,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "faster-hex" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" - [[package]] name = "fastrand" version = "1.9.0" @@ -1493,7 +1542,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -1503,18 +1552,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "filetime" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -1724,764 +1761,10 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "gix" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31887c304d9a935f3e5494fb5d6a0106c34e965168ec0db9b457424eedd0c741" -dependencies = [ - "gix-actor", - "gix-attributes", - "gix-command", - "gix-commitgraph", - "gix-config", - "gix-credentials", - "gix-date", - "gix-diff", - "gix-discover", - "gix-features", - "gix-filter", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-hashtable", - "gix-ignore", - "gix-index", - "gix-lock", - "gix-macros", - "gix-negotiate", - "gix-object", - "gix-odb", - "gix-pack", - "gix-path", - "gix-pathspec", - "gix-prompt", - "gix-protocol", - "gix-ref", - "gix-refspec", - "gix-revision", - "gix-revwalk", - "gix-sec", - "gix-submodule", - "gix-tempfile", - "gix-trace", - "gix-transport", - "gix-traverse", - "gix-url", - "gix-utils", - "gix-validate", - "gix-worktree", - "gix-worktree-state", - "once_cell", - "parking_lot", - "reqwest 0.11.27", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-actor" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a7bb9fad6125c81372987c06469601d37e1a2d421511adb69971b9083517a8a" -dependencies = [ - "bstr", - "btoi", - "gix-date", - "itoa", - "thiserror", - "winnow 0.5.40", -] - -[[package]] -name = "gix-attributes" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37ce99c7e81288c28b703641b6d5d119aacc45c1a6b247156e6249afa486257" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-quote", - "gix-trace", - "kstring", - "smallvec", - "thiserror", - "unicode-bom", -] - -[[package]] -name = "gix-bitmap" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae" -dependencies = [ - "thiserror", -] - -[[package]] -name = "gix-chunk" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c8751169961ba7640b513c3b24af61aa962c967aaf04116734975cd5af0c52" -dependencies = [ - "thiserror", -] - -[[package]] -name = "gix-command" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d76867867da891cbe32021ad454e8cae90242f6afb06762e4dd0d357afd1d7b" -dependencies = [ - "bstr", - "gix-path", - "gix-trace", - "shell-words", -] - -[[package]] -name = "gix-commitgraph" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "133b06f67f565836ec0c473e2116a60fb74f80b6435e21d88013ac0e3c60fc78" -dependencies = [ - "bstr", - "gix-chunk", - "gix-features", - "gix-hash", - "memmap2", - "thiserror", -] - -[[package]] -name = "gix-config" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62bf2073b6ce3921ffa6d8326f645f30eec5fc4a8e8a4bc0fcb721a2f3f69dc" -dependencies = [ - "bstr", - "gix-config-value", - "gix-features", - "gix-glob", - "gix-path", - "gix-ref", - "gix-sec", - "memchr", - "once_cell", - "smallvec", - "thiserror", - "unicode-bom", - "winnow 0.5.40", -] - -[[package]] -name = "gix-config-value" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b328997d74dd15dc71b2773b162cb4af9a25c424105e4876e6d0686ab41c383e" -dependencies = [ - "bitflags 2.6.0", - "bstr", - "gix-path", - "libc", - "thiserror", -] - -[[package]] -name = "gix-credentials" -version = "0.24.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198588f532e4d1202e04e6c3f50e4d7c060dffc66801c6f53cc246f1d234739e" -dependencies = [ - "bstr", - "gix-command", - "gix-config-value", - "gix-path", - "gix-prompt", - "gix-sec", - "gix-trace", - "gix-url", - "thiserror", -] - -[[package]] -name = "gix-date" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0" -dependencies = [ - "bstr", - "itoa", - "thiserror", - "time", -] - -[[package]] -name = "gix-diff" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdcb5e49c4b9729dd1c361040ae5c3cd7c497b2260b18c954f62db3a63e98cf" -dependencies = [ - "bstr", - "gix-hash", - "gix-object", - "thiserror", -] - -[[package]] -name = "gix-discover" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4669218f3ec0cbbf8f16857b32200890f8ca585f36f5817242e4115fe4551af" -dependencies = [ - "bstr", - "dunce", - "gix-fs", - "gix-hash", - "gix-path", - "gix-ref", - "gix-sec", - "thiserror", -] - -[[package]] -name = "gix-features" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69" -dependencies = [ - "bytes", - "crc32fast", - "flate2", - "gix-hash", - "gix-trace", - "gix-utils", - "libc", - "once_cell", - "prodash", - "sha1_smol", - "thiserror", - "walkdir", -] - -[[package]] -name = "gix-filter" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9240862840fb740d209422937195e129e4ed3da49af212383260134bea8f6c1a" -dependencies = [ - "bstr", - "encoding_rs", - "gix-attributes", - "gix-command", - "gix-hash", - "gix-object", - "gix-packetline-blocking", - "gix-path", - "gix-quote", - "gix-trace", - "gix-utils", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-fs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2184c40e7910529677831c8b481acf788ffd92427ed21fad65b6aa637e631b8" -dependencies = [ - "gix-features", - "gix-utils", -] - -[[package]] -name = "gix-glob" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7df15afa265cc8abe92813cd354d522f1ac06b29ec6dfa163ad320575cb447" -dependencies = [ - "bitflags 2.6.0", - "bstr", - "gix-features", - "gix-path", -] - -[[package]] -name = "gix-hash" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" -dependencies = [ - "faster-hex", - "thiserror", -] - -[[package]] -name = "gix-hashtable" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddf80e16f3c19ac06ce415a38b8591993d3f73aede049cb561becb5b3a8e242" -dependencies = [ - "gix-hash", - "hashbrown 0.14.5", - "parking_lot", -] - -[[package]] -name = "gix-ignore" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6afb8f98e314d4e1adc822449389ada863c174b5707cedd327d67b84dba527" -dependencies = [ - "bstr", - "gix-glob", - "gix-path", - "gix-trace", - "unicode-bom", -] - -[[package]] -name = "gix-index" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7152181ba8f0a3addc5075dd612cea31fc3e252b29c8be8c45f4892bf87426" -dependencies = [ - "bitflags 2.6.0", - "bstr", - "btoi", - "filetime", - "gix-bitmap", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-traverse", - "itoa", - "libc", - "memmap2", - "rustix 0.38.34", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-lock" -version = "13.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c359f81f01b8352063319bcb39789b7ea0887b406406381106e38c4a34d049" -dependencies = [ - "gix-tempfile", - "gix-utils", - "thiserror", -] - -[[package]] -name = "gix-macros" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "999ce923619f88194171a67fb3e6d613653b8d4d6078b529b15a765da0edcc17" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "gix-negotiate" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a163adb84149e522e991cbe27250a6e01de56f98cd05b174614ce3f8a4e8b140" -dependencies = [ - "bitflags 2.6.0", - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-object" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693ce9d30741506cb082ef2d8b797415b48e032cce0ab23eff894c19a7e4777b" -dependencies = [ - "bstr", - "btoi", - "gix-actor", - "gix-date", - "gix-features", - "gix-hash", - "gix-validate", - "itoa", - "smallvec", - "thiserror", - "winnow 0.5.40", -] - -[[package]] -name = "gix-odb" -version = "0.57.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba2fa9e81f2461b78b4d81a807867667326c84cdab48e0aed7b73a593aa1be4" -dependencies = [ - "arc-swap", - "gix-date", - "gix-features", - "gix-fs", - "gix-hash", - "gix-object", - "gix-pack", - "gix-path", - "gix-quote", - "parking_lot", - "tempfile", - "thiserror", -] - -[[package]] -name = "gix-pack" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da5f3e78c96b76c4e6fe5e8e06b76221e4a0ee9a255aa935ed1fdf68988dfd8" -dependencies = [ - "clru", - "gix-chunk", - "gix-features", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-path", - "gix-tempfile", - "memmap2", - "parking_lot", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-packetline" -version = "0.17.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b70486beda0903b6d5b65dfa6e40585098cdf4e6365ca2dff4f74c387354a515" -dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror", -] - -[[package]] -name = "gix-packetline-blocking" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31d42378a3d284732e4d589979930d0d253360eccf7ec7a80332e5ccb77e14a" -dependencies = [ - "bstr", - "faster-hex", - "gix-trace", - "thiserror", -] - -[[package]] -name = "gix-path" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d23d5bbda31344d8abc8de7c075b3cf26e5873feba7c4a15d916bce67382bd9" -dependencies = [ - "bstr", - "gix-trace", - "home", - "once_cell", - "thiserror", -] - -[[package]] -name = "gix-pathspec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbd49750edb26b0a691e5246fc635fa554d344da825cd20fa9ee0da9c1b761f" -dependencies = [ - "bitflags 2.6.0", - "bstr", - "gix-attributes", - "gix-config-value", - "gix-glob", - "gix-path", - "thiserror", -] - -[[package]] -name = "gix-prompt" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e0595d2be4b6d6a71a099e989bdd610882b882da35fb8503d91d6f81aa0936f" -dependencies = [ - "gix-command", - "gix-config-value", - "parking_lot", - "rustix 0.38.34", - "thiserror", -] - -[[package]] -name = "gix-protocol" -version = "0.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905cd00946ed8ed6f4f2281f98a889c5b3d38361cd94b8d5a5771d25ab33b99" -dependencies = [ - "bstr", - "gix-credentials", - "gix-date", - "gix-features", - "gix-hash", - "gix-transport", - "gix-utils", - "maybe-async", - "thiserror", - "winnow 0.6.18", -] - -[[package]] -name = "gix-quote" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbff4f9b9ea3fa7a25a70ee62f545143abef624ac6aa5884344e70c8b0a1d9ff" -dependencies = [ - "bstr", - "gix-utils", - "thiserror", -] - -[[package]] -name = "gix-ref" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5818958994ad7879fa566f5441ebcc48f0926aa027b28948e6fbf6578894dc31" -dependencies = [ - "gix-actor", - "gix-date", - "gix-features", - "gix-fs", - "gix-hash", - "gix-lock", - "gix-object", - "gix-path", - "gix-tempfile", - "gix-utils", - "gix-validate", - "memmap2", - "thiserror", - "winnow 0.5.40", -] - -[[package]] -name = "gix-refspec" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613aa4d93034c5791d13bdc635e530f4ddab1412ddfb4a8215f76213177b61c7" -dependencies = [ - "bstr", - "gix-hash", - "gix-revision", - "gix-validate", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-revision" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "288f6549d7666db74dc3f169a9a333694fc28ecd2f5aa7b2c979c89eb556751a" -dependencies = [ - "bstr", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "gix-trace", - "thiserror", -] - -[[package]] -name = "gix-revwalk" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9b4d91dfc5c14fee61a28c65113ded720403b65a0f46169c0460f731a5d03c" -dependencies = [ - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-sec" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1547d26fa5693a7f34f05b4a3b59a90890972922172653bcb891ab3f09f436df" -dependencies = [ - "bitflags 2.6.0", - "gix-path", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "gix-submodule" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73182f6c1f5ed1ed94ba16581ac62593d5e29cd1c028b2af618f836283b8f8d4" -dependencies = [ - "bstr", - "gix-config", - "gix-path", - "gix-pathspec", - "gix-refspec", - "gix-url", - "thiserror", -] - -[[package]] -name = "gix-tempfile" -version = "13.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a761d76594f4443b675e85928e4902dec333273836bd386906f01e7e346a0d11" -dependencies = [ - "gix-fs", - "libc", - "once_cell", - "parking_lot", - "tempfile", -] - -[[package]] -name = "gix-trace" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" - -[[package]] -name = "gix-transport" -version = "0.41.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8e5f72ec9cad9ee44714b9a4ec7427b540a2418b62111f5e3a715bebe1ed9d" -dependencies = [ - "base64 0.21.7", - "bstr", - "gix-command", - "gix-credentials", - "gix-features", - "gix-packetline", - "gix-quote", - "gix-sec", - "gix-url", - "reqwest 0.11.27", - "thiserror", -] - -[[package]] -name = "gix-traverse" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc30c5b5e4e838683b59e1b0574ce6bc1c35916df9709aaab32bb7751daf08b" -dependencies = [ - "gix-commitgraph", - "gix-date", - "gix-hash", - "gix-hashtable", - "gix-object", - "gix-revwalk", - "smallvec", - "thiserror", -] - -[[package]] -name = "gix-url" -version = "0.27.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2eb9b35bba92ea8f0b5ab406fad3cf6b87f7929aa677ff10aa042c6da621156" -dependencies = [ - "bstr", - "gix-features", - "gix-path", - "home", - "thiserror", - "url", -] - -[[package]] -name = "gix-utils" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" -dependencies = [ - "fastrand 2.1.0", - "unicode-normalization", -] - -[[package]] -name = "gix-validate" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" -dependencies = [ - "bstr", - "thiserror", -] - -[[package]] -name = "gix-worktree" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca36bb3dc54038c66507dc75c4d8edbee2d6d5cc45227b4eb508ad13dd60a006" -dependencies = [ - "bstr", - "gix-attributes", - "gix-features", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-ignore", - "gix-index", - "gix-object", - "gix-path", -] - -[[package]] -name = "gix-worktree-state" -version = "0.7.0" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae178614b70bdb0c7f6f21b8c9fb711ab78bd7e8e1866f565fcf28876747f1d" -dependencies = [ - "bstr", - "gix-features", - "gix-filter", - "gix-fs", - "gix-glob", - "gix-hash", - "gix-index", - "gix-object", - "gix-path", - "gix-worktree", - "io-close", - "thiserror", -] +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "glob" @@ -2532,7 +1815,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -2580,6 +1863,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -2943,20 +2235,23 @@ dependencies = [ "tokio", "tokio-rustls 0.26.0", "tower-service", - "webpki-roots 0.26.3", + "webpki-roots", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper 0.14.30", + "http-body-util", + "hyper 1.4.1", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] @@ -3116,16 +2411,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "io-close" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cadcf447f06744f8ce713d2d6239bb5bde2c357a452397a9ed90c625da390bc" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -3277,9 +2562,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", @@ -3296,15 +2581,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "kstring" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" -dependencies = [ - "static_assertions", -] - [[package]] name = "kv-log-macro" version = "1.0.7" @@ -3351,6 +2627,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "ledger-apdu" version = "0.10.0" @@ -3378,7 +2660,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee" dependencies = [ - "byteorder", + "byteorder 1.5.0", "cfg-if", "hex", "hidapi", @@ -3486,32 +2768,12 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "maybe-async" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - [[package]] name = "mime" version = "0.3.17" @@ -3655,15 +2917,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.32.2" @@ -3788,7 +3041,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -4023,20 +3276,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "predicates" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" -dependencies = [ - "difflib", - "float-cmp", - "itertools 0.10.5", - "normalize-line-endings", - "predicates-core", - "regex", -] - [[package]] name = "predicates" version = "3.1.2" @@ -4045,7 +3284,10 @@ checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", + "float-cmp", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] @@ -4102,12 +3344,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "prodash" -version = "28.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" - [[package]] name = "quick-error" version = "1.2.3" @@ -4179,7 +3415,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -4189,15 +3425,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -4207,15 +3437,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.3" @@ -4280,50 +3501,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-rustls 0.24.2", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-rustls 0.24.1", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots 0.25.4", - "winreg", -] - [[package]] name = "reqwest" version = "0.12.7" @@ -4343,11 +3520,13 @@ dependencies = [ "http-body-util", "hyper 1.4.1", "hyper-rustls 0.27.3", + "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4359,8 +3538,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper", + "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.0", "tokio-util", "tower-service", @@ -4369,7 +3550,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.26.3", + "webpki-roots", "windows-registry", ] @@ -4483,9 +3664,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -4939,12 +4120,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - [[package]] name = "sha2" version = "0.9.9" @@ -4989,10 +4164,10 @@ dependencies = [ ] [[package]] -name = "shell-words" -version = "1.1.0" +name = "shell-escape" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" [[package]] name = "shlex" @@ -5009,12 +4184,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - [[package]] name = "signature" version = "2.1.0" @@ -5022,7 +4191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -5046,24 +4215,13 @@ dependencies = [ "autocfg", ] -[[package]] -name = "slip10" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28724a6e6f70b0cb115c580891483da6f3aa99e6a353598303a57f89d23aa6bc" -dependencies = [ - "ed25519-dalek 1.0.1", - "hmac 0.9.0", - "sha2 0.9.9", -] - [[package]] name = "slipped10" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a45443e66aa5d96db5e02d17db056e1ca970232a4fe73e1f9bc1816d68f4e98" dependencies = [ - "ed25519-dalek 2.1.1", + "ed25519-dalek", "hmac 0.9.0", "sha2 0.9.9", ] @@ -5118,11 +4276,11 @@ dependencies = [ [[package]] name = "soroban-builtin-sdk-macros" -version = "21.2.1" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" +checksum = "c45d2492cd44f05cc79eeb857985f153f12a4423ce51b4b746b5925024c473b1" dependencies = [ - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.77", @@ -5130,7 +4288,7 @@ dependencies = [ [[package]] name = "soroban-cli" -version = "21.5.0" +version = "22.0.0" dependencies = [ "assert_cmd", "assert_fs", @@ -5144,18 +4302,16 @@ dependencies = [ "clap", "clap-markdown", "clap_complete", - "crate-git-revision 0.0.4", + "crate-git-revision", "csv", "directories", - "dirs 4.0.0", "dotenvy", - "ed25519-dalek 2.1.1", + "ed25519-dalek", "ethnum", "flate2", "fqdn", "futures", "futures-util", - "gix", "glob", "heck 0.5.0", "hex", @@ -5169,10 +4325,10 @@ dependencies = [ "open", "pathdiff", "phf", - "predicates 2.1.5", + "predicates", "rand", "regex", - "reqwest 0.12.7", + "reqwest", "rpassword", "rust-embed", "semver", @@ -5181,8 +4337,8 @@ dependencies = [ "serde-aux", "serde_json", "sha2 0.10.8", + "shell-escape", "shlex", - "soroban-env-host", "soroban-ledger-snapshot", "soroban-sdk", "soroban-spec", @@ -5193,7 +4349,7 @@ dependencies = [ "stellar-rpc-client", "stellar-strkey 0.0.11", "stellar-xdr", - "strsim 0.10.0", + "strsim", "strum 0.17.1", "strum_macros 0.17.1", "tempfile", @@ -5202,27 +4358,28 @@ dependencies = [ "thiserror", "tokio", "tokio-util", - "toml 0.5.11", - "toml_edit 0.21.1", + "toml", + "toml_edit", "tracing", "tracing-appender", "tracing-subscriber", "ulid", "url", "walkdir", + "wasm-gen", "wasm-opt", - "wasmparser 0.90.0", + "wasmparser", "which", ] [[package]] name = "soroban-env-common" -version = "21.2.1" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" +checksum = "39b6d2ec8955243394278e1fae88be3b367fcfed9cf74e5044799a90786a8642" dependencies = [ "arbitrary", - "crate-git-revision 0.0.6", + "crate-git-revision", "ethnum", "num-derive", "num-traits", @@ -5231,14 +4388,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-xdr", - "wasmparser 0.116.1", + "wasmparser", ] [[package]] name = "soroban-env-guest" -version = "21.2.1" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" +checksum = "4002fc582cd20cc9b9fbb73959bc5d6b5b15feda11485cbfab0c28e78ecbab3e" dependencies = [ "soroban-env-common", "static_assertions", @@ -5246,14 +4403,17 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "21.2.1" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" +checksum = "8cb9be0260d39a648db0d33e1c6f8f494ec0c4f5be2b8a0a4e15ed4b7c6a92b0" dependencies = [ - "backtrace", - "curve25519-dalek 4.1.3", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "curve25519-dalek", "ecdsa", - "ed25519-dalek 2.1.1", + "ed25519-dalek", "elliptic-curve", "generic-array", "getrandom", @@ -5273,17 +4433,17 @@ dependencies = [ "soroban-env-common", "soroban-wasmi", "static_assertions", - "stellar-strkey 0.0.8", - "wasmparser 0.116.1", + "stellar-strkey 0.0.9", + "wasmparser", ] [[package]] name = "soroban-env-macros" -version = "21.2.1" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" +checksum = "a328297a568ae98999fdb06902e3362dfd8a2bfa9abea40beaeb7dc93a402fe7" dependencies = [ - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", "serde", @@ -5294,13 +4454,13 @@ dependencies = [ [[package]] name = "soroban-hello" -version = "21.5.0" +version = "0.0.0" [[package]] name = "soroban-ledger-snapshot" -version = "21.7.2" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf596b2083946a95914a55d7d29cee6a8095b515fd06211851f45bf6af5a496" +checksum = "56375490f176006a636db0e50c2269c55626e0ff7222711bb78d77028376fe0d" dependencies = [ "serde", "serde_json", @@ -5312,33 +4472,34 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "21.7.2" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e39bf9e8ab05579c836e8e5be5f2f4c5ba75e7337ece20e975e82fc3a9d41e" +checksum = "6d063d0df000aaec20105aab3d743660322bc0269934ea95d79fa19aa8792385" dependencies = [ "arbitrary", "bytes-lit", "ctor", - "ed25519-dalek 2.1.1", + "ed25519-dalek", "rand", + "rustc_version", "serde", "serde_json", "soroban-env-guest", "soroban-env-host", "soroban-ledger-snapshot", "soroban-sdk-macros", - "stellar-strkey 0.0.8", + "stellar-strkey 0.0.9", ] [[package]] name = "soroban-sdk-macros" -version = "21.7.2" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa00b8ca6e392f013359c06d790d2d379f9c8d6f8a6dfe563ec64311e5d3" +checksum = "508c9d819a05109120664aab86c371e1b72c5bea20b1a13158b4ef7948d9f673" dependencies = [ - "crate-git-revision 0.0.6", + "crate-git-revision", "darling", - "itertools 0.11.0", + "itertools 0.10.5", "proc-macro2", "quote", "rustc_version", @@ -5352,19 +4513,19 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "21.7.2" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c723195463d8742bcb481520bd8b8325da66c39ea236ad46261e6af992e8a8" +checksum = "69001c97783ed3ce197eac2404e7beeabedd16e40e6f0aa210d1bc6a13063c33" dependencies = [ "base64 0.13.1", "stellar-xdr", "thiserror", - "wasmparser 0.116.1", + "wasmparser", ] [[package]] name = "soroban-spec-json" -version = "21.5.0" +version = "22.0.0" dependencies = [ "pretty_assertions", "serde", @@ -5378,9 +4539,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "21.7.2" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f1b0ec2af54e38f138910f09e101b100130efe625f69ece51c76dd4f06f8b2" +checksum = "a45dbf346f91ed23ea63b1c256c522da9e6f0e2db1887b990a8f0f1d842a3093" dependencies = [ "prettyplease", "proc-macro2", @@ -5394,26 +4555,25 @@ dependencies = [ [[package]] name = "soroban-spec-tools" -version = "21.5.0" +version = "22.0.0" dependencies = [ "base64 0.21.7", "ethnum", "hex", "itertools 0.10.5", "serde_json", - "soroban-env-host", "soroban-spec", "stellar-strkey 0.0.11", "stellar-xdr", "thiserror", "tokio", - "wasmparser 0.90.0", + "wasmparser", "which", ] [[package]] name = "soroban-spec-typescript" -version = "21.5.0" +version = "22.0.0" dependencies = [ "base64 0.21.7", "heck 0.4.1", @@ -5434,28 +4594,26 @@ dependencies = [ [[package]] name = "soroban-test" -version = "21.5.0" +version = "22.0.0" dependencies = [ "assert_cmd", "assert_fs", - "ed25519-dalek 2.1.1", + "ed25519-dalek", "fs_extra", "hex", - "predicates 2.1.5", + "predicates", "sep5", "serde_json", "sha2 0.10.8", "soroban-cli", - "soroban-env-host", "soroban-ledger-snapshot", - "soroban-sdk", "soroban-spec", "soroban-spec-tools", "stellar-rpc-client", "stellar-strkey 0.0.11", "thiserror", "tokio", - "toml 0.8.19", + "toml", "ulid", "walkdir", "which", @@ -5463,9 +4621,9 @@ dependencies = [ [[package]] name = "soroban-token-sdk" -version = "21.7.2" +version = "22.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6ede0da76646037f3ea5db9ccd37830865444bb24f137cb8f0af8282c784f5" +checksum = "17bb933a3dcf41d234f6d669b077eb755663773630c6899a1c8a30dddf950f52" dependencies = [ "soroban-sdk", ] @@ -5505,21 +4663,25 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stellar-bye" +version = "0.0.0" + [[package]] name = "stellar-cli" -version = "21.5.0" +version = "22.0.0" dependencies = [ "soroban-cli", ] [[package]] name = "stellar-ledger" -version = "21.5.0" +version = "22.0.0" dependencies = [ "async-trait", "bollard", - "byteorder", - "ed25519-dalek 2.1.1", + "byteorder 1.5.0", + "ed25519-dalek", "env_logger", "futures", "hex", @@ -5531,15 +4693,14 @@ dependencies = [ "once_cell", "phf", "pretty_assertions", - "reqwest 0.11.27", + "reqwest", "sep5", "serde", "serde_derive", "serde_json", "serial_test", - "sha2 0.9.9", - "slip10", - "soroban-env-host", + "sha2 0.10.8", + "slipped10", "soroban-spec", "stellar-rpc-client", "stellar-strkey 0.0.11", @@ -5553,9 +4714,9 @@ dependencies = [ [[package]] name = "stellar-rpc-client" -version = "21.4.0" +version = "22.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c735c8e456fa7c37f72dd2a6abc022a111b3bf9b0520d50eef091eb1336ad370" +checksum = "eaba3219c517ceba11f99e1f9adb1e801fe8416bc9ff35c6842b5fe8cac19290" dependencies = [ "clap", "hex", @@ -5566,8 +4727,9 @@ dependencies = [ "serde", "serde-aux", "serde_json", + "serde_with", "sha2 0.10.8", - "stellar-strkey 0.0.8", + "stellar-strkey 0.0.9", "stellar-xdr", "termcolor", "termcolor_output", @@ -5583,7 +4745,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" dependencies = [ "base32", - "crate-git-revision 0.0.6", + "crate-git-revision", + "thiserror", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e3aa3ed00e70082cb43febc1c2afa5056b9bb3e348bbb43d0cd0aa88a611144" +dependencies = [ + "crate-git-revision", + "data-encoding", "thiserror", ] @@ -5593,27 +4766,27 @@ version = "0.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0989c9a05eccbd08b60e603a1c7e3ed3ec92c0de73b8681fc964d272ab2b2697" dependencies = [ - "crate-git-revision 0.0.6", + "crate-git-revision", "data-encoding", ] [[package]] name = "stellar-xdr" -version = "21.2.0" +version = "22.0.0-rc.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" +checksum = "c88dc0e928b9cb65ea43836b52560bb4ead3e32895f5019ca223dc7cd1966cbf" dependencies = [ "arbitrary", "base64 0.13.1", "clap", - "crate-git-revision 0.0.6", + "crate-git-revision", "escape-bytes", "hex", "schemars", "serde", "serde_json", "serde_with", - "stellar-strkey 0.0.8", + "stellar-strkey 0.0.9", "thiserror", ] @@ -5630,12 +4803,6 @@ dependencies = [ "precomputed-hash", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -5730,12 +4897,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.1" @@ -5747,20 +4908,20 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -5860,37 +5021,44 @@ dependencies = [ "test-case-core", ] +[[package]] +name = "test_constructor" +version = "0.0.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "test_custom_account" -version = "21.5.0" +version = "0.0.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_custom_types" -version = "21.5.0" +version = "0.0.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_hello_world" -version = "21.5.0" +version = "0.0.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_swap" -version = "21.5.0" +version = "0.0.0" dependencies = [ "soroban-sdk", ] [[package]] name = "test_token" -version = "21.5.0" +version = "0.0.0" dependencies = [ "soroban-sdk", "soroban-token-sdk", @@ -5898,7 +5066,7 @@ dependencies = [ [[package]] name = "test_udt" -version = "21.5.0" +version = "22.0.0" dependencies = [ "soroban-sdk", ] @@ -5913,7 +5081,7 @@ dependencies = [ "bollard", "bollard-stubs", "bytes", - "dirs 5.0.1", + "dirs", "docker_credential", "either", "futures", @@ -5921,7 +5089,7 @@ dependencies = [ "memchr", "parse-display", "pin-project-lite", - "reqwest 0.12.7", + "reqwest", "serde", "serde_json", "serde_with", @@ -5970,9 +5138,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -6134,15 +5300,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - [[package]] name = "toml" version = "0.8.19" @@ -6152,7 +5309,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit", ] [[package]] @@ -6164,17 +5321,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.3.0", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.20" @@ -6185,7 +5331,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow", ] [[package]] @@ -6320,12 +5466,6 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" -[[package]] -name = "unicode-bom" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -6513,11 +5653,21 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-gen" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b854b1461005a7b3365742310f7faa3cac3add809d66928c64a40c7e9e842ebb" +dependencies = [ + "byteorder 0.5.3", + "leb128", +] + [[package]] name = "wasm-opt" -version = "0.114.2" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effbef3bd1dde18acb401f73e740a6f3d4a1bc651e9773bddc512fe4d8d68f67" +checksum = "2fd87a4c135535ffed86123b6fb0f0a5a0bc89e50416c942c5f0662c645f679c" dependencies = [ "anyhow", "libc", @@ -6531,9 +5681,9 @@ dependencies = [ [[package]] name = "wasm-opt-cxx-sys" -version = "0.114.2" +version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c09e24eb283919ace2ed5733bda4842a59ce4c8de110ef5c6d98859513d17047" +checksum = "8c57b28207aa724318fcec6575fe74803c23f6f266fce10cbc9f3f116762f12e" dependencies = [ "anyhow", "cxx", @@ -6543,9 +5693,9 @@ dependencies = [ [[package]] name = "wasm-opt-sys" -version = "0.114.2" +version = "0.116.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f2f817bed2e8d65eb779fa37317e74de15585751f903c9118342d1970703a4" +checksum = "8a1cce564dc768dacbdb718fc29df2dba80bd21cb47d8f77ae7e3d95ceb98cbe" dependencies = [ "anyhow", "cc", @@ -6555,9 +5705,9 @@ dependencies = [ [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -6584,15 +5734,6 @@ dependencies = [ "paste", ] -[[package]] -name = "wasmparser" -version = "0.90.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62c8d843f4423efee314dc75a1049886deba3214f7e7f9ff0e4e58b4d618581" -dependencies = [ - "indexmap 1.9.3", -] - [[package]] name = "wasmparser" version = "0.116.1" @@ -6632,12 +5773,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - [[package]] name = "webpki-roots" version = "0.26.3" @@ -6884,15 +6019,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.6.18" @@ -6924,7 +6050,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "byteorder", + "byteorder 1.5.0", "zerocopy-derive", ] diff --git a/Cargo.toml b/Cargo.toml index 7282ff497..d2572e9c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,59 +6,66 @@ members = [ "cmd/crates/*", "cmd/crates/soroban-test/tests/fixtures/test-wasms/*", "cmd/crates/soroban-test/tests/fixtures/hello", + "cmd/crates/soroban-test/tests/fixtures/bye", +] +default-members = [ + "cmd/soroban-cli", + "cmd/crates/soroban-spec-tools", + "cmd/crates/soroban-test", +] +exclude = [ + "cmd/crates/soroban-test/tests/fixtures/hello", + "cmd/crates/soroban-test/tests/fixtures/bye", ] -default-members = ["cmd/soroban-cli", "cmd/crates/soroban-spec-tools", "cmd/crates/soroban-test"] -exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] -version = "21.5.0" +version = "22.0.0" rust-version = "1.81.0" -[workspace.dependencies.soroban-env-host] -version = "=21.2.1" - -[workspace.dependencies.soroban-simulation] -version = "=21.2.1" - -[workspace.dependencies.soroban-spec] -version = "=21.7.2" - -[workspace.dependencies.soroban-spec-rust] -version = "=21.7.2" +# Dependencies located in this repo: +[workspace.dependencies.soroban-cli] +version = "=22.0.0" +path = "cmd/soroban-cli" [workspace.dependencies.soroban-spec-json] -version = "=21.5.0" +version = "=22.0.0" path = "./cmd/crates/soroban-spec-json" [workspace.dependencies.soroban-spec-typescript] -version = "21.5.0" +version = "22.0.0" path = "./cmd/crates/soroban-spec-typescript" [workspace.dependencies.soroban-spec-tools] -version = "21.5.0" +version = "22.0.0" path = "./cmd/crates/soroban-spec-tools" +# Dependencies from the rs-stellar-xdr repo: +[workspace.dependencies.stellar-xdr] +version = "=22.0.0-rc.1.1" +default-features = true + +# Dependencies from the rs-soroban-sdk repo: +[workspace.dependencies.soroban-spec] +version = "=22.0.0-rc.3" + +[workspace.dependencies.soroban-spec-rust] +version = "=22.0.0-rc.3" + [workspace.dependencies.soroban-sdk] -version = "=21.7.2" +version = "=22.0.0-rc.3" [workspace.dependencies.soroban-token-sdk] -version = "=21.7.2" +version = "=22.0.0-rc.3" [workspace.dependencies.soroban-ledger-snapshot] -version = "=21.7.2" - -[workspace.dependencies.soroban-cli] -version = "=21.5.0" -path = "cmd/soroban-cli" +version = "=22.0.0-rc.3" +# Dependencies from the rs-stellar-rpc-client repo: [workspace.dependencies.soroban-rpc] package = "stellar-rpc-client" -version = "21.4.0" - -[workspace.dependencies.stellar-xdr] -version = "21.2.0" -default-features = true +version = "=22.0.0-rc.1" +# Dependencies from elsewhere shared by crates: [workspace.dependencies] stellar-strkey = "0.0.11" sep5 = "0.0.4" @@ -70,11 +77,9 @@ hex = "0.4.3" itertools = "0.10.0" async-trait = "0.1.76" bollard = "0.16.0" - serde-aux = "4.1.2" serde_json = "1.0.82" serde = "1.0.82" - clap = { version = "4.1.8", features = [ "derive", "env", @@ -86,17 +91,20 @@ tracing = "0.1.37" tracing-subscriber = "0.3.16" tracing-appender = "0.2.2" which = "4.4.0" -wasmparser = "0.90.0" +wasmparser = "0.116.1" directories = "5.0.1" ulid = "1.1" termcolor = "1.1.3" termcolor_output = "1.0.1" ed25519-dalek = ">= 2.1.1" - -# networking +http = "1.0.0" jsonrpsee-http-client = "0.20.1" jsonrpsee-core = "0.20.1" -tokio = "1.28.1" +walkdir = "2.5.0" +toml_edit = "0.22.20" +toml = "0.8.19" +reqwest = "0.12.7" +predicates = "3.1.2" [profile.test-wasms] inherits = "release" diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md index 5128ce396..1ba366ac0 100644 --- a/FULL_HELP_DOCS.md +++ b/FULL_HELP_DOCS.md @@ -18,7 +18,7 @@ For additional information see: - Stellar Docs: https://developers.stellar.org - Smart Contract Docs: https://developers.stellar.org/docs/build/smart-contracts/overview -- CLI Docs: https://developers.stellar.org/docs/tools/stellar-cli +- CLI Docs: https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli To get started generate a new identity: @@ -45,8 +45,10 @@ Anything after the `--` double dash (the "slop") is parsed as arguments to the c * `contract` — Tools for smart contract developers * `events` — Watch the network for contract events +* `env` — Prints the current environment variables or defaults to the stdout, in a format that can be used as .env file. Environment variables have precedency over defaults * `keys` — Create and manage identities including keys and addresses -* `network` — Start and configure networks +* `network` — Configure connection to networks +* `container` — Start local networks in containers * `snapshot` — Download a snapshot of a ledger from an archive * `tx` — Sign, Simulate, and Send transactions * `xdr` — Decode and encode XDR @@ -84,7 +86,7 @@ Tools for smart contract developers * `fetch` — Fetch a contract's Wasm binary * `id` — Generate the contract id for a given contract or asset * `info` — Access info about contracts -* `init` — Initialize a Soroban project with an example contract +* `init` — Initialize a Soroban contract project * `inspect` — (Deprecated in favor of `contract info` subcommands) Inspect a WASM file listing contract functions, meta, etc * `install` — Install a WASM file to the ledger without creating a contract instance * `invoke` — Invoke a contract function @@ -117,6 +119,7 @@ Get Id of builtin Soroban Asset Contract. Deprecated, use `stellar contract id a * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -134,6 +137,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -146,7 +150,7 @@ Deploy builtin Soroban Asset Contract * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout @@ -180,6 +184,7 @@ Remove contract alias * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -193,13 +198,14 @@ Add contract alias ###### **Arguments:** -* `` — The contract alias that will be removed +* `` — The contract alias that will be used ###### **Options:** * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--overwrite` — Overwrite the contract alias if it already exists @@ -222,6 +228,7 @@ Show the contract id associated with a given alias * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -293,6 +300,7 @@ Generate a TypeScript / JavaScript package * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -328,6 +336,7 @@ To view the commands that will be executed, without executing them, use the --pr If ommitted, wasm files are written only to the cargo target directory. * `--print-commands-only` — Print commands to build without executing them +* `--meta ` — Add key-value to contract meta (adds the meta to the `contractmetav0` custom section) @@ -359,6 +368,7 @@ If no keys are specified the contract itself is extended. Temporary * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -371,7 +381,7 @@ If no keys are specified the contract itself is extended. * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout @@ -379,7 +389,11 @@ If no keys are specified the contract itself is extended. Deploy a wasm contract -**Usage:** `stellar contract deploy [OPTIONS] --source-account <--wasm |--wasm-hash >` +**Usage:** `stellar contract deploy [OPTIONS] --source-account <--wasm |--wasm-hash > [-- ...]` + +###### **Arguments:** + +* `` — If provided, will be passed to the contract's `__constructor` function with provided arguments for that function as `--arg-name value` ###### **Options:** @@ -387,6 +401,7 @@ Deploy a wasm contract * `--wasm-hash ` — Hash of the already installed/deployed WASM file * `--salt ` — Custom salt 32-byte salt for the token id * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -399,7 +414,7 @@ Deploy a wasm contract * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts Default value: `false` @@ -420,6 +435,7 @@ Fetch a contract's Wasm binary * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -448,6 +464,7 @@ Deploy builtin Soroban Asset Contract * `--asset ` — ID of the Stellar classic asset to wrap, e.g. "USDC:G...5" * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -465,6 +482,7 @@ Deploy normal Wasm Contract * `--salt ` — ID of the Soroban contract * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -504,8 +522,9 @@ Outputs no data when no data is present in the contract. * `--wasm ` — Wasm file to extract the data from * `--wasm-hash ` — Wasm hash to get the data for -* `--id ` — Contract id to get the data for +* `--id ` — Contract id or contract alias to get the data for * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -543,8 +562,9 @@ Outputs no data when no data is present in the contract. * `--wasm ` — Wasm file to extract the data from * `--wasm-hash ` — Wasm hash to get the data for -* `--id ` — Contract id to get the data for +* `--id ` — Contract id or contract alias to get the data for * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -582,8 +602,9 @@ Outputs no data when no data is present in the contract. * `--wasm ` — Wasm file to extract the data from * `--wasm-hash ` — Wasm hash to get the data for -* `--id ` — Contract id to get the data for +* `--id ` — Contract id or contract alias to get the data for * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -607,7 +628,9 @@ Outputs no data when no data is present in the contract. ## `stellar contract init` -Initialize a Soroban project with an example contract +Initialize a Soroban contract project. + +This command will create a Cargo workspace project and add a sample Stellar contract. The name of the contract can be specified by `--name`. It can be run multiple times with different names in order to generate multiple contracts, and files won't be overwritten unless `--overwrite` is passed. **Usage:** `stellar contract init [OPTIONS] ` @@ -617,13 +640,9 @@ Initialize a Soroban project with an example contract ###### **Options:** -* `-w`, `--with-example ` — An optional flag to specify Soroban example contracts to include. A hello-world contract will be included by default. +* `--name ` — An optional flag to specify a new contract's name. - 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`, `other_custom_types`, `simple_account`, `single_offer`, `timelock`, `token`, `ttl`, `upgradeable_contract`, `workspace` - -* `--frontend-template ` — An optional flag to pass in a url for a frontend template repository. - - Default value: `` + Default value: `hello-world` * `--overwrite` — Overwrite all existing files. @@ -663,6 +682,7 @@ Install a WASM file to the ledger without creating a contract instance ###### **Options:** * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -675,7 +695,7 @@ Install a WASM file to the ledger without creating a contract instance * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--wasm ` — Path to wasm binary * `-i`, `--ignore-checks` — Whether to ignore safety checks when deploying contracts @@ -702,6 +722,7 @@ stellar contract invoke ... -- --help * `--id ` — Contract ID to invoke * `--is-view` — View the result simulating and do not sign and submit transaction. Deprecated use `--send=no` * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -714,7 +735,7 @@ stellar contract invoke ... -- --help * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--send ` — Whether or not to send a transaction Default value: `default` @@ -747,7 +768,7 @@ Optimize a WASM file Print the current value of a contract-data ledger entry -**Usage:** `stellar contract read [OPTIONS] --source-account ` +**Usage:** `stellar contract read [OPTIONS]` ###### **Options:** @@ -779,10 +800,9 @@ Print the current value of a contract-data ledger entry Temporary * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config -* `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail -* `--hd-path ` — If using a seed phrase, which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." @@ -816,6 +836,7 @@ If no keys are specificed the contract itself is restored. * `--ledgers-to-extend ` — Number of ledgers to extend the entry * `--ttl-ledger-only` — Only print the new Time To Live ledger * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -828,7 +849,7 @@ If no keys are specificed the contract itself is restored. * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout @@ -876,11 +897,25 @@ Watch the network for contract events * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config +## `stellar env` + +Prints the current environment variables or defaults to the stdout, in a format that can be used as .env file. Environment variables have precedency over defaults + +**Usage:** `stellar env [OPTIONS]` + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." + + + ## `stellar keys` Create and manage identities including keys and addresses @@ -896,6 +931,7 @@ Create and manage identities including keys and addresses * `ls` — List identities * `rm` — Remove an identity * `show` — Given an identity return its private key +* `use` — Set the default identity that will be used on all commands. This allows you to skip `--source-account` or setting a environment variable, while reusing this value in all commands that require it @@ -949,6 +985,7 @@ Fund an identity on a test network ###### **Options:** * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--hd-path ` — If identity is a seed phrase use this hd path, default is 0 @@ -977,8 +1014,13 @@ Generate a new identity with a seed phrase, currently 12 words * `--hd-path ` — When generating a secret key, which `hd_path` should be used from the original `seed_phrase` * `-d`, `--default-seed` — Generate the default seed phrase. Useful for testing. Equivalent to --seed 0000000000000000 * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config +* `--fund` — Fund generated key pair + + Default value: `false` +* `--overwrite` — Overwrite existing identity if it already exists @@ -1031,9 +1073,26 @@ Given an identity return its private key +## `stellar keys use` + +Set the default identity that will be used on all commands. This allows you to skip `--source-account` or setting a environment variable, while reusing this value in all commands that require it + +**Usage:** `stellar keys use [OPTIONS] ` + +###### **Arguments:** + +* `` — Set the default network name + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." + + + ## `stellar network` -Start and configure networks +Configure connection to networks **Usage:** `stellar network ` @@ -1044,7 +1103,8 @@ Start and configure networks * `ls` — List networks * `start` — ⚠️ Deprecated: use `stellar container start` instead * `stop` — ⚠️ Deprecated: use `stellar container stop` instead -* `container` — Commands to start, stop and get logs for a quickstart container +* `use` — Set the default network that will be used on all commands. This allows you to skip `--network` or setting a environment variable, while reusing this value in all commands that require it +* `container` — ⚠️ Deprecated: use `stellar container` instead @@ -1061,6 +1121,7 @@ Add a new network ###### **Options:** * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — Optional header (e.g. API Key) to include in requests to the RPC * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." @@ -1152,8 +1213,27 @@ Stop a network started with `network start`. For example, if you ran `stellar ne +## `stellar network use` + +Set the default network that will be used on all commands. This allows you to skip `--network` or setting a environment variable, while reusing this value in all commands that require it + +**Usage:** `stellar network use [OPTIONS] ` + +###### **Arguments:** + +* `` — Set the default network name + +###### **Options:** + +* `--global` — Use global config +* `--config-dir ` — Location of config directory, default is "." + + + ## `stellar network container` +⚠️ Deprecated: use `stellar container` instead + Commands to start, stop and get logs for a quickstart container **Usage:** `stellar network container ` @@ -1162,7 +1242,7 @@ Commands to start, stop and get logs for a quickstart container * `logs` — Get logs from a running network container * `start` — Start a container running a Stellar node, RPC, API, and friendbot (faucet) -* `stop` — Stop a network container started with `network container start` +* `stop` — Stop a network container started with `stellar container start` @@ -1186,7 +1266,7 @@ Get logs from a running network container Start a container running a Stellar node, RPC, API, and friendbot (faucet). -`stellar network container start NETWORK [OPTIONS]` +`stellar container start NETWORK [OPTIONS]` By default, when starting a testnet container, without any optional arguments, it will run the equivalent of the following docker command: @@ -1216,7 +1296,7 @@ By default, when starting a testnet container, without any optional arguments, i ## `stellar network container stop` -Stop a network container started with `network container start` +Stop a network container started with `stellar container start` **Usage:** `stellar network container stop [OPTIONS] ` @@ -1230,6 +1310,84 @@ Stop a network container started with `network container start` +## `stellar container` + +Start local networks in containers + +**Usage:** `stellar container ` + +###### **Subcommands:** + +* `logs` — Get logs from a running network container +* `start` — Start a container running a Stellar node, RPC, API, and friendbot (faucet) +* `stop` — Stop a network container started with `stellar container start` + + + +## `stellar container logs` + +Get logs from a running network container + +**Usage:** `stellar container logs [OPTIONS] ` + +###### **Arguments:** + +* `` — Container to get logs from + +###### **Options:** + +* `-d`, `--docker-host ` — Optional argument to override the default docker host. This is useful when you are using a non-standard docker host path for your Docker-compatible container runtime, e.g. Docker Desktop defaults to $HOME/.docker/run/docker.sock instead of /var/run/docker.sock + + + +## `stellar container start` + +Start a container running a Stellar node, RPC, API, and friendbot (faucet). + +`stellar container start NETWORK [OPTIONS]` + +By default, when starting a testnet container, without any optional arguments, it will run the equivalent of the following docker command: + +`docker run --rm -p 8000:8000 --name stellar stellar/quickstart:testing --testnet --enable rpc,horizon` + +**Usage:** `stellar container start [OPTIONS] ` + +###### **Arguments:** + +* `` — Network to start + + Possible values: `local`, `testnet`, `futurenet`, `pubnet` + + +###### **Options:** + +* `-d`, `--docker-host ` — Optional argument to override the default docker host. This is useful when you are using a non-standard docker host path for your Docker-compatible container runtime, e.g. Docker Desktop defaults to $HOME/.docker/run/docker.sock instead of /var/run/docker.sock +* `--name ` — Optional argument to specify the container name +* `-l`, `--limits ` — Optional argument to specify the limits for the local network only +* `-p`, `--ports-mapping ` — Argument to specify the `HOST_PORT:CONTAINER_PORT` mapping + + Default value: `8000:8000` +* `-t`, `--image-tag-override ` — Optional argument to override the default docker image tag for the given network +* `--protocol-version ` — Optional argument to specify the protocol version for the local network only + + + +## `stellar container stop` + +Stop a network container started with `stellar container start` + +**Usage:** `stellar container stop [OPTIONS] ` + +###### **Arguments:** + +* `` — Container to stop + +###### **Options:** + +* `-d`, `--docker-host ` — Optional argument to override the default docker host. This is useful when you are using a non-standard docker host path for your Docker-compatible container runtime, e.g. Docker Desktop defaults to $HOME/.docker/run/docker.sock instead of /var/run/docker.sock + + + ## `stellar snapshot` Download a snapshot of a ledger from an archive @@ -1248,18 +1406,20 @@ Create a ledger snapshot using a history archive. Filters (address, wasm-hash) specify what ledger entries to include. -Account addresses include the account, and trust lines. +Account addresses include the account, and trustlines. Contract addresses include the related wasm, contract data. If a contract is a Stellar asset contract, it includes the asset issuer's account and trust lines, but does not include all the trust lines of other accounts holding the asset. To include them specify the addresses of relevant accounts. +Any invalid contract id passed as `--address` will be ignored. + **Usage:** `stellar snapshot create [OPTIONS] --output ` ###### **Options:** * `--ledger ` — The ledger sequence number to snapshot. Defaults to latest history archived ledger -* `--address
` — Account or contract address to include in the snapshot +* `--address
` — Account or contract address/alias to include in the snapshot * `--wasm-hash ` — WASM hashes to include in the snapshot * `--output ` — Format of the out file @@ -1271,6 +1431,7 @@ If a contract is a Stellar asset contract, it includes the asset issuer's accoun * `--global` — Use global config * `--config-dir ` — Location of config directory, default is "." * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--archive-url ` — Archive URL @@ -1302,6 +1463,7 @@ Simulate a transaction envelope from stdin ###### **Options:** * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1320,6 +1482,7 @@ Calculate the hash of a transaction envelope from stdin ###### **Options:** * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config @@ -1337,6 +1500,7 @@ Sign a transaction envelope appending the signature to the envelope * `--hd-path ` — If using a seed phrase to sign, sets which hierarchical deterministic path to use, e.g. `m/44'/148'/{hd_path}`. Example: `--hd-path 1`. Default: `0` * `--sign-with-lab` — Sign with https://lab.stellar.org * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -1353,6 +1517,7 @@ Send a transaction envelope to the network ###### **Options:** * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--global` — Use global config @@ -1393,8 +1558,9 @@ Transfers the XLM balance of an account to another account and removes the sourc * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1419,8 +1585,9 @@ Bumps forward the sequence number of the source account to the given sequence nu * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1445,8 +1612,9 @@ Creates, updates, or deletes a trustline Learn more about trustlines https://dev * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1456,7 +1624,7 @@ Creates, updates, or deletes a trustline Learn more about trustlines https://dev * `--line ` * `--limit ` — Limit for the trust line, 0 to remove the trust line - Default value: `18446744073709551615` + Default value: `9223372036854775807` @@ -1474,8 +1642,9 @@ Creates and funds a new account with the specified starting balance * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1485,7 +1654,7 @@ Creates and funds a new account with the specified starting balance * `--destination ` — Account Id to create, e.g. `GBX...` * `--starting-balance ` — Initial balance in stroops of the account, default 1 XLM - Default value: `10_000_000` + Default value: `10000000` @@ -1503,8 +1672,9 @@ Sets, modifies, or deletes a data entry (name/value pair) that is attached to an * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1530,8 +1700,9 @@ Sends an amount in a specific asset to a destination account * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1560,8 +1731,9 @@ Set option for an account such as flags, inflation destination, signers, home do * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1577,9 +1749,9 @@ Set option for an account such as flags, inflation destination, signers, home do * `--signer ` — Add, update, or remove a signer from an account * `--signer-weight ` — Signer weight is a number from 0-255 (inclusive). The signer is deleted if the weight is 0 * `--set-required` — When enabled, an issuer must approve an account before that account can hold its asset. https://developers.stellar.org/docs/tokens/control-asset-access#authorization-required-0x1 -* `--set-revocable` — When enabled, an issuer can revoke an existing trustline’s authorization, thereby freezing the asset held by an account. https://developers.stellar.org/docs/tokens/control-asset-access#authorization-revocable-0x2 +* `--set-revocable` — When enabled, an issuer can revoke an existing trustline's authorization, thereby freezing the asset held by an account. https://developers.stellar.org/docs/tokens/control-asset-access#authorization-revocable-0x2 * `--set-clawback-enabled` — Enables the issuing account to take back (burning) all of the asset. https://developers.stellar.org/docs/tokens/control-asset-access#clawback-enabled-0x8 -* `--set-immutable` — With this setting, none of the other authorization flags (`AUTH_REQUIRED_FLAG`, `AUTH_REVOCABLE_FLAG`) can be set, and the issuing account can’t be merged. https://developers.stellar.org/docs/tokens/control-asset-access#authorization-immutable-0x4 +* `--set-immutable` — With this setting, none of the other authorization flags (`AUTH_REQUIRED_FLAG`, `AUTH_REVOCABLE_FLAG`) can be set, and the issuing account can't be merged. https://developers.stellar.org/docs/tokens/control-asset-access#authorization-immutable-0x4 * `--clear-required` * `--clear-revocable` * `--clear-immutable` @@ -1601,8 +1773,9 @@ Allows issuing account to configure authorization and trustline flags to an asse * `--cost` — Output the cost execution to stderr * `--instructions ` — Number of instructions to simulate * `--build-only` — Build the transaction and only write the base64 xdr to stdout -* `--sim-only` — Simulate the transaction and only write the base64 xdr to stdout +* `--sim-only` — (Deprecated) simulate the transaction and only write the base64 xdr to stdout * `--rpc-url ` — RPC server endpoint +* `--rpc-header ` — RPC Header(s) to include in requests to the RPC provider * `--network-passphrase ` — Network passphrase to sign the transaction sent to the rpc server * `--network ` — Name of network to use from config * `--source-account ` — Account that where transaction originates from. Alias `source`. Can be an identity (--source alice), a public key (--source GDKW...), a muxed account (--source MDA…), a secret key (--source SC36…), or a seed phrase (--source "kite urban…"). If `--build-only` or `--sim-only` flags were NOT provided, this key will also be used to sign the final transaction. In that case, trying to sign with public key will fail @@ -1632,6 +1805,7 @@ Decode and encode XDR * `guess` — Guess the XDR type * `decode` — Decode XDR * `encode` — Encode XDR +* `compare` — Compare two XDR values with each other * `version` — Print version information ###### **Arguments:** @@ -1770,6 +1944,31 @@ Encode XDR Default value: `single-base64` + Possible values: `single`, `single-base64`, `stream` + + + + +## `stellar xdr compare` + +Compare two XDR values with each other + +Outputs: `-1` when the left XDR value is less than the right XDR value, `0` when the left XDR value is equal to the right XDR value, `1` when the left XDR value is greater than the right XDR value + +**Usage:** `stellar xdr compare [OPTIONS] --type ` + +###### **Arguments:** + +* `` — XDR file to decode and compare with the right value +* `` — XDR file to decode and compare with the left value + +###### **Options:** + +* `--type ` — XDR type of both inputs +* `--input ` + + Default value: `single-base64` + Possible values: `single`, `single-base64` diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..a084d8624 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Stellar Development Foundation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile index 6d55aacc4..92cdacd73 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ endif # By default make `?=` operator will treat empty assignment as a set value and will not use the default value. # Both cases should fallback to default of getting the version from git tag. ifeq ($(strip $(REPOSITORY_VERSION)),) - override REPOSITORY_VERSION = "$(shell git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//')" + override REPOSITORY_VERSION = "$(shell ( git describe --tags --always --abbrev=0 --match='v[0-9]*.[0-9]*.[0-9]*' 2> /dev/null | sed 's/^.//' ) )" endif REPOSITORY_BRANCH := "$(shell git rev-parse --abbrev-ref HEAD)" BUILD_TIMESTAMP ?= $(shell date '+%Y-%m-%dT%H:%M:%S') @@ -31,6 +31,7 @@ install_rust: install install: cargo install --force --locked --path ./cmd/stellar-cli --debug cargo install --force --locked --path ./cmd/crates/soroban-test/tests/fixtures/hello --root ./target --debug --quiet + cargo install --force --locked --path ./cmd/crates/soroban-test/tests/fixtures/bye --root ./target --debug --quiet # regenerate the example lib in `cmd/crates/soroban-spec-typsecript/fixtures/ts` build-snapshot: typescript-bindings-fixtures @@ -46,13 +47,8 @@ build-test: build-test-wasms install generate-full-help-doc: cargo run --bin doc-gen --features clap-markdown -generate-examples-list: - curl -sSL https://api.github.com/repos/stellar/soroban-examples/git/trees/main \ - | jq -r '.tree[] | select(.type != "blob" and .path != "hello_world" and (.path | startswith(".") | not)) | .path' \ - > cmd/soroban-cli/example_contracts.list - test: build-test - cargo test + cargo test --workspace e2e-test: cargo test --features it --test it -- integration diff --git a/README.md b/README.md index c39d883c2..f8ba1f052 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,13 @@ This repo is home to the Stellar CLI, the command-line multi-tool for running an - [Documentation](#documentation) - [Cookbook](#cookbook) -- [Installation](#installation) -- [Installation with Experimental Features](#installation-with-experimental-features) +- [Install](#install) - [Autocomplete](#autocomplete) - [Latest Release](#latest-release) - [Upcoming Features](#upcoming-features) - [To Contribute](#to-contribute) - [Additional Developer Resources](#additional-developer-resources) - - ## Documentation For installation options see below, for usage instructions [see the full help docs](FULL_HELP_DOCS.md). @@ -27,28 +24,24 @@ For installation options see below, for usage instructions [see the full help do ## Cookbook To understand how to get the most of the Stellar CLI, see the [Stellar CLI Cookbook](https://github.com/stellar/stellar-cli/tree/main/cookbook) for recipes and a collection of resources to teach you how to use the CLI. Examples of recipes included in the CLI cookbook include: send payments, manage contract lifecycle, extend contract instance/storage/wasm, and more. -## Installation -Install the latest version from source: -``` -cargo install --locked stellar-cli --features opt -``` +## Install + +Install with Homebrew (macOS, Linux): -Install with `cargo-binstall`: ``` -cargo install --locked cargo-binstall -cargo binstall -y stellar-cli +brew install stellar-cli ``` -Install with Homebrew: - +Install the latest version from source: ``` -brew install stellar-cli +cargo install --locked stellar-cli --features opt ``` -## Installation with Experimental Features -To use the potentially unreleased bleeding edge CLI functionalities, install from git: +Install or run the unreleased main branch with nix: ``` -cargo install --locked stellar-cli --features opt --git https://github.com/stellar/stellar-cli.git +$ nix run 'github:stellar/stellar-cli' -- --help +or install +$ nix profile install github:stellar/stellar-cli ``` ## Autocomplete @@ -60,12 +53,12 @@ stellar completion --shell Possible SHELL values are `bash`, `elvish`, `fish`, `powershell`, `zsh`, etc. To enable autocomplete in the current bash shell, run: -``` +```bash source <(stellar completion --shell bash) ``` To enable autocomplete permanently, run: -``` +```bash echo "source <(stellar completion --shell bash)" >> ~/.bashrc ``` diff --git a/action.yml b/action.yml new file mode 100644 index 000000000..9b1426d94 --- /dev/null +++ b/action.yml @@ -0,0 +1,64 @@ +name: 'Install stellar-cli' +description: 'Install the stellar-cli' +inputs: + version: + description: | + Recommended for use only in testing new versions of the action prior to + release. For regular use, use the version of the action corresponding to + the version of the stellar-cli that should be installed. + required: false +runs: + using: "composite" + steps: + - name: Setup install path + shell: bash + run: | + mkdir -p $HOME/.local/bin + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Determine version to install + id: version + shell: bash + run: | + echo "version=${{ inputs.version || github.action_ref }}" >> "$GITHUB_OUTPUT" + - name: Copy binary to install location + shell: bash + run: | + version="${{ steps.version.outputs.version }}" + case "${{ runner.os }}-${{ runner.arch }}" in + 'Linux-X64') + os_arch=x86_64-unknown-linux-gnu + ;; + 'Linux-ARM64') + os_arch=aarch64-unknown-linux-gnu + ;; + 'macOS-X64') + os_arch=x86_64-apple-darwin + ;; + 'macOS-ARM64') + os_arch=aarch64-apple-darwin + ;; + 'Windows-X64') + os_arch=x86_64-pc-windows-msvc + ;; + *) + echo "Unsupported OS / Arch pair: ${{ runner.os }} ${{ runner.arch }}" >&2 + exit 1 + esac + file="stellar-cli-$version-$os_arch.tar.gz" + url="https://github.com/stellar/stellar-cli/releases/download/v$version/$file" + echo "$url" + curl -fL "$url" | tar xvz -C $HOME/.local/bin + - name: Verify binary against attestation + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + version="${{ steps.version.outputs.version }}" + subject="$(gh attestation verify ~/.local/bin/stellar --repo stellar/stellar-cli --format json -q '.[].verificationResult.signature.certificate.subjectAlternativeName')" + echo "Found subject: $subject" >&2 + expected_subject="https://github.com/stellar/stellar-cli/.github/workflows/binaries.yml@refs/tags/v$version" + echo "Expected subject: $expected_subject" >&2 + if [[ "$subject" != "$expected_subject" ]]; then + echo "Attestation verification found unexpected subject" >&2 + exit 1 + fi diff --git a/cmd/crates/soroban-spec-tools/Cargo.toml b/cmd/crates/soroban-spec-tools/Cargo.toml index 514b82924..a17a5d31f 100644 --- a/cmd/crates/soroban-spec-tools/Cargo.toml +++ b/cmd/crates/soroban-spec-tools/Cargo.toml @@ -19,9 +19,7 @@ crate-type = ["rlib"] [dependencies] soroban-spec = { workspace = true } stellar-strkey = { workspace = true } -stellar-xdr = { workspace = true, features = ["curr", "std", "serde"] } -soroban-env-host = { workspace = true } - +stellar-xdr = { workspace = true, features = ["curr", "std", "serde", "base64"] } serde_json = { workspace = true } itertools = { workspace = true } ethnum = { workspace = true } @@ -29,9 +27,6 @@ hex = { workspace = true } wasmparser = { workspace = true } base64 = { workspace = true } thiserror = "1.0.31" -# soroban-ledger-snapshot = { workspace = true } -# soroban-sdk = { workspace = true } -# sep5 = { workspace = true } [dev-dependencies] diff --git a/cmd/crates/soroban-spec-tools/src/contract.rs b/cmd/crates/soroban-spec-tools/src/contract.rs index 334b2425f..501609111 100644 --- a/cmd/crates/soroban-spec-tools/src/contract.rs +++ b/cmd/crates/soroban-spec-tools/src/contract.rs @@ -4,10 +4,10 @@ use std::{ io::{self, Cursor}, }; -use soroban_env_host::xdr::{ - self, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, - ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, - StringM, WriteXdr, +use stellar_xdr::curr::{ + self as xdr, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion, + ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, + ScSpecUdtStructV0, ScSpecUdtUnionV0, StringM, WriteXdr, }; pub struct Spec { @@ -40,9 +40,9 @@ pub enum Error { impl Spec { pub fn new(bytes: &[u8]) -> Result { - let mut env_meta: Option<&[u8]> = None; - let mut meta: Option<&[u8]> = None; - let mut spec: Option<&[u8]> = None; + let mut env_meta: Option> = None; + let mut meta: Option> = None; + let mut spec: Option> = None; for payload in wasmparser::Parser::new(0).parse_all(bytes) { let payload = payload?; if let wasmparser::Payload::CustomSection(section) = payload { @@ -52,13 +52,19 @@ impl Spec { "contractspecv0" => &mut spec, _ => continue, }; - *out = Some(section.data()); + + if let Some(existing_data) = out { + let combined_data = [existing_data, section.data()].concat(); + *out = Some(combined_data); + } else { + *out = Some(section.data().to_vec()); + } }; } let mut env_meta_base64 = None; let env_meta = if let Some(env_meta) = env_meta { - env_meta_base64 = Some(base64.encode(env_meta)); + env_meta_base64 = Some(base64.encode(&env_meta)); let cursor = Cursor::new(env_meta); let mut read = Limited::new(cursor, Limits::none()); ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? @@ -68,7 +74,7 @@ impl Spec { let mut meta_base64 = None; let meta = if let Some(meta) = meta { - meta_base64 = Some(base64.encode(meta)); + meta_base64 = Some(base64.encode(&meta)); let cursor = Cursor::new(meta); let mut depth_limit_read = Limited::new(cursor, Limits::none()); ScMetaEntry::read_xdr_iter(&mut depth_limit_read) @@ -78,7 +84,7 @@ impl Spec { }; let (spec_base64, spec) = if let Some(spec) = spec { - let (spec_base64, spec) = Spec::spec_to_base64(spec)?; + let (spec_base64, spec) = Spec::spec_to_base64(&spec)?; (Some(spec_base64), spec) } else { (None, vec![]) @@ -121,10 +127,16 @@ impl Display for Spec { writeln!(f, "Env Meta: {env_meta}")?; for env_meta_entry in &self.env_meta { match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - let protocol = v >> 32; - let interface = v & 0xffff_ffff; - writeln!(f, " • Interface Version: {v} (protocol: {protocol}, interface: {interface})")?; + ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion( + ScEnvMetaEntryInterfaceVersion { + protocol, + pre_release, + }, + ) => { + writeln!(f, " • Protocol Version: {protocol}")?; + if pre_release != &0 { + writeln!(f, " • Pre-release Version: {pre_release})")?; + } } } } diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json index f45484ffb..a4e3759e2 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package-lock.json @@ -8,22 +8,26 @@ "name": "test_custom_types", "version": "0.0.0", "dependencies": { - "@stellar/stellar-sdk": "12.1.0", + "@stellar/stellar-sdk": "13.0.0", "buffer": "6.0.3" }, "devDependencies": { - "typescript": "5.3.3" + "typescript": "^5.6.2" } }, "node_modules/@stellar/js-xdr": { - "version": "3.1.1", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", + "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { - "version": "12.0.1", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.0.1.tgz", + "integrity": "sha512-Xbd12mc9Oj/130Tv0URmm3wXG77XMshZtZ2yNCjqX5ZbMD5IYpbBs3DVCteLU/4SLj/Fnmhh1dzhrQXnk4r+pQ==", "license": "Apache-2.0", "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", @@ -31,17 +35,20 @@ "tweetnacl": "^1.0.3" }, "optionalDependencies": { - "sodium-native": "^4.1.1" + "sodium-native": "^4.3.0" } }, "node_modules/@stellar/stellar-sdk": { - "version": "12.1.0", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-13.0.0.tgz", + "integrity": "sha512-+wvmKi+XWwu27nLYTM17EgBdpbKohEkOfCIK4XKfsI4WpMXAqvnqSm98i9h5dAblNB+w8BJqzGs1JY0PtTGm4A==", "license": "Apache-2.0", "dependencies": { - "@stellar/stellar-base": "^12.0.1", - "axios": "^1.7.2", + "@stellar/stellar-base": "^13.0.1", + "axios": "^1.7.7", "bignumber.js": "^9.1.2", "eventsource": "^2.0.2", + "feaxios": "^0.0.20", "randombytes": "^2.1.0", "toml": "^3.0.0", "urijs": "^1.19.1" @@ -49,12 +56,15 @@ }, "node_modules/asynckit": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -63,6 +73,8 @@ }, "node_modules/base32.js": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", + "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -88,6 +100,8 @@ }, "node_modules/bignumber.js": { "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "license": "MIT", "engines": { "node": "*" @@ -117,6 +131,8 @@ }, "node_modules/combined-stream": { "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -127,6 +143,8 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -139,8 +157,19 @@ "node": ">=12.0.0" } }, + "node_modules/feaxios": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/feaxios/-/feaxios-0.0.20.tgz", + "integrity": "sha512-g3hm2YDNffNxA3Re3Hd8ahbpmDee9Fv1Pb1C/NoWsjY7mtD8nyNeJytUzn+DK0Hyl9o6HppeWOrtnqgmhOYfWA==", + "license": "MIT", + "dependencies": { + "is-retry-allowed": "^3.0.0" + } + }, "node_modules/follow-redirects": { - "version": "1.15.6", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", @@ -158,7 +187,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -189,10 +220,26 @@ }, "node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/is-retry-allowed": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-3.0.0.tgz", + "integrity": "sha512-9xH0xvoggby+u0uGF7cZXdrutWiBiaFG8ZT4YFPXL8NzkyAwX3AKGLeFQLvzDpM430+nDFBZ1LHkie/8ocL06A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -200,6 +247,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -209,7 +258,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.1", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz", + "integrity": "sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==", "license": "MIT", "optional": true, "bin": { @@ -220,6 +271,8 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, "node_modules/randombytes": { @@ -249,6 +302,8 @@ }, "node_modules/sha.js": { "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", @@ -259,8 +314,9 @@ } }, "node_modules/sodium-native": { - "version": "4.1.1", - "hasInstallScript": true, + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.1.tgz", + "integrity": "sha512-YdP64gAdpIKHfL4ttuX4aIfjeunh9f+hNeQJpE9C8UMndB3zkgZ7YmmGT4J2+v6Ibyp6Wem8D1TcSrtdW0bqtg==", "license": "MIT", "optional": true, "dependencies": { @@ -273,13 +329,16 @@ }, "node_modules/tweetnacl": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "license": "Unlicense" }, "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json index 5cc5e337f..8c3762b91 100644 --- a/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json +++ b/cmd/crates/soroban-spec-typescript/fixtures/test_custom_types/package.json @@ -8,10 +8,10 @@ "build": "tsc" }, "dependencies": { - "buffer": "6.0.3", - "@stellar/stellar-sdk": "12.1.0" + "@stellar/stellar-sdk": "13.0.0", + "buffer": "6.0.3" }, "devDependencies": { - "typescript": "5.3.3" + "typescript": "^5.6.2" } } diff --git a/cmd/crates/soroban-spec-typescript/src/project_template/package.json b/cmd/crates/soroban-spec-typescript/src/project_template/package.json index e9ba581a3..b5c6459e4 100644 --- a/cmd/crates/soroban-spec-typescript/src/project_template/package.json +++ b/cmd/crates/soroban-spec-typescript/src/project_template/package.json @@ -8,10 +8,10 @@ "build": "tsc" }, "dependencies": { - "buffer": "6.0.3", - "@stellar/stellar-sdk": "12.1.0" + "@stellar/stellar-sdk": "13.0.0", + "buffer": "6.0.3" }, "devDependencies": { - "typescript": "5.3.3" + "typescript": "^5.6.2" } } diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh b/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh index d85eff115..031061cca 100755 --- a/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh +++ b/cmd/crates/soroban-spec-typescript/ts-tests/initialize.sh @@ -37,6 +37,7 @@ function deploy_all() { } function bind() { exe eval "./soroban contract bindings typescript --contract-id $(cat $1) --output-dir ./node_modules/$2 --overwrite" + exe eval "sh -c \"cd ./node_modules/$2 && npm install && npm run build\"" } function bind_all() { bind contract-id-custom-types.txt test-custom-types 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 deee8f09d..45cc97a67 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json +++ b/cmd/crates/soroban-spec-typescript/ts-tests/package-lock.json @@ -7,14 +7,14 @@ "hasInstallScript": true, "devDependencies": { "@ava/typescript": "^4.1.0", - "@stellar/stellar-sdk": "12.2.0", + "@stellar/stellar-sdk": "13.0.0", "@types/node": "^20.4.9", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "ava": "^5.3.1", "dotenv": "^16.3.1", "eslint": "^8.53.0", - "typescript": "^5.3.3" + "typescript": "^5.6.2" } }, "node_modules/@ava/typescript": { @@ -202,13 +202,15 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/@stellar/stellar-base": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-12.1.0.tgz", - "integrity": "sha512-pWwn+XWP5NotmIteZNuJzHeNn9DYSqH3lsYbtFUoSYy1QegzZdi9D8dK6fJ2fpBAnf/rcDjHgHOw3gtHaQFVbg==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-13.0.1.tgz", + "integrity": "sha512-Xbd12mc9Oj/130Tv0URmm3wXG77XMshZtZ2yNCjqX5ZbMD5IYpbBs3DVCteLU/4SLj/Fnmhh1dzhrQXnk4r+pQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@stellar/js-xdr": "^3.1.2", "base32.js": "^0.1.0", @@ -218,19 +220,21 @@ "tweetnacl": "^1.0.3" }, "optionalDependencies": { - "sodium-native": "^4.1.1" + "sodium-native": "^4.3.0" } }, "node_modules/@stellar/stellar-sdk": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-12.2.0.tgz", - "integrity": "sha512-Wy5sDOqb5JvAC76f4sQIV6Pe3JNyZb0PuyVNjwt3/uWsjtxRkFk6s2yTHTefBLWoR+mKxDjO7QfzhycF1v8FXQ==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-13.0.0.tgz", + "integrity": "sha512-+wvmKi+XWwu27nLYTM17EgBdpbKohEkOfCIK4XKfsI4WpMXAqvnqSm98i9h5dAblNB+w8BJqzGs1JY0PtTGm4A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@stellar/stellar-base": "^12.1.0", - "axios": "^1.7.2", + "@stellar/stellar-base": "^13.0.1", + "axios": "^1.7.7", "bignumber.js": "^9.1.2", "eventsource": "^2.0.2", + "feaxios": "^0.0.20", "randombytes": "^2.1.0", "toml": "^3.0.0", "urijs": "^1.19.1" @@ -601,7 +605,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ava": { "version": "5.3.1", @@ -700,10 +705,11 @@ } }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -721,6 +727,7 @@ "resolved": "https://registry.npmjs.org/base32.js/-/base32.js-0.1.0.tgz", "integrity": "sha512-n3TkB02ixgBOhTvANakDb4xaMXnYUVkNoRFJjQflcqMQhyEKxEHdj3E6N8t8sUQ0mjH/3/JxzlXuz3ul/J90pQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -750,6 +757,7 @@ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -1043,6 +1051,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1162,6 +1171,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -1581,6 +1591,16 @@ "reusify": "^1.0.4" } }, + "node_modules/feaxios": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/feaxios/-/feaxios-0.0.20.tgz", + "integrity": "sha512-g3hm2YDNffNxA3Re3Hd8ahbpmDee9Fv1Pb1C/NoWsjY7mtD8nyNeJytUzn+DK0Hyl9o6HppeWOrtnqgmhOYfWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-retry-allowed": "^3.0.0" + } + }, "node_modules/figures": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", @@ -1658,9 +1678,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -1668,6 +1688,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -1682,6 +1703,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2029,6 +2051,19 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true }, + "node_modules/is-retry-allowed": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-3.0.0.tgz", + "integrity": "sha512-9xH0xvoggby+u0uGF7cZXdrutWiBiaFG8ZT4YFPXL8NzkyAwX3AKGLeFQLvzDpM430+nDFBZ1LHkie/8ocL06A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", @@ -2247,6 +2282,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2256,6 +2292,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -2303,10 +2340,11 @@ "dev": true }, "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz", + "integrity": "sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw==", "dev": true, + "license": "MIT", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -2703,7 +2741,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", @@ -2906,6 +2945,7 @@ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -2967,11 +3007,11 @@ } }, "node_modules/sodium-native": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.1.1.tgz", - "integrity": "sha512-LXkAfRd4FHtkQS4X6g+nRcVaN7mWVNepV06phIsC6+IZFvGh1voW5TNQiQp2twVaMf05gZqQjuS+uWLM6gHhNQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.1.tgz", + "integrity": "sha512-YdP64gAdpIKHfL4ttuX4aIfjeunh9f+hNeQJpE9C8UMndB3zkgZ7YmmGT4J2+v6Ibyp6Wem8D1TcSrtdW0bqtg==", "dev": true, - "hasInstallScript": true, + "license": "MIT", "optional": true, "dependencies": { "node-gyp-build": "^4.8.0" @@ -3167,7 +3207,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/type-check": { "version": "0.4.0", @@ -3194,10 +3235,11 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/cmd/crates/soroban-spec-typescript/ts-tests/package.json b/cmd/crates/soroban-spec-typescript/ts-tests/package.json index 734b26bba..a8bb3c1ff 100644 --- a/cmd/crates/soroban-spec-typescript/ts-tests/package.json +++ b/cmd/crates/soroban-spec-typescript/ts-tests/package.json @@ -8,14 +8,14 @@ }, "devDependencies": { "@ava/typescript": "^4.1.0", + "@stellar/stellar-sdk": "13.0.0", "@types/node": "^20.4.9", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "ava": "^5.3.1", "dotenv": "^16.3.1", "eslint": "^8.53.0", - "@stellar/stellar-sdk": "12.2.0", - "typescript": "^5.3.3" + "typescript": "^5.6.2" }, "ava": { "typescript": { @@ -29,4 +29,3 @@ ] } } - diff --git a/cmd/crates/soroban-test/Cargo.toml b/cmd/crates/soroban-test/Cargo.toml index 89c8d16d8..a0cfd705e 100644 --- a/cmd/crates/soroban-test/Cargo.toml +++ b/cmd/crates/soroban-test/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/soroban-test" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.5.0" +version.workspace = true edition = "2021" rust-version.workspace = true autobins = false @@ -17,12 +17,10 @@ crate-type = ["rlib", "cdylib"] [dependencies] -soroban-env-host = { workspace = true } soroban-spec = { workspace = true } soroban-spec-tools = { workspace = true } soroban-ledger-snapshot = { workspace = true } stellar-strkey = { workspace = true } -soroban-sdk = { workspace = true } sep5 = { workspace = true } soroban-cli = { workspace = true } soroban-rpc = { workspace = true } @@ -32,9 +30,9 @@ thiserror = "1.0.31" sha2 = "0.10.6" assert_cmd = "2.0.4" assert_fs = "1.0.7" -predicates = "2.1.5" +predicates = { workspace = true } fs_extra = "1.3.0" -toml = "0.8.10" +toml = { workspace = true } [dev-dependencies] diff --git a/cmd/crates/soroban-test/src/lib.rs b/cmd/crates/soroban-test/src/lib.rs index 849b78796..2c62578ef 100644 --- a/cmd/crates/soroban-test/src/lib.rs +++ b/cmd/crates/soroban-test/src/lib.rs @@ -235,6 +235,7 @@ impl TestEnv { config::Args { network: network::Args { rpc_url: Some(self.rpc_url.clone()), + rpc_headers: [].to_vec(), network_passphrase: Some(LOCAL_NETWORK_PASSPHRASE.to_string()), network: None, }, diff --git a/cmd/crates/soroban-test/src/wasm.rs b/cmd/crates/soroban-test/src/wasm.rs index d03114b93..3e473ed46 100644 --- a/cmd/crates/soroban-test/src/wasm.rs +++ b/cmd/crates/soroban-test/src/wasm.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, fs, path::PathBuf}; use sha2::{Digest, Sha256}; -use soroban_env_host::xdr; +use soroban_cli::xdr; #[derive(thiserror::Error, Debug)] pub enum Error { diff --git a/cmd/crates/soroban-test/tests/fixtures/bye/Cargo.lock b/cmd/crates/soroban-test/tests/fixtures/bye/Cargo.lock new file mode 100644 index 000000000..59924f294 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/bye/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "stellar-bye" +version = "0.1.0" diff --git a/cmd/crates/soroban-test/tests/fixtures/bye/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/bye/Cargo.toml new file mode 100644 index 000000000..deccb9c75 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/bye/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "stellar-bye" +version = "0.0.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/cmd/crates/soroban-test/tests/fixtures/bye/src/main.rs b/cmd/crates/soroban-test/tests/fixtures/bye/src/main.rs new file mode 100644 index 000000000..5fb678863 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/bye/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Bye, world!"); +} diff --git a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml index 0c3b1512f..fe7eb636a 100644 --- a/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/hello/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "soroban-hello" -version = "21.5.0" +version = "0.0.0" edition = "2021" publish = false diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/constructor/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/constructor/Cargo.toml new file mode 100644 index 000000000..69d8a6430 --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/constructor/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "test_constructor" +version = "0.0.0" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"]} diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/constructor/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/constructor/src/lib.rs new file mode 100644 index 000000000..8b867968f --- /dev/null +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/constructor/src/lib.rs @@ -0,0 +1,18 @@ +#![no_std] +use soroban_sdk::{contract, contractimpl, symbol_short, Env, Symbol}; + +#[contract] +pub struct Contract; +const COUNTER: Symbol = symbol_short!("COUNTER"); + +#[contractimpl] +impl Contract { + /// Example constructor + pub fn __constructor(env: Env, counter: u32) { + env.storage().persistent().set(&COUNTER, &counter); + } + /// Counter value + pub fn counter(env: Env) -> u32 { + env.storage().persistent().get(&COUNTER).unwrap() + } +} diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml index d4405c512..765f671c6 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "test_custom_account" -version = "21.5.0" +version = "0.0.0" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" publish = false -rust-version.workspace = true [lib] crate-type = ["cdylib"] diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/src/lib.rs index c71324110..ed924322b 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_account/src/lib.rs @@ -115,7 +115,9 @@ impl CustomAccountInterface for Contract { return Err(Error::NotPermitted); } } - Context::CreateContractHostFn(_) => return Err(Error::InvalidContext), + Context::CreateContractWithCtorHostFn(_) | Context::CreateContractHostFn(_) => { + return Err(Error::InvalidContext) + } }; } diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml index 17e618805..b5a17c6a5 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/custom_type/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "test_custom_types" -version = "21.5.0" +version = "0.0.0" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" publish = false -rust-version.workspace = true [lib] crate-type = ["cdylib", "rlib"] diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml index 9ad83fd1c..a13c98a39 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "test_hello_world" -version = "21.5.0" +version = "0.0.0" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" publish = false -rust-version.workspace = true [lib] crate-type = ["cdylib", "rlib"] diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs index 40006a1b7..b4b53c237 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/hello_world/src/lib.rs @@ -77,7 +77,7 @@ mod test { #[test] fn test_hello() { let env = Env::default(); - let contract_id = env.register_contract(None, Contract); + let contract_id = env.register(Contract, ()); let client = ContractClient::new(&env, &contract_id); let world = symbol_short!("world"); let res = client.hello(&world); diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/Cargo.toml index f74201a31..495a52d71 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "test_swap" -version.workspace = true +version = "0.0.0" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" publish = false -rust-version.workspace = true [lib] crate-type = ["cdylib"] diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs index e32b45e0a..a60615162 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/swap/src/test.rs @@ -11,7 +11,9 @@ use token::Client as TokenClient; use token::StellarAssetClient as TokenAdminClient; fn create_token_contract<'a>(e: &Env, admin: &Address) -> (TokenClient<'a>, TokenAdminClient<'a>) { - let contract_address = e.register_stellar_asset_contract(admin.clone()); + let contract_address = e + .register_stellar_asset_contract_v2(admin.clone()) + .address(); ( TokenClient::new(e, &contract_address), TokenAdminClient::new(e, &contract_address), @@ -19,7 +21,7 @@ fn create_token_contract<'a>(e: &Env, admin: &Address) -> (TokenClient<'a>, Toke } fn create_atomic_swap_contract(e: &Env) -> AtomicSwapContractClient { - AtomicSwapContractClient::new(e, &e.register_contract(None, AtomicSwapContract {})) + AtomicSwapContractClient::new(e, &e.register(AtomicSwapContract {}, ())) } #[test] diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/Cargo.toml index 880c39d52..6456983e8 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "test_token" -version.workspace = true +version = "0.0.0" description = "Soroban standard token contract" authors = ["Stellar Development Foundation "] license = "Apache-2.0" edition = "2021" publish = false -rust-version.workspace = true [lib] crate-type = ["cdylib"] diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/test.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/test.rs index 08be5f9ef..cd892a646 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/test.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/token/src/test.rs @@ -9,7 +9,7 @@ use soroban_sdk::{ }; fn create_token<'a>(e: &Env, admin: &Address) -> TokenClient<'a> { - let token = TokenClient::new(e, &e.register_contract(None, Token {})); + let token = TokenClient::new(e, &e.register(Token {}, ())); token.initialize(admin, &7, &"name".into_val(e), &"symbol".into_val(e)); token } @@ -247,7 +247,7 @@ fn initialize_already_initialized() { fn decimal_is_over_max() { let e = Env::default(); let admin = Address::generate(&e); - let token = TokenClient::new(&e, &e.register_contract(None, Token {})); + let token = TokenClient::new(&e, &e.register(Token {}, ())); token.initialize( &admin, &(u32::from(u8::MAX) + 1), diff --git a/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs index 695d8a7a3..efb3cf480 100644 --- a/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/test-wasms/udt/src/lib.rs @@ -80,7 +80,7 @@ mod test { #[test] fn test_add() { let e = Env::default(); - let contract_id = e.register_contract(None, Contract); + let contract_id = e.register(Contract, ()); let client = ContractClient::new(&e, &contract_id); let udt = UdtStruct { diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/add/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/add/Cargo.toml index 660b7eb11..ca38cca49 100644 --- a/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/add/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/add/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = { version = "=21.7.2" } [dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } +soroban-sdk = { version = "=21.7.2", features = ["testutils"] } diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/call/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/call/Cargo.toml index 48e32b14d..a1ba4ed43 100644 --- a/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/call/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/workspace-with-default-members/contracts/call/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = { version = "=21.7.2" } [dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } +soroban-sdk = { version = "=21.7.2", features = ["testutils"] } diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/Cargo.toml index 660b7eb11..ca38cca49 100644 --- a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = { version = "=21.7.2" } [dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } +soroban-sdk = { version = "=21.7.2", features = ["testutils"] } diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/add2/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/add2/Cargo.toml index bae834e02..b6b570327 100644 --- a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/add2/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/add2/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = { version = "=21.7.2" } [dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } +soroban-sdk = { version = "=21.7.2", features = ["testutils"] } diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs index 1552f5855..d04162b5a 100644 --- a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs +++ b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs @@ -1,9 +1,11 @@ #![no_std] -use soroban_sdk::{contract, contractimpl}; +use soroban_sdk::{contract, contractimpl, contractmeta}; #[contract] pub struct Contract; +contractmeta!(key = "Description", val = "A test add contract"); + #[contractimpl] impl Contract { pub fn add(x: u64, y: u64) -> u128 { diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/call/Cargo.toml b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/call/Cargo.toml index 48e32b14d..a1ba4ed43 100644 --- a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/call/Cargo.toml +++ b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/call/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = { version = "=21.7.2" } [dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } +soroban-sdk = { version = "=21.7.2", features = ["testutils"] } diff --git a/cmd/crates/soroban-test/tests/it/arg_parsing.rs b/cmd/crates/soroban-test/tests/it/arg_parsing.rs index c245fd8c9..d25d22354 100644 --- a/cmd/crates/soroban-test/tests/it/arg_parsing.rs +++ b/cmd/crates/soroban-test/tests/it/arg_parsing.rs @@ -1,6 +1,6 @@ use crate::util::CUSTOM_TYPES; use serde_json::json; -use soroban_env_host::xdr::{ +use soroban_cli::xdr::{ ScBytes, ScSpecTypeBytesN, ScSpecTypeDef, ScSpecTypeOption, ScSpecTypeUdt, ScVal, }; use soroban_spec_tools::{from_string_primitive, Spec}; diff --git a/cmd/crates/soroban-test/tests/it/build.rs b/cmd/crates/soroban-test/tests/it/build.rs index aba6aadf5..925d88df1 100644 --- a/cmd/crates/soroban-test/tests/it/build.rs +++ b/cmd/crates/soroban-test/tests/it/build.rs @@ -116,3 +116,37 @@ cargo rustc --manifest-path=contracts/add/Cargo.toml --crate-type=cdylib --targe ", )); } + +#[test] +fn build_with_metadata() { + let sandbox = TestEnv::default(); + let cargo_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let fixture_path = cargo_dir.join("tests/fixtures/workspace/contracts/add"); + let outdir = sandbox.dir().join("out"); + + sandbox + .new_assert_cmd("contract") + .current_dir(&fixture_path) + .arg("build") + .arg("--meta") + .arg("contract meta=added on build") + .arg("--out-dir") + .arg(&outdir) + .assert() + .success(); + + // verify that the metadata added in the contract code via contractmetadata! macro is present + // as well as the meta that is included on build + let wasm_path = fixture_path.join(&outdir).join("add.wasm"); + sandbox + .new_assert_cmd("contract") + .current_dir(&fixture_path) + .arg("info") + .arg("meta") + .arg("--wasm") + .arg(wasm_path) + .assert() + .success() + .stdout(predicate::str::contains("Description: A test add contract")) + .stdout(predicate::str::contains("contract meta: added on build")); +} diff --git a/cmd/crates/soroban-test/tests/it/config.rs b/cmd/crates/soroban-test/tests/it/config.rs index 70dfaa693..b796910a8 100644 --- a/cmd/crates/soroban-test/tests/it/config.rs +++ b/cmd/crates/soroban-test/tests/it/config.rs @@ -1,4 +1,5 @@ use assert_fs::TempDir; +use predicates::prelude::predicate; use soroban_test::{AssertExt, TestEnv}; use std::{fs, path::Path}; @@ -288,3 +289,107 @@ fn use_env() { .success() .stdout("SDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCQYFD\n"); } + +#[test] +fn config_dirs_precedence() { + let sandbox = TestEnv::default(); + + sandbox + .new_assert_cmd("keys") + .env( + "SOROBAN_SECRET_KEY", + "SC4ZPYELVR7S7EE7KZDZN3ETFTNQHHLTUL34NUAAWZG5OK2RGJ4V2U3Z", + ) + .arg("add") + .arg("alice") + .assert() + .success(); + + fs::rename( + sandbox.dir().join(".stellar"), + sandbox.dir().join("_soroban"), + ) + .unwrap(); + + sandbox + .new_assert_cmd("keys") + .env( + "SOROBAN_SECRET_KEY", + "SAQMV6P3OWM2SKCK3OEWNXSRYWK5RNNUL5CPHQGIJF2WVT4EI2BZ63GG", + ) + .arg("add") + .arg("alice") + .assert() + .success(); + + fs::rename( + sandbox.dir().join("_soroban"), + sandbox.dir().join(".soroban"), + ) + .unwrap(); + + sandbox + .new_assert_cmd("keys") + .arg("show") + .arg("alice") + .arg("--verbose") + .assert() + .success() + .stderr(predicate::str::contains( + "WARN soroban_cli::utils: the .stellar and .soroban config directories exist at path", + )) + .stdout("SAQMV6P3OWM2SKCK3OEWNXSRYWK5RNNUL5CPHQGIJF2WVT4EI2BZ63GG\n"); +} + +#[test] +fn set_default_identity() { + let sandbox = TestEnv::default(); + + sandbox + .new_assert_cmd("keys") + .env( + "SOROBAN_SECRET_KEY", + "SC4ZPYELVR7S7EE7KZDZN3ETFTNQHHLTUL34NUAAWZG5OK2RGJ4V2U3Z", + ) + .arg("add") + .arg("alice") + .assert() + .success(); + + sandbox + .new_assert_cmd("keys") + .arg("use") + .arg("alice") + .assert() + .stderr(predicate::str::contains( + "The default source account is set to `alice`", + )) + .success(); + + sandbox + .new_assert_cmd("env") + .assert() + .stdout(predicate::str::contains("STELLAR_ACCOUNT=alice")) + .success(); +} + +#[test] +fn set_default_network() { + let sandbox = TestEnv::default(); + + sandbox + .new_assert_cmd("network") + .arg("use") + .arg("testnet") + .assert() + .stderr(predicate::str::contains( + "The default network is set to `testnet`", + )) + .success(); + + sandbox + .new_assert_cmd("env") + .assert() + .stdout(predicate::str::contains("STELLAR_NETWORK=testnet")) + .success(); +} diff --git a/cmd/crates/soroban-test/tests/it/init.rs b/cmd/crates/soroban-test/tests/it/init.rs index 6eada4ce6..c3cc9b694 100644 --- a/cmd/crates/soroban-test/tests/it/init.rs +++ b/cmd/crates/soroban-test/tests/it/init.rs @@ -1,6 +1,6 @@ use assert_fs::prelude::*; use predicates::prelude::predicate; -use soroban_test::TestEnv; +use soroban_test::{AssertExt, TestEnv}; #[test] fn init() { @@ -24,3 +24,64 @@ fn init() { == Some(&format!("{major}.0.0")) })); } + +#[test] +fn init_and_deploy() { + let name = "hello_world"; + let sandbox = TestEnv::default(); + + sandbox + .new_assert_cmd("contract") + .arg("init") + .arg("--name") + .arg(name) + .arg("project") + .assert() + .success(); + + let manifest_path = sandbox + .dir() + .join(format!("project/contracts/{name}/Cargo.toml")); + assert!(manifest_path.exists()); + + sandbox + .new_assert_cmd("contract") + .arg("build") + .arg("--manifest-path") + .arg(manifest_path) + .assert() + .success(); + + let target_dir = sandbox + .dir() + .join("project/target/wasm32-unknown-unknown/release"); + assert!(target_dir.exists()); + + let assert = sandbox + .new_assert_cmd("contract") + .arg("deploy") + .arg("--wasm") + .arg(target_dir.join(format!("{name}.wasm"))) + .assert(); + + let contract = assert.stdout_as_str(); + + assert.success(); + + let assert = sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--id") + .arg(contract) + .arg("--") + .arg("hello") + .arg("--to") + .arg("bar") + .assert(); + + let output = assert.stdout_as_str(); + + assert_eq!(output, r#"["Hello","bar"]"#); + + assert.success(); +} diff --git a/cmd/crates/soroban-test/tests/it/integration.rs b/cmd/crates/soroban-test/tests/it/integration.rs index 3e8521869..3ec0d61ed 100644 --- a/cmd/crates/soroban-test/tests/it/integration.rs +++ b/cmd/crates/soroban-test/tests/it/integration.rs @@ -1,9 +1,10 @@ mod bindings; +mod constructor; mod cookbook; mod custom_types; mod dotenv; -mod fund; mod hello_world; +mod keys; mod snapshot; mod tx; mod util; diff --git a/cmd/crates/soroban-test/tests/it/integration/constructor.rs b/cmd/crates/soroban-test/tests/it/integration/constructor.rs new file mode 100644 index 000000000..1919b56b0 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/constructor.rs @@ -0,0 +1,73 @@ +use assert_cmd::Command; + +use soroban_cli::xdr::{ + self, CreateContractArgsV2, HostFunction, InvokeHostFunctionOp, Limits, OperationBody, ReadXdr, + Transaction, TransactionV1Envelope, +}; +use soroban_test::{AssertExt, TestEnv}; + +use super::util::CONSTRUCTOR; + +fn constructor_cmd(sandbox: &TestEnv, value: u32, arg: &str) -> Command { + let mut cmd = sandbox.new_assert_cmd("contract"); + cmd.arg("deploy") + .arg("--wasm") + .arg(CONSTRUCTOR.path()) + .arg("--alias=init"); + if !arg.is_empty() { + cmd.arg(arg); + } + cmd.arg("--").arg("--counter").arg(value.to_string()); + cmd +} + +#[tokio::test] +async fn deploy_constructor_contract() { + let sandbox = TestEnv::new(); + let value = 100; + let build = constructor_cmd(&sandbox, value, "--build-only") + .assert() + .stdout_as_str(); + let tx = xdr::TransactionEnvelope::from_xdr_base64(&build, Limits::none()).unwrap(); + let ops = if let xdr::TransactionEnvelope::Tx(TransactionV1Envelope { + tx: Transaction { operations, .. }, + .. + }) = tx + { + operations + } else { + panic!() + } + .to_vec(); + let first = ops.first().unwrap(); + let args = match first { + xdr::Operation { + body: + OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: + HostFunction::CreateContractV2(CreateContractArgsV2 { + constructor_args, .. + }), + .. + }), + .. + } => constructor_args, + _ => panic!("expected invoke host function with create contract v2"), + } + .to_vec(); + + match args.first().unwrap() { + xdr::ScVal::U32(u32) => assert_eq!(*u32, value), + _ => panic!("Expected U32"), + } + + constructor_cmd(&sandbox, value, "").assert().success(); + + let res = sandbox + .new_assert_cmd("contract") + .args(["invoke", "--id=init", "--", "counter"]) + .assert() + .success() + .stdout_as_str(); + assert_eq!(res.trim(), value.to_string()); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/cookbook.rs b/cmd/crates/soroban-test/tests/it/integration/cookbook.rs index 82b9c5f43..5e9a30eb2 100644 --- a/cmd/crates/soroban-test/tests/it/integration/cookbook.rs +++ b/cmd/crates/soroban-test/tests/it/integration/cookbook.rs @@ -252,8 +252,6 @@ mod tests { .arg("xdr") .arg("--key") .arg("COUNTER") - .arg("--source-account") - .arg(source) .assert() .stdout_as_str(); let key_xdr = read_xdr.split(',').next().unwrap_or("").trim(); diff --git a/cmd/crates/soroban-test/tests/it/integration/fund.rs b/cmd/crates/soroban-test/tests/it/integration/fund.rs deleted file mode 100644 index 263775412..000000000 --- a/cmd/crates/soroban-test/tests/it/integration/fund.rs +++ /dev/null @@ -1,23 +0,0 @@ -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() - // Don't expect error if friendbot indicated that the account is - // already fully funded to the starting balance, because the - // user's goal is to get funded, and the account is funded - // so it is success much the same. - .success(); -} 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 767c9a07c..fd38c2012 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -20,9 +20,7 @@ async fn invoke_view_with_non_existent_source_account() { let id = deploy_hello(sandbox).await; let world = "world"; let mut cmd = hello_world_cmd(&id, world); - cmd.config.source_account = Address::default(); - cmd.is_view = true; - let res = sandbox.run_cmd_with(cmd, "test").await.unwrap(); + let res = sandbox.run_cmd_with(cmd, "").await.unwrap(); assert_eq!(res, TxnResult::Res(format!(r#"["Hello",{world:?}]"#))); } @@ -74,7 +72,7 @@ async fn invoke() { .assert() .stdout_as_str(); let dir = sandbox.dir(); - let seed_phrase = std::fs::read_to_string(dir.join(".soroban/identity/test.toml")).unwrap(); + let seed_phrase = std::fs::read_to_string(dir.join(".stellar/identity/test.toml")).unwrap(); let s = toml::from_str::(&seed_phrase).unwrap(); let secret::Secret::SeedPhrase { seed_phrase } = s else { panic!("Expected seed phrase") @@ -98,7 +96,7 @@ async fn invoke() { .arg("--id") .arg(id) .assert() - .stdout(predicates::str::contains(id).not()) + .stdout(predicates::str::contains(id)) .success(); invoke_hello_world_with_lib(sandbox, id).await; let config_locator = locator::Args { @@ -113,7 +111,7 @@ async fn invoke() { }, ) .unwrap(); - let sk_from_file = std::fs::read_to_string(dir.join(".soroban/identity/testone.toml")).unwrap(); + let sk_from_file = std::fs::read_to_string(dir.join(".stellar/identity/testone.toml")).unwrap(); assert_eq!(sk_from_file, format!("secret_key = \"{secret_key_1}\"\n")); let secret_key_1_readin = sandbox @@ -161,7 +159,7 @@ pub(crate) fn invoke_hello_world(sandbox: &TestEnv, id: &str) { fn hello_world_cmd(id: &str, arg: &str) -> contract::invoke::Cmd { contract::invoke::Cmd { - contract_id: id.to_string(), + contract_id: id.parse().unwrap(), slop: vec!["hello".into(), format!("--world={arg}").into()], ..Default::default() } diff --git a/cmd/crates/soroban-test/tests/it/integration/keys.rs b/cmd/crates/soroban-test/tests/it/integration/keys.rs new file mode 100644 index 000000000..267a0b095 --- /dev/null +++ b/cmd/crates/soroban-test/tests/it/integration/keys.rs @@ -0,0 +1,76 @@ +use predicates::prelude::predicate; +use soroban_test::AssertExt; +use soroban_test::TestEnv; + +fn pubkey_for_identity(sandbox: &TestEnv, name: &str) -> String { + let output = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg(name) + .assert() + .stdout_as_str(); + return output; +} + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn fund() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test2") + .assert() + .success(); + sandbox + .new_assert_cmd("keys") + .arg("fund") + .arg("test2") + .assert() + // Don't expect error if friendbot indicated that the account is + // already fully funded to the starting balance, because the + // user's goal is to get funded, and the account is funded + // so it is success much the same. + .success(); +} + +#[tokio::test] +#[allow(clippy::too_many_lines)] +async fn overwrite_identity() { + let sandbox = &TestEnv::new(); + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test2") + .assert() + .success(); + + let initial_pubkey = sandbox + .new_assert_cmd("keys") + .arg("address") + .arg("test2") + .assert() + .stdout_as_str(); + + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test2") + .assert() + .stderr(predicate::str::contains( + "error: An identity with the name 'test2' already exists", + )); + + assert_eq!(initial_pubkey, pubkey_for_identity(&sandbox, "test2")); + + sandbox + .new_assert_cmd("keys") + .arg("generate") + .arg("test2") + .arg("--overwrite") + .assert() + .stderr(predicate::str::contains("Overwriting identity 'test2'")) + .success(); + + assert_ne!(initial_pubkey, pubkey_for_identity(&sandbox, "test2")); +} diff --git a/cmd/crates/soroban-test/tests/it/integration/tx.rs b/cmd/crates/soroban-test/tests/it/integration/tx.rs index 5cc6bdb9b..c3cd2693b 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx.rs @@ -1,5 +1,5 @@ use soroban_cli::assembled::simulate_and_assemble_transaction; -use soroban_sdk::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; +use soroban_cli::xdr::{Limits, ReadXdr, TransactionEnvelope, WriteXdr}; use soroban_test::{AssertExt, TestEnv}; use crate::integration::util::{deploy_contract, DeployKind, HELLO_WORLD}; diff --git a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs index 9df808b34..0a0a103e8 100644 --- a/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs +++ b/cmd/crates/soroban-test/tests/it/integration/tx/operations.rs @@ -1,8 +1,8 @@ use soroban_cli::{ tx::{builder, ONE_XLM}, utils::contract_id_hash_from_asset, + xdr::{self, ReadXdr, SequenceNumber}, }; -use soroban_sdk::xdr::{self, ReadXdr, SequenceNumber}; use soroban_test::{AssertExt, TestEnv}; use crate::integration::{ diff --git a/cmd/crates/soroban-test/tests/it/integration/util.rs b/cmd/crates/soroban-test/tests/it/integration/util.rs index 6feae7860..486b00a1b 100644 --- a/cmd/crates/soroban-test/tests/it/integration/util.rs +++ b/cmd/crates/soroban-test/tests/it/integration/util.rs @@ -1,9 +1,12 @@ -use soroban_cli::commands; -use soroban_sdk::xdr::{Limits, WriteXdr}; +use soroban_cli::{ + commands, + xdr::{Limits, WriteXdr}, +}; use soroban_test::{TestEnv, Wasm}; use std::fmt::Display; pub const HELLO_WORLD: &Wasm = &Wasm::Custom("test-wasms", "test_hello_world"); +pub const CONSTRUCTOR: &Wasm = &Wasm::Custom("test-wasms", "test_constructor"); pub const CUSTOM_TYPES: &Wasm = &Wasm::Custom("test-wasms", "test_custom_types"); pub const CUSTOM_ACCOUNT: &Wasm = &Wasm::Custom("test-wasms", "test_custom_account"); pub const SWAP: &Wasm = &Wasm::Custom("test-wasms", "test_swap"); diff --git a/cmd/crates/soroban-test/tests/it/plugin.rs b/cmd/crates/soroban-test/tests/it/plugin.rs index 450902d65..5e5eabee9 100644 --- a/cmd/crates/soroban-test/tests/it/plugin.rs +++ b/cmd/crates/soroban-test/tests/it/plugin.rs @@ -1,7 +1,7 @@ /* -This function calls the soroban executable via cargo and checks that the output +This function calls the stellar executable via cargo and checks that the output is correct. The PATH environment variable is set to include the target/bin -directory, so that the soroban executable can be found. +directory, so that the stellar executable can be found. */ use std::{ffi::OsString, path::PathBuf}; @@ -11,7 +11,7 @@ fn soroban_hello() { // Add the target/bin directory to the iterator of paths let paths = get_paths(); // Call soroban with the PATH variable set to include the target/bin directory - assert_cmd::Command::cargo_bin("soroban") + assert_cmd::Command::cargo_bin("stellar") .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) .arg("hello") .env("PATH", &paths) @@ -19,23 +19,37 @@ fn soroban_hello() { .stdout("Hello, world!\n"); } +#[test] +fn stellar_bye() { + // Add the target/bin directory to the iterator of paths + let paths = get_paths(); + // Call soroban with the PATH variable set to include the target/bin directory + assert_cmd::Command::cargo_bin("stellar") + .unwrap_or_else(|_| assert_cmd::Command::new("stellar")) + .arg("bye") + .env("PATH", &paths) + .assert() + .stdout("Bye, world!\n"); +} + #[test] fn list() { // Call `soroban --list` with the PATH variable set to include the target/bin directory - assert_cmd::Command::cargo_bin("soroban") - .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + assert_cmd::Command::cargo_bin("stellar") + .unwrap_or_else(|_| assert_cmd::Command::new("stellar")) .arg("--list") .env("PATH", get_paths()) .assert() - .stdout(predicates::str::contains("hello")); + .stdout(predicates::str::contains("hello")) + .stdout(predicates::str::contains("bye")); } #[test] #[cfg(not(unix))] fn has_no_path() { // Call soroban with the PATH variable set to include just target/bin directory - assert_cmd::Command::cargo_bin("soroban") - .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + assert_cmd::Command::cargo_bin("stellar") + .unwrap_or_else(|_| assert_cmd::Command::new("stellar")) .arg("hello") .env("PATH", target_bin()) .assert() @@ -45,8 +59,8 @@ fn has_no_path() { #[test] fn has_no_path_failure() { // Call soroban with the PATH variable set to include just target/bin directory - assert_cmd::Command::cargo_bin("soroban") - .unwrap_or_else(|_| assert_cmd::Command::new("soroban")) + assert_cmd::Command::cargo_bin("stellar") + .unwrap_or_else(|_| assert_cmd::Command::new("stellar")) .arg("hello") .assert() .stderr(predicates::str::contains("error: no such command: `hello`")); diff --git a/cmd/crates/stellar-ledger/Cargo.toml b/cmd/crates/stellar-ledger/Cargo.toml index b8894b71d..b3d6318a4 100644 --- a/cmd/crates/stellar-ledger/Cargo.toml +++ b/cmd/crates/stellar-ledger/Cargo.toml @@ -10,27 +10,31 @@ version.workspace = true edition = "2021" rust-version.workspace = true +# This crate has not yet ever been published. Skip publishing until these +# security issues are addressed: +# https://github.com/stellar/stellar-cli/issues/1706 +publish = false + [dependencies] soroban-spec = { workspace = true } thiserror = "1.0.32" serde = "1.0.82" serde_derive = "1.0.82" serde_json = "1.0.82" -sha2 = "0.9.9" -soroban-env-host = { workspace = true } +sha2 = { workspace = true } ed25519-dalek = { workspace = true } stellar-strkey = { workspace = true } ledger-transport-hid = "0.10.0" ledger-transport = "0.10.0" sep5.workspace = true -slip10 = "0.4.3" +slip10 = { package = "slipped10", version = "0.4.6" } tracing = { workspace = true } hex.workspace = true byteorder = "1.5.0" bollard = { workspace = true } home = "0.5.9" tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.11", features = ["json"] } +reqwest = { workspace = true, features = ["json"] } soroban-rpc.workspace = true phf = { version = "0.11.2", features = ["macros"] } futures = "0.3.30" @@ -48,7 +52,7 @@ once_cell = "1.19.0" pretty_assertions = "1.2.1" serial_test = "3.0.0" httpmock = "0.7.0-rc.1" -test-case = "*" +test-case = "3.3.1" testcontainers = "0.20.1" diff --git a/cmd/crates/stellar-ledger/src/lib.rs b/cmd/crates/stellar-ledger/src/lib.rs index c74365af2..e90c31bb9 100644 --- a/cmd/crates/stellar-ledger/src/lib.rs +++ b/cmd/crates/stellar-ledger/src/lib.rs @@ -5,12 +5,11 @@ use ledger_transport_hid::{ LedgerHIDError, TransportNativeHID, }; -use soroban_env_host::xdr::{Hash, Transaction}; use std::vec; use stellar_strkey::DecodeError; use stellar_xdr::curr::{ - self as xdr, Limits, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - WriteXdr, + self as xdr, Hash, Limits, Transaction, TransactionSignaturePayload, + TransactionSignaturePayloadTaggedTransaction, WriteXdr, }; pub use crate::signer::Blob; @@ -312,10 +311,9 @@ mod test { use crate::Blob; use test_helpers::test::emulator_http_transport::EmulatorHttpTransport; - use soroban_env_host::xdr::Transaction; use std::vec; - use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; + use super::xdr::{self, Operation, OperationBody, Transaction, Uint256}; use crate::{test_network_hash, Error, LedgerSigner}; diff --git a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs index d45981eef..1b0c09bab 100644 --- a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs @@ -1,8 +1,6 @@ use ledger_transport::Exchange; use once_cell::sync::Lazy; use serde::Deserialize; -use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; -use soroban_env_host::xdr::{Hash, Transaction}; use std::ops::Range; use std::sync::Mutex; use std::vec; @@ -15,8 +13,10 @@ use std::sync::Arc; use std::{collections::HashMap, time::Duration}; use stellar_xdr::curr::{ - Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, + self as xdr, Hash, Memo, MuxedAccount, Operation, OperationBody, PaymentOp, Preconditions, + SequenceNumber, Transaction, TransactionExt, Uint256, }; + use testcontainers::{core::ContainerPort, runners::AsyncRunner, ContainerAsync, ImageExt}; use tokio::time::sleep; diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml index 646e8a92e..2f2a26255 100644 --- a/cmd/soroban-cli/Cargo.toml +++ b/cmd/soroban-cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/stellar-cli" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.5.0" +version.workspace = true edition = "2021" rust-version.workspace = true autobins = false @@ -40,7 +40,6 @@ opt = ["dep:wasm-opt"] [dependencies] stellar-xdr = { workspace = true, features = ["cli"] } -soroban-env-host = { workspace = true } soroban-spec = { workspace = true } soroban-spec-json = { workspace = true } soroban-spec-rust = { workspace = true } @@ -84,18 +83,17 @@ reqwest = { version = "0.12.7", default-features = false, features = [ jsonrpsee-http-client = "0.20.1" jsonrpsee-core = "0.20.1" regex = "1.6.0" -wasm-opt = { version = "0.114.0", optional = true } +wasm-opt = { version = "0.116.1", optional = true } chrono = { version = "0.4.27", features = ["serde"] } rpassword = "7.2.0" -dirs = "4.0.0" -toml = "0.5.9" +toml = { workspace = true } itertools = "0.10.5" shlex = "1.1.0" sep5 = { workspace = true } ethnum = { workspace = true } clap-markdown = { version = "0.1.4", optional = true } which = { workspace = true, features = ["regex"] } -strsim = "0.10.0" +strsim = "0.11.1" heck = "0.5.0" tracing = { workspace = true } tracing-appender = { workspace = true } @@ -107,14 +105,10 @@ directories = { workspace = true } ulid = { workspace = true, features = ["serde"] } strum = "0.17.1" strum_macros = "0.17.1" -gix = { version = "0.58.0", default-features = false, features = [ - "blocking-http-transport-reqwest-rust-tls", - "worktree-mutation", -] } async-compression = { version = "0.4.12", features = ["tokio", "gzip"] } - +shell-escape = "0.1.5" tempfile = "3.8.1" -toml_edit = "0.21.0" +toml_edit = { workspace = true } rust-embed = { version = "8.2.0", features = ["debug-embed"] } bollard = { workspace = true } futures-util = "0.3.30" @@ -129,9 +123,10 @@ glob = "0.3.1" fqdn = "0.3.12" open = "5.3.0" url = "2.5.2" +wasm-gen = "0.1.4" [build-dependencies] -crate-git-revision = "0.0.4" +crate-git-revision = "0.0.6" serde.workspace = true thiserror.workspace = true @@ -139,6 +134,6 @@ thiserror.workspace = true [dev-dependencies] assert_cmd = "2.0.4" assert_fs = "1.0.7" -predicates = "2.1.5" +predicates = { workspace = true } walkdir = "2.5.0" mockito = "1.5.0" diff --git a/cmd/soroban-cli/example_contracts.list b/cmd/soroban-cli/example_contracts.list deleted file mode 100644 index 2ad5f2c6e..000000000 --- a/cmd/soroban-cli/example_contracts.list +++ /dev/null @@ -1,25 +0,0 @@ -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 -other_custom_types -simple_account -single_offer -timelock -token -ttl -upgradeable_contract -workspace diff --git a/cmd/soroban-cli/src/cli.rs b/cmd/soroban-cli/src/cli.rs index 5470562db..76c5c3194 100644 --- a/cmd/soroban-cli/src/cli.rs +++ b/cmd/soroban-cli/src/cli.rs @@ -2,8 +2,10 @@ use clap::CommandFactory; use dotenvy::dotenv; use tracing_subscriber::{fmt, EnvFilter}; +use crate::config::Config; +use crate::print::Print; use crate::upgrade_check::upgrade_check; -use crate::{commands, print, Root}; +use crate::{commands, Root}; #[tokio::main] pub async fn main() { @@ -32,6 +34,8 @@ pub async fn main() { } } + set_env_from_config(); + let mut root = Root::new().unwrap_or_else(|e| match e { commands::Error::Clap(e) => { let mut cmd = Root::command(); @@ -42,6 +46,7 @@ pub async fn main() { std::process::exit(1); } }); + // Now use root to setup the logger if let Some(level) = root.global_args.log_level() { let mut e_filter = EnvFilter::from_default_env() @@ -78,9 +83,33 @@ pub async fn main() { upgrade_check(root.global_args.quiet).await; }); - let printer = print::Print::new(root.global_args.quiet); + let printer = Print::new(root.global_args.quiet); if let Err(e) = root.run().await { printer.errorln(format!("error: {e}")); std::process::exit(1); } } + +// Load ~/.config/stellar/config.toml defaults as env vars. +fn set_env_from_config() { + if let Ok(config) = Config::new() { + set_env_value_from_config("STELLAR_ACCOUNT", config.defaults.identity); + set_env_value_from_config("STELLAR_NETWORK", config.defaults.network); + } +} + +// Set an env var from a config file if the env var is not already set. +// Additionally, a `$NAME_SOURCE` variant will be set, which allows +// `stellar env` to properly identity the source. +fn set_env_value_from_config(name: &str, value: Option) { + let Some(value) = value else { + return; + }; + + std::env::remove_var(format!("{name}_SOURCE")); + + if std::env::var(name).is_err() { + std::env::set_var(name, value); + std::env::set_var(format!("{name}_SOURCE"), "use"); + } +} diff --git a/cmd/soroban-cli/src/commands/network/container/logs.rs b/cmd/soroban-cli/src/commands/container/logs.rs similarity index 94% rename from cmd/soroban-cli/src/commands/network/container/logs.rs rename to cmd/soroban-cli/src/commands/container/logs.rs index aaccffdde..85824b06c 100644 --- a/cmd/soroban-cli/src/commands/network/container/logs.rs +++ b/cmd/soroban-cli/src/commands/container/logs.rs @@ -1,7 +1,7 @@ use futures_util::TryStreamExt; use crate::{ - commands::{global, network::container::shared::Error as ConnectionError}, + commands::{container::shared::Error as ConnectionError, global}, print, }; diff --git a/cmd/soroban-cli/src/commands/network/container.rs b/cmd/soroban-cli/src/commands/container/mod.rs similarity index 91% rename from cmd/soroban-cli/src/commands/network/container.rs rename to cmd/soroban-cli/src/commands/container/mod.rs index d14dc72e4..08203095d 100644 --- a/cmd/soroban-cli/src/commands/network/container.rs +++ b/cmd/soroban-cli/src/commands/container/mod.rs @@ -16,13 +16,13 @@ pub enum Cmd { Logs(logs::Cmd), /// Start a container running a Stellar node, RPC, API, and friendbot (faucet). /// - /// `stellar network container start NETWORK [OPTIONS]` + /// `stellar container start NETWORK [OPTIONS]` /// /// By default, when starting a testnet container, without any optional arguments, it will run the equivalent of the following docker command: /// /// `docker run --rm -p 8000:8000 --name stellar stellar/quickstart:testing --testnet --enable rpc,horizon` Start(start::Cmd), - /// Stop a network container started with `network container start`. + /// Stop a network container started with `stellar container start`. Stop(stop::Cmd), } diff --git a/cmd/soroban-cli/src/commands/network/container/shared.rs b/cmd/soroban-cli/src/commands/container/shared.rs similarity index 100% rename from cmd/soroban-cli/src/commands/network/container/shared.rs rename to cmd/soroban-cli/src/commands/container/shared.rs diff --git a/cmd/soroban-cli/src/commands/network/container/start.rs b/cmd/soroban-cli/src/commands/container/start.rs similarity index 99% rename from cmd/soroban-cli/src/commands/network/container/start.rs rename to cmd/soroban-cli/src/commands/container/start.rs index 2f16d3c1e..6c5a5c291 100644 --- a/cmd/soroban-cli/src/commands/network/container/start.rs +++ b/cmd/soroban-cli/src/commands/container/start.rs @@ -9,8 +9,8 @@ use futures_util::TryStreamExt; use crate::{ commands::{ + container::shared::{Error as ConnectionError, Network}, global, - network::container::shared::{Error as ConnectionError, Network}, }, print, }; diff --git a/cmd/soroban-cli/src/commands/network/container/stop.rs b/cmd/soroban-cli/src/commands/container/stop.rs similarity index 95% rename from cmd/soroban-cli/src/commands/network/container/stop.rs rename to cmd/soroban-cli/src/commands/container/stop.rs index 77f674c46..6211c629f 100644 --- a/cmd/soroban-cli/src/commands/network/container/stop.rs +++ b/cmd/soroban-cli/src/commands/container/stop.rs @@ -1,5 +1,5 @@ use crate::{ - commands::{global, network::container::shared::Error as BollardConnectionError}, + commands::{container::shared::Error as BollardConnectionError, global}, print, }; diff --git a/cmd/soroban-cli/src/commands/contract/alias/add.rs b/cmd/soroban-cli/src/commands/contract/alias/add.rs index 5a7a17ddb..923ead5e9 100644 --- a/cmd/soroban-cli/src/commands/contract/alias/add.rs +++ b/cmd/soroban-cli/src/commands/contract/alias/add.rs @@ -13,9 +13,9 @@ pub struct Cmd { pub config_locator: locator::Args, #[command(flatten)] - network: network::Args, + pub network: network::Args, - /// The contract alias that will be removed. + /// The contract alias that will be used. pub alias: String, /// Overwrite the contract alias if it already exists. @@ -41,7 +41,7 @@ pub enum Error { AlreadyExist { alias: String, network_passphrase: String, - contract: String, + contract: stellar_strkey::Contract, }, } @@ -57,7 +57,7 @@ impl Cmd { .get_contract_id(&self.alias, network_passphrase)?; if let Some(contract) = contract { - if contract != self.contract_id.to_string() && !self.overwrite { + if contract != self.contract_id && !self.overwrite { return Err(Error::AlreadyExist { alias: alias.to_string(), network_passphrase: network_passphrase.to_string(), diff --git a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs index 4a8af47e2..21fa2f383 100644 --- a/cmd/soroban-cli/src/commands/contract/arg_parsing.rs +++ b/cmd/soroban-cli/src/commands/contract/arg_parsing.rs @@ -8,7 +8,7 @@ use clap::value_parser; use ed25519_dalek::SigningKey; use heck::ToKebabCase; -use soroban_env_host::xdr::{ +use crate::xdr::{ self, Hash, InvokeContractArgs, ScAddress, ScSpecEntry, ScSpecFunctionV0, ScSpecTypeDef, ScVal, ScVec, }; @@ -62,6 +62,9 @@ pub fn build_host_function_parameters( } cmd.build(); let long_help = cmd.render_long_help(); + + // get_matches_from exits the process if `help`, `--help` or `-h`are passed in the slop + // see clap documentation for more info: https://github.com/clap-rs/clap/blob/v4.1.8/src/builder/command.rs#L631 let mut matches_ = cmd.get_matches_from(slop); let Some((function, matches_)) = &matches_.remove_subcommand() else { println!("{long_help}"); diff --git a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs index 84e2b1762..679e757f4 100644 --- a/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs +++ b/cmd/soroban-cli/src/commands/contract/bindings/typescript.rs @@ -5,14 +5,13 @@ use soroban_spec_tools::contract as contract_spec; use soroban_spec_typescript::{self as typescript, boilerplate::Project}; use stellar_strkey::DecodeError; +use crate::print::Print; use crate::wasm; use crate::{ commands::{contract::fetch, global, NetworkRunnable}, - config::{ - self, locator, - network::{self, Network}, - }, + config::{self, locator, network}, get_spec::{self, get_remote_contract_spec}, + xdr::{Hash, ScAddress}, }; #[derive(Parser, Debug, Clone)] @@ -83,20 +82,27 @@ impl NetworkRunnable for Cmd { global_args: Option<&global::Args>, config: Option<&config::Args>, ) -> Result<(), Error> { + let print = Print::new(global_args.is_some_and(|a| a.quiet)); + + 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() + }); + + let contract_id = self + .locator + .resolve_contract_id(&self.contract_id, &network.network_passphrase)? + .0; + let contract_address = ScAddress::Contract(Hash(contract_id)); + let spec = if let Some(wasm) = &self.wasm { + print.infoln("Loading contract spec from file..."); let wasm: wasm::Args = wasm.into(); wasm.parse()?.spec } else { - let network = config.map_or_else( - || self.network.get(&self.locator).map_err(Error::from), - |c| c.get_network().map_err(Error::from), - )?; - - let contract_id = self - .locator - .resolve_contract_id(&self.contract_id, &network.network_passphrase)? - .0; - + print.globeln(format!("Downloading contract spec: {contract_address}")); get_remote_contract_spec( &contract_id, &self.locator, @@ -119,16 +125,7 @@ impl NetworkRunnable for Cmd { } std::fs::create_dir_all(&self.output_dir)?; let p: Project = self.output_dir.clone().try_into()?; - let Network { - rpc_url, - network_passphrase, - .. - } = self.network.get(&self.locator).ok().unwrap_or_else(|| { - network::DEFAULTS - .get("futurenet") - .expect("why did we remove the default futurenet network?") - .into() - }); + print.infoln(format!("Network: {}", network.network_passphrase)); let absolute_path = self.output_dir.canonicalize()?; let file_name = absolute_path .file_name() @@ -136,24 +133,19 @@ impl NetworkRunnable for Cmd { let contract_name = &file_name .to_str() .ok_or_else(|| Error::NotUtf8(file_name.to_os_string()))?; + print.infoln(format!("Embedding contract address: {contract_address}")); p.init( contract_name, - &self.contract_id, - &rpc_url, - &network_passphrase, + &contract_address.to_string(), + &network.rpc_url, + &network.network_passphrase, &spec, )?; - std::process::Command::new("npm") - .arg("install") - .current_dir(&self.output_dir) - .spawn()? - .wait()?; - std::process::Command::new("npm") - .arg("run") - .arg("build") - .current_dir(&self.output_dir) - .spawn()? - .wait()?; + print.checkln("Generated!"); + print.infoln(format!( + "Run \"npm install && npm run build\" in {:?} to build the JavaScript NPM package.", + self.output_dir + )); Ok(()) } } diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs index c71327a38..ac1aa1302 100644 --- a/cmd/soroban-cli/src/commands/contract/build.rs +++ b/cmd/soroban-cli/src/commands/contract/build.rs @@ -1,6 +1,8 @@ +use cargo_metadata::{Metadata, MetadataCommand, Package}; use clap::Parser; use itertools::Itertools; use std::{ + borrow::Cow, collections::HashSet, env, ffi::OsStr, @@ -9,8 +11,9 @@ use std::{ path::{self, Path, PathBuf}, process::{Command, ExitStatus, Stdio}, }; +use stellar_xdr::curr::{Limits, ScMetaEntry, ScMetaV0, StringM, WriteXdr}; -use cargo_metadata::{Metadata, MetadataCommand, Package}; +use crate::{commands::global, print::Print}; /// Build a contract from source /// @@ -62,6 +65,20 @@ pub struct Cmd { /// Print commands to build without executing them #[arg(long, conflicts_with = "out_dir", help_heading = "Other")] pub print_commands_only: bool, + /// Add key-value to contract meta (adds the meta to the `contractmetav0` custom section) + #[arg(long, num_args=1, value_parser=parse_meta_arg, action=clap::ArgAction::Append, help_heading = "Metadata")] + pub meta: Vec<(String, String)>, +} + +fn parse_meta_arg(s: &str) -> Result<(String, String), Error> { + let parts = s.splitn(2, '='); + + let (key, value) = parts + .map(str::trim) + .next_tuple() + .ok_or_else(|| Error::MetaArg("must be in the form 'key=value'".to_string()))?; + + Ok((key.to_string(), value.to_string())) } #[derive(thiserror::Error, Debug)] @@ -82,10 +99,23 @@ pub enum Error { CopyingWasmFile(io::Error), #[error("getting the current directory: {0}")] GettingCurrentDir(io::Error), + #[error("retreiving CARGO_HOME: {0}")] + CargoHome(io::Error), + #[error("reading wasm file: {0}")] + ReadingWasmFile(io::Error), + #[error("writing wasm file: {0}")] + WritingWasmFile(io::Error), + #[error("invalid meta entry: {0}")] + MetaArg(String), } +const WASM_TARGET: &str = "wasm32-unknown-unknown"; +const META_CUSTOM_SECTION_NAME: &str = "contractmetav0"; + impl Cmd { - pub fn run(&self) -> Result<(), Error> { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let working_dir = env::current_dir().map_err(Error::GettingCurrentDir)?; let metadata = self.metadata()?; @@ -111,7 +141,7 @@ impl Cmd { manifest_path.to_string_lossy() )); cmd.arg("--crate-type=cdylib"); - cmd.arg("--target=wasm32-unknown-unknown"); + cmd.arg(format!("--target={WASM_TARGET}")); if self.profile == "release" { cmd.arg("--release"); } else { @@ -131,28 +161,46 @@ impl Cmd { cmd.arg(format!("--features={activate}")); } } - let cmd_str = format!( - "cargo {}", - cmd.get_args().map(OsStr::to_string_lossy).join(" ") + + if let Some(rustflags) = make_rustflags_to_remap_absolute_paths(&print)? { + cmd.env("CARGO_BUILD_RUSTFLAGS", rustflags); + } + + let mut cmd_str_parts = Vec::::new(); + cmd_str_parts.extend(cmd.get_envs().map(|(key, val)| { + format!( + "{}={}", + key.to_string_lossy(), + shell_escape::escape(val.unwrap_or_default().to_string_lossy()) + ) + })); + cmd_str_parts.push("cargo".to_string()); + cmd_str_parts.extend( + cmd.get_args() + .map(OsStr::to_string_lossy) + .map(Cow::into_owned), ); + let cmd_str = cmd_str_parts.join(" "); if self.print_commands_only { println!("{cmd_str}"); } else { - eprintln!("{cmd_str}"); + print.infoln(cmd_str); let status = cmd.status().map_err(Error::CargoCmd)?; if !status.success() { return Err(Error::Exit(status)); } + let file = format!("{}.wasm", p.name.replace('-', "_")); + let target_file_path = Path::new(target_dir) + .join(WASM_TARGET) + .join(&self.profile) + .join(&file); + + self.handle_contract_metadata_args(&target_file_path)?; + if let Some(out_dir) = &self.out_dir { fs::create_dir_all(out_dir).map_err(Error::CreatingOutDir)?; - - let file = format!("{}.wasm", p.name.replace('-', "_")); - let target_file_path = Path::new(target_dir) - .join("wasm32-unknown-unknown") - .join(&self.profile) - .join(&file); let out_file_path = Path::new(out_dir).join(&file); fs::copy(target_file_path, out_file_path).map_err(Error::CopyingWasmFile)?; } @@ -228,4 +276,130 @@ impl Cmd { // the output. cmd.exec() } + + fn handle_contract_metadata_args(&self, target_file_path: &PathBuf) -> Result<(), Error> { + if self.meta.is_empty() { + return Ok(()); + } + + let mut wasm_bytes = fs::read(target_file_path).map_err(Error::ReadingWasmFile)?; + + for (k, v) in self.meta.clone() { + let key: StringM = k + .clone() + .try_into() + .map_err(|e| Error::MetaArg(format!("{k} is an invalid metadata key: {e}")))?; + + let val: StringM = v + .clone() + .try_into() + .map_err(|e| Error::MetaArg(format!("{v} is an invalid metadata value: {e}")))?; + let meta_entry = ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }); + let xdr: Vec = meta_entry + .to_xdr(Limits::none()) + .map_err(|e| Error::MetaArg(format!("failed to encode metadata entry: {e}")))?; + + wasm_gen::write_custom_section(&mut wasm_bytes, META_CUSTOM_SECTION_NAME, &xdr); + } + + fs::write(target_file_path, wasm_bytes).map_err(Error::WritingWasmFile) + } +} + +/// Configure cargo/rustc to replace absolute paths in panic messages / debuginfo +/// with relative paths. +/// +/// This is required for reproducible builds. +/// +/// This works for paths to crates in the registry. The compiler already does +/// something similar for standard library paths and local paths. It may not +/// work for crates that come from other sources, including the standard library +/// compiled from source, though it may be possible to accomodate such cases in +/// the future. +/// +/// This in theory breaks the ability of debuggers to find source code, but +/// since we are only targetting wasm, which is not typically run in a debugger, +/// and stellar-cli only compiles contracts in release mode, the impact is on +/// debugging is expected to be minimal. +/// +/// This works by setting the `CARGO_BUILD_RUSTFLAGS` environment variable, +/// with appropriate `--remap-path-prefix` option. It preserves the values of an +/// existing `CARGO_BUILD_RUSTFLAGS` environment variable. +/// +/// This must be done some via some variation of `RUSTFLAGS` and not as +/// arguments to `cargo rustc` because the latter only applies to the crate +/// directly being compiled, while `RUSTFLAGS` applies to all crates, including +/// dependencies. +/// +/// `CARGO_BUILD_RUSTFLAGS` is an alias for the `build.rustflags` configuration +/// variable. Cargo automatically merges the contents of the environment variable +/// and the variables from config files; and `build.rustflags` has the lowest +/// priority of all the variations of rustflags that Cargo accepts. And because +/// we merge our values with an existing `CARGO_BUILD_RUSTFLAGS`, +/// our setting of this environment variable should not interfere with the +/// user's ability to set rustflags in any way they want, but it does mean +/// that if the user sets a higher-priority rustflags that our path remapping +/// will be ignored. +/// +/// The major downside of using `CARGO_BUILD_RUSTFLAGS` is that it is whitespace +/// separated, which means we cannot support paths with spaces. If we encounter +/// such paths we will emit a warning. Spaces could be accomodated by using +/// `CARGO_ENCODED_RUSTFLAGS`, but that has high precedence over other rustflags, +/// so we could be interfering with the user's own use of rustflags. There is +/// no "encoded" variant of `CARGO_BUILD_RUSTFLAGS` at time of writing. +/// +/// This assumes that paths are Unicode and that any existing `CARGO_BUILD_RUSTFLAGS` +/// variables are Unicode. Non-Unicode paths will fail to correctly perform the +/// the absolute path replacement. Non-Unicode `CARGO_BUILD_RUSTFLAGS` will result in the +/// existing rustflags being ignored, which is also the behavior of +/// Cargo itself. +fn make_rustflags_to_remap_absolute_paths(print: &Print) -> Result, Error> { + let cargo_home = home::cargo_home().map_err(Error::CargoHome)?; + let cargo_home = format!("{}", cargo_home.display()); + + if cargo_home.find(|c: char| c.is_whitespace()).is_some() { + print.warnln("Cargo home directory contains whitespace. Dependency paths will not be remapped; builds may not be reproducible."); + return Ok(None); + } + + if env::var("RUSTFLAGS").is_ok() { + print.warnln("`RUSTFLAGS` set. Dependency paths will not be remapped; builds may not be reproducible."); + return Ok(None); + } + + if env::var("CARGO_ENCODED_RUSTFLAGS").is_ok() { + print.warnln("`CARGO_ENCODED_RUSTFLAGS` set. Dependency paths will not be remapped; builds may not be reproducible."); + return Ok(None); + } + + if env::var("TARGET_wasm32-unknown-unknown_RUSTFLAGS").is_ok() { + print.warnln("`TARGET_wasm32-unknown-unknown_RUSTFLAGS` set. Dependency paths will not be remapped; builds may not be reproducible."); + return Ok(None); + } + + let registry_prefix = format!("{cargo_home}/registry/src/"); + let new_rustflag = format!("--remap-path-prefix={registry_prefix}="); + + let mut rustflags = get_rustflags().unwrap_or_default(); + rustflags.push(new_rustflag); + + let rustflags = rustflags.join(" "); + + Ok(Some(rustflags)) +} + +/// Get any existing `CARGO_BUILD_RUSTFLAGS`, split on whitespace. +/// +/// This conveniently ignores non-Unicode values, as does Cargo. +fn get_rustflags() -> Option> { + if let Ok(a) = env::var("CARGO_BUILD_RUSTFLAGS") { + let args = a + .split_whitespace() + .map(str::trim) + .filter(|s| !s.is_empty()) + .map(str::to_string); + return Some(args.collect()); + } + + None } diff --git a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs index 8ff8e08b3..04a0380ed 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/asset.rs @@ -1,13 +1,10 @@ -use clap::{arg, command, Parser}; -use soroban_env_host::{ - xdr::{ - Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs, - Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData, - LedgerKeyContractData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, - ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, VecM, WriteXdr, - }, - HostError, +use crate::xdr::{ + Asset, ContractDataDurability, ContractExecutable, ContractIdPreimage, CreateContractArgs, + Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerKey::ContractData, + LedgerKeyContractData, Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, + ScAddress, ScVal, SequenceNumber, Transaction, TransactionExt, VecM, WriteXdr, }; +use clap::{arg, command, Parser}; use std::convert::Infallible; use std::{array::TryFromSliceError, fmt::Debug, num::ParseIntError}; @@ -19,17 +16,13 @@ use crate::{ NetworkRunnable, }, config::{self, data, network}, - rpc::{Client, Error as SorobanRpcError}, + rpc::Error as SorobanRpcError, tx::builder, utils::contract_id_hash_from_asset, }; #[derive(thiserror::Error, Debug)] pub enum Error { - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), #[error("error parsing int: {0}")] ParseIntError(#[from] ParseIntError), #[error(transparent)] @@ -95,16 +88,16 @@ impl NetworkRunnable for Cmd { let asset = &self.asset; let network = config.get_network()?; - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; let source_account = config.source_account()?; - // Get the account sequence number - let public_strkey = source_account.to_string(); // TODO: use symbols for the method names (both here and in serve) - let account_details = client.get_account(&public_strkey).await?; + let account_details = client + .get_account(&source_account.clone().to_string()) + .await?; let sequence: i64 = account_details.seq_num.into(); let network_passphrase = &network.network_passphrase; let contract_id = contract_id_hash_from_asset(asset, network_passphrase); @@ -117,12 +110,12 @@ impl NetworkRunnable for Cmd { source_account, )?; if self.fee.build_only { - return Ok(TxnResult::Txn(tx)); + return Ok(TxnResult::Txn(Box::new(tx))); } let txn = simulate_and_assemble_transaction(&client, &tx).await?; let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); if self.fee.sim_only { - return Ok(TxnResult::Txn(txn)); + return Ok(TxnResult::Txn(Box::new(txn))); } let get_txn_resp = client .send_transaction_polling(&self.config.sign_with_local_key(txn).await?) diff --git a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs index 36ce2e9f7..1d85832b0 100644 --- a/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/deploy/wasm.rs @@ -1,37 +1,38 @@ use std::array::TryFromSliceError; +use std::ffi::OsString; use std::fmt::Debug; use std::num::ParseIntError; +use crate::xdr::{ + AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, + CreateContractArgs, CreateContractArgsV2, Error as XdrError, Hash, HostFunction, + InvokeContractArgs, InvokeHostFunctionOp, Limits, Memo, MuxedAccount, Operation, OperationBody, + Preconditions, PublicKey, ScAddress, SequenceNumber, Transaction, TransactionExt, Uint256, + VecM, WriteXdr, +}; use clap::{arg, command, Parser}; use rand::Rng; use regex::Regex; -use soroban_env_host::{ - xdr::{ - AccountId, ContractExecutable, ContractIdPreimage, ContractIdPreimageFromAddress, - CreateContractArgs, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, Limits, - Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScAddress, - SequenceNumber, Transaction, TransactionExt, Uint256, VecM, WriteXdr, - }, - HostError, -}; + +use soroban_spec_tools::contract as contract_spec; use crate::{ assembled::simulate_and_assemble_transaction, - commands::{contract::install, HEADING_RPC}, - config::{self, data, locator, network}, - rpc::{self, Client}, - utils, wasm, -}; -use crate::{ commands::{ - contract::{self, id::wasm::get_contract_id}, + contract::{self, arg_parsing, id::wasm::get_contract_id, install}, global, txn_result::{TxnEnvelopeResult, TxnResult}, - NetworkRunnable, + NetworkRunnable, HEADING_RPC, }, + config::{self, data, locator, network}, print::Print, + rpc, + utils::{self, rpc::get_remote_wasm_from_hash}, + wasm, }; +pub const CONSTRUCTOR_FUNCTION_NAME: &str = "__constructor"; + #[derive(Parser, Debug, Clone)] #[command(group( clap::ArgGroup::new("wasm_src") @@ -64,14 +65,15 @@ pub struct Cmd { /// configuration without asking for confirmation. #[arg(long, value_parser = clap::builder::ValueParser::new(alias_validator))] pub alias: Option, + /// If provided, will be passed to the contract's `__constructor` function with provided arguments for that function as `--arg-name value` + #[arg(last = true, id = "CONTRACT_CONSTRUCTOR_ARGS")] + pub slop: Vec, } #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Install(#[from] install::Error), - #[error(transparent)] - Host(#[from] HostError), #[error("error parsing int: {0}")] ParseIntError(#[from] ParseIntError), #[error("internal conversion error: {0}")] @@ -116,6 +118,10 @@ pub enum Error { InvalidAliasFormat { alias: String }, #[error(transparent)] Locator(#[from] locator::Error), + #[error(transparent)] + ContractSpec(#[from] contract_spec::Error), + #[error(transparent)] + ArgParse(#[from] arg_parsing::Error), #[error("Only ed25519 accounts are allowed")] OnlyEd25519AccountsAllowed, } @@ -163,6 +169,7 @@ impl NetworkRunnable for Cmd { type Error = Error; type Result = TxnResult; + #[allow(clippy::too_many_lines)] async fn run_against_rpc_server( &self, global_args: Option<&global::Args>, @@ -213,26 +220,59 @@ impl NetworkRunnable for Cmd { None => rand::thread_rng().gen::<[u8; 32]>(), }; - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; + let MuxedAccount::Ed25519(bytes) = config.source_account()? else { return Err(Error::OnlyEd25519AccountsAllowed); }; + let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(bytes)); + let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { + address: ScAddress::Account(source_account.clone()), + salt: Uint256(salt), + }); + let contract_id = + get_contract_id(contract_id_preimage.clone(), &network.network_passphrase)?; + let raw_wasm = if let Some(wasm) = self.wasm.as_ref() { + wasm::Args { wasm: wasm.clone() }.read()? + } else { + get_remote_wasm_from_hash(&client, &wasm_hash).await? + }; + let entries = soroban_spec_tools::contract::Spec::new(&raw_wasm)?.spec; + let res = soroban_spec_tools::Spec::new(entries.clone()); + let constructor_params = if let Ok(func) = res.find_function(CONSTRUCTOR_FUNCTION_NAME) { + if func.inputs.len() == 0 { + None + } else { + let mut slop = vec![OsString::from(CONSTRUCTOR_FUNCTION_NAME)]; + slop.extend_from_slice(&self.slop); + Some( + arg_parsing::build_host_function_parameters( + &stellar_strkey::Contract(contract_id.0), + &slop, + &entries, + config, + )? + .2, + ) + } + } else { + None + }; - let key = stellar_strkey::ed25519::PublicKey(bytes.into()); // Get the account sequence number - let account_details = client.get_account(&key.to_string()).await?; + let account_details = client.get_account(&source_account.to_string()).await?; let sequence: i64 = account_details.seq_num.into(); - let (txn, contract_id) = build_create_contract_tx( + let txn = Box::new(build_create_contract_tx( wasm_hash, sequence + 1, self.fee.fee, - &network.network_passphrase, - salt, - key, - )?; + source_account, + contract_id_preimage, + constructor_params.as_ref(), + )?); if self.fee.build_only { print.checkln("Transaction built!"); @@ -242,7 +282,7 @@ impl NetworkRunnable for Cmd { print.infoln("Simulating deploy transaction…"); let txn = simulate_and_assemble_transaction(&client, &txn).await?; - let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); + let txn = Box::new(self.fee.apply_to_assembled_txn(txn).transaction().clone()); if self.fee.sim_only { print.checkln("Done!"); @@ -253,7 +293,7 @@ impl NetworkRunnable for Cmd { print.log_transaction(&txn, &network, true)?; let get_txn_resp = client - .send_transaction_polling(&config.sign_with_local_key(txn).await?) + .send_transaction_polling(&config.sign_with_local_key(*txn).await?) .await? .try_into()?; @@ -272,33 +312,39 @@ impl NetworkRunnable for Cmd { } fn build_create_contract_tx( - hash: Hash, + wasm_hash: Hash, sequence: i64, fee: u32, - network_passphrase: &str, - salt: [u8; 32], - key: stellar_strkey::ed25519::PublicKey, -) -> Result<(Transaction, stellar_strkey::Contract), Error> { - let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(key.0.into())); - - let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { - address: ScAddress::Account(source_account), - salt: Uint256(salt), - }); - let contract_id = get_contract_id(contract_id_preimage.clone(), network_passphrase)?; - - let op = Operation { - source_account: None, - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { - host_function: HostFunction::CreateContract(CreateContractArgs { - contract_id_preimage, - executable: ContractExecutable::Wasm(hash), + key: AccountId, + contract_id_preimage: ContractIdPreimage, + constructor_params: Option<&InvokeContractArgs>, +) -> Result { + let op = if let Some(InvokeContractArgs { args, .. }) = constructor_params { + Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::CreateContractV2(CreateContractArgsV2 { + contract_id_preimage, + executable: ContractExecutable::Wasm(wasm_hash), + constructor_args: args.clone(), + }), + auth: VecM::default(), + }), + } + } else { + Operation { + source_account: None, + body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { + host_function: HostFunction::CreateContract(CreateContractArgs { + contract_id_preimage, + executable: ContractExecutable::Wasm(wasm_hash), + }), + auth: VecM::default(), }), - auth: VecM::default(), - }), + } }; let tx = Transaction { - source_account: MuxedAccount::Ed25519(key.0.into()), + source_account: key.into(), fee, seq_num: SequenceNumber(sequence), cond: Preconditions::None, @@ -307,7 +353,7 @@ fn build_create_contract_tx( ext: TransactionExt::V0, }; - Ok((tx, contract_id)) + Ok(tx) } #[cfg(test)] @@ -320,18 +366,26 @@ mod tests { .unwrap() .try_into() .unwrap(); + let salt = [0u8; 32]; + let key = + &utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") + .unwrap(); + let source_account = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( + key.verifying_key().to_bytes(), + ))); + + let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { + address: ScAddress::Account(source_account.clone()), + salt: Uint256(salt), + }); + let result = build_create_contract_tx( Hash(hash), 300, 1, - "Public Global Stellar Network ; September 2015", - [0u8; 32], - stellar_strkey::ed25519::PublicKey( - utils::parse_secret_key("SBFGFF27Y64ZUGFAIG5AMJGQODZZKV2YQKAVUUN4HNE24XZXD2OEUVUP") - .unwrap() - .verifying_key() - .to_bytes(), - ), + source_account, + contract_id_preimage, + None, ); assert!(result.is_ok()); diff --git a/cmd/soroban-cli/src/commands/contract/extend.rs b/cmd/soroban-cli/src/commands/contract/extend.rs index 3f7477254..e56cbe166 100644 --- a/cmd/soroban-cli/src/commands/contract/extend.rs +++ b/cmd/soroban-cli/src/commands/contract/extend.rs @@ -1,12 +1,12 @@ use std::{fmt::Debug, path::Path, str::FromStr}; -use clap::{command, Parser}; -use soroban_env_host::xdr::{ +use crate::xdr::{ Error as XdrError, ExtendFootprintTtlOp, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData, LedgerFootprint, Limits, Memo, Operation, OperationBody, Preconditions, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, WriteXdr, }; +use clap::{command, Parser}; use crate::{ assembled::simulate_and_assemble_transaction, @@ -16,9 +16,7 @@ use crate::{ NetworkRunnable, }, config::{self, data, locator, network}, - key, - rpc::{self, Client}, - wasm, Pwd, + key, rpc, wasm, Pwd, }; const MAX_LEDGERS_TO_EXTEND: u32 = 535_679; @@ -132,15 +130,17 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let keys = self.key.parse_keys(&config.locator, &network)?; - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; let source_account = config.source_account()?; let extend_to = self.ledgers_to_extend(); // Get the account sequence number - let account_details = client.get_account(&source_account.to_string()).await?; + let account_details = client + .get_account(&source_account.clone().to_string()) + .await?; let sequence: i64 = account_details.seq_num.into(); - let tx = Transaction { + let tx = Box::new(Transaction { source_account, fee: self.fee.fee, seq_num: SequenceNumber(sequence + 1), @@ -167,7 +167,7 @@ impl NetworkRunnable for Cmd { }, resource_fee: 0, }), - }; + }); if self.fee.build_only { return Ok(TxnResult::Txn(tx)); } @@ -186,10 +186,7 @@ impl NetworkRunnable for Cmd { if !events.is_empty() { tracing::info!("Events:\n {events:#?}"); } - let meta = res - .result_meta - .as_ref() - .ok_or(Error::MissingOperationResult)?; + let meta = res.result_meta.ok_or(Error::MissingOperationResult)?; // The transaction from core will succeed regardless of whether it actually found & extended // the entry, so we have to inspect the result meta to tell if it worked or not. diff --git a/cmd/soroban-cli/src/commands/contract/fetch.rs b/cmd/soroban-cli/src/commands/contract/fetch.rs index fd8070898..31ed191ff 100644 --- a/cmd/soroban-cli/src/commands/contract/fetch.rs +++ b/cmd/soroban-cli/src/commands/contract/fetch.rs @@ -7,12 +7,14 @@ use std::{fmt::Debug, fs, io}; use clap::{arg, command, Parser}; -use crate::commands::{global, NetworkRunnable}; -use crate::config::{ - self, locator, - network::{self, Network}, +use crate::{ + commands::{global, NetworkRunnable}, + config::{ + self, locator, + network::{self, Network}, + }, + wasm, Pwd, }; -use crate::{wasm, Pwd}; #[derive(Parser, Debug, Default, Clone)] #[allow(clippy::struct_excessive_bools)] @@ -20,7 +22,7 @@ use crate::{wasm, Pwd}; pub struct Cmd { /// Contract ID to fetch #[arg(long = "id", env = "STELLAR_CONTRACT_ID")] - pub contract_id: String, + pub contract_id: config::ContractAddress, /// Where to write output otherwise stdout is used #[arg(long, short = 'o')] pub out_file: Option, @@ -109,6 +111,12 @@ impl NetworkRunnable for Cmd { config: Option<&config::Args>, ) -> Result, Error> { let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?; - return Ok(wasm::fetch_from_contract(&self.contract_id, &network, &self.locator).await?); + Ok(wasm::fetch_from_contract( + &self + .contract_id + .resolve_contract_id(&self.locator, &network.network_passphrase)?, + &network, + ) + .await?) } } diff --git a/cmd/soroban-cli/src/commands/contract/id/asset.rs b/cmd/soroban-cli/src/commands/contract/id/asset.rs index cdf826015..921592cf8 100644 --- a/cmd/soroban-cli/src/commands/contract/id/asset.rs +++ b/cmd/soroban-cli/src/commands/contract/id/asset.rs @@ -20,7 +20,7 @@ pub enum Error { #[error(transparent)] ConfigError(#[from] config::Error), #[error(transparent)] - Xdr(#[from] soroban_env_host::xdr::Error), + Xdr(#[from] crate::xdr::Error), } impl Cmd { pub fn run(&self) -> Result<(), Error> { diff --git a/cmd/soroban-cli/src/commands/contract/id/wasm.rs b/cmd/soroban-cli/src/commands/contract/id/wasm.rs index c020e94b2..349dd167b 100644 --- a/cmd/soroban-cli/src/commands/contract/id/wasm.rs +++ b/cmd/soroban-cli/src/commands/contract/id/wasm.rs @@ -1,9 +1,9 @@ -use clap::{arg, command, Parser}; -use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{ +use crate::xdr::{ self, AccountId, ContractIdPreimage, ContractIdPreimageFromAddress, Hash, HashIdPreimage, HashIdPreimageContractId, Limits, PublicKey, ScAddress, Uint256, WriteXdr, }; +use clap::{arg, command, Parser}; +use sha2::{Digest, Sha256}; use crate::config; diff --git a/cmd/soroban-cli/src/commands/contract/info/env_meta.rs b/cmd/soroban-cli/src/commands/contract/info/env_meta.rs index 03225c328..bc2d03bc6 100644 --- a/cmd/soroban-cli/src/commands/contract/info/env_meta.rs +++ b/cmd/soroban-cli/src/commands/contract/info/env_meta.rs @@ -1,14 +1,17 @@ use std::fmt::Debug; use clap::{command, Parser}; -use stellar_xdr::curr::ScEnvMetaEntry; use soroban_spec_tools::contract; use soroban_spec_tools::contract::Spec; -use crate::commands::contract::info::env_meta::Error::{NoEnvMetaPresent, NoSACEnvMeta}; -use crate::commands::contract::info::shared; -use crate::commands::contract::info::shared::{fetch_wasm, MetasInfoOutput}; +use crate::{ + commands::contract::info::{ + env_meta::Error::{NoEnvMetaPresent, NoSACEnvMeta}, + shared::{self, fetch_wasm, MetasInfoOutput}, + }, + xdr::{ScEnvMetaEntry, ScEnvMetaEntryInterfaceVersion}, +}; #[derive(Parser, Debug, Clone)] pub struct Cmd { @@ -54,12 +57,16 @@ impl Cmd { let mut meta_str = "Contract env-meta:\n".to_string(); for env_meta_entry in &spec.env_meta { match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - let protocol = v >> 32; - let interface = v & 0xffff_ffff; + ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion( + ScEnvMetaEntryInterfaceVersion { + protocol, + pre_release, + }, + ) => { meta_str.push_str(&format!(" • Protocol: v{protocol}\n")); - meta_str.push_str(&format!(" • Interface: v{interface}\n")); - meta_str.push_str(&format!(" • Interface Version: {v}\n")); + if pre_release != &0 { + meta_str.push_str(&format!(" • Pre-release: v{pre_release}\n")); + } } } } diff --git a/cmd/soroban-cli/src/commands/contract/info/shared.rs b/cmd/soroban-cli/src/commands/contract/info/shared.rs index 25cbc9258..33a95b607 100644 --- a/cmd/soroban-cli/src/commands/contract/info/shared.rs +++ b/cmd/soroban-cli/src/commands/contract/info/shared.rs @@ -1,14 +1,14 @@ use std::path::PathBuf; use clap::arg; -use soroban_env_host::xdr; -use soroban_rpc::Client; -use crate::commands::contract::info::shared::Error::InvalidWasmHash; -use crate::config::{locator, network}; -use crate::utils::rpc::get_remote_wasm_from_hash; -use crate::wasm; -use crate::wasm::Error::ContractIsStellarAsset; +use crate::{ + commands::contract::info::shared::Error::InvalidWasmHash, + config::{self, locator, network}, + utils::rpc::get_remote_wasm_from_hash, + wasm::{self, Error::ContractIsStellarAsset}, + xdr, +}; #[derive(Debug, clap::Args, Clone, Default)] #[command(group( @@ -24,9 +24,9 @@ pub struct Args { /// Wasm hash to get the data for #[arg(long = "wasm-hash", group = "Source")] pub wasm_hash: Option, - /// Contract id to get the data for + /// Contract id or contract alias to get the data for #[arg(long = "id", env = "STELLAR_CONTRACT_ID", group = "Source")] - pub contract_id: Option, + pub contract_id: Option, #[command(flatten)] pub network: network::Args, #[command(flatten)] @@ -56,14 +56,21 @@ pub enum Error { InvalidWasmHash(String), #[error(transparent)] Rpc(#[from] soroban_rpc::Error), + #[error(transparent)] + Locator(#[from] locator::Error), } pub async fn fetch_wasm(args: &Args) -> Result>, Error> { - let network = &args.network.get(&args.locator)?; + // Check if a local WASM file path is provided + if let Some(path) = &args.wasm { + // Read the WASM file and return its contents + let wasm_bytes = wasm::Args { wasm: path.clone() }.read()?; + return Ok(Some(wasm_bytes)); + } - let wasm = if let Some(path) = &args.wasm { - wasm::Args { wasm: path.clone() }.read()? - } else if let Some(wasm_hash) = &args.wasm_hash { + // If no local wasm, then check for wasm_hash and fetch from the network + let network = &args.network.get(&args.locator)?; + let wasm = if let Some(wasm_hash) = &args.wasm_hash { let hash = hex::decode(wasm_hash) .map_err(|_| InvalidWasmHash(wasm_hash.clone()))? .try_into() @@ -71,14 +78,17 @@ pub async fn fetch_wasm(args: &Args) -> Result>, Error> { let hash = xdr::Hash(hash); - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; + client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; get_remote_wasm_from_hash(&client, &hash).await? } else if let Some(contract_id) = &args.contract_id { - let res = wasm::fetch_from_contract(contract_id, network, &args.locator).await; + let contract_id = + contract_id.resolve_contract_id(&args.locator, &network.network_passphrase)?; + let res = wasm::fetch_from_contract(&contract_id, network).await; if let Some(ContractIsStellarAsset) = res.as_ref().err() { return Ok(None); } diff --git a/cmd/soroban-cli/src/commands/contract/init.rs b/cmd/soroban-cli/src/commands/contract/init.rs index fd4cf483a..9147bacf0 100644 --- a/cmd/soroban-cli/src/commands/contract/init.rs +++ b/cmd/soroban-cli/src/commands/contract/init.rs @@ -1,85 +1,77 @@ -use clap::{ - builder::{PossibleValue, PossibleValuesParser, ValueParser}, - Parser, -}; -use gix::{clone, create, open, progress, remote}; -use rust_embed::RustEmbed; -use serde_json::{from_str, json, to_string_pretty, Error as JsonError, Value as JsonValue}; +use std::borrow::Cow; use std::{ - env, - ffi::OsStr, - fs::{ - copy, create_dir_all, metadata, read_dir, read_to_string, write, File, Metadata, - OpenOptions, - }, - io::{self, Read, Write}, - num::NonZeroU32, + fs::{create_dir_all, metadata, write, Metadata}, + io, path::{Path, PathBuf}, str, - sync::atomic::AtomicBool, }; -use toml_edit::{Document, TomlError}; -use crate::utils::http; -use crate::{commands::global, print}; +use clap::Parser; +use rust_embed::RustEmbed; -const SOROBAN_EXAMPLES_URL: &str = "https://github.com/stellar/soroban-examples.git"; -const GITHUB_URL: &str = "https://github.com"; -const WITH_EXAMPLE_LONG_HELP_TEXT: &str = - "An optional flag to specify Soroban example contracts to include. A hello-world contract will be included by default."; +use crate::{commands::global, error_on_use_of_removed_arg, print, utils}; + +const EXAMPLE_REMOVAL_NOTICE: &str = "Adding examples via cli is no longer supported. \ +You can still clone examples from the repo https://github.com/stellar/soroban-examples"; +const FRONTEND_EXAMPLE_REMOVAL_NOTICE: &str = "Using frontend template via cli is no longer \ +supported. You can search for frontend templates using github tags, \ +such as `soroban-template` or `soroban-frontend-template`"; #[derive(Parser, Debug, Clone)] #[group(skip)] pub struct Cmd { pub project_path: String, - #[arg(short, long, num_args = 1.., value_parser=possible_example_values(), long_help=WITH_EXAMPLE_LONG_HELP_TEXT)] - pub with_example: Vec, + #[arg( + long, + default_value = "hello-world", + long_help = "An optional flag to specify a new contract's name." + )] + pub name: String, + // TODO: remove in future version (23+) https://github.com/stellar/stellar-cli/issues/1586 #[arg( + short, long, - default_value = "", - long_help = "An optional flag to pass in a url for a frontend template repository." + hide = true, + display_order = 100, + value_parser = error_on_use_of_removed_arg!(String, EXAMPLE_REMOVAL_NOTICE) )] - pub frontend_template: String, + pub with_example: Option, + + // TODO: remove in future version (23+) https://github.com/stellar/stellar-cli/issues/1586 + #[arg( + long, + hide = true, + display_order = 100, + value_parser = error_on_use_of_removed_arg!(String, FRONTEND_EXAMPLE_REMOVAL_NOTICE), + )] + pub frontend_template: Option, #[arg(long, long_help = "Overwrite all existing files.")] pub overwrite: bool, } -fn possible_example_values() -> ValueParser { - let example_contracts = include_str!("../../../example_contracts.list") - .lines() - .collect::>(); - let parser = PossibleValuesParser::new(example_contracts.iter().map(PossibleValue::new)); - parser.into() -} - #[derive(thiserror::Error, Debug)] pub enum Error { #[error("{0}: {1}")] Io(String, io::Error), - #[error("io error:")] - StdIo(#[from] io::Error), - - #[error("{0}: {1}")] - Json(String, JsonError), - - #[error("failed to parse toml file: {0}")] - TomlParse(#[from] TomlError), + #[error(transparent)] + Std(#[from] std::io::Error), #[error("failed to convert bytes to string: {0}")] ConvertBytesToString(#[from] str::Utf8Error), - #[error("preparing fetch repository: {0}")] - PrepareFetch(Box), + #[error("contract package already exists: {0}")] + AlreadyExists(String), - #[error("failed to fetch repository: {0}")] - Fetch(Box), + #[error("provided project path exists and is not a directory")] + PathExistsNotDir, - #[error("failed to checkout main worktree: {0}")] - Checkout(#[from] clone::checkout::main_worktree::Error), + #[error("provided project path exists and is not a cargo workspace root directory. Hint: run init on an empty or non-existing directory" + )] + PathExistsNotCargoProject, } impl Cmd { @@ -95,8 +87,13 @@ impl Cmd { } #[derive(RustEmbed)] -#[folder = "src/utils/contract-init-template"] -struct TemplateFiles; +#[folder = "src/utils/contract-workspace-template"] +struct WorkspaceTemplateFiles; + +#[derive(RustEmbed)] +#[folder = "src/utils/contract-template"] +struct ContractTemplateFiles; + struct Runner { args: Cmd, print: print::Print, @@ -106,53 +103,49 @@ impl Runner { fn run(&self) -> Result<(), Error> { let project_path = PathBuf::from(&self.args.project_path); self.print - .infoln(format!("Initializing project at {project_path:?}")); + .infoln(format!("Initializing workspace at {project_path:?}")); // create a project dir, and copy the contents of the base template (contract-init-template) into it Self::create_dir_all(&project_path)?; - self.copy_template_files()?; - - if !Self::check_internet_connection() { - self.print.warnln("It doesn't look like you're connected to the internet. We're still able to initialize a new project, but additional examples and the frontend template will not be included."); - return Ok(()); - } - - if !self.args.frontend_template.is_empty() { - // create a temp dir for the template repo - let fe_template_dir = tempfile::tempdir() - .map_err(|e| Error::Io("creating temp dir for frontend template".to_string(), e))?; + self.copy_template_files( + project_path.as_path(), + &mut WorkspaceTemplateFiles::iter(), + WorkspaceTemplateFiles::get, + )?; - // clone the template repo into the temp dir - Self::clone_repo(&self.args.frontend_template, fe_template_dir.path())?; - - // copy the frontend template files into the project - self.copy_frontend_files(fe_template_dir.path(), &project_path)?; - } + let contract_path = project_path.join("contracts").join(&self.args.name); + self.print + .infoln(format!("Initializing contract at {contract_path:?}")); - // if there are --with-example flags, include the example contracts - if self.include_example_contracts() { - // create an examples temp dir - let examples_dir = tempfile::tempdir() - .map_err(|e| Error::Io("creating temp dir for soroban-examples".to_string(), e))?; - - // clone the soroban-examples repo into the temp dir - Self::clone_repo(SOROBAN_EXAMPLES_URL, examples_dir.path())?; - - // copy the example contracts into the project - self.copy_example_contracts( - examples_dir.path(), - &project_path, - &self.args.with_example, - )?; - } + Self::create_dir_all(contract_path.as_path())?; + self.copy_template_files( + contract_path.as_path(), + &mut ContractTemplateFiles::iter(), + ContractTemplateFiles::get, + )?; Ok(()) } - fn copy_template_files(&self) -> Result<(), Error> { - let project_path = Path::new(&self.args.project_path); - for item in TemplateFiles::iter() { - let mut to = project_path.join(item.as_ref()); + fn copy_template_files( + &self, + root_path: &Path, + files: &mut dyn Iterator>, + getter: fn(&str) -> Option, + ) -> Result<(), Error> { + for item in &mut *files { + let mut to = root_path.join(item.as_ref()); + // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template + // so that it will be included the package. This is making sure that the Cargo file is + // written as Cargo.toml in the new project. This is a workaround for this issue: + // https://github.com/rust-lang/cargo/issues/8597. + let item_path = Path::new(item.as_ref()); + let is_toml = item_path.file_name().unwrap() == "Cargo.toml.removeextension"; + if is_toml { + let item_parent_path = item_path.parent().unwrap(); + to = root_path.join(item_parent_path).join("Cargo.toml"); + } + let exists = Self::file_exists(&to); if exists && !self.args.overwrite { self.print @@ -162,20 +155,19 @@ impl Runner { Self::create_dir_all(to.parent().unwrap())?; - let Some(file) = TemplateFiles::get(item.as_ref()) else { + let Some(file) = getter(item.as_ref()) else { self.print .warnln(format!("Failed to read file: {}", item.as_ref())); continue; }; - let file_contents = - std::str::from_utf8(file.data.as_ref()).map_err(Error::ConvertBytesToString)?; + let mut file_contents = str::from_utf8(file.data.as_ref()) + .map_err(Error::ConvertBytesToString)? + .to_string(); - // We need to include the Cargo.toml file as Cargo.toml.removeextension in the template so that it will be included the package. This is making sure that the Cargo file is written as Cargo.toml in the new project. This is a workaround for this issue: https://github.com/rust-lang/cargo/issues/8597. - let item_path = Path::new(item.as_ref()); - if item_path.file_name().unwrap() == "Cargo.toml.removeextension" { - let item_parent_path = item_path.parent().unwrap(); - to = project_path.join(item_parent_path).join("Cargo.toml"); + if is_toml { + let new_content = file_contents.replace("%contract-template%", &self.args.name); + file_contents = new_content; } if exists { @@ -184,70 +176,7 @@ impl Runner { } else { self.print.plusln(format!("Writing {to:?}")); } - Self::write(&to, file_contents)?; - } - Ok(()) - } - - fn copy_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { - let contents_to_exclude_from_copy = [ - ".git", - ".github", - "Makefile", - ".vscode", - "target", - "Cargo.lock", - ]; - for entry in - read_dir(from).map_err(|e| Error::Io(format!("reading directory: {from:?}"), e))? - { - let entry = - entry.map_err(|e| Error::Io(format!("reading entry in directory {from:?}",), e))?; - let path = entry.path(); - let entry_name = entry.file_name().to_string_lossy().to_string(); - let new_path = to.join(&entry_name); - - if contents_to_exclude_from_copy.contains(&entry_name.as_str()) { - continue; - } - - if path.is_dir() { - Self::create_dir_all(&new_path)?; - self.copy_contents(&path, &new_path)?; - } else { - let exists = Self::file_exists(&new_path); - let new_path_str = new_path.to_string_lossy(); - if exists { - let append = - new_path_str.contains(".gitignore") || new_path_str.contains("README.md"); - if append { - self.append_contents(&path, &new_path)?; - } - - if self.args.overwrite && !append { - self.print.plusln(format!( - "Writing {new_path_str} (overwriting existing file)" - )); - } else { - self.print.infoln(format!( - "Skipped creating {new_path_str} as it already exists" - )); - continue; - } - } else { - self.print.plus(format!("Writing {new_path_str}")); - } - copy(&path, &new_path).map_err(|e| { - Error::Io( - format!( - "copying from {:?} to {:?}", - path.to_string_lossy(), - new_path - ), - e, - ) - })?; - } + Self::write(&to, &file_contents)?; } Ok(()) @@ -260,172 +189,6 @@ impl Runner { .unwrap_or(false) } - fn check_internet_connection() -> bool { - if let Ok(_req) = http::blocking_client().get(GITHUB_URL).send() { - return true; - } - - false - } - - fn include_example_contracts(&self) -> bool { - !self.args.with_example.is_empty() - } - - fn clone_repo(from_url: &str, to_path: &Path) -> Result<(), Error> { - let mut prepare = clone::PrepareFetch::new( - from_url, - to_path, - create::Kind::WithWorktree, - create::Options { - destination_must_be_empty: false, - fs_capabilities: None, - }, - open::Options::isolated(), - ) - .map_err(|e| Error::PrepareFetch(Box::new(e)))? - .with_shallow(remote::fetch::Shallow::DepthAtRemote( - NonZeroU32::new(1).unwrap(), - )); - - let (mut checkout, _outcome) = prepare - .fetch_then_checkout(progress::Discard, &AtomicBool::new(false)) - .map_err(|e| Error::Fetch(Box::new(e)))?; - let (_repo, _outcome) = checkout - .main_worktree(progress::Discard, &AtomicBool::new(false)) - .map_err(Error::Checkout)?; - - Ok(()) - } - - fn copy_example_contracts( - &self, - from: &Path, - to: &Path, - contracts: &[String], - ) -> Result<(), Error> { - let project_contracts_path = to.join("contracts"); - for contract in contracts { - self.print - .infoln(format!("Initializing example contract: {contract}")); - let contract_as_string = contract.to_string(); - let contract_path = Path::new(&contract_as_string); - let from_contract_path = from.join(contract_path); - let to_contract_path = project_contracts_path.join(contract_path); - Self::create_dir_all(&to_contract_path)?; - - self.copy_contents(&from_contract_path, &to_contract_path)?; - Self::edit_contract_cargo_file(&to_contract_path)?; - } - - Ok(()) - } - - fn edit_contract_cargo_file(contract_path: &Path) -> Result<(), Error> { - let cargo_path = contract_path.join("Cargo.toml"); - - let cargo_toml_str = Self::read_to_string(&cargo_path)?; - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = "[^\"]+""#) - .unwrap() - .replace_all( - cargo_toml_str.as_str(), - "soroban-sdk = { workspace = true }", - ); - - let cargo_toml_str = regex::Regex::new(r#"soroban-sdk = \{(.*) version = "[^"]+"(.+)}"#) - .unwrap() - .replace_all(&cargo_toml_str, "soroban-sdk = {$1 workspace = true$2}"); - - let mut doc = cargo_toml_str - .parse::() - .map_err(Error::TomlParse)?; - doc.remove("profile"); - - Self::write(&cargo_path, &doc.to_string())?; - - Ok(()) - } - - fn copy_frontend_files(&self, from: &Path, to: &Path) -> Result<(), Error> { - self.print.infoln("Initializing with frontend template"); - self.copy_contents(from, to)?; - Self::edit_package_json_files(to) - } - - fn edit_package_json_files(project_path: &Path) -> Result<(), Error> { - let package_name = if let Some(name) = project_path.file_name() { - name.to_owned() - } else { - let current_dir = env::current_dir() - .map_err(|e| Error::Io("getting current dir from env".to_string(), e))?; - let file_name = current_dir - .file_name() - .unwrap_or(OsStr::new("soroban-astro-template")) - .to_os_string(); - file_name - }; - - Self::edit_package_name(project_path, &package_name, "package.json")?; - Self::edit_package_name(project_path, &package_name, "package-lock.json") - } - - fn edit_package_name( - project_path: &Path, - package_name: &OsStr, - file_name: &str, - ) -> Result<(), Error> { - let file_path = project_path.join(file_name); - let file_contents = Self::read_to_string(&file_path)?; - - let mut doc: JsonValue = from_str(&file_contents).map_err(|e| { - Error::Json(format!("parsing {file_name} file in: {project_path:?}"), e) - })?; - - doc["name"] = json!(package_name.to_string_lossy()); - - let formatted_json = to_string_pretty(&doc) - .map_err(|e| Error::Json("calling to_string_pretty for package.json".to_string(), e))?; - - Self::write(&file_path, &formatted_json)?; - - Ok(()) - } - - // Appends the contents of a file to another file, separated by a delimiter - fn append_contents(&self, from: &Path, to: &Path) -> Result<(), Error> { - let mut from_file = File::open(from)?; - let mut from_content = String::new(); - from_file.read_to_string(&mut from_content)?; - - let mut to_file = OpenOptions::new().read(true).append(true).open(to)?; - let mut to_content = String::new(); - to_file.read_to_string(&mut to_content)?; - - let delimiter = Self::get_merged_file_delimiter(to); - // if the to file already contains the delimiter, we don't need to append the contents again - if to_content.contains(&delimiter) { - return Ok(()); - } - - to_file.write_all(delimiter.as_bytes())?; - to_file.write_all(from_content.as_bytes())?; - - self.print.infoln(format!("Merging {to:?} contents")); - Ok(()) - } - - fn get_merged_file_delimiter(file_path: &Path) -> String { - let comment = if file_path.to_string_lossy().contains("README.md") { - "---\n".to_string() - } else if file_path.to_string_lossy().contains("gitignore") { - "# The following is from the Frontend Template's .gitignore".to_string() - } else { - String::new() - }; - - format!("\n\n{comment}\n\n").to_string() - } - fn create_dir_all(path: &Path) -> Result<(), Error> { create_dir_all(path).map_err(|e| Error::Io(format!("creating directory: {path:?}"), e)) } @@ -433,22 +196,14 @@ impl Runner { fn write(path: &Path, contents: &str) -> Result<(), Error> { write(path, contents).map_err(|e| Error::Io(format!("writing file: {path:?}"), e)) } - - fn read_to_string(path: &Path) -> Result { - read_to_string(path).map_err(|e| Error::Io(format!("reading file: {path:?}"), e)) - } } #[cfg(test)] mod tests { + use std::fs; + use std::fs::read_to_string; + use itertools::Itertools; - use std::{ - collections::HashMap, - fs::{self, read_to_string}, - path::PathBuf, - time::SystemTime, - }; - use walkdir::WalkDir; use super::*; @@ -461,115 +216,9 @@ mod tests { let runner = Runner { args: Cmd { project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: String::new(), - overwrite: false, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - assert_base_template_files_exist(&project_dir); - assert_default_hello_world_contract_files_exist(&project_dir); - assert_excluded_paths_do_not_exist(&project_dir); - - assert_contract_cargo_file_is_well_formed(&project_dir, "hello_world"); - - assert_excluded_paths_do_not_exist(&project_dir); - - temp_dir.close().unwrap(); - } - - #[test] - fn test_init_including_example_contract() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: ["alloc".to_owned()].to_vec(), - frontend_template: String::new(), - overwrite: false, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - assert_base_template_files_exist(&project_dir); - assert_default_hello_world_contract_files_exist(&project_dir); - assert_excluded_paths_do_not_exist(&project_dir); - - // check that alloc contract files exist - assert_contract_files_exist(&project_dir, "alloc"); - - // check that expected files are excluded from the alloc contract dir - assert_example_contract_excluded_files_do_not_exist(&project_dir, "alloc"); - - // check that the alloc contract's Cargo.toml file uses the workspace for dependencies - assert_contract_cargo_file_is_well_formed(&project_dir, "alloc"); - - temp_dir.close().unwrap(); - } - - #[test] - fn test_init_including_multiple_example_contracts() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join("project"); - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: ["account".to_owned(), "atomic_swap".to_owned()].to_vec(), - frontend_template: String::new(), - overwrite: false, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - assert_base_template_files_exist(&project_dir); - assert_default_hello_world_contract_files_exist(&project_dir); - assert_excluded_paths_do_not_exist(&project_dir); - - // check that account contract files exist and that expected files are excluded - assert_contract_files_exist(&project_dir, "account"); - assert_example_contract_excluded_files_do_not_exist(&project_dir, "account"); - assert_contract_cargo_file_is_well_formed(&project_dir, "account"); - - // check that atomic_swap contract files exist and that expected files are excluded - assert_contract_files_exist(&project_dir, "atomic_swap"); - assert_example_contract_excluded_files_do_not_exist(&project_dir, "atomic_swap"); - assert_contract_cargo_file_is_well_formed(&project_dir, "atomic_swap"); - - temp_dir.close().unwrap(); - } - - #[test] - fn test_init_with_invalid_example_contract() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join("project"); - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: ["invalid_example".to_owned(), "atomic_swap".to_owned()].to_vec(), - frontend_template: String::new(), - overwrite: false, - }, - print: print::Print::new(false), - }; - assert!(runner.run().is_err()); - - temp_dir.close().unwrap(); - } - - #[test] - fn test_init_with_frontend_template() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + name: "hello_world".to_string(), + with_example: None, + frontend_template: None, overwrite: false, }, print: print::Print::new(false), @@ -577,158 +226,31 @@ mod tests { runner.run().unwrap(); assert_base_template_files_exist(&project_dir); - assert_default_hello_world_contract_files_exist(&project_dir); - assert_excluded_paths_do_not_exist(&project_dir); - // check that the contract's Cargo.toml file uses the workspace for dependencies - assert_contract_cargo_file_is_well_formed(&project_dir, "hello_world"); + assert_contract_files_exist(&project_dir, "hello_world"); assert_excluded_paths_do_not_exist(&project_dir); - assert_astro_files_exist(&project_dir); - assert_gitignore_includes_astro_paths(&project_dir); - assert_package_json_files_have_correct_name(&project_dir, TEST_PROJECT_NAME); - assert_readme_includes_frontend_readme_appended(&project_dir); - - temp_dir.close().unwrap(); - } - - #[test] - fn test_init_with_overwrite() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - - // First initialization - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), - overwrite: false, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - // Get initial modification times - let initial_mod_times = get_mod_times(&project_dir); - - // Second initialization with overwrite - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), - overwrite: true, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - // Get new modification times - let new_mod_times = get_mod_times(&project_dir); - - // Compare modification times - for (path, initial_time) in initial_mod_times { - let new_time = new_mod_times.get(&path).expect("File should still exist"); - assert!( - new_time > &initial_time, - "File {} should have a later modification time", - path.display() - ); - } - - temp_dir.close().unwrap(); - } - - fn get_mod_times(dir: &Path) -> HashMap { - let mut mod_times = HashMap::new(); - for entry in WalkDir::new(dir) { - let entry = entry.unwrap(); - if entry.file_type().is_file() { - let path = entry.path().to_owned(); - let metadata = fs::metadata(&path).unwrap(); - mod_times.insert(path, metadata.modified().unwrap()); - } - } - mod_times - } - - #[test] - fn test_init_from_within_an_existing_project() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join("./"); - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), - overwrite: false, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - assert_base_template_files_exist(&project_dir); - assert_default_hello_world_contract_files_exist(&project_dir); - assert_excluded_paths_do_not_exist(&project_dir); - - // check that the contract's Cargo.toml file uses the workspace for dependencies assert_contract_cargo_file_is_well_formed(&project_dir, "hello_world"); assert_excluded_paths_do_not_exist(&project_dir); - assert_astro_files_exist(&project_dir); - assert_gitignore_includes_astro_paths(&project_dir); - assert_package_json_files_have_correct_name( - &project_dir, - &project_dir.file_name().unwrap().to_string_lossy(), - ); - - temp_dir.close().unwrap(); - } - - #[test] - fn test_init_does_not_duplicate_frontend_readme_contents_when_run_more_than_once() { - let temp_dir = tempfile::tempdir().unwrap(); - let project_dir = temp_dir.path().join(TEST_PROJECT_NAME); - let runner = Runner { - args: Cmd { - project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), - overwrite: false, - }, - print: print::Print::new(false), - }; - runner.run().unwrap(); - - // call init again to make sure the README.md's contents are not duplicated let runner = Runner { args: Cmd { project_path: project_dir.to_string_lossy().to_string(), - with_example: vec![], - frontend_template: "https://github.com/stellar/soroban-astro-template".to_owned(), + name: "contract2".to_string(), + with_example: None, + frontend_template: None, overwrite: false, }, print: print::Print::new(false), }; runner.run().unwrap(); - assert_base_template_files_exist(&project_dir); - assert_default_hello_world_contract_files_exist(&project_dir); + assert_contract_files_exist(&project_dir, "contract2"); assert_excluded_paths_do_not_exist(&project_dir); - // check that the contract's Cargo.toml file uses the workspace for dependencies - assert_contract_cargo_file_is_well_formed(&project_dir, "hello_world"); + assert_contract_cargo_file_is_well_formed(&project_dir, "contract2"); assert_excluded_paths_do_not_exist(&project_dir); - assert_astro_files_exist(&project_dir); - assert_gitignore_includes_astro_paths(&project_dir); - assert_package_json_files_have_correct_name( - &project_dir, - &project_dir.file_name().unwrap().to_string_lossy(), - ); - assert_readme_includes_frontend_readme_appended(&project_dir); - temp_dir.close().unwrap(); } @@ -740,10 +262,6 @@ mod tests { } } - fn assert_default_hello_world_contract_files_exist(project_dir: &Path) { - assert_contract_files_exist(project_dir, "hello_world"); - } - fn assert_contract_files_exist(project_dir: &Path, contract_name: &str) { let contract_dir = project_dir.join("contracts").join(contract_name); @@ -757,7 +275,7 @@ mod tests { let contract_dir = project_dir.join("contracts").join(contract_name); let cargo_toml_path = contract_dir.as_path().join("Cargo.toml"); let cargo_toml_str = read_to_string(cargo_toml_path.clone()).unwrap(); - let doc = cargo_toml_str.parse::().unwrap(); + let doc: toml_edit::DocumentMut = cargo_toml_str.parse().unwrap(); assert!( doc.get("dependencies") .unwrap() @@ -813,21 +331,13 @@ mod tests { ); } - fn assert_example_contract_excluded_files_do_not_exist( - project_dir: &Path, - contract_name: &str, - ) { - let contract_dir = project_dir.join("contracts").join(contract_name); - assert!(!contract_dir.as_path().join("Makefile").exists()); - } - fn assert_excluded_paths_do_not_exist(project_dir: &Path) { let base_excluded_paths = [".git", ".github", "Makefile", ".vscode", "target"]; for path in &base_excluded_paths { let filepath = project_dir.join(path); assert!(!filepath.exists(), "{filepath:?} should not exist"); } - let contract_excluded_paths = ["Makefile", "target", "Cargo.lock"]; + let contract_excluded_paths = ["target", "Cargo.lock"]; let contract_dirs = fs::read_dir(project_dir.join("contracts")) .unwrap() .map(|entry| entry.unwrap().path()); @@ -838,50 +348,4 @@ mod tests { assert!(!filepath.exists(), "{filepath:?} should not exist"); }); } - - fn assert_gitignore_includes_astro_paths(project_dir: &Path) { - let gitignore_path = project_dir.join(".gitignore"); - let gitignore_str = read_to_string(gitignore_path).unwrap(); - assert!(gitignore_str.contains(".astro/")); - assert!(gitignore_str.contains("node_modules")); - assert!(gitignore_str.contains("npm-debug.log*")); - } - - fn assert_astro_files_exist(project_dir: &Path) { - assert!(project_dir.join("public").exists()); - assert!(project_dir.join("src").exists()); - assert!(project_dir.join("src").join("components").exists()); - assert!(project_dir.join("src").join("layouts").exists()); - assert!(project_dir.join("src").join("pages").exists()); - assert!(project_dir.join("astro.config.mjs").exists()); - assert!(project_dir.join("tsconfig.json").exists()); - } - - fn assert_package_json_files_have_correct_name( - project_dir: &Path, - expected_package_name: &str, - ) { - let package_json_path = project_dir.join("package.json"); - let package_json_str = read_to_string(package_json_path).unwrap(); - assert!(package_json_str.contains(&format!("\"name\": \"{expected_package_name}\""))); - - let package_lock_json_path = project_dir.join("package-lock.json"); - let package_lock_json_str = read_to_string(package_lock_json_path).unwrap(); - assert!(package_lock_json_str.contains(&format!("\"name\": \"{expected_package_name}\""))); - } - - fn assert_readme_includes_frontend_readme_appended(project_dir: &Path) { - let readme_path = project_dir.join("README.md"); - let readme_str = read_to_string(readme_path).unwrap(); - assert!(readme_str.contains("Soroban Frontend in Astro")); - let expected = "The following is the Frontend Template's README.md"; - assert!(readme_str.contains(expected)); - - let readme_path = project_dir.join("README.md"); - let readme_str = read_to_string(readme_path).unwrap(); - let readme_frontend_merge_delimiter = "The following is the Frontend Template's README.md"; - let count = readme_str.matches(readme_frontend_merge_delimiter).count(); - // making sure it is in there just once so that it isn't duplicated if `contract init` is run again - assert!(count == 1); - } } diff --git a/cmd/soroban-cli/src/commands/contract/inspect.rs b/cmd/soroban-cli/src/commands/contract/inspect.rs index 46fdc33a7..86559641b 100644 --- a/cmd/soroban-cli/src/commands/contract/inspect.rs +++ b/cmd/soroban-cli/src/commands/contract/inspect.rs @@ -1,5 +1,5 @@ +use crate::xdr; use clap::{command, Parser}; -use soroban_env_host::xdr; use soroban_spec_tools::contract; use std::{fmt::Debug, path::PathBuf}; use tracing::debug; diff --git a/cmd/soroban-cli/src/commands/contract/install.rs b/cmd/soroban-cli/src/commands/contract/install.rs index b70cadb78..0a9ec856d 100644 --- a/cmd/soroban-cli/src/commands/contract/install.rs +++ b/cmd/soroban-cli/src/commands/contract/install.rs @@ -2,23 +2,28 @@ use std::array::TryFromSliceError; use std::fmt::Debug; use std::num::ParseIntError; -use clap::{command, Parser}; -use soroban_env_host::xdr::{ +use crate::xdr::{ self, ContractCodeEntryExt, Error as XdrError, Hash, HostFunction, InvokeHostFunctionOp, LedgerEntryData, Limits, OperationBody, ReadXdr, ScMetaEntry, ScMetaV0, Transaction, TransactionResult, TransactionResultResult, VecM, WriteXdr, }; +use clap::{command, Parser}; use super::restore; -use crate::assembled::simulate_and_assemble_transaction; -use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; -use crate::commands::{global, NetworkRunnable}; -use crate::config::{self, data, network}; -use crate::key; -use crate::print::Print; -use crate::rpc::{self, Client}; -use crate::tx::builder::{self, TxExt}; -use crate::{utils, wasm}; +use crate::{ + assembled::simulate_and_assemble_transaction, + commands::{ + global, + txn_result::{TxnEnvelopeResult, TxnResult}, + NetworkRunnable, + }, + config::{self, data, network}, + key, + print::Print, + rpc, + tx::builder::{self, TxExt}, + utils, wasm, +}; const CONTRACT_META_SDK_KEY: &str = "rssdkver"; const PUBLIC_NETWORK_PASSPHRASE: &str = "Public Global Stellar Network ; September 2015"; @@ -103,7 +108,7 @@ impl NetworkRunnable for Cmd { let config = config.unwrap_or(&self.config); let contract = self.wasm.read()?; let network = config.get_network()?; - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; @@ -132,14 +137,16 @@ impl NetworkRunnable for Cmd { // Get the account sequence number let source_account = config.source_account()?; - let account_details = client.get_account(&source_account.to_string()).await?; + let account_details = client + .get_account(&source_account.clone().to_string()) + .await?; let sequence: i64 = account_details.seq_num.into(); let (tx_without_preflight, hash) = build_install_contract_code_tx(&contract, sequence + 1, self.fee.fee, &source_account)?; if self.fee.build_only { - return Ok(TxnResult::Txn(tx_without_preflight)); + return Ok(TxnResult::Txn(Box::new(tx_without_preflight))); } // Don't check whether the contract is already installed when the user @@ -179,7 +186,7 @@ impl NetworkRunnable for Cmd { print.infoln("Simulating install transaction…"); let txn = simulate_and_assemble_transaction(&client, &tx_without_preflight).await?; - let txn = self.fee.apply_to_assembled_txn(txn).transaction().clone(); + let txn = Box::new(self.fee.apply_to_assembled_txn(txn).transaction().clone()); if self.fee.sim_only { return Ok(TxnResult::Txn(txn)); @@ -188,7 +195,7 @@ impl NetworkRunnable for Cmd { print.globeln("Submitting install transaction…"); let txn_resp = client - .send_transaction_polling(&self.config.sign_with_local_key(txn).await?) + .send_transaction_polling(&self.config.sign_with_local_key(*txn).await?) .await?; if args.map_or(true, |a| !a.no_cache) { @@ -199,7 +206,7 @@ impl NetworkRunnable for Cmd { if let Some(TransactionResult { result: TransactionResultResult::TxInternalError, .. - }) = txn_resp.result.as_ref() + }) = txn_resp.result { // Now just need to restore it and don't have to install again restore::Cmd { diff --git a/cmd/soroban-cli/src/commands/contract/invoke.rs b/cmd/soroban-cli/src/commands/contract/invoke.rs index 55e2554bf..c7b631343 100644 --- a/cmd/soroban-cli/src/commands/contract/invoke.rs +++ b/cmd/soroban-cli/src/commands/contract/invoke.rs @@ -7,32 +7,30 @@ use std::{fmt::Debug, fs, io}; use clap::{arg, command, Parser, ValueEnum}; -use soroban_env_host::{ - xdr::{ - self, AccountEntry, AccountEntryExt, AccountId, ContractEvent, ContractEventType, - DiagnosticEvent, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, LedgerEntryData, - Limits, Memo, MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, - ScSpecEntry, SequenceNumber, String32, StringM, Thresholds, Transaction, TransactionExt, - Uint256, VecM, WriteXdr, - }, - HostError, -}; - -use soroban_rpc::{SimulateHostFunctionResult, SimulateTransactionResponse}; +use soroban_rpc::{Client, SimulateHostFunctionResult, SimulateTransactionResponse}; use soroban_spec::read::FromWasmError; use super::super::events; use super::arg_parsing; -use crate::assembled::simulate_and_assemble_transaction; -use crate::commands::contract::arg_parsing::{build_host_function_parameters, output_to_string}; -use crate::commands::txn_result::{TxnEnvelopeResult, TxnResult}; -use crate::commands::NetworkRunnable; -use crate::get_spec::{self, get_remote_contract_spec}; -use crate::print; use crate::{ - commands::global, + assembled::simulate_and_assemble_transaction, + commands::{ + contract::arg_parsing::{build_host_function_parameters, output_to_string}, + global, + txn_result::{TxnEnvelopeResult, TxnResult}, + NetworkRunnable, + }, config::{self, data, locator, network}, - rpc, Pwd, + get_spec::{self, get_remote_contract_spec}, + print, rpc, + xdr::{ + self, AccountEntry, AccountEntryExt, AccountId, ContractEvent, ContractEventType, + DiagnosticEvent, HostFunction, InvokeContractArgs, InvokeHostFunctionOp, Limits, Memo, + MuxedAccount, Operation, OperationBody, Preconditions, PublicKey, ScSpecEntry, + SequenceNumber, String32, StringM, Thresholds, Transaction, TransactionExt, Uint256, VecM, + WriteXdr, + }, + Pwd, }; use soroban_spec_tools::contract; @@ -42,7 +40,7 @@ use soroban_spec_tools::contract; pub struct Cmd { /// Contract ID to invoke #[arg(long = "id", env = "STELLAR_CONTRACT_ID")] - pub contract_id: String, + pub contract_id: config::ContractAddress, // For testing only #[arg(skip)] pub wasm: Option, @@ -80,10 +78,6 @@ impl Pwd for Cmd { pub enum Error { #[error("cannot add contract to ledger entries: {0}")] CannotAddContractToLedgerEntries(xdr::Error), - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), #[error("reading file {0:?}: {1}")] CannotReadContractFile(PathBuf, io::Error), #[error("committing file {filepath}: {error}")] @@ -99,8 +93,6 @@ pub enum Error { ParseIntError(#[from] ParseIntError), #[error(transparent)] Rpc(#[from] rpc::Error), - #[error("unexpected contract code data type: {0:?}")] - UnexpectedContractCodeDataType(LedgerEntryData), #[error("missing operation result")] MissingOperationResult, #[error("error loading signing key: {0}")] @@ -169,7 +161,7 @@ impl Cmd { .transpose() } - fn send(&self, sim_res: &SimulateTransactionResponse) -> Result { + fn should_send_tx(&self, sim_res: &SimulateTransactionResponse) -> Result { Ok(match self.send { Send::Default => { if self.is_view { @@ -185,6 +177,28 @@ impl Cmd { Send::Yes => ShouldSend::Yes, }) } + + // uses a default account to check if the tx should be sent after the simulation + async fn should_send_after_sim( + &self, + host_function_params: InvokeContractArgs, + rpc_client: Client, + ) -> Result { + let account_details = default_account_entry(); + let sequence: i64 = account_details.seq_num.into(); + let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; + + let tx = build_invoke_contract_tx( + host_function_params.clone(), + sequence + 1, + self.fee.fee, + account_id, + )?; + let txn = simulate_and_assemble_transaction(&rpc_client, &tx).await?; + let txn = self.fee.apply_to_assembled_txn(txn); // do we need this part? + let sim_res = txn.sim_response(); + self.should_send_tx(sim_res) + } } #[async_trait::async_trait] @@ -201,29 +215,15 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let contract_id = self - .config - .locator - .resolve_contract_id(&self.contract_id, &network.network_passphrase)?; + .contract_id + .resolve_contract_id(&config.locator, &network.network_passphrase)?; let spec_entries = self.spec_entries()?; if let Some(spec_entries) = &spec_entries { // For testing wasm arg parsing let _ = build_host_function_parameters(&contract_id, &self.slop, spec_entries, config)?; } - let client = rpc::Client::new(&network.rpc_url)?; - let account_details = if self.is_view { - default_account_entry() - } else { - client - .verify_network_passphrase(Some(&network.network_passphrase)) - .await?; - - client - .get_account(&config.source_account()?.to_string()) - .await? - }; - let sequence: i64 = account_details.seq_num.into(); - let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; + let client = network.rpc_client()?; let spec_entries = get_remote_contract_spec( &contract_id.0, @@ -235,38 +235,56 @@ impl NetworkRunnable for Cmd { .await .map_err(Error::from)?; - // Get the ledger footprint let (function, spec, host_function_params, signers) = build_host_function_parameters(&contract_id, &self.slop, &spec_entries, config)?; - let tx = build_invoke_contract_tx( + + let should_send_tx = self + .should_send_after_sim(host_function_params.clone(), client.clone()) + .await?; + + let account_details = if should_send_tx == ShouldSend::Yes { + client + .verify_network_passphrase(Some(&network.network_passphrase)) + .await?; + + client + .get_account(&config.source_account()?.to_string()) + .await? + } else { + default_account_entry() + }; + let sequence: i64 = account_details.seq_num.into(); + let AccountId(PublicKey::PublicKeyTypeEd25519(account_id)) = account_details.account_id; + + let tx = Box::new(build_invoke_contract_tx( host_function_params.clone(), sequence + 1, self.fee.fee, account_id, - )?; + )?); if self.fee.build_only { return Ok(TxnResult::Txn(tx)); } let txn = simulate_and_assemble_transaction(&client, &tx).await?; - let txn = self.fee.apply_to_assembled_txn(txn); + let assembled = self.fee.apply_to_assembled_txn(txn); + let mut txn = Box::new(assembled.transaction().clone()); if self.fee.sim_only { - return Ok(TxnResult::Txn(txn.transaction().clone())); + return Ok(TxnResult::Txn(txn)); } - let sim_res = txn.sim_response(); + let sim_res = assembled.sim_response(); if global_args.map_or(true, |a| !a.no_cache) { data::write(sim_res.clone().into(), &network.rpc_uri()?)?; } - let should_send = self.send(sim_res)?; + let should_send = self.should_send_tx(sim_res)?; let (return_value, events) = match should_send { ShouldSend::Yes => { let global::Args { no_cache, .. } = global_args.cloned().unwrap_or_default(); // Need to sign all auth entries - let mut txn = txn.transaction().clone(); if let Some(tx) = config.sign_soroban_authorizations(&txn, &signers).await? { - txn = tx; + txn = Box::new(tx); } let res = client - .send_transaction_polling(&config.sign_with_local_key(txn).await?) + .send_transaction_polling(&config.sign_with_local_key(*txn).await?) .await?; if !no_cache { data::write(res.clone().try_into()?, &network.rpc_uri()?)?; @@ -371,6 +389,7 @@ pub enum Send { Yes, } +#[derive(Debug, PartialEq)] enum ShouldSend { DefaultNo, No, diff --git a/cmd/soroban-cli/src/commands/contract/mod.rs b/cmd/soroban-cli/src/commands/contract/mod.rs index 9ac6b8527..d72ce62b6 100644 --- a/cmd/soroban-cli/src/commands/contract/mod.rs +++ b/cmd/soroban-cli/src/commands/contract/mod.rs @@ -53,7 +53,12 @@ pub enum Cmd { #[command(subcommand)] Info(info::Cmd), - /// Initialize a Soroban project with an example contract + /// Initialize a Soroban contract project. + /// + /// This command will create a Cargo workspace project and add a sample Stellar contract. + /// The name of the contract can be specified by `--name`. It can be run multiple times + /// with different names in order to generate multiple contracts, and files won't + /// be overwritten unless `--overwrite` is passed. Init(init::Cmd), /// (Deprecated in favor of `contract info` subcommands) Inspect a WASM file listing contract functions, meta, etc @@ -141,7 +146,7 @@ impl Cmd { match &self { Cmd::Asset(asset) => asset.run().await?, Cmd::Bindings(bindings) => bindings.run().await?, - Cmd::Build(build) => build.run()?, + Cmd::Build(build) => build.run(global_args)?, Cmd::Extend(extend) => extend.run().await?, Cmd::Alias(alias) => alias.run(global_args)?, Cmd::Deploy(deploy) => deploy.run(global_args).await?, @@ -168,11 +173,11 @@ pub enum Durability { Temporary, } -impl From<&Durability> for soroban_env_host::xdr::ContractDataDurability { +impl From<&Durability> for crate::xdr::ContractDataDurability { fn from(d: &Durability) -> Self { match d { - Durability::Persistent => soroban_env_host::xdr::ContractDataDurability::Persistent, - Durability::Temporary => soroban_env_host::xdr::ContractDataDurability::Temporary, + Durability::Persistent => crate::xdr::ContractDataDurability::Persistent, + Durability::Temporary => crate::xdr::ContractDataDurability::Temporary, } } } diff --git a/cmd/soroban-cli/src/commands/contract/read.rs b/cmd/soroban-cli/src/commands/contract/read.rs index 3cb253bb1..58efc93b7 100644 --- a/cmd/soroban-cli/src/commands/contract/read.rs +++ b/cmd/soroban-cli/src/commands/contract/read.rs @@ -3,20 +3,17 @@ use std::{ io::{self, stdout}, }; -use clap::{command, Parser, ValueEnum}; -use soroban_env_host::{ - xdr::{ - ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, - Limits, ScVal, WriteXdr, - }, - HostError, +use crate::xdr::{ + ContractDataEntry, Error as XdrError, LedgerEntryData, LedgerKey, LedgerKeyContractData, + Limits, ScVal, WriteXdr, }; +use clap::{command, Parser, ValueEnum}; use crate::{ commands::{global, NetworkRunnable}, config::{self, locator}, key, - rpc::{self, Client, FullLedgerEntries, FullLedgerEntry}, + rpc::{self, FullLedgerEntries, FullLedgerEntry}, }; #[derive(Parser, Debug, Clone)] @@ -28,7 +25,7 @@ pub struct Cmd { #[command(flatten)] pub key: key::Args, #[command(flatten)] - config: config::Args, + config: config::ArgsLocatorAndNetwork, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] @@ -77,10 +74,6 @@ pub enum Error { Rpc(#[from] rpc::Error), #[error(transparent)] Xdr(#[from] XdrError), - #[error(transparent)] - // TODO: the Display impl of host errors is pretty user-unfriendly - // (it just calls Debug). I think we can do better than that - Host(#[from] HostError), #[error("no matching contract data entries were found for the specified contract id")] NoContractDataEntryFoundForContractID, #[error(transparent)] @@ -89,6 +82,8 @@ pub enum Error { OnlyDataAllowed, #[error(transparent)] Locator(#[from] locator::Error), + #[error(transparent)] + Network(#[from] config::network::Error), } impl Cmd { @@ -179,14 +174,15 @@ impl NetworkRunnable for Cmd { async fn run_against_rpc_server( &self, - _: Option<&global::Args>, - config: Option<&config::Args>, + _global_args: Option<&global::Args>, + _config: Option<&config::Args>, ) -> Result { - let config = config.unwrap_or(&self.config); - let network = config.get_network()?; + let locator = self.config.locator.clone(); + let network = self.config.network.get(&locator)?; + tracing::trace!(?network); - let client = Client::new(&network.rpc_url)?; - let keys = self.key.parse_keys(&config.locator, &network)?; + let client = network.rpc_client()?; + let keys = self.key.parse_keys(&locator, &network)?; Ok(client.get_full_ledger_entries(&keys).await?) } } diff --git a/cmd/soroban-cli/src/commands/contract/restore.rs b/cmd/soroban-cli/src/commands/contract/restore.rs index 05fa90695..cb35e6304 100644 --- a/cmd/soroban-cli/src/commands/contract/restore.rs +++ b/cmd/soroban-cli/src/commands/contract/restore.rs @@ -1,12 +1,12 @@ use std::{fmt::Debug, path::Path, str::FromStr}; -use clap::{command, Parser}; -use soroban_env_host::xdr::{ +use crate::xdr::{ Error as XdrError, ExtensionPoint, LedgerEntry, LedgerEntryChange, LedgerEntryData, LedgerFootprint, Limits, Memo, Operation, OperationBody, OperationMeta, Preconditions, RestoreFootprintOp, SequenceNumber, SorobanResources, SorobanTransactionData, Transaction, TransactionExt, TransactionMeta, TransactionMetaV3, TtlEntry, WriteXdr, }; +use clap::{command, Parser}; use stellar_strkey::DecodeError; use crate::{ @@ -17,9 +17,7 @@ use crate::{ NetworkRunnable, }, config::{self, data, locator, network}, - key, - rpc::{self, Client}, - wasm, Pwd, + key, rpc, wasm, Pwd, }; #[derive(Parser, Debug, Clone)] @@ -134,15 +132,16 @@ impl NetworkRunnable for Cmd { let network = config.get_network()?; tracing::trace!(?network); let entry_keys = self.key.parse_keys(&config.locator, &network)?; - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; let source_account = config.source_account()?; // Get the account sequence number - let public_strkey = source_account.to_string(); - let account_details = client.get_account(&public_strkey).await?; + let account_details = client + .get_account(&source_account.clone().to_string()) + .await?; let sequence: i64 = account_details.seq_num.into(); - let tx = Transaction { + let tx = Box::new(Transaction { source_account, fee: self.fee.fee, seq_num: SequenceNumber(sequence + 1), @@ -168,12 +167,12 @@ impl NetworkRunnable for Cmd { }, resource_fee: 0, }), - }; + }); if self.fee.build_only { return Ok(TxnResult::Txn(tx)); } let res = client - .send_transaction_polling(&config.sign_with_local_key(tx).await?) + .send_transaction_polling(&config.sign_with_local_key(*tx).await?) .await?; if args.map_or(true, |a| !a.no_cache) { data::write(res.clone().try_into()?, &network.rpc_uri()?)?; @@ -208,7 +207,7 @@ impl NetworkRunnable for Cmd { ); } Ok(TxnResult::Res( - parse_operations(operations).ok_or(Error::MissingOperationResult)?, + parse_operations(&operations.to_vec()).ok_or(Error::MissingOperationResult)?, )) } } diff --git a/cmd/soroban-cli/src/commands/env/mod.rs b/cmd/soroban-cli/src/commands/env/mod.rs new file mode 100644 index 000000000..298cac3d2 --- /dev/null +++ b/cmd/soroban-cli/src/commands/env/mod.rs @@ -0,0 +1,61 @@ +use crate::{ + commands::global, + config::locator::{self}, + print::Print, +}; +use clap::Parser; + +#[derive(Debug, Parser)] +pub struct Cmd { + #[command(flatten)] + pub config_locator: locator::Args, +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Locator(#[from] locator::Error), +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + let mut lines: Vec<(String, String)> = Vec::new(); + + if let Some(data) = get("STELLAR_NETWORK") { + lines.push(data); + } + + if let Some(data) = get("STELLAR_ACCOUNT") { + lines.push(data); + } + + if lines.is_empty() { + print.warnln("No defaults or environment variables set".to_string()); + return Ok(()); + } + + let max_len = lines.iter().map(|l| l.0.len()).max().unwrap_or(0); + + lines.sort(); + + for (value, source) in lines { + println!("{value:max_len$} # {source}"); + } + + Ok(()) + } +} + +fn get(env_var: &str) -> Option<(String, String)> { + // The _SOURCE env var is set from cmd/soroban-cli/src/cli.rs#set_env_value_from_config + let source = std::env::var(format!("{env_var}_SOURCE")) + .ok() + .unwrap_or("env".to_string()); + + if let Ok(value) = std::env::var(env_var) { + return Some((format!("{env_var}={value}"), source)); + } + + None +} diff --git a/cmd/soroban-cli/src/commands/events.rs b/cmd/soroban-cli/src/commands/events.rs index 5e2bda66f..48d79c1b7 100644 --- a/cmd/soroban-cli/src/commands/events.rs +++ b/cmd/soroban-cli/src/commands/events.rs @@ -1,11 +1,13 @@ use clap::{arg, command, Parser}; use std::io; -use soroban_env_host::xdr::{self, Limits, ReadXdr}; +use crate::xdr::{self, Limits, ReadXdr}; use super::{global, NetworkRunnable}; -use crate::config::{self, locator, network}; -use crate::rpc; +use crate::{ + config::{self, locator, network}, + rpc, +}; #[derive(Parser, Debug, Clone)] #[group(skip)] @@ -40,7 +42,7 @@ pub struct Cmd { num_args = 1..=6, help_heading = "FILTERS" )] - contract_ids: Vec, + contract_ids: Vec, /// A set of (up to 4) topic filters to filter event topics on. A single /// topic filter can contain 1-4 different segment filters, separated by /// commas, with an asterisk (`*` character) indicating a wildcard segment. @@ -207,7 +209,7 @@ impl NetworkRunnable for Cmd { self.network.get(&self.locator) }?; - let client = rpc::Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; @@ -216,9 +218,8 @@ impl NetworkRunnable for Cmd { .contract_ids .iter() .map(|id| { - Ok(self - .locator - .resolve_contract_id(id, &network.network_passphrase)? + Ok(id + .resolve_contract_id(&self.locator, &network.network_passphrase)? .to_string()) }) .collect::, Error>>()?; diff --git a/cmd/soroban-cli/src/commands/global.rs b/cmd/soroban-cli/src/commands/global.rs index be883b6fd..02c0b3b58 100644 --- a/cmd/soroban-cli/src/commands/global.rs +++ b/cmd/soroban-cli/src/commands/global.rs @@ -4,7 +4,7 @@ use clap::{ }; use std::path::PathBuf; -use super::config; +use super::{config, HEADING_GLOBAL}; const USAGE_STYLES: Styles = Styles::styled() .header(AnsiColor::Green.on_default().effects(Effects::BOLD)) @@ -24,19 +24,19 @@ pub struct Args { pub locator: config::locator::Args, /// Filter logs output. To turn on `stellar_cli::log::footprint=debug` or off `=off`. Can also use env var `RUST_LOG`. - #[arg(long, short = 'f', global = true)] + #[arg(long, short = 'f', global = true, help_heading = HEADING_GLOBAL)] pub filter_logs: Vec, /// Do not write logs to stderr including `INFO` - #[arg(long, short = 'q', global = true)] + #[arg(long, short = 'q', global = true, help_heading = HEADING_GLOBAL)] pub quiet: bool, /// Log DEBUG events - #[arg(long, short = 'v', global = true)] + #[arg(long, short = 'v', global = true, help_heading = HEADING_GLOBAL)] pub verbose: bool, /// Log DEBUG and TRACE events - #[arg(long, visible_alias = "vv", global = true)] + #[arg(long, visible_alias = "vv", global = true, help_heading = HEADING_GLOBAL)] pub very_verbose: bool, /// List installed plugins. E.g. `stellar-hello` @@ -44,7 +44,7 @@ pub struct Args { pub list: bool, /// Do not cache your simulations and transactions - #[arg(long, env = "STELLAR_NO_CACHE", global = true)] + #[arg(long, env = "STELLAR_NO_CACHE", global = true, help_heading = HEADING_GLOBAL)] pub no_cache: bool, } diff --git a/cmd/soroban-cli/src/commands/keys/default.rs b/cmd/soroban-cli/src/commands/keys/default.rs new file mode 100644 index 000000000..9aa180b6d --- /dev/null +++ b/cmd/soroban-cli/src/commands/keys/default.rs @@ -0,0 +1,35 @@ +use clap::command; + +use crate::{commands::global, config::locator, print::Print}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Set the default network name. + pub name: String, + + #[command(flatten)] + pub config_locator: locator::Args, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let printer = Print::new(global_args.quiet); + let _ = self.config_locator.read_identity(&self.name)?; + + self.config_locator.write_default_identity(&self.name)?; + + printer.infoln(format!( + "The default source account is set to `{}`", + self.name, + )); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/keys/generate.rs b/cmd/soroban-cli/src/commands/keys/generate.rs index 16e6a7fdb..c6623386c 100644 --- a/cmd/soroban-cli/src/commands/keys/generate.rs +++ b/cmd/soroban-cli/src/commands/keys/generate.rs @@ -4,19 +4,26 @@ use super::super::config::{ locator, network, secret::{self, Secret}, }; +use crate::{commands::global, print::Print}; #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] Config(#[from] locator::Error), + #[error(transparent)] Secret(#[from] secret::Error), + #[error(transparent)] Network(#[from] network::Error), + + #[error("An identity with the name '{0}' already exists")] + IdentityAlreadyExists(String), } #[derive(Debug, clap::Parser, Clone)] #[group(skip)] +#[allow(clippy::struct_excessive_bools)] pub struct Cmd { /// Name of identity pub name: String, @@ -46,21 +53,51 @@ pub struct Cmd { #[command(flatten)] pub network: network::Args, + + /// Fund generated key pair + #[arg(long, default_value = "false")] + pub fund: bool, + + /// Overwrite existing identity if it already exists. + #[arg(long)] + pub overwrite: bool, } impl Cmd { - pub async fn run(&self) -> Result<(), Error> { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let print = Print::new(global_args.quiet); + + if self.config_locator.read_identity(&self.name).is_ok() { + if !self.overwrite { + return Err(Error::IdentityAlreadyExists(self.name.clone())); + } + + print.exclaimln(format!("Overwriting identity '{}'", &self.name)); + } + + if !self.fund { + print.warnln( + "Behavior of `generate` will change in the \ + future, and it will no longer fund by default. If you want to fund please \ + provide `--fund` flag. If you don't need to fund your keys in the future, ignore this \ + warning. It can be suppressed with -q flag.", + ); + } + let seed_phrase = if self.default_seed { Secret::test_seed_phrase() } else { Secret::from_seed(self.seed.as_deref()) }?; + let secret = if self.as_secret { seed_phrase.private_key(self.hd_path)?.into() } else { seed_phrase }; + self.config_locator.write_identity(&self.name, &secret)?; + if !self.no_fund { let addr = secret.public_key(self.hd_path)?; let network = self.network.get(&self.config_locator)?; @@ -72,6 +109,7 @@ impl Cmd { }) .unwrap_or_default(); } + Ok(()) } } diff --git a/cmd/soroban-cli/src/commands/keys/mod.rs b/cmd/soroban-cli/src/commands/keys/mod.rs index 42814092f..8729ee9af 100644 --- a/cmd/soroban-cli/src/commands/keys/mod.rs +++ b/cmd/soroban-cli/src/commands/keys/mod.rs @@ -1,7 +1,9 @@ +use crate::commands::global; use clap::Parser; pub mod add; pub mod address; +pub mod default; pub mod fund; pub mod generate; pub mod ls; @@ -12,18 +14,30 @@ pub mod show; pub enum Cmd { /// Add a new identity (keypair, ledger, macOS keychain) Add(add::Cmd), + /// Given an identity return its address (public key) Address(address::Cmd), + /// Fund an identity on a test network Fund(fund::Cmd), + /// Generate a new identity with a seed phrase, currently 12 words Generate(generate::Cmd), + /// List identities Ls(ls::Cmd), + /// Remove an identity Rm(rm::Cmd), + /// Given an identity return its private key Show(show::Cmd), + + /// Set the default identity that will be used on all commands. + /// This allows you to skip `--source-account` or setting a environment + /// variable, while reusing this value in all commands that require it. + #[command(name = "use")] + Default(default::Cmd), } #[derive(thiserror::Error, Debug)] @@ -33,30 +47,37 @@ pub enum Error { #[error(transparent)] Address(#[from] address::Error), + #[error(transparent)] Fund(#[from] fund::Error), #[error(transparent)] Generate(#[from] generate::Error), + #[error(transparent)] Rm(#[from] rm::Error), + #[error(transparent)] Ls(#[from] ls::Error), #[error(transparent)] Show(#[from] show::Error), + + #[error(transparent)] + Default(#[from] default::Error), } impl Cmd { - pub async fn run(&self) -> Result<(), Error> { + pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { match self { Cmd::Add(cmd) => cmd.run()?, Cmd::Address(cmd) => cmd.run()?, Cmd::Fund(cmd) => cmd.run().await?, - Cmd::Generate(cmd) => cmd.run().await?, + Cmd::Generate(cmd) => cmd.run(global_args).await?, Cmd::Ls(cmd) => cmd.run()?, Cmd::Rm(cmd) => cmd.run()?, Cmd::Show(cmd) => cmd.run()?, + Cmd::Default(cmd) => cmd.run(global_args)?, }; Ok(()) } diff --git a/cmd/soroban-cli/src/commands/mod.rs b/cmd/soroban-cli/src/commands/mod.rs index b2e431e6e..25d61364d 100644 --- a/cmd/soroban-cli/src/commands/mod.rs +++ b/cmd/soroban-cli/src/commands/mod.rs @@ -7,7 +7,9 @@ use crate::config; pub mod cache; pub mod completion; +pub mod container; pub mod contract; +pub mod env; pub mod events; pub mod global; pub mod keys; @@ -20,6 +22,7 @@ pub mod version; pub mod txn_result; pub const HEADING_RPC: &str = "Options (RPC)"; +pub const HEADING_GLOBAL: &str = "Options (Global)"; const ABOUT: &str = "Work seamlessly with Stellar accounts, contracts, and assets from the command line. @@ -35,7 +38,7 @@ For additional information see: - Stellar Docs: https://developers.stellar.org - Smart Contract Docs: https://developers.stellar.org/docs/build/smart-contracts/overview -- CLI Docs: https://developers.stellar.org/docs/tools/stellar-cli"; +- CLI Docs: https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli"; // long_about is shown when someone uses `--help`; short help when using `-h` const LONG_ABOUT: &str = " @@ -111,11 +114,13 @@ impl Root { Cmd::Events(events) => events.run().await?, Cmd::Xdr(xdr) => xdr.run()?, Cmd::Network(network) => network.run(&self.global_args).await?, + Cmd::Container(container) => container.run(&self.global_args).await?, Cmd::Snapshot(snapshot) => snapshot.run(&self.global_args).await?, Cmd::Version(version) => version.run(), - Cmd::Keys(id) => id.run().await?, + Cmd::Keys(id) => id.run(&self.global_args).await?, Cmd::Tx(tx) => tx.run(&self.global_args).await?, - Cmd::Cache(data) => data.run()?, + Cmd::Cache(cache) => cache.run()?, + Cmd::Env(env) => env.run(&self.global_args)?, }; Ok(()) } @@ -134,17 +139,27 @@ pub enum Cmd { /// Tools for smart contract developers #[command(subcommand)] Contract(contract::Cmd), + /// Watch the network for contract events Events(events::Cmd), + /// Prints the current environment variables or defaults to the stdout, in + /// a format that can be used as .env file. Environment variables have + /// precedency over defaults. + Env(env::Cmd), + /// Create and manage identities including keys and addresses #[command(subcommand)] Keys(keys::Cmd), - /// Start and configure networks + /// Configure connection to networks #[command(subcommand)] Network(network::Cmd), + /// Start local networks in containers + #[command(subcommand)] + Container(container::Cmd), + /// Download a snapshot of a ledger from an archive. #[command(subcommand)] Snapshot(snapshot::Cmd), @@ -159,9 +174,11 @@ pub enum Cmd { /// Print shell completion code for the specified shell. #[command(long_about = completion::LONG_ABOUT)] Completion(completion::Cmd), + /// Cache for transactions and contract specs #[command(subcommand)] Cache(cache::Cmd), + /// Print version information Version(version::Cmd), } @@ -171,24 +188,39 @@ pub enum Error { // TODO: stop using Debug for displaying errors #[error(transparent)] Contract(#[from] contract::Error), + #[error(transparent)] Events(#[from] events::Error), + #[error(transparent)] Keys(#[from] keys::Error), + #[error(transparent)] Xdr(#[from] stellar_xdr::cli::Error), + #[error(transparent)] Clap(#[from] clap::error::Error), + #[error(transparent)] Plugin(#[from] plugin::Error), + #[error(transparent)] Network(#[from] network::Error), + + #[error(transparent)] + Container(#[from] container::Error), + #[error(transparent)] Snapshot(#[from] snapshot::Error), + #[error(transparent)] Tx(#[from] tx::Error), + #[error(transparent)] Cache(#[from] cache::Error), + + #[error(transparent)] + Env(#[from] env::Error), } #[async_trait] diff --git a/cmd/soroban-cli/src/commands/network/default.rs b/cmd/soroban-cli/src/commands/network/default.rs new file mode 100644 index 000000000..337e08a69 --- /dev/null +++ b/cmd/soroban-cli/src/commands/network/default.rs @@ -0,0 +1,34 @@ +use clap::command; + +use crate::{commands::global, print::Print}; + +use super::locator; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] locator::Error), +} + +#[derive(Debug, clap::Parser, Clone)] +#[group(skip)] +pub struct Cmd { + /// Set the default network name. + pub name: String, + + #[command(flatten)] + pub config_locator: locator::Args, +} + +impl Cmd { + pub fn run(&self, global_args: &global::Args) -> Result<(), Error> { + let printer = Print::new(global_args.quiet); + let _ = self.config_locator.read_network(&self.name)?; + + self.config_locator.write_default_network(&self.name)?; + + printer.infoln(format!("The default network is set to `{}`", self.name)); + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/commands/network/mod.rs b/cmd/soroban-cli/src/commands/network/mod.rs index 8dd61b394..5cc2acc43 100644 --- a/cmd/soroban-cli/src/commands/network/mod.rs +++ b/cmd/soroban-cli/src/commands/network/mod.rs @@ -5,7 +5,7 @@ use crate::rpc::{self}; use super::{config::locator, global}; pub mod add; -pub mod container; +pub mod default; pub mod ls; pub mod rm; @@ -13,10 +13,13 @@ pub mod rm; pub enum Cmd { /// Add a new network Add(add::Cmd), + /// Remove a network Rm(rm::Cmd), + /// List networks Ls(ls::Cmd), + /// ⚠️ Deprecated: use `stellar container start` instead /// /// Start network @@ -28,19 +31,31 @@ pub enum Cmd { /// By default, when starting a testnet container, without any optional arguments, it will run the equivalent of the following docker command: /// /// `docker run --rm -p 8000:8000 --name stellar stellar/quickstart:testing --testnet --enable rpc,horizon` - Start(container::StartCmd), + Start(crate::commands::container::StartCmd), + /// ⚠️ Deprecated: use `stellar container stop` instead /// /// Stop a network started with `network start`. For example, if you ran `stellar network start local`, you can use `stellar network stop local` to stop it. - Stop(container::StopCmd), + Stop(crate::commands::container::StopCmd), + /// Set the default network that will be used on all commands. + /// This allows you to skip `--network` or setting a environment variable, + /// while reusing this value in all commands that require it. + #[command(name = "use")] + Default(default::Cmd), + + /// ⚠️ Deprecated: use `stellar container` instead + /// /// Commands to start, stop and get logs for a quickstart container #[command(subcommand)] - Container(container::Cmd), + Container(crate::commands::container::Cmd), } #[derive(thiserror::Error, Debug)] pub enum Error { + #[error(transparent)] + Default(#[from] default::Error), + #[error(transparent)] Add(#[from] add::Error), @@ -52,14 +67,14 @@ pub enum Error { // TODO: remove once `network start` is removed #[error(transparent)] - Start(#[from] container::start::Error), + Start(#[from] crate::commands::container::start::Error), // TODO: remove once `network stop` is removed #[error(transparent)] - Stop(#[from] container::stop::Error), + Stop(#[from] crate::commands::container::stop::Error), #[error(transparent)] - Container(#[from] container::Error), + Container(#[from] crate::commands::container::Error), #[error(transparent)] Config(#[from] locator::Error), @@ -81,6 +96,7 @@ pub enum Error { impl Cmd { pub async fn run(&self, global_args: &global::Args) -> Result<(), Error> { match self { + Cmd::Default(cmd) => cmd.run(global_args)?, Cmd::Add(cmd) => cmd.run()?, Cmd::Rm(new) => new.run()?, Cmd::Ls(cmd) => cmd.run()?, @@ -88,12 +104,14 @@ impl Cmd { // TODO Remove this once `network start` is removed Cmd::Start(cmd) => { - eprintln!("⚠️ Warning: `network start` has been deprecated. Use `network container start` instead"); + eprintln!("⚠️ Warning: `network start` has been deprecated. Use `container start` instead"); cmd.run(global_args).await?; } // TODO Remove this once `network stop` is removed Cmd::Stop(cmd) => { - println!("⚠️ Warning: `network stop` has been deprecated. Use `network container stop` instead"); + println!( + "⚠️ Warning: `network stop` has been deprecated. Use `container stop` instead" + ); cmd.run(global_args).await?; } }; diff --git a/cmd/soroban-cli/src/commands/plugin.rs b/cmd/soroban-cli/src/commands/plugin.rs index 2fa028d5c..d933564f2 100644 --- a/cmd/soroban-cli/src/commands/plugin.rs +++ b/cmd/soroban-cli/src/commands/plugin.rs @@ -1,4 +1,4 @@ -use std::process::Command; +use std::{path::PathBuf, process::Command}; use clap::CommandFactory; use which::which; @@ -44,7 +44,7 @@ pub fn run() -> Result<(), Error> { return Ok(()); } - let bin = which(format!("soroban-{name}")).map_err(|_| { + let bin = find_bin(&name).map_err(|_| { let suggestion = if let Ok(bins) = list() { let suggested_name = bins .iter() @@ -53,6 +53,7 @@ pub fn run() -> Result<(), Error> { .min_by(|a, b| a.1.total_cmp(&b.1)) .map(|(a, _)| a.to_string()) .unwrap_or_default(); + if suggested_name.is_empty() { suggested_name } else { @@ -64,8 +65,10 @@ pub fn run() -> Result<(), Error> { } else { String::new() }; + Error::ExecutableNotFound(name, suggestion) })?; + std::process::exit( Command::new(bin) .args(args) @@ -78,19 +81,29 @@ pub fn run() -> Result<(), Error> { const MAX_HEX_LENGTH: usize = 10; +fn find_bin(name: &str) -> Result { + if let Ok(path) = which(format!("stellar-{name}")) { + Ok(path) + } else { + which(format!("soroban-{name}")) + } +} + pub fn list() -> Result, Error> { let re_str = if cfg!(target_os = "windows") { - r"^soroban-.*.exe$" + r"^(soroban|stellar)-.*.exe$" } else { - r"^soroban-.*" + r"^(soroban|stellar)-.*" }; + let re = regex::Regex::new(re_str)?; + Ok(which::which_re(re)? .filter_map(|b| { let s = b.file_name()?.to_str()?; Some(s.strip_suffix(".exe").unwrap_or(s).to_string()) }) .filter(|s| !(utils::is_hex_string(s) && s.len() > MAX_HEX_LENGTH)) - .map(|s| s.replace("soroban-", "")) + .map(|s| s.replace("soroban-", "").replace("stellar-", "")) .collect()) } diff --git a/cmd/soroban-cli/src/commands/snapshot/create.rs b/cmd/soroban-cli/src/commands/snapshot/create.rs index 13bef9465..6ce48d3f2 100644 --- a/cmd/soroban-cli/src/commands/snapshot/create.rs +++ b/cmd/soroban-cli/src/commands/snapshot/create.rs @@ -27,7 +27,6 @@ use tokio::io::BufReader; use tokio_util::io::StreamReader; use url::Url; -use crate::utils::http; use crate::{ commands::{config::data, global, HEADING_RPC}, config::{self, locator, network::passphrase}, @@ -35,6 +34,7 @@ use crate::{ tx::builder, utils::get_name_from_stellar_asset_contract_storage, }; +use crate::{config::address::Address, utils::http}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, ValueEnum)] pub enum Output { @@ -55,7 +55,7 @@ fn default_out_path() -> PathBuf { /// /// Filters (address, wasm-hash) specify what ledger entries to include. /// -/// Account addresses include the account, and trust lines. +/// Account addresses include the account, and trustlines. /// /// Contract addresses include the related wasm, contract data. /// @@ -63,6 +63,9 @@ fn default_out_path() -> PathBuf { /// account and trust lines, but does not include all the trust lines of other /// accounts holding the asset. To include them specify the addresses of /// relevant accounts. +/// +/// Any invalid contract id passed as `--address` will be ignored. +/// #[derive(Parser, Debug, Clone)] #[group(skip)] #[command(arg_required_else_help = true)] @@ -70,9 +73,9 @@ pub struct Cmd { /// The ledger sequence number to snapshot. Defaults to latest history archived ledger. #[arg(long)] ledger: Option, - /// Account or contract address to include in the snapshot. + /// Account or contract address/alias to include in the snapshot. #[arg(long = "address", help_heading = "Filter Options")] - address: Vec, + address: Vec, /// WASM hashes to include in the snapshot. #[arg(long = "wasm-hash", help_heading = "Filter Options")] wasm_hashes: Vec, @@ -213,11 +216,13 @@ impl Cmd { } // Search the buckets using the user inputs as the starting inputs. - let (account_ids, contract_ids): (HashSet, HashSet) = - self.address.iter().cloned().partition_map(|a| match a { - ScAddress::Account(account_id) => Either::Left(account_id), - ScAddress::Contract(_) => Either::Right(a), - }); + let (account_ids, contract_ids): (HashSet, HashSet) = self + .address + .iter() + .cloned() + .filter_map(|a| self.resolve_address(&a, network_passphrase)) + .partition_map(|a| a); + let mut current = SearchInputs { account_ids, contract_ids, @@ -388,6 +393,44 @@ impl Cmd { }) .ok_or(Error::ArchiveUrlNotConfigured) } + + fn resolve_address( + &self, + address: &str, + network_passphrase: &str, + ) -> Option> { + self.resolve_contract(address, network_passphrase) + .map(Either::Right) + .or_else(|| self.resolve_account(address).map(Either::Left)) + } + + // Resolve an account address to an account id. The address can be a + // G-address or a key name (as in `stellar keys address NAME`). + fn resolve_account(&self, address: &str) -> Option { + let address: Address = address.parse().ok()?; + + Some(AccountId(xdr::PublicKey::PublicKeyTypeEd25519( + match address.resolve_muxed_account(&self.locator, None).ok()? { + xdr::MuxedAccount::Ed25519(uint256) => uint256, + xdr::MuxedAccount::MuxedEd25519(xdr::MuxedAccountMed25519 { ed25519, .. }) => { + ed25519 + } + }, + ))) + } + // Resolve a contract address to a contract id. The contract can be a + // C-address or a contract alias. + fn resolve_contract(&self, address: &str, network_passphrase: &str) -> Option { + address.parse().ok().or_else(|| { + Some(ScAddress::Contract( + self.locator + .resolve_contract_id(address, network_passphrase) + .ok()? + .0 + .into(), + )) + }) + } } async fn get_history( diff --git a/cmd/soroban-cli/src/commands/tx/args.rs b/cmd/soroban-cli/src/commands/tx/args.rs index 7e032fd53..1da1f230a 100644 --- a/cmd/soroban-cli/src/commands/tx/args.rs +++ b/cmd/soroban-cli/src/commands/tx/args.rs @@ -39,7 +39,7 @@ impl Args { let source_account = self.source_account()?; let seq_num = self .config - .next_sequence_number(&source_account.to_string()) + .next_sequence_number(source_account.clone().account_id()) .await?; // Once we have a way to add operations this will be updated to allow for a different source account let operation = xdr::Operation { @@ -87,7 +87,7 @@ impl Args { let network = self.config.get_network()?; let client = Client::new(&network.rpc_url)?; if self.fee.build_only { - return Ok(TxnEnvelopeResult::TxnEnvelope(tx.into())); + return Ok(TxnEnvelopeResult::TxnEnvelope(Box::new(tx.into()))); } let txn_resp = client diff --git a/cmd/soroban-cli/src/commands/tx/hash.rs b/cmd/soroban-cli/src/commands/tx/hash.rs index 8d8ec6d82..c422302f6 100644 --- a/cmd/soroban-cli/src/commands/tx/hash.rs +++ b/cmd/soroban-cli/src/commands/tx/hash.rs @@ -7,7 +7,7 @@ pub enum Error { #[error(transparent)] TxEnvelopeFromStdin(#[from] super::xdr::Error), #[error(transparent)] - XdrToBase64(#[from] soroban_env_host::xdr::Error), + XdrToBase64(#[from] crate::xdr::Error), #[error(transparent)] Config(#[from] network::Error), } diff --git a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs index 1ea4e737e..da9acc8cf 100644 --- a/cmd/soroban-cli/src/commands/tx/new/change_trust.rs +++ b/cmd/soroban-cli/src/commands/tx/new/change_trust.rs @@ -10,7 +10,7 @@ pub struct Cmd { #[arg(long)] pub line: builder::Asset, /// Limit for the trust line, 0 to remove the trust line - #[arg(long, default_value = u64::MAX.to_string())] + #[arg(long, default_value = i64::MAX.to_string())] pub limit: i64, } diff --git a/cmd/soroban-cli/src/commands/tx/new/create_account.rs b/cmd/soroban-cli/src/commands/tx/new/create_account.rs index 967c0cf43..9cc3a62ff 100644 --- a/cmd/soroban-cli/src/commands/tx/new/create_account.rs +++ b/cmd/soroban-cli/src/commands/tx/new/create_account.rs @@ -11,7 +11,7 @@ pub struct Cmd { #[arg(long)] pub destination: xdr::AccountId, /// Initial balance in stroops of the account, default 1 XLM - #[arg(long, default_value = "10_000_000")] + #[arg(long, default_value = "10000000")] pub starting_balance: i64, } diff --git a/cmd/soroban-cli/src/commands/tx/new/set_options.rs b/cmd/soroban-cli/src/commands/tx/new/set_options.rs index 3410b69e8..69cd10745 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_options.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_options.rs @@ -40,7 +40,7 @@ pub struct Cmd { /// https://developers.stellar.org/docs/tokens/control-asset-access#authorization-required-0x1 pub set_required: bool, #[arg(long, conflicts_with = "clear_revocable")] - /// When enabled, an issuer can revoke an existing trustline’s authorization, thereby freezing the asset held by an account. + /// When enabled, an issuer can revoke an existing trustline's authorization, thereby freezing the asset held by an account. /// https://developers.stellar.org/docs/tokens/control-asset-access#authorization-revocable-0x2 pub set_revocable: bool, #[arg(long, conflicts_with = "clear_clawback_enabled")] @@ -48,7 +48,7 @@ pub struct Cmd { /// https://developers.stellar.org/docs/tokens/control-asset-access#clawback-enabled-0x8 pub set_clawback_enabled: bool, #[arg(long, conflicts_with = "clear_immutable")] - /// With this setting, none of the other authorization flags (`AUTH_REQUIRED_FLAG`, `AUTH_REVOCABLE_FLAG`) can be set, and the issuing account can’t be merged. + /// With this setting, none of the other authorization flags (`AUTH_REQUIRED_FLAG`, `AUTH_REVOCABLE_FLAG`) can be set, and the issuing account can't be merged. /// https://developers.stellar.org/docs/tokens/control-asset-access#authorization-immutable-0x4 pub set_immutable: bool, #[arg(long)] diff --git a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs index 2955fe5b0..d9b70ecbd 100644 --- a/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs +++ b/cmd/soroban-cli/src/commands/tx/new/set_trustline_flags.rs @@ -1,8 +1,6 @@ use clap::{command, Parser}; -use soroban_sdk::xdr::{self}; - -use crate::{commands::tx, tx::builder}; +use crate::{commands::tx, tx::builder, xdr}; #[allow(clippy::struct_excessive_bools, clippy::doc_markdown)] #[derive(Parser, Debug, Clone)] diff --git a/cmd/soroban-cli/src/commands/tx/send.rs b/cmd/soroban-cli/src/commands/tx/send.rs index c3856114d..1e542eb6f 100644 --- a/cmd/soroban-cli/src/commands/tx/send.rs +++ b/cmd/soroban-cli/src/commands/tx/send.rs @@ -1,8 +1,11 @@ +use crate::{print::Print, utils::transaction_hash}; use async_trait::async_trait; use soroban_rpc::GetTransactionResponse; -use crate::commands::{global, NetworkRunnable}; -use crate::config::{self, locator, network}; +use crate::{ + commands::{global, NetworkRunnable}, + config::{self, locator, network}, +}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -44,7 +47,7 @@ impl NetworkRunnable for Cmd { type Result = GetTransactionResponse; async fn run_against_rpc_server( &self, - _: Option<&global::Args>, + globals: Option<&global::Args>, config: Option<&config::Args>, ) -> Result { let network = if let Some(config) = config { @@ -52,8 +55,16 @@ impl NetworkRunnable for Cmd { } else { self.network.get(&self.locator)? }; - let client = crate::rpc::Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; let tx_env = super::xdr::tx_envelope_from_stdin()?; + + if let Ok(Ok(hash)) = super::xdr::unwrap_envelope_v1(tx_env.clone()) + .map(|tx| transaction_hash(&tx, &network.network_passphrase)) + { + let print = Print::new(globals.map_or(false, |g| g.quiet)); + print.infoln(format!("Transaction Hash: {}", hex::encode(hash))); + } + Ok(client.send_transaction_polling(&tx_env).await?) } } diff --git a/cmd/soroban-cli/src/commands/tx/simulate.rs b/cmd/soroban-cli/src/commands/tx/simulate.rs index 1f534884d..cb2ed7c69 100644 --- a/cmd/soroban-cli/src/commands/tx/simulate.rs +++ b/cmd/soroban-cli/src/commands/tx/simulate.rs @@ -16,6 +16,8 @@ pub enum Error { Rpc(#[from] crate::rpc::Error), #[error(transparent)] Xdr(#[from] xdr::Error), + #[error(transparent)] + Network(#[from] config::network::Error), } /// Command to simulate a transaction envelope via rpc @@ -50,7 +52,7 @@ impl NetworkRunnable for Cmd { ) -> Result { let config = config.unwrap_or(&self.config); let network = config.get_network()?; - let client = crate::rpc::Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; let tx = super::xdr::unwrap_envelope_v1(super::xdr::tx_envelope_from_stdin()?)?; let tx = simulate_and_assemble_transaction(&client, &tx).await?; Ok(tx) diff --git a/cmd/soroban-cli/src/commands/tx/xdr.rs b/cmd/soroban-cli/src/commands/tx/xdr.rs index 71bd59af6..fcacce9ac 100644 --- a/cmd/soroban-cli/src/commands/tx/xdr.rs +++ b/cmd/soroban-cli/src/commands/tx/xdr.rs @@ -3,8 +3,7 @@ use std::{ path::PathBuf, }; -use soroban_env_host::xdr::ReadXdr; -use soroban_sdk::xdr::{Limits, Transaction, TransactionEnvelope, TransactionV1Envelope}; +use crate::xdr::{Limits, ReadXdr, Transaction, TransactionEnvelope, TransactionV1Envelope}; #[derive(Debug, thiserror::Error)] pub enum Error { diff --git a/cmd/soroban-cli/src/commands/txn_result.rs b/cmd/soroban-cli/src/commands/txn_result.rs index 08482e94f..568fd79d2 100644 --- a/cmd/soroban-cli/src/commands/txn_result.rs +++ b/cmd/soroban-cli/src/commands/txn_result.rs @@ -1,8 +1,8 @@ -use soroban_env_host::xdr::{Transaction, TransactionEnvelope, TransactionV1Envelope, VecM}; +use crate::xdr::{Transaction, TransactionEnvelope, TransactionV1Envelope, VecM}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TxnResult { - Txn(Transaction), + Txn(Box), Res(R), } @@ -16,12 +16,12 @@ impl TxnResult { pub fn to_envelope(self) -> TxnEnvelopeResult { match self { - TxnResult::Txn(tx) => { - TxnEnvelopeResult::TxnEnvelope(TransactionEnvelope::Tx(TransactionV1Envelope { - tx, + TxnResult::Txn(tx) => TxnEnvelopeResult::TxnEnvelope(Box::new( + TransactionEnvelope::Tx(TransactionV1Envelope { + tx: *tx, signatures: VecM::default(), - })) - } + }), + )), TxnResult::Res(res) => TxnEnvelopeResult::Res(res), } } @@ -29,6 +29,6 @@ impl TxnResult { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TxnEnvelopeResult { - TxnEnvelope(TransactionEnvelope), + TxnEnvelope(Box), Res(R), } diff --git a/cmd/soroban-cli/src/commands/version.rs b/cmd/soroban-cli/src/commands/version.rs index 5e6d0e4dd..f785399e9 100644 --- a/cmd/soroban-cli/src/commands/version.rs +++ b/cmd/soroban-cli/src/commands/version.rs @@ -1,5 +1,4 @@ use clap::Parser; -use soroban_env_host::meta; use std::fmt::Debug; #[derive(Parser, Debug, Clone)] @@ -22,12 +21,9 @@ pub fn git() -> &'static str { } pub fn long() -> String { - let env = soroban_env_host::VERSION; - let xdr = soroban_env_host::VERSION.xdr; + let xdr = stellar_xdr::VERSION; [ format!("{} ({})", pkg(), git()), - format!("soroban-env {} ({})", env.pkg, env.rev), - format!("soroban-env interface version {}", meta::INTERFACE_VERSION), format!( "stellar-xdr {} ({}) xdr curr ({})", diff --git a/cmd/soroban-cli/src/config/alias.rs b/cmd/soroban-cli/src/config/alias.rs index 68ad556d6..9d1d8c11b 100644 --- a/cmd/soroban-cli/src/config/alias.rs +++ b/cmd/soroban-cli/src/config/alias.rs @@ -1,8 +1,49 @@ -use std::collections::HashMap; +use std::{collections::HashMap, convert::Infallible, str::FromStr}; use serde::{Deserialize, Serialize}; +use super::locator; + #[derive(Serialize, Deserialize, Default)] pub struct Data { pub ids: HashMap, } + +/// Address can be either a contract address, C.. or eventually an alias of a contract address. +#[derive(Clone, Debug)] +pub enum ContractAddress { + ContractId(stellar_strkey::Contract), + Alias(String), +} + +impl Default for ContractAddress { + fn default() -> Self { + ContractAddress::Alias(String::default()) + } +} + +impl FromStr for ContractAddress { + type Err = Infallible; + + fn from_str(value: &str) -> Result { + Ok(stellar_strkey::Contract::from_str(value).map_or_else( + |_| ContractAddress::Alias(value.to_string()), + ContractAddress::ContractId, + )) + } +} + +impl ContractAddress { + pub fn resolve_contract_id( + &self, + locator: &locator::Args, + network_passphrase: &str, + ) -> Result { + match self { + ContractAddress::ContractId(muxed_account) => Ok(*muxed_account), + ContractAddress::Alias(alias) => locator + .get_contract_id(alias, network_passphrase)? + .ok_or_else(|| locator::Error::ContractNotFound(alias.to_owned())), + } + } +} diff --git a/cmd/soroban-cli/src/config/data.rs b/cmd/soroban-cli/src/config/data.rs index bbfc6994e..f032a94d3 100644 --- a/cmd/soroban-cli/src/config/data.rs +++ b/cmd/soroban-cli/src/config/data.rs @@ -209,7 +209,7 @@ mod test { assert_eq!(rpc_uri, new_rpc_uri); match (action, original_action) { (Action::Simulate { response: a }, Action::Simulate { response: b }) => { - assert_eq!(a.cost.cpu_insns, b.cost.cpu_insns); + assert_eq!(a.min_resource_fee, b.min_resource_fee); } _ => panic!("Action mismatch"), } diff --git a/cmd/soroban-cli/src/config/locator.rs b/cmd/soroban-cli/src/config/locator.rs index 2fad2bb62..b6f5c75c1 100644 --- a/cmd/soroban-cli/src/config/locator.rs +++ b/cmd/soroban-cli/src/config/locator.rs @@ -1,27 +1,30 @@ use clap::arg; +use directories::UserDirs; use itertools::Itertools; use serde::de::DeserializeOwned; use std::{ ffi::OsStr, fmt::Display, fs::{self, create_dir_all, OpenOptions}, - io, - io::Write, + io::{self, Write}, path::{Path, PathBuf}, str::FromStr, }; use stellar_strkey::{Contract, DecodeError}; -use crate::{utils::find_config_dir, Pwd}; +use crate::{commands::HEADING_GLOBAL, utils::find_config_dir, Pwd}; use super::{ alias, network::{self, Network}, secret::Secret, + Config, }; #[derive(thiserror::Error, Debug)] pub enum Error { + #[error(transparent)] + TomlSerialize(#[from] toml::ser::Error), #[error("Failed to find home directory")] HomeDirNotFound, #[error("Failed read current directory")] @@ -34,6 +37,8 @@ pub enum Error { SecretFileRead { path: PathBuf }, #[error("Failed to read network file: {path};\nProbably need to use `stellar network add`")] NetworkFileRead { path: PathBuf }, + #[error("Failed to read file: {path}")] + FileRead { path: PathBuf }, #[error(transparent)] Toml(#[from] toml::de::Error), #[error("Secret file failed to deserialize")] @@ -72,6 +77,8 @@ pub enum Error { CannotAccessAliasConfigFile, #[error("cannot parse contract ID {0}: {1}")] CannotParseContractId(String, DecodeError), + #[error("contract not found: {0}")] + ContractNotFound(String), #[error("Failed to read upgrade check file: {path}: {error}")] UpgradeCheckReadFailed { path: PathBuf, error: io::Error }, #[error("Failed to write upgrade check file: {path}: {error}")] @@ -82,11 +89,11 @@ pub enum Error { #[group(skip)] pub struct Args { /// Use global config - #[arg(long, global = true)] + #[arg(long, global = true, help_heading = HEADING_GLOBAL)] pub global: bool, /// Location of config directory, default is "." - #[arg(long, global = true)] + #[arg(long, global = true, help_heading = HEADING_GLOBAL)] pub config_dir: Option, } @@ -145,7 +152,7 @@ impl Args { pub fn local_config(&self) -> Result { let pwd = self.current_dir()?; - Ok(find_config_dir(pwd.clone()).unwrap_or_else(|_| pwd.join(".soroban"))) + Ok(find_config_dir(pwd.clone()).unwrap_or_else(|_| pwd.join(".stellar"))) } pub fn current_dir(&self) -> Result { @@ -163,6 +170,14 @@ impl Args { KeyType::Network.write(name, network, &self.config_dir()?) } + pub fn write_default_network(&self, name: &str) -> Result<(), Error> { + Config::new()?.set_network(name).save() + } + + pub fn write_default_identity(&self, name: &str) -> Result<(), Error> { + Config::new()?.set_identity(name).save() + } + pub fn list_identities(&self) -> Result, Error> { Ok(KeyType::Identity .list_paths(&self.local_and_global()?)? @@ -320,12 +335,17 @@ impl Args { &self, alias: &str, network_passphrase: &str, - ) -> Result, Error> { + ) -> Result, Error> { let Some(alias_data) = self.load_contract_from_alias(alias)? else { return Ok(None); }; - Ok(alias_data.ids.get(network_passphrase).cloned()) + alias_data + .ids + .get(network_passphrase) + .map(|id| id.parse()) + .transpose() + .map_err(|e| Error::CannotParseContractId(alias.to_owned(), e)) } pub fn resolve_contract_id( @@ -333,14 +353,18 @@ impl Args { alias_or_contract_id: &str, network_passphrase: &str, ) -> Result { - let contract_id = self - .get_contract_id(alias_or_contract_id, network_passphrase)? - .unwrap_or_else(|| alias_or_contract_id.to_string()); + let Some(contract) = self.get_contract_id(alias_or_contract_id, network_passphrase)? else { + return alias_or_contract_id + .parse() + .map_err(|e| Error::CannotParseContractId(alias_or_contract_id.to_owned(), e)); + }; + Ok(contract) + } +} - Ok(Contract( - soroban_spec_tools::utils::contract_id_from_str(&contract_id) - .map_err(|e| Error::CannotParseContractId(contract_id.clone(), e))?, - )) +impl Pwd for Args { + fn set_pwd(&mut self, pwd: &Path) { + self.config_dir = Some(pwd.to_path_buf()); } } @@ -396,11 +420,10 @@ impl KeyType { } pub fn read_from_path(path: &Path) -> Result { - let data = fs::read(path).map_err(|_| Error::NetworkFileRead { + let data = fs::read_to_string(path).map_err(|_| Error::NetworkFileRead { path: path.to_path_buf(), })?; - let res = toml::from_slice(data.as_slice()); - Ok(res?) + Ok(toml::from_str(&data)?) } pub fn read_with_global(&self, key: &str, pwd: &Path) -> Result { @@ -468,18 +491,35 @@ impl KeyType { } pub fn global_config_path() -> Result { - Ok(if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { + let config_dir = if let Ok(config_home) = std::env::var("XDG_CONFIG_HOME") { PathBuf::from_str(&config_home).map_err(|_| Error::XdgConfigHome(config_home))? } else { - dirs::home_dir() + UserDirs::new() .ok_or(Error::HomeDirNotFound)? + .home_dir() .join(".config") + }; + + let soroban_dir = config_dir.join("soroban"); + let stellar_dir = config_dir.join("stellar"); + let soroban_exists = soroban_dir.exists(); + let stellar_exists = stellar_dir.exists(); + + if stellar_exists && soroban_exists { + tracing::warn!("the .stellar and .soroban config directories exist at path {config_dir:?}, using the .stellar"); } - .join("soroban")) -} -impl Pwd for Args { - fn set_pwd(&mut self, pwd: &Path) { - self.config_dir = Some(pwd.to_path_buf()); + if stellar_exists { + return Ok(stellar_dir); } + + if soroban_exists { + return Ok(soroban_dir); + } + + Ok(stellar_dir) +} + +pub fn config_file() -> Result { + Ok(global_config_path()?.join("config.toml")) } diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 12f571a50..a429ff434 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -1,8 +1,10 @@ use address::Address; use clap::{arg, command}; use serde::{Deserialize, Serialize}; - -use soroban_rpc::Client; +use std::{ + fs::{self, File}, + io::Write, +}; use crate::{ print::Print, @@ -10,7 +12,6 @@ use crate::{ xdr::{self, SequenceNumber, Transaction, TransactionEnvelope}, Pwd, }; - use network::Network; pub mod address; @@ -22,6 +23,8 @@ pub mod secret; pub mod sign_with; pub mod upgrade_check; +pub use alias::ContractAddress; + #[derive(thiserror::Error, Debug)] pub enum Error { #[error(transparent)] @@ -98,7 +101,7 @@ impl Args { ) -> Result, Error> { let network = self.get_network()?; let source_key = self.key_pair()?; - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; let latest_ledger = client.get_latest_ledger().await?.sequence; let seq_num = latest_ledger + 60; // ~ 5 min Ok(signer::sign_soroban_authorizations( @@ -114,10 +117,19 @@ impl Args { Ok(self.network.get(&self.locator)?) } - pub async fn next_sequence_number(&self, account_str: &str) -> Result { + pub async fn next_sequence_number( + &self, + account: impl Into, + ) -> Result { let network = self.get_network()?; - let client = Client::new(&network.rpc_url)?; - Ok((client.get_account(account_str).await?.seq_num.0 + 1).into()) + let client = network.rpc_client()?; + Ok((client + .get_account(&account.into().to_string()) + .await? + .seq_num + .0 + + 1) + .into()) } } @@ -127,9 +139,6 @@ impl Pwd for Args { } } -#[derive(Default, Serialize, Deserialize)] -pub struct Config {} - #[derive(Debug, clap::Args, Clone, Default)] #[group(skip)] pub struct ArgsLocatorAndNetwork { @@ -145,3 +154,47 @@ impl ArgsLocatorAndNetwork { Ok(self.network.get(&self.locator)?) } } + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct Config { + pub defaults: Defaults, +} + +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct Defaults { + pub network: Option, + pub identity: Option, +} + +impl Config { + pub fn new() -> Result { + let path = locator::config_file()?; + + if path.exists() { + let data = fs::read_to_string(&path).map_err(|_| locator::Error::FileRead { path })?; + Ok(toml::from_str(&data)?) + } else { + Ok(Config::default()) + } + } + + #[must_use] + pub fn set_network(mut self, s: &str) -> Self { + self.defaults.network = Some(s.to_string()); + self + } + + #[must_use] + pub fn set_identity(mut self, s: &str) -> Self { + self.defaults.identity = Some(s.to_string()); + self + } + + pub fn save(&self) -> Result<(), locator::Error> { + let toml_string = toml::to_string(&self)?; + let mut file = File::create(locator::config_file()?)?; + file.write_all(toml_string.as_bytes())?; + + Ok(()) + } +} diff --git a/cmd/soroban-cli/src/config/network.rs b/cmd/soroban-cli/src/config/network.rs index b6f6d8c1d..829716753 100644 --- a/cmd/soroban-cli/src/config/network.rs +++ b/cmd/soroban-cli/src/config/network.rs @@ -1,7 +1,11 @@ use clap::arg; +use itertools::Itertools; +use jsonrpsee_http_client::HeaderMap; use phf::phf_map; +use reqwest::header::{HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use std::collections::HashMap; use std::str::FromStr; use stellar_strkey::ed25519::PublicKey; use url::Url; @@ -18,8 +22,23 @@ pub mod passphrase; pub enum Error { #[error(transparent)] Config(#[from] locator::Error), - #[error("network arg or rpc url and network passphrase are required if using the network")] + #[error( + r#"Access to the network is required +`--network` or `--rpc-url` and `--network-passphrase` are required if using the network. +Alternatively you can use their corresponding environment variables: +STELLAR_NETWORK, STELLAR_RPC_URL and STELLAR_NETWORK_PASSPHRASE"# + )] Network, + #[error( + "rpc-url is used but network passphrase is missing, use `--network-passphrase` or `STELLAR_NETWORK_PASSPHRASE`" + )] + MissingNetworkPassphrase, + #[error( + "network passphrase is used but rpc-url is missing, use `--rpc-url` or `STELLAR_RPC_URL`" + )] + MissingRpcUrl, + #[error("cannot use both `--rpc-url` and `--network`")] + CannotUseBothRpcAndNetwork, #[error(transparent)] Rpc(#[from] rpc::Error), #[error(transparent)] @@ -30,6 +49,12 @@ pub enum Error { InvalidUrl(String), #[error("funding failed: {0}")] FundingFailed(String), + #[error(transparent)] + InvalidHeaderName(#[from] InvalidHeaderName), + #[error(transparent)] + InvalidHeaderValue(#[from] InvalidHeaderValue), + #[error("invalid HTTP header: must be in the form 'key:value'")] + InvalidHeader, } #[derive(Debug, clap::Args, Clone, Default)] @@ -38,17 +63,24 @@ pub struct Args { /// RPC server endpoint #[arg( long = "rpc-url", - requires = "network_passphrase", - required_unless_present = "network", env = "STELLAR_RPC_URL", help_heading = HEADING_RPC, )] pub rpc_url: Option, + /// RPC Header(s) to include in requests to the RPC provider + #[arg( + long = "rpc-header", + env = "STELLAR_RPC_HEADERS", + help_heading = HEADING_RPC, + num_args = 1, + action = clap::ArgAction::Append, + value_delimiter = '\n', + value_parser = parse_http_header, + )] + pub rpc_headers: Vec<(String, String)>, /// Network passphrase to sign the transaction sent to the rpc server #[arg( long = "network-passphrase", - requires = "rpc_url", - required_unless_present = "network", env = "STELLAR_NETWORK_PASSPHRASE", help_heading = HEADING_RPC, )] @@ -56,8 +88,6 @@ pub struct Args { /// Name of network to use from config #[arg( long, - required_unless_present = "rpc_url", - required_unless_present = "network_passphrase", env = "STELLAR_NETWORK", help_heading = HEADING_RPC, )] @@ -66,20 +96,20 @@ pub struct Args { impl Args { pub fn get(&self, locator: &locator::Args) -> Result { - if let Some(name) = self.network.as_deref() { - if let Ok(network) = locator.read_network(name) { - return Ok(network); - } - } - if let (Some(rpc_url), Some(network_passphrase)) = - (self.rpc_url.clone(), self.network_passphrase.clone()) - { - Ok(Network { + match ( + self.network.as_deref(), + self.rpc_url.clone(), + self.network_passphrase.clone(), + ) { + (None, None, None) => Err(Error::Network), + (_, Some(_), None) => Err(Error::MissingNetworkPassphrase), + (_, None, Some(_)) => Err(Error::MissingRpcUrl), + (Some(network), None, None) => Ok(locator.read_network(network)?), + (_, Some(rpc_url), Some(network_passphrase)) => Ok(Network { rpc_url, + rpc_headers: self.rpc_headers.clone(), network_passphrase, - }) - } else { - Err(Error::Network) + }), } } } @@ -94,6 +124,17 @@ pub struct Network { help_heading = HEADING_RPC, )] pub rpc_url: String, + /// Optional header (e.g. API Key) to include in requests to the RPC + #[arg( + long = "rpc-header", + env = "STELLAR_RPC_HEADERS", + help_heading = HEADING_RPC, + num_args = 1, + action = clap::ArgAction::Append, + value_delimiter = '\n', + value_parser = parse_http_header, + )] + pub rpc_headers: Vec<(String, String)>, /// Network passphrase to sign the transaction sent to the rpc server #[arg( long, @@ -103,6 +144,21 @@ pub struct Network { pub network_passphrase: String, } +fn parse_http_header(header: &str) -> Result<(String, String), Error> { + let header_components = header.splitn(2, ':'); + + let (key, value) = header_components + .map(str::trim) + .next_tuple() + .ok_or_else(|| Error::InvalidHeader)?; + + // Check that the headers are properly formatted + HeaderName::from_str(key)?; + HeaderValue::from_str(value)?; + + Ok((key.to_string(), value.to_string())) +} + impl Network { pub async fn helper_url(&self, addr: &str) -> Result { tracing::debug!("address {addr:?}"); @@ -160,6 +216,19 @@ impl Network { pub fn rpc_uri(&self) -> Result { Url::from_str(&self.rpc_url).map_err(|_| Error::InvalidUrl(self.rpc_url.to_string())) } + + pub fn rpc_client(&self) -> Result { + let mut header_hash_map = HashMap::new(); + for (header_name, header_value) in &self.rpc_headers { + header_hash_map.insert(header_name.to_string(), header_value.to_string()); + } + + let header_map: HeaderMap = (&header_hash_map) + .try_into() + .map_err(|_| Error::InvalidHeader)?; + + Ok(rpc::Client::new_with_headers(&self.rpc_url, header_map)?) + } } pub static DEFAULTS: phf::Map<&'static str, (&'static str, &'static str)> = phf_map! { @@ -186,6 +255,7 @@ impl From<&(&str, &str)> for Network { fn from(n: &(&str, &str)) -> Self { Self { rpc_url: n.0.to_string(), + rpc_headers: Vec::new(), network_passphrase: n.1.to_string(), } } @@ -197,11 +267,15 @@ mod tests { use mockito::Server; use serde_json::json; + const INVALID_HEADER_NAME: &str = "api key"; + const INVALID_HEADER_VALUE: &str = "cannot include a carriage return \r in the value"; + #[tokio::test] async fn test_helper_url_local_network() { let network = Network { rpc_url: "http://localhost:8000".to_string(), network_passphrase: passphrase::LOCAL.to_string(), + rpc_headers: Vec::new(), }; let result = network @@ -239,6 +313,7 @@ mod tests { let network = Network { rpc_url: server.url(), network_passphrase: passphrase::TESTNET.to_string(), + rpc_headers: Vec::new(), }; let url = network .helper_url("GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI") @@ -269,6 +344,7 @@ mod tests { let network = Network { rpc_url: server.url(), network_passphrase: passphrase::TESTNET.to_string(), + rpc_headers: Vec::new(), }; let url = network .helper_url("GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI") @@ -276,4 +352,107 @@ mod tests { .unwrap(); assert_eq!(url.as_str(), "https://friendbot.stellar.org/secret?api_key=123456&user=demo&addr=GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI"); } + + // testing parse_header function + #[tokio::test] + async fn test_parse_http_header_ok() { + let result = parse_http_header("Authorization: Bearer 1234"); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_parse_http_header_error_with_invalid_name() { + let invalid_header = format!("{INVALID_HEADER_NAME}: Bearer 1234"); + let result = parse_http_header(&invalid_header); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + format!("invalid HTTP header name") + ); + } + + #[tokio::test] + async fn test_parse_http_header_error_with_invalid_value() { + let invalid_header = format!("Authorization: {INVALID_HEADER_VALUE}"); + let result = parse_http_header(&invalid_header); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + format!("failed to parse header value") + ); + } + + // testing rpc_client function - we're testing this and the parse_http_header function separately because when a user has their network already configured in a toml file, the parse_http_header function is not called and we want to make sure that if the toml file is correctly formatted, the rpc_client function will work as expected + + #[tokio::test] + async fn test_rpc_client_is_ok_when_there_are_no_headers() { + let network = Network { + rpc_url: "http://localhost:1234".to_string(), + network_passphrase: "Network passphrase".to_string(), + rpc_headers: [].to_vec(), + }; + + let result = network.rpc_client(); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_rpc_client_is_ok_with_correctly_formatted_headers() { + let network = Network { + rpc_url: "http://localhost:1234".to_string(), + network_passphrase: "Network passphrase".to_string(), + rpc_headers: [("Authorization".to_string(), "Bearer 1234".to_string())].to_vec(), + }; + + let result = network.rpc_client(); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_rpc_client_is_ok_with_multiple_headers() { + let network = Network { + rpc_url: "http://localhost:1234".to_string(), + network_passphrase: "Network passphrase".to_string(), + rpc_headers: [ + ("Authorization".to_string(), "Bearer 1234".to_string()), + ("api-key".to_string(), "5678".to_string()), + ] + .to_vec(), + }; + + let result = network.rpc_client(); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_rpc_client_returns_err_with_invalid_header_name() { + let network = Network { + rpc_url: "http://localhost:8000".to_string(), + network_passphrase: passphrase::LOCAL.to_string(), + rpc_headers: [(INVALID_HEADER_NAME.to_string(), "Bearer".to_string())].to_vec(), + }; + + let result = network.rpc_client(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + format!("invalid HTTP header: must be in the form 'key:value'") + ); + } + + #[tokio::test] + async fn test_rpc_client_returns_err_with_invalid_header_value() { + let network = Network { + rpc_url: "http://localhost:8000".to_string(), + network_passphrase: passphrase::LOCAL.to_string(), + rpc_headers: [("api-key".to_string(), INVALID_HEADER_VALUE.to_string())].to_vec(), + }; + + let result = network.rpc_client(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + format!("invalid HTTP header: must be in the form 'key:value'") + ); + } } diff --git a/cmd/soroban-cli/src/fee.rs b/cmd/soroban-cli/src/fee.rs index c2bc32bdb..ea968870f 100644 --- a/cmd/soroban-cli/src/fee.rs +++ b/cmd/soroban-cli/src/fee.rs @@ -1,9 +1,13 @@ use clap::arg; use crate::assembled::Assembled; -use soroban_env_host::xdr; +use crate::xdr; -use crate::commands::HEADING_RPC; +use crate::{commands::HEADING_RPC, deprecated_arg}; + +const DEPRECATION_MESSAGE: &str = "--sim-only is deprecated and will be removed \ +in the future versions of CLI. The same functionality is offered by `tx simulate` command. To \ +replicate the behaviour, run `stellar --build only | stellar tx simulate`"; #[derive(Debug, clap::Args, Clone)] #[group(skip)] @@ -20,8 +24,14 @@ pub struct Args { /// Build the transaction and only write the base64 xdr to stdout #[arg(long, help_heading = HEADING_RPC)] pub build_only: bool, - /// Simulate the transaction and only write the base64 xdr to stdout - #[arg(long, help_heading = HEADING_RPC, conflicts_with = "build_only")] + /// (Deprecated) simulate the transaction and only write the base64 xdr to stdout + #[arg( + long, + help_heading = HEADING_RPC, + conflicts_with = "build_only", + display_order = 100, + value_parser = deprecated_arg!(bool, DEPRECATION_MESSAGE)) + ] pub sim_only: bool, } diff --git a/cmd/soroban-cli/src/get_spec.rs b/cmd/soroban-cli/src/get_spec.rs index 125fa984c..26e609543 100644 --- a/cmd/soroban-cli/src/get_spec.rs +++ b/cmd/soroban-cli/src/get_spec.rs @@ -1,8 +1,6 @@ -use soroban_env_host::xdr; +use crate::xdr; -use soroban_env_host::xdr::{ - ContractDataEntry, ContractExecutable, ScContractInstance, ScSpecEntry, ScVal, -}; +use crate::xdr::{ContractDataEntry, ContractExecutable, ScContractInstance, ScSpecEntry, ScVal}; use soroban_spec::read::FromWasmError; pub use soroban_spec_tools::contract as contract_spec; diff --git a/cmd/soroban-cli/src/key.rs b/cmd/soroban-cli/src/key.rs index b4fd358aa..b704541c4 100644 --- a/cmd/soroban-cli/src/key.rs +++ b/cmd/soroban-cli/src/key.rs @@ -1,13 +1,13 @@ +use crate::xdr::{ + self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, + ScVal, +}; use crate::{ commands::contract::Durability, - config::{locator, network::Network}, + config::{alias, locator, network::Network}, wasm, }; use clap::arg; -use soroban_env_host::xdr::{ - self, LedgerKey, LedgerKeyContractCode, LedgerKeyContractData, Limits, ReadXdr, ScAddress, - ScVal, -}; use std::path::PathBuf; #[derive(thiserror::Error, Debug)] @@ -34,7 +34,7 @@ pub struct Args { required_unless_present = "wasm", required_unless_present = "wasm_hash" )] - pub contract_id: Option, + pub contract_id: Option, /// Storage key (symbols only) #[arg(long = "key", conflicts_with = "key_xdr")] pub key: Option>, @@ -97,8 +97,11 @@ impl Args { } else { vec![ScVal::LedgerKeyContractInstance] }; - let contract = - locator.resolve_contract_id(self.contract_id.as_ref().unwrap(), network_passphrase)?; + let contract = self + .contract_id + .as_ref() + .unwrap() + .resolve_contract_id(locator, network_passphrase)?; Ok(keys .into_iter() diff --git a/cmd/soroban-cli/src/lib.rs b/cmd/soroban-cli/src/lib.rs index 4c904855c..302ecab00 100644 --- a/cmd/soroban-cli/src/lib.rs +++ b/cmd/soroban-cli/src/lib.rs @@ -5,8 +5,8 @@ )] use std::path::Path; -pub(crate) use soroban_env_host::xdr; pub(crate) use soroban_rpc as rpc; +pub use stellar_xdr::curr as xdr; mod cli; pub use cli::main; diff --git a/cmd/soroban-cli/src/log.rs b/cmd/soroban-cli/src/log.rs index 28ed4041b..1a1c27035 100644 --- a/cmd/soroban-cli/src/log.rs +++ b/cmd/soroban-cli/src/log.rs @@ -1,17 +1,14 @@ use crate::xdr; + pub mod auth; -pub mod budget; pub mod cost; pub mod event; pub mod footprint; -pub mod host_event; pub use auth::*; -pub use budget::*; pub use cost::*; pub use event::*; pub use footprint::*; -pub use host_event::*; pub fn extract_events(tx_meta: &xdr::TransactionMeta) -> Vec { match tx_meta { diff --git a/cmd/soroban-cli/src/log/auth.rs b/cmd/soroban-cli/src/log/auth.rs index c37e7ed3e..4a6b4bea8 100644 --- a/cmd/soroban-cli/src/log/auth.rs +++ b/cmd/soroban-cli/src/log/auth.rs @@ -1,4 +1,4 @@ -use soroban_env_host::xdr::{SorobanAuthorizationEntry, VecM}; +use crate::xdr::{SorobanAuthorizationEntry, VecM}; pub fn auth(auth: &[VecM]) { if !auth.is_empty() { diff --git a/cmd/soroban-cli/src/log/budget.rs b/cmd/soroban-cli/src/log/budget.rs deleted file mode 100644 index 59ff4aad4..000000000 --- a/cmd/soroban-cli/src/log/budget.rs +++ /dev/null @@ -1,5 +0,0 @@ -use soroban_env_host::budget::Budget; - -pub fn budget(budget: &Budget) { - tracing::debug!("{budget:#?}"); -} diff --git a/cmd/soroban-cli/src/log/cost.rs b/cmd/soroban-cli/src/log/cost.rs index 3e049a6cd..8dfc18df6 100644 --- a/cmd/soroban-cli/src/log/cost.rs +++ b/cmd/soroban-cli/src/log/cost.rs @@ -1,4 +1,4 @@ -use soroban_env_host::xdr::SorobanResources; +use crate::xdr::SorobanResources; use std::fmt::{Debug, Display}; struct Cost<'a>(&'a SorobanResources); diff --git a/cmd/soroban-cli/src/log/footprint.rs b/cmd/soroban-cli/src/log/footprint.rs index bfbc9f7a3..7c351de1d 100644 --- a/cmd/soroban-cli/src/log/footprint.rs +++ b/cmd/soroban-cli/src/log/footprint.rs @@ -1,4 +1,4 @@ -use soroban_env_host::xdr::LedgerFootprint; +use crate::xdr::LedgerFootprint; pub fn footprint(footprint: &LedgerFootprint) { tracing::debug!("{footprint:#?}"); diff --git a/cmd/soroban-cli/src/log/host_event.rs b/cmd/soroban-cli/src/log/host_event.rs deleted file mode 100644 index 4238a74c3..000000000 --- a/cmd/soroban-cli/src/log/host_event.rs +++ /dev/null @@ -1,7 +0,0 @@ -use soroban_env_host::events::HostEvent; - -pub fn host_events(events: &[HostEvent]) { - for (i, event) in events.iter().enumerate() { - tracing::info!("{i}: {event:#?}"); - } -} diff --git a/cmd/soroban-cli/src/print.rs b/cmd/soroban-cli/src/print.rs index 5b98687bd..fb9fb43dd 100644 --- a/cmd/soroban-cli/src/print.rs +++ b/cmd/soroban-cli/src/print.rs @@ -1,6 +1,6 @@ use std::{env, fmt::Display}; -use soroban_env_host::xdr::{Error as XdrError, Transaction}; +use crate::xdr::{Error as XdrError, Transaction}; use crate::{ config::network::Network, utils::explorer_url_for_transaction, utils::transaction_hash, @@ -8,6 +8,7 @@ use crate::{ const TERMS: &[&str] = &["Apple_Terminal", "vscode"]; +#[derive(Clone)] pub struct Print { pub quiet: bool, } @@ -104,3 +105,4 @@ create_print_functions!(plus, plusln, "➕"); create_print_functions!(save, saveln, "💾"); create_print_functions!(search, searchln, "🔎"); create_print_functions!(warn, warnln, "⚠️"); +create_print_functions!(exclaim, exclaimln, "❗️"); diff --git a/cmd/soroban-cli/src/signer.rs b/cmd/soroban-cli/src/signer.rs index bf02fdb17..5bf22499c 100644 --- a/cmd/soroban-cli/src/signer.rs +++ b/cmd/soroban-cli/src/signer.rs @@ -1,7 +1,7 @@ use ed25519_dalek::ed25519::signature::Signer as _; use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{ +use crate::xdr::{ self, AccountId, DecoratedSignature, Hash, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, SorobanAuthorizationEntry, diff --git a/cmd/soroban-cli/src/tx/builder/transaction.rs b/cmd/soroban-cli/src/tx/builder/transaction.rs index 51232d5c5..7cd922679 100644 --- a/cmd/soroban-cli/src/tx/builder/transaction.rs +++ b/cmd/soroban-cli/src/tx/builder/transaction.rs @@ -28,7 +28,7 @@ impl TxExt for xdr::Transaction { source_account, fee, seq_num: seq_num.into(), - cond: soroban_env_host::xdr::Preconditions::None, + cond: crate::xdr::Preconditions::None, memo: Memo::None, operations: [operation].try_into().unwrap(), ext: TransactionExt::V0, diff --git a/cmd/soroban-cli/src/upgrade_check.rs b/cmd/soroban-cli/src/upgrade_check.rs index 294056dd1..81ad7a0ee 100644 --- a/cmd/soroban-cli/src/upgrade_check.rs +++ b/cmd/soroban-cli/src/upgrade_check.rs @@ -56,7 +56,7 @@ pub async fn upgrade_check(quiet: bool) { let current_version = crate::commands::version::pkg(); let mut stats = UpgradeCheck::load().unwrap_or_else(|e| { - tracing::error!("Failed to load upgrade check data: {e}"); + tracing::debug!("Failed to load upgrade check data: {e}"); UpgradeCheck::default() }); @@ -72,7 +72,7 @@ pub async fn upgrade_check(quiet: bool) { }; } Err(e) => { - tracing::error!("Failed to fetch stellar-cli info from crates.io: {e}"); + tracing::debug!("Failed to fetch stellar-cli info from crates.io: {e}"); // Only update the latest check time if the fetch failed // This way we don't spam the user with errors stats.latest_check_time = now; @@ -80,7 +80,7 @@ pub async fn upgrade_check(quiet: bool) { } if let Err(e) = stats.save() { - tracing::error!("Failed to save upgrade check data: {e}"); + tracing::debug!("Failed to save upgrade check data: {e}"); } } diff --git a/cmd/soroban-cli/src/utils.rs b/cmd/soroban-cli/src/utils.rs index ee8bb1ece..0c4207a4e 100644 --- a/cmd/soroban-cli/src/utils.rs +++ b/cmd/soroban-cli/src/utils.rs @@ -2,7 +2,7 @@ use phf::phf_map; use sha2::{Digest, Sha256}; use stellar_strkey::ed25519::PrivateKey; -use soroban_env_host::xdr::{ +use crate::xdr::{ self, Asset, ContractIdPreimage, Hash, HashIdPreimage, HashIdPreimageContractId, Limits, ScMap, ScMapEntry, ScVal, Transaction, TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, WriteXdr, @@ -95,10 +95,12 @@ pub fn find_config_dir(mut pwd: std::path::PathBuf) -> std::io::Result { + #[error("This argument has been removed and will be not be recognized by the future versions of CLI: {0}" + )] + RemovedArgument(&'a str), + } + + #[macro_export] + /// Mark argument as removed with an error to be printed when it's used. + macro_rules! error_on_use_of_removed_arg { + ($_type:ident, $message: expr) => { + |a: &str| { + Err::<$_type, utils::args::DeprecatedError>( + utils::args::DeprecatedError::RemovedArgument($message), + ) + } + }; + } + + /// Mark argument as deprecated with warning to be printed when it's used. + #[macro_export] + macro_rules! deprecated_arg { + (bool, $message: expr) => { + <_ as clap::builder::TypedValueParser>::map( + clap::builder::BoolValueParser::new(), + |x| { + if (x) { + $crate::print::Print::new(false).warnln($message); + } + x + }, + ) + }; + } +} + pub mod rpc { - use soroban_env_host::xdr; + use crate::xdr; use soroban_rpc::{Client, Error}; use stellar_xdr::curr::{Hash, LedgerEntryData, LedgerKey, Limits, ReadXdr}; diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/Cargo.toml.removeextension b/cmd/soroban-cli/src/utils/contract-template/Cargo.toml.removeextension similarity index 89% rename from cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/Cargo.toml.removeextension rename to cmd/soroban-cli/src/utils/contract-template/Cargo.toml.removeextension index 2d8b3ac4e..8da1aed87 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/Cargo.toml.removeextension +++ b/cmd/soroban-cli/src/utils/contract-template/Cargo.toml.removeextension @@ -1,5 +1,5 @@ [package] -name = "hello-world" +name = "%contract-template%" version = "0.0.0" edition = "2021" publish = false diff --git a/cmd/soroban-cli/src/utils/contract-template/Makefile b/cmd/soroban-cli/src/utils/contract-template/Makefile new file mode 100644 index 000000000..7f774ad12 --- /dev/null +++ b/cmd/soroban-cli/src/utils/contract-template/Makefile @@ -0,0 +1,16 @@ +default: build + +all: test + +test: build + cargo test + +build: + stellar contract build + @ls -l target/wasm32-unknown-unknown/release/*.wasm + +fmt: + cargo fmt --all + +clean: + cargo clean diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs b/cmd/soroban-cli/src/utils/contract-template/src/lib.rs similarity index 100% rename from cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/lib.rs rename to cmd/soroban-cli/src/utils/contract-template/src/lib.rs diff --git a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs b/cmd/soroban-cli/src/utils/contract-template/src/test.rs similarity index 86% rename from cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs rename to cmd/soroban-cli/src/utils/contract-template/src/test.rs index af69a7ecb..0bdcba082 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/contracts/hello_world/src/test.rs +++ b/cmd/soroban-cli/src/utils/contract-template/src/test.rs @@ -6,7 +6,7 @@ use soroban_sdk::{vec, Env, String}; #[test] fn test() { let env = Env::default(); - let contract_id = env.register_contract(None, Contract); + let contract_id = env.register(Contract, ()); let client = ContractClient::new(&env, &contract_id); let words = client.hello(&String::from_str(&env, "Dev")); diff --git a/cmd/soroban-cli/src/utils/contract-init-template/.gitignore b/cmd/soroban-cli/src/utils/contract-workspace-template/.gitignore similarity index 62% rename from cmd/soroban-cli/src/utils/contract-init-template/.gitignore rename to cmd/soroban-cli/src/utils/contract-workspace-template/.gitignore index 97880f395..d74cc01f6 100644 --- a/cmd/soroban-cli/src/utils/contract-init-template/.gitignore +++ b/cmd/soroban-cli/src/utils/contract-workspace-template/.gitignore @@ -1,5 +1,6 @@ # Rust's output directory target -# Local Soroban settings +# Local settings .soroban +.stellar diff --git a/cmd/soroban-cli/src/utils/contract-init-template/Cargo.toml.removeextension b/cmd/soroban-cli/src/utils/contract-workspace-template/Cargo.toml.removeextension similarity index 100% rename from cmd/soroban-cli/src/utils/contract-init-template/Cargo.toml.removeextension rename to cmd/soroban-cli/src/utils/contract-workspace-template/Cargo.toml.removeextension diff --git a/cmd/soroban-cli/src/utils/contract-init-template/README.md b/cmd/soroban-cli/src/utils/contract-workspace-template/README.md similarity index 100% rename from cmd/soroban-cli/src/utils/contract-init-template/README.md rename to cmd/soroban-cli/src/utils/contract-workspace-template/README.md diff --git a/cmd/soroban-cli/src/wasm.rs b/cmd/soroban-cli/src/wasm.rs index 7ab2c243d..30f7a3e4a 100644 --- a/cmd/soroban-cli/src/wasm.rs +++ b/cmd/soroban-cli/src/wasm.rs @@ -1,9 +1,6 @@ -use crate::config::locator; -use crate::config::network::Network; +use crate::xdr::{self, Hash, LedgerKey, LedgerKeyContractCode}; use clap::arg; use sha2::{Digest, Sha256}; -use soroban_env_host::xdr::{self, Hash, LedgerKey, LedgerKeyContractCode}; -use soroban_rpc::Client; use soroban_spec_tools::contract::{self, Spec}; use std::{ fs, io, @@ -11,10 +8,14 @@ use std::{ }; use stellar_xdr::curr::{ContractDataEntry, ContractExecutable, ScVal}; -use crate::utils::rpc::get_remote_wasm_from_hash; -use crate::utils::{self}; - -use crate::wasm::Error::{ContractIsStellarAsset, UnexpectedContractToken}; +use crate::{ + config::{ + locator, + network::{Error as NetworkError, Network}, + }, + utils::{self, rpc::get_remote_wasm_from_hash}, + wasm::Error::{ContractIsStellarAsset, UnexpectedContractToken}, +}; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -41,12 +42,14 @@ pub enum Error { #[error(transparent)] Rpc(#[from] soroban_rpc::Error), #[error("unexpected contract data {0:?}")] - UnexpectedContractToken(ContractDataEntry), + UnexpectedContractToken(Box), #[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, + #[error(transparent)] + Network(#[from] NetworkError), } #[derive(Debug, clap::Args, Clone)] @@ -118,17 +121,11 @@ pub fn len(p: &Path) -> Result { } pub async fn fetch_from_contract( - contract_id: &str, + stellar_strkey::Contract(contract_id): &stellar_strkey::Contract, network: &Network, - locator: &locator::Args, ) -> Result, Error> { tracing::trace!(?network); - - let contract_id = &locator - .resolve_contract_id(contract_id, &network.network_passphrase)? - .0; - - let client = Client::new(&network.rpc_url)?; + let client = network.rpc_client()?; client .verify_network_passphrase(Some(&network.network_passphrase)) .await?; @@ -139,5 +136,5 @@ pub async fn fetch_from_contract( ContractExecutable::StellarAsset => Err(ContractIsStellarAsset), }; } - Err(UnexpectedContractToken(data_entry)) + Err(UnexpectedContractToken(Box::new(data_entry))) } diff --git a/cmd/stellar-cli/Cargo.toml b/cmd/stellar-cli/Cargo.toml index 2a388b4de..e1f1dce6b 100644 --- a/cmd/stellar-cli/Cargo.toml +++ b/cmd/stellar-cli/Cargo.toml @@ -6,7 +6,7 @@ repository = "https://github.com/stellar/stellar-cli" authors = ["Stellar Development Foundation "] license = "Apache-2.0" readme = "README.md" -version = "21.5.0" +version.workspace = true edition = "2021" rust-version.workspace = true autobins = false diff --git a/cmd/stellar-cli/README.md b/cmd/stellar-cli/README.md index 9b95e474a..48be33da7 100644 --- a/cmd/stellar-cli/README.md +++ b/cmd/stellar-cli/README.md @@ -6,12 +6,13 @@ Docs: https://developers.stellar.org ## Install +Install with Homebrew (macOS, Linux): + ``` -cargo install --locked stellar-cli +brew install stellar-cli ``` -To install with the `opt` feature, which includes a WASM optimization feature and wasm-opt built in: - +Install the latest version from source: ``` cargo install --locked stellar-cli --features opt ``` diff --git a/cookbook/contract-lifecycle.mdx b/cookbook/contract-lifecycle.mdx index b64262b34..2be63c23f 100644 --- a/cookbook/contract-lifecycle.mdx +++ b/cookbook/contract-lifecycle.mdx @@ -9,7 +9,7 @@ To manage the lifecycle of a Stellar smart contract using the CLI, follow these 1. Create an identity for Alice: ```bash -stellar keys generate alice +stellar keys generate alice -q ``` 2. Fund the identity: @@ -47,7 +47,7 @@ stellar contract invoke --id --source alice --network testnet -- f 6. View the contract's state: ```bash -stellar contract read --id --network testnet --source alice --durability --key +stellar contract read --id --network testnet --durability --key ``` Note: `` is either `persistent` or `temporary`. `KEY` provides the key of the storage entry being read. diff --git a/cookbook/payments-and-assets.mdx b/cookbook/payments-and-assets.mdx index e3be8dafc..a198973e1 100644 --- a/cookbook/payments-and-assets.mdx +++ b/cookbook/payments-and-assets.mdx @@ -17,11 +17,11 @@ By setting the `STELLAR_NETWORK` environment variable, we will not have to set t 2. Fund the accounts: ```bash -stellar keys generate alice +stellar keys generate alice -q ``` ```bash -stellar keys generate bob +stellar keys generate bob -q ``` ```bash diff --git a/deny.toml b/deny.toml new file mode 100644 index 000000000..238e91a14 --- /dev/null +++ b/deny.toml @@ -0,0 +1,344 @@ +# This template contains all of the possible sections and their default values + +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# The values provided in this template are the default values that will be used +# when any section or field is not specified in your own configuration + +# Root options + +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + { triple = "x86_64-unknown-linux-gnu" }, + { triple = "aarch64-unknown-linux-gnu" }, + { triple = "x86_64-apple-darwin" }, + { triple = "aarch64-apple-darwin" }, + { triple = "x86_64-pc-windows-msvc" }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) + +exclude = [ +] + +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = true +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The path where the advisory database is cloned/fetched into +db-path = "~/.cargo/advisory-db" +# The url(s) of the advisory databases to use +db-urls = ["https://github.com/rustsec/advisory-db"] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +ignore = [ + #"RUSTSEC-0000-0000", +] +# Threshold for security vulnerabilities, any vulnerability with a CVSS score +# lower than the range specified will be ignored. Note that ignored advisories +# will still output a note when they are encountered. +# * None - CVSS Score 0.0 +# * Low - CVSS Score 0.1 - 3.9 +# * Medium - CVSS Score 4.0 - 6.9 +# * High - CVSS Score 7.0 - 8.9 +# * Critical - CVSS Score 9.0 - 10.0 +#severity-threshold = + +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +# The lint level for crates which do not have a detectable license +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + "BSD-3-Clause", + "Apache-2.0 WITH LLVM-exception", + "Unicode-DFS-2016", + "ISC", + "BSD-2-Clause", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ + #"Nokia", +] +# Lint level for licenses considered copyleft +copyleft = "deny" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # The following dep licenses were inspected and approved for use because we + # do not fork them. Related conversation and approval is at: + # https://stellarfoundation.slack.com/archives/C07TC92R2P7/p1730924548048429?thread_ts=1730900591.962089&cid=C07TC92R2P7 + { allow = ["MPL-2.0"], name = "option-ext" }, + { allow = ["MPL-2.0"], name = "webpki-roots" }, + # The following dep licensed was manually reviewed and approved for use. Related conversation and approval is at: + # https://stellarfoundation.slack.com/archives/C07TC92R2P7/p1730917671563029?thread_ts=1730903286.490759&cid=C07TC92R2P7 + { allow = ["OpenSSL"], name = "ring" }, +] + +[[licenses.clarify]] +name = "ring" +expression = "MIT AND ISC AND OpenSSL" +license-files = [ + { path = "LICENSE", hash = 0xbd0eed23 } +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +#[[licenses.clarify]] +# The name of the crate the clarification applies to +#name = "ring" +# The optional version constraint for the crate +#version = "*" +# The SPDX expression for the license requirements of the crate +#expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +#license-files = [ + # Each entry is a crate relative path, and the (opaque) hash of its contents + #{ path = "LICENSE", hash = 0xbd0eed23 } +#] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "deny" +# Lint level for when a crate version requirement is `*` +wildcards = "deny" +allow-wildcard-paths = true +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#name = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + + # Requires updating slipped10 to newest sha2 others are dependents that will be updated + { crate = "sha2", reason = "temporary duplicate until upstream updates" }, + { crate = "digest", reason = "temporary duplicate until upstream updates" }, + { crate = "block-buffer", reason = "temporary duplicate until upstream updates" }, + # slipped again 0.12.1 + { crate = "hmac", reason = "temp" }, + + # update rpassword, hidapi (then ledger-transport-hid), and dirs-sys (then directories) to 0.52 + # { crate = "window-sys", reason = "temp" }, + + # syn is too large of a surface to check + { crate = "syn", reason = "Too many crates haven't updated to v2" }, + + # Need to release new version and update all stellar crates + { crate = "stellar-strkey", reason = "Temp until new release and updates upstream", version = "0.0.8" }, + + # Need to update jsonrpsee in stellar-rpc-client + { crate = "rustls-pemfile", reason = "Temp until new release and updates upstream" }, + { crate = "rustls-webpki", reason = "Temp until new release and updates upstream" }, + { crate = "rustls-native-certs", reason = "Temp until new release and updates upstream", version = "0.7.3" }, + { crate = "rustls", reason = "Temp until new release and updates upstream" }, + { crate = "hyper", reason = "temporary duplicate until upstream updates" }, + { crate = "hyper-rustls", reason = "temporary duplicate until upstream updates", version = "0.27.3" }, + { crate = "http-body", reason = "temporary duplicate until upstream updates" }, + { crate = "http", reason = "temporary duplicate until upstream updates" }, + { crate = "h2", reason = "temporary duplicate until upstream updates", version = "0.3.26" }, + { crate = "base64", reason = "temporary duplicate until upstream updates", version = "0.22.1" }, + # Upgrade stellar-rpc-client to use 0.26.0 + { crate = "tokio-rustls", reason = "temporary duplicate until upstream updates" }, + + # wasm-opt + { crate = "heck", reason = "wasm-opt needs to update to 0.5", version = "0.5.0"}, + { crate = "strum", reason = "wasm-opt needs to update", version = "0.26.3" }, + { crate = "strum_macros", reason = "wasm-opt needs to update", version = "0.26.4" }, + + + # soroban-env-host must upgrade ark-* to 0.14.5 + { crate = "hashbrown", reason = "temp", version = "13.2"}, + + { crate = "windows-sys", reason = "temp", version = "0.59.0"}, + { crate = "windows-targets", reason = "temp", version = "0.52.6"}, + { crate = "windows_x86_64_gnu", reason = "temp", version = "0.52.6"}, + { crate = "windows_x86_64_msvc", reason = "temp", version = "0.52.6"}, + # { crate = "dir-sys", reason = "temp", version } + # + # update tracing-subscriber + { crate = "regex-syntax", reason = "temp", version = "0.8.4" }, + { crate = "regex-automata", reason = "temp", version = "0.4.7" }, + + # wasm-gen update + { crate = "byteorder", reason = "temp", version = "1.5.0" }, + + # testcontainers + { crate = "idna", reason = "temp", version = "0.5.0" }, + + + # { name = "hashbrown", version = "=0.13.2" }, + # { name = "syn", version = "=1.0.109" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = ["stellar"] +# 1 or more gitlab.com organizations to allow git sources for +# gitlab = [""] +# 1 or more bitbucket.org organizations to allow git sources for +# bitbucket = [""] diff --git a/flake.lock b/flake.lock index fef18714e..fd92bacb2 100644 --- a/flake.lock +++ b/flake.lock @@ -36,11 +36,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1718428119, - "narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", + "lastModified": 1728538411, + "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", + "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", "type": "github" }, "original": { @@ -62,11 +62,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1727144949, - "narHash": "sha256-uMZMjoCS2nf40TAE1686SJl3OXWfdfM+BDEfRdr+uLc=", + "lastModified": 1729823394, + "narHash": "sha256-RiinJqorqSLKh1oSpiMHnBe6nQdJzE45lX6fSnAuDnI=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "2e19799819104b46019d339e78d21c14372d3666", + "rev": "7e52e80f5faa374ad4c607d62c6d362589cb523f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 3b826ee77..8e5ace887 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "stellar-cli development shell"; + description = "stellar-cli"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -14,30 +14,45 @@ pkgs = import nixpkgs { inherit system overlays; }; - in - with pkgs; - { - devShells.default = mkShell { - buildInputs = [ + stellardev = { + name = "stellar"; + src = ./.; + nativeBuildInputs = pkgs.lib.optionals (pkgs.stdenv.isDarwin) [ + pkgs.darwin.apple_sdk.frameworks.SystemConfiguration + ]; + buildInputs = with pkgs; [ openssl pkg-config - libudev-zero jq (rust-bin.stable.latest.default.override { extensions = [ "rust-src" ]; targets = [ "wasm32-unknown-unknown" ]; }) - ]; - shellHook = - '' - echo "Using `nix --version`" - alias stellar="cargo run --bin stellar --" - [ -f ./local.sh ] && source ./local.sh - shell=$0 - shell=`basename $SHELL` - source <(stellar completion --shell $shell) - ''; + ] ++ lib.optionals (stdenv.isLinux) [libudev-zero]; }; + stellarcli = stellardev // { + cargoLock = { + lockFile = ./Cargo.lock; + }; + + cargoLock.outputHashes = { + # This is needed for any git+https dependency in Cargo.lock + # "somepackage-1.2.3" = "sha256-somehash"; + }; + + doCheck = false; + + GIT_REVISION = "${self.rev or self.dirtyRev or "unknown"}"; + }; + rustPlatformMod = pkgs.makeRustPlatform { + cargo = pkgs.rust-bin.stable.latest.default; + rustc = pkgs.rust-bin.stable.latest.default; + }; + in + with pkgs; + { + devShells.default = mkShell stellardev; + packages.default = rustPlatformMod.buildRustPackage stellarcli; } ); } diff --git a/installer.iss b/installer.iss new file mode 100644 index 000000000..895740ccb --- /dev/null +++ b/installer.iss @@ -0,0 +1,94 @@ +#define STELLAR_CLI_VERSION GetEnv("STELLAR_CLI_VERSION") +#define STELLAR_CLI_INSTALLER GetEnv("STELLAR_CLI_INSTALLER") + +[Setup] +AppName=Stellar CLI +AppVersion={#STELLAR_CLI_VERSION} +DefaultDirName={commonpf}\Stellar CLI +DefaultGroupName=Stellar CLI +OutputBaseFilename=stellar-installer +PrivilegesRequired=admin +LicenseFile=LICENSE +UninstallDisplayIcon={app}\stellar.ico +Compression=lzma +SolidCompression=yes +ChangesEnvironment=yes + +[Files] +Source: "stellar.exe"; DestDir: "{app}" +Source: "stellar.ico"; DestDir: "{app}" +Source: "LICENSE"; DestDir: "{app}"; Flags: ignoreversion + +[Icons] +; Windows optimizes start menu, and removes the uninstall entry. Unless we +; specify it twice. 🫠 +Name: "{group}\Uninstall Stellar CLI"; Filename: "{uninstallexe}" +Name: "{group}\Uninstall Stellar CLI"; Filename: "{uninstallexe}" +Name: "{group}\Stellar Developer Docs"; Filename: "https://stellar.org/docs" + +[Code] +const EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'; + +procedure EnvAddPath(Path: string); +var + Paths: string; +begin + { Retrieve current path (use empty string if entry not exists) } + if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) + then Paths := ''; + + { Skip if string already found in path } + if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit; + + { App string to the end of the path variable } + Paths := Paths + ';'+ Path +';' + + { Overwrite (or create if missing) path environment variable } + if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) + then Log(Format('The [%s] added to PATH: [%s]', [Path, Paths])) + else Log(Format('Error while adding the [%s] to PATH: [%s]', [Path, Paths])); +end; + +procedure EnvRemovePath(Path: string); +var + Paths: string; + P: Integer; +begin + { Skip if registry entry not exists } + if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then + exit; + + { Skip if string not found in path } + P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';'); + if P = 0 then exit; + + { Update path variable } + Delete(Paths, P - 1, Length(Path) + 1); + + { Overwrite path environment variable } + if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) + then Log(Format('The [%s] removed from PATH: [%s]', [Path, Paths])) + else Log(Format('Error while removing the [%s] from PATH: [%s]', [Path, Paths])); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssPostInstall + then EnvAddPath(ExpandConstant('{app}')); +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +begin + if CurUninstallStep = usPostUninstall + then EnvRemovePath(ExpandConstant('{app}')); +end; + +[UninstallDelete] +; Remove the Start Menu group +Type: filesandordirs; Name: "{group}" + +; Remove installed files and directory +Type: files; Name: "{app}\stellar.exe" +Type: files; Name: "{uninstallexe}" +Type: files; Name: "{app}\stellar.ico" +Type: dirifempty; Name: "{app}" diff --git a/scripts/check-dependencies.bash b/scripts/check-dependencies.bash deleted file mode 100755 index c82d2f2b1..000000000 --- a/scripts/check-dependencies.bash +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -set -e - -SED=sed -if [ -z "$(sed --version 2>&1 | grep GNU)" ]; then - SED=gsed -fi - -CURL="curl -sL --fail-with-body" -if ! CARGO_OUTPUT=$(cargo tree -p soroban-env-host 2>&1); then - echo "The project depends on multiple versions of the soroban-env-host Rust library, please unify them." - echo "Make sure the soroban-sdk dependency indirectly points to the same soroban-env-host dependency imported explicitly." - echo - echo "This is soroban-env-host version imported by soroban-sdk:" - cargo tree --depth 1 -p soroban-sdk | grep env-host - echo - echo - echo - echo "Full error:" - echo $CARGO_OUTPUT - exit 1 -fi - -# revision of the https://github.com/stellar/rs-stellar-xdr library used by the Rust code -RS_STELLAR_XDR_REVISION="" - -# revision of https://github.com/stellar/stellar-xdr/ used by the Rust code -STELLAR_XDR_REVISION_FROM_RUST="" - -function stellar_xdr_version_from_rust_dep_tree { - LINE=$(grep stellar-xdr | head -n 1) - # try to obtain a commit - COMMIT=$(echo $LINE | $SED -n 's/.*rev=\(.*\)#.*/\1/p') - if [ -n "$COMMIT" ]; then - echo "$COMMIT" - return - fi - # obtain a crate version - echo $LINE | $SED -n 's/.*stellar-xdr \(v\)\{0,1\}\([^ ]*\).*/\2/p' -} - -if CARGO_OUTPUT=$(cargo tree --depth 0 -p stellar-xdr 2>&1); then - RS_STELLAR_XDR_REVISION=$(echo "$CARGO_OUTPUT" | stellar_xdr_version_from_rust_dep_tree) - if [ ${#RS_STELLAR_XDR_REVISION} -eq 40 ]; then - # revision is a git hash - STELLAR_XDR_REVISION_FROM_RUST=$($CURL https://raw.githubusercontent.com/stellar/rs-stellar-xdr/${RS_STELLAR_XDR_REVISION}/xdr/curr-version) - else - # revision is a crate version - CARGO_SRC_BASE_DIR=$(realpath ${CARGO_HOME:-$HOME/.cargo}/registry/src/index*) - STELLAR_XDR_REVISION_FROM_RUST=$(cat "${CARGO_SRC_BASE_DIR}/stellar-xdr-${RS_STELLAR_XDR_REVISION}/xdr/curr-version") - fi -else - echo "The project depends on multiple versions of the Rust rs-stellar-xdr library" - echo "Make sure a single version of stellar-xdr is used" - echo - echo - echo - echo "Full error:" - echo $CARGO_OUTPUT -fi diff --git a/stellar.ico b/stellar.ico new file mode 100644 index 000000000..4bbfa5f04 Binary files /dev/null and b/stellar.ico differ