## fix: match expected/actual state changes, overrides, and balance changes by identity, not array position - #190
Open
batuhankocyigit wants to merge 2 commits into
Conversation
Collaborator
🟡 Heimdall Review Status
|
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.
The bug
buildOverrideComparisons,buildChangeComparisons, andbuildBalanceComparisonsinsrc/lib/validation-results-utils.tspair eachexpected entry with an actual entry purely by array index:
The two sides don't share an ordering guarantee, though:
(
state-diff.ts), which always sorts a contract's overrides/changes bykey ascending before returning them (
.sort((a, b) => a.key.localeCompare(b.key))).(
getExpectedDatainvalidation-service.tsreturnsparsedConfig.stateChangesas-is, unsorted).A config author has no reason to write state changes in raw-hex-key
ascending order — listing them in whatever order is narratively useful
(e.g. "the important flag first, then the owner slot") is completely
reasonable. When that order doesn't happen to match the pipeline's sorted
output, index-based pairing cross-wires unrelated entries.
matchesChange/
matchesOverride(which checkexpected.key === actual.key) thencorrectly detect the key mismatch on the wrongly-paired entries — and
hasBlockingErrorsreports a blocking validation failure for a task whoseactual on-chain effect is byte-for-byte identical to what the config
expects.
To be clear about the direction of the bug: this is a false positive (a
correct task gets flagged as mismatched), not a false negative — it
doesn't cause an incorrect task to be accepted. But it directly undermines
the tool's job of telling a signer whether a task is safe to sign, and a
signer who sees a reported mismatch has no way to tell, from the tool's
output alone, whether it's this ordering artifact or a real problem.
Reproduction
Added as the first case in the new test file: two storage-slot changes on
one contract, identical values on both the expected and actual side, only
the order differs (config lists them as [paused, owner]; the simulation
naturally returns them key-sorted as [owner, paused]). Before this fix,
hasBlockingErrors(items)returnstruefor this input despite the taskbeing entirely correct.
The fix
Match each expected entry to its actual counterpart by identity instead of
position:
address(case-insensitively), then look up the specific override/change by
keywithin that contract via a
Map.address+fieldtogether, since acontract can have more than one tracked balance-type field.
matchesOverride/matchesChange/matchesBalanceandhasBlockingErrorsare untouched — this only changes which actual entrygets handed to them for comparison.
Testing
New file
src/lib/__tests__/validation-results-utils.test.ts:buildValidationItems+hasBlockingErrors(fails before the fix, passes after): reordered butsemantically-identical expected/actual data no longer produces a
blocking mismatch, and each entry is verified to be paired with its
correct counterpart by key.
aftervalue confirms the fixdoesn't mask real mismatches —
hasBlockingErrorsstill correctlyreturns
true.Full verification:
npx tsc --noEmit— cleannpx eslint src/lib/validation-results-utils.ts src/lib/__tests__/validation-results-utils.test.ts— cleannpx prettier --check— cleannpx jest --runInBand(full suite) — 108/108 passing, no regressionsScope
Two files: the fix itself and its test. No changes to the matching
functions' semantics (
matchesChangeetc.), the blocking-decision logic,or any UI code.