feat(cli): add check-status, verify-hash and generate-bindings commands + multi-role token tests - #829
Merged
p3ris0n merged 4 commits intoAug 26, 2026
Conversation
Adds a `bc-forge check-status` command that pings every contract declared under `contracts` in .bc-forge.json and reports per-contract reachability and latency. Each contract is probed by reading its instance ledger entry via getLedgerEntries. An entry proves the contract is deployed and served by the RPC node, so the check needs no contract-specific method and works uniformly across token, wrapper and any future contract. Four distinct statuses are reported rather than a single pass/fail, so an operator can tell the failure modes apart: - responsive instance entry returned, with measured latency - not_deployed RPC answered but holds no instance for that id - unreachable the RPC call itself failed - invalid contract id is malformed or absent from the config Contract ids are validated locally before any network call, so a typo in the config is reported immediately instead of as a spurious RPC failure. The command exits non-zero when any contract is not responsive, making it usable as a deployment gate in CI. The clock is injected so latency assertions in tests are deterministic. Closes BCPathway#699
Adds a `bc-forge verify-hash` command that answers whether a deployed
contract is actually running the code in the local build tree.
The local side hashes the raw .wasm bytes with SHA-256, which is exactly
how Soroban derives the code hash it stores, so the two values are
directly comparable. Verified against sha256sum on the same artifact.
The on-chain side reads the contract instance ledger entry and walks
val -> instance -> executable to the referenced wasmHash, rather than
trusting a hash cached in .bc-forge.json, so the check reflects what the
network currently serves.
Distinct verdicts separate the failure modes an operator cares about:
- match local build is what the network runs
- mismatch contract runs different code than the local build
- missing_local the build artifact was not found or is unreadable
- missing_onchain no instance entry, or a Stellar-asset contract with
no uploaded WASM
- invalid malformed input or a failed RPC lookup
The command exits non-zero on anything other than a match so it can gate
a release pipeline.
Closes BCPathway#700
…nerator Adds a `bc-forge generate-bindings` command that drives `stellar contract bindings <language>` so binding generation is reachable from the project CLI with the configured network already applied. All seven generators the Stellar CLI exposes are supported: typescript, rust, python, java, flutter, swift and php. Option combinations are validated before spawning, so misuse is reported by this CLI rather than as an opaque subprocess failure: - exactly one contract source (--wasm, --wasm-hash or --contract-id) - --output-dir required for the generators that write a package - the rust generator accepts a local --wasm only, matching its actual flag surface, and is rejected early for network sources RPC flags are forwarded only when generating from the network, since a local wasm needs no node. The binary is resolved through STELLAR_CLI_BIN / SOROBAN_CLI_BIN to accommodate the soroban -> stellar rename, and a missing binary produces an install hint instead of a bare ENOENT. The command runner is injected so tests assert the argument vector without spawning a process. Verified end to end against Stellar CLI 25.2.0: typescript generation produced a package exposing the real contract methods, and the rust generator emitted a client module. Closes BCPathway#701
Adds integration coverage for one address holding several roles at once, exercised through the token contract rather than asserted at the storage layer alone. Role membership is a single per-address bitmask under AdminKey::RoleMask, so granting a second role is a bitwise OR onto the existing mask. The tests grant Minter | Pauser to one address and then execute a real mint and a real pause from it, which is the behaviour the bitmask layout is supposed to make possible. Coverage: - both roles are held simultaneously and share one mask entry, while never-granted roles stay absent - the address mints, pauses, is confirmed to have actually halted transfers, and unpauses - exercising one role does not consume or disturb the other - clearing one bit leaves the other role held and enforced, and the cleared role is genuinely rejected - an address holding only one of the two roles cannot exercise the other - assignments are isolated per address and do not leak to a bystander - an address with no roles can neither mint nor pause The setup writes the RoleMask entry directly, matching how grant_role persists a combined assignment, so the tests exercise the bitmask path rather than the superseded per-role legacy keys. Closes BCPathway#759
This was referenced Aug 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bundles three CLI deployment utilities and the multi-role RBAC test coverage.
check-status(#699)Pings every contract declared under
contractsin.bc-forge.jsonby reading its instance ledger entry, reporting per-contract reachability and latency. Distinct verdicts (responsive,not_deployed,unreachable,invalid) separate the failure modes; exits non-zero when any contract is not responsive so it can gate a deployment.verify-hash(#700)Answers whether a deployed contract is actually running the code in the local build tree. The local side takes the SHA-256 of the raw
.wasmbytes (how Soroban derives its code hash); the on-chain side reads the contract instance ledger entry and walks to the referencedwasmHashrather than trusting a hash cached in config. Exits non-zero on anything but a match.generate-bindings(#701)Wraps
stellar contract bindings <language>with the configured network already applied. All seven generators supported (typescript, rust, python, java, flutter, swift, php). Option combinations are validated before spawning so misuse is reported by this CLI rather than as an opaque subprocess failure. The binary resolves throughSTELLAR_CLI_BIN/SOROBAN_CLI_BINfor the soroban→stellar rename.Multi-role token tests (#759)
Role membership is a single per-address bitmask under
AdminKey::RoleMask, so granting a second role is a bitwise OR onto the existing mask. These tests grantMinter | Pauserto one address and execute a real mint and a real pause from it — covering simultaneous roles in one mask entry, verified transfer halt, per-address isolation, and that clearing one bit leaves the other enforced.Verification
tsc -bclean; 72/72 vitest pass (41 new + 31 existing)--helpcargo fmt --all -- --checkandcargo clippy --all-targets --all-features -- -D warningscleancheck-statusandverify-hashexercised against live Soroban testnet RPC;generate-bindingsverified end-to-end against Stellar CLI 25.2.0Note: PR #643 covers related ground via fuzz tests in
contracts/admin/src/fuzz_roles.rs(issue #484). The tests here are token-level integration tests in a separate file, so the two do not conflict.Closes #699
Closes #700
Closes #701
Closes #759