Repository navigation
Conversation
set_custodian and a Custodian change accepted the vault's own address. Capital deployed there never leaves, and the books count it as both held and deployed. Every path that sets the custodian now refuses it with InvalidCustodian: the direct setter during setup and after, and a timelocked change when it is proposed and again when it applies.
finish_setup checked the bindings, the token's manager role, the share supply, the allowlist and the freeze, but not the rest of the wiring. It now also needs the asset to have the share token's decimals, four different addresses for the asset, the token, the oracle and the vault, an oracle that answers state and freshness_duration, and a custodian that is not the vault. Each check calls out with try_ and counts an error as false, and health() reports each one as a new field. The harness deploy stops if any of them is false.
The kit spec's Health table, invariant, custodian rows, runbook and adopter interfaces, the authorization matrix's deploy steps and the testing guide list the new wiring checks and the refusal of the vault as its own custodian. The README shows a deploy run that prints the new health() fields.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
An Oracle change checked only that the new address names this vault with its setup closed. Another kit contract bound to the vault, such as the compliance contract, passed at proposal and then trapped at apply on a raw state() call. feed_bound_here now also needs state() and freshness_duration() to answer, so such a swap is refused with FeedNotBound when it is proposed and again when it applies. The setup check and the swap guard share one helper.
The design doc's governance row lists the conditions setup closes on, and the authorization matrix lists each vault entrypoint's guards. Both now include matching decimals, four distinct addresses, an oracle that answers the feed interface and a custodian other than the vault. The matrix rows for set_custodian, propose_change and apply_change name InvalidCustodian and its tests.
hpmaxi
marked this pull request as ready for review
October 20, 2026 03:59
Brings main into this branch through feedback. Conflicts: - README.md: the sample deploy run takes main's key and funding lines and keeps this branch's run. - docs/kit-spec.md: keep this branch's text, with formulas in GitHub's dollar-backtick delimiters, as main now writes them.
luchobonatti
approved these changes
Oct 23, 2026
This branch was successfully deployed
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.
Summary
Closes #326
A vault could finish setup with part of its wiring wrong.
finish_setupchecked the bindings, the token's manager role, the share supply, the allowlist and the freeze. It did not check that the asset has the share token's decimals, that the asset, the token, the oracle and the vault are four different addresses, that the oracle answers the feed's views, or that a custodian is set. The vault's own address was also accepted as its custodian.Now
finish_setuprefuses withSetupIncompleteuntil each of these holds, andhealth()reports each one as its own field. Every path that sets the custodian refuses the vault's own address with a new error,InvalidCustodian.Changes
finish_setupalso needs the asset to have the share token's decimals, four different addresses for the asset, the token, the oracle and the vault, an oracle that answersstateandfreshness_duration, and a custodian that is not the vaulttry_and counts an error as false; a new feed with no report still passeshealth()gainsdecimals_match,addresses_distinct,custodian_setandfeed_answersset_custodian(during setup and after) and aCustodianchange (when proposed and when applied) refuse the vault's own address withInvalidCustodian(6075), through a newcustodian_not_the_vaultguardOraclechange refuses, withFeedNotBound, a contract that does not answerstateandfreshness_duration, when proposed and again when applied. It used to pass the proposal and trap at apply. The setup check and the swap use one helperMiswirerows cover the new checks in the setup and health tableshealth()field is false; a new guards check refuses the vault as its own custodian on a real networkDeviations
set_custodianchecks the address before the setup state, so after setup the vault's own address fails withInvalidCustodian, notUseTimelock.Acceptance criteria
SetupIncomplete, and itshealth()field is falsehealth()fields and passes on a local networkTest plan
Automated tests
cargo test --locked: 681 passed (435 vault, 98 oracle, 80 token, 36 compliance, 7 identity verifier, 19 timelock, 6 storage). Four tests are new, and the setup and health tables have five new rows each. Each of the five commits passes on its own.cargo clippy --locked --all-targets --workspace -- -D warnings, and on the vault with--features short_wind_down: clean.cargo test -p async-vault -p nav-oracle --features upgrade_wasm with_wasm, after the builds indocs/TESTING.md): vault 5 and oracle 2 pass.Manual verification
docker run --rm -d -p 8000:8000 --name stellar-local stellar/quickstart --local --enable core,rpc.stellar contract build, thenCARGO_TARGET_DIR=target/short-wind-down stellar contract build --package async-vault --features short_wind_down.npm run build:clients, thenHARNESS_NETWORK=local npm run deploy -w scripts/harness. Thehealthline showsdecimals_match,addresses_distinct,custodian_setandfeed_answerstrue.test:happy-path,test:guardsandtest:wind-downwithnpm run <script> -w scripts/harness.Breaking changes
Fresh deployments only.
async_vault'sHealthgains four fields:decimals_match,addresses_distinct,custodian_setandfeed_answers. A client decodinghealth()needs the regenerated type.VaultError::InvalidCustodian = 6075.set_custodian(vault),propose_change(Custodian(vault))and applying such a change refuse the vault's own address. After setup,set_custodian(vault)fails withInvalidCustodianinstead ofUseTimelock.finish_setuprefuses withSetupIncompleteuntil a custodian is set, so a deploy callsset_custodianfirst (the harness already does). The asset must have the share token's decimals (7 today).propose_change(Oracle(address))and itsapply_changerefuse an address that does not answerstate()andfreshness_duration()withFeedNotBound(6072). Applying one used to fail with a host error instead.FeedNotBound's doc says so.Checklist
Screenshots
None.