fix: open_cdp for iETH/iBTC/iADA — pin the local UPLC evaluator past a 0.2.20 false-negative - #52
Merged
Merged
Conversation
open_cdp failed for iETH, iBTC and iADA with ADA collateral, always with `Spend[n] … Trace <expected> CDP output is invalid`, while iUSD, iSOL, iEUR and iJPY built fine. The transactions were valid all along: the exact bytes the local evaluator refused were accepted by the ledger evaluator (Ogmios via Blockfrost), which returned execution units for every script. The cause is @lucid-evolution/uplc 0.2.20, fixed upstream in 0.2.22. It arrives three levels down, via @indigo-labs/indigo-sdk -> @lucid-evolution/lucid@0.4.29. Isolated by capturing the arguments passed to eval_phase_two_raw and replaying them against both versions: 0.2.20 rejects, 0.2.22 accepts. The trigger is CBOR length encoding. The cdp_creator validator builds its expected CDP datum by embedding the collateral_asset field copied from the collateral-asset reference input, and 0.2.20 compares that sub-term byte-sensitively rather than structurally. The on-chain collateral-asset entries split exactly on that encoding: indefinite-length for the four assets that worked, definite-length for the three that did not. The override alone is not enough. The WASM copy step scanned node_modules with wildcards, so `find` matched both 0.2.20 and 0.2.22 and whichever came last won; the build kept shipping the 0.2.20 WASM against 0.2.22 JS glue. The CML pattern had the same hazard, with two copies present and a wildcard matching a different scope than the comment claimed. Resolve each WASM through the same dependency chain the bundle imports instead. No SDK change is needed: open_cdp works on the unchanged 0.3.28 once the evaluator is fixed. Verified: all seven assets build unsigned CBOR through the MCP protocol; the packed tarball installed into a clean directory with no repo and no overrides still builds all seven, so the fix reaches consumers. Refs INDY-137
Independent of the evaluator fix in the previous commit — open_cdp already works on 0.3.28 once the evaluator is pinned. This brings the SDK up to the current release so later work starts from it. The API change is mechanical: `currentSlot` was dropped from every builder signature, since the SDK now derives it from lucid. Removed the argument at each call site along with the local bindings that fed it. `@lucid-evolution/lucid` moves in step, otherwise two distinct `LucidEvolution` types coexist and every build closure fails to typecheck. The Pyth wiring test asserts argument positions, which all shift down by one, and depositCdp drops from 10 arguments to 9. The uplc override stays: @qbtlabs/x402 still pulls lucid 0.4.29, so without it a 0.2.20 evaluator can still reach the bundle. Verified: all seven assets build unsigned CBOR through the MCP protocol, tsc --noEmit clean, 144 tests pass.
Merged
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.
Fixes INDY-137.
open_cdpwith ADA collateral failed for iETH, iBTC and iADA — always withSpend[n] … Trace <expected> CDP output is invalid— while iUSD, iSOL, iEUR and iJPYbuilt fine. Reported by an external integrator against the published 0.4.0.
The transactions were valid all along. The exact bytes the local evaluator refused were
accepted by the ledger evaluator (Ogmios via Blockfrost), which returned execution units
for every script including
spend:1.Cause
@lucid-evolution/uplc0.2.20 has a bug in local script evaluation, fixed upstream in0.2.22. It arrives three levels down:
indigo-mcp→@indigo-labs/indigo-sdk→@lucid-evolution/lucid@0.4.29→@lucid-evolution/uplc@0.2.20.Isolated by capturing the exact arguments handed to
eval_phase_two_rawand replaying themagainst both versions:
The trigger is CBOR length encoding.
cdp_creatorbuilds its expected CDP datum byembedding the
collateral_assetfield copied verbatim from the collateral-asset referenceinput, and 0.2.20 compares that sub-term byte-sensitively rather than structurally:
The on-chain collateral-asset entries split exactly on that encoding — indefinite for the
four assets that worked, definite for the three that did not. Forcing the encoding flips the
local result in both directions across all seven.
Changes
package.json— pin the evaluator:scripts/build.sh— the override alone is not enough. The WASM copy step scannednode_moduleswith wildcards, sofindmatched both 0.2.20 and 0.2.22 and whichever camelast won; the build kept shipping the 0.2.20 WASM against 0.2.22 JS glue. The CML pattern had
the same hazard, with two copies present and the wildcard matching a different scope than the
comment claimed. Each WASM is now resolved through the same dependency chain the bundle
imports.
Second commit, independent:
@indigo-labs/indigo-sdk0.3.28 → 0.5.9 and@lucid-evolution/lucid0.4.29 → 0.6.1. Not required for the fix —open_cdpalready workson 0.3.28 once the evaluator is pinned — but it brings the SDK current. The API change is
mechanical:
currentSlotwas dropped from every builder signature, so the argument comes outat each call site; lucid moves in step or two distinct
LucidEvolutiontypes coexist. ThePyth wiring test's argument positions shift down by one.
The override is kept even though lucid 0.6.1 already requires 0.2.22, because
@qbtlabs/x402still pulls lucid 0.4.29 into the tree.
Verification
tsc --noEmitclean · 144/144 tests · Prettier clean.npm packed the result and installed the tarball into a clean directory with no repo andno overrides: all seven still build.
uplcis bundled rather than a runtime dependency, andthe shipped WASM is the 960980-byte 0.2.22 build (the broken one is 1005786), so the fix
reaches consumers.
aiken build --trace-level compact(v1.1.21) reproduced all48 validators in
smart-contracts-aiken'splutus.jsonbyte-for-byte, and the deployedscript equals that blueprint applied to its parameters.
Follow-ups (not in this PR)
@indigoprotocol/indigo-mcp@0.4.0.redeem_cdpremains broken by an SDK guard that does not account for the PythDeferredValidationpath; present in every published version through 0.5.9.leverage_cdpis unusable for every asset:findAllRobsandfindTreasuryOrefscanenterprise script addresses while the real UTxOs carry staking credentials.