Skip to content

test(stellar): add tests for contracts.ts helper functions (#359) - #469

Merged
DeFiVC merged 1 commit into
ChainLearnOfficial:mainfrom
oluwagbemigaphilip:test/contracts-helpers-359
Sep 27, 2026
Merged

DeFiVC merged 1 commit into
ChainLearnOfficial:mainfrom
oluwagbemigaphilip:test/contracts-helpers-359

Conversation

@oluwagbemigaphilip

Copy link
Copy Markdown

What this is

Test-only. No production code changed. Following up on my note in #459: #442 already covers getContractAddress's lazy env resolution; the actual read/write helpers #359 asks for (getContractBalance, verifyCredentialOnChain, getProgressOnChain, plus claimRewardOnChain/mintCredentialOnChain/readCredentialMetadata) had none. Adds 20 tests in src/tests/shared/stellar-contracts-helpers.test.ts.

Since the helpers already existed, this uses Refs #359, not Closes.

Approach

Mocks lib/stellar/transactions (simulateContractCall, signAndSubmitTransaction) at the boundary, but builds simulation-result fixtures with @stellar/stellar-sdk's own nativeToScVal(...).toXDR("base64") rather than hand-rolled strings — so decodeFirstSimResult's real base64 XDR decode path is what's under test, not a stand-in that merely looks right.

⚠️ Three things this surfaced — documented, not fixed here

  1. verifyCredentialOnChain fails open. Boolean(record.valid ?? true) treats an absent valid field the same as an explicit true. If the contract only sets valid on failure (or omits it in some path), every such credential verifies as valid.
  2. issuedAt is silently dropped for a u64 issued_at. Verified against the real SDK: scValToNative decodes a u32 to a JS number but a u64/i128 to a bigint. The function's typeof record.issued_at === "number" guard only matches the former, so a perfectly valid u64 timestamp — a plausible choice, since u32 overflows in 2106 — silently produces issuedAt: undefined.
  3. readCredentialMetadata returns the raw, un-decoded RPC response, not decoded metadata. Every sibling helper passes its result through decodeFirstSimResult; this one returns simulateContractCall's return value cast straight to Record<string, unknown> — the caller gets { results: [{ xdr: "<base64>" }] }, not metadata fields. This looks like it would break any real caller.

Each is backed by its own test (search the diff for KNOWN BUG), so they're reproducible, not just a claim in this description.

Verification

  • 20/20 new tests pass.
  • eslint and tsc --noEmit report nothing for the new file.
  • Full suite: byte-identical set of 20 failing files / 50 failing tests before and after this change (confirmed via git stash) — pre-existing on main, unrelated. With this file: 429 passing vs 409 on main (the 20 new tests).

Refs #359

ChainLearnOfficial#442 already added stellar-contracts-env.test.ts for getContractAddress's
lazy env resolution. The rest of lib/stellar/contracts.ts — the actual
read/write helpers ChainLearnOfficial#359 asks for — had no tests. Adds 20 in
stellar-contracts-helpers.test.ts, mocking lib/stellar/transactions and
using @stellar/stellar-sdk's own nativeToScVal to build real ScVal XDR
fixtures rather than hand-rolled stand-ins, so decodeFirstSimResult's
actual decode path is exercised.

Covers getContractBalance/readRewardBalance (incl. values beyond
Number.MAX_SAFE_INTEGER, missing-config and RPC-rejection errors),
decodeFirstSimResult's error paths and the bare-array response shape,
verifyCredentialOnChain, getProgressOnChain, and that
claimRewardOnChain/mintCredentialOnChain delegate to
signAndSubmitTransaction and pass a failed result through unchanged.

Writing these tests surfaced three behaviors worth a maintainer's
attention, each documented with a dedicated test and NOT fixed here
(test-only PR):

1. verifyCredentialOnChain fails open: Boolean(record.valid ?? true)
   treats an absent `valid` field the same as an explicit `true`.
2. verifyCredentialOnChain silently drops issuedAt when the contract
   encodes issued_at as u64 rather than u32 — Soroban decodes u32 to a
   JS number but u64/i128 to a bigint, and the `typeof === "number"`
   guard only matches the former. u64 is a plausible choice for a Unix
   timestamp (u32 overflows in 2106).
3. readCredentialMetadata returns simulateContractCall's raw,
   un-decoded RPC response (`{ results: [{ xdr }] }`) instead of the
   decoded metadata object every sibling helper produces via
   decodeFirstSimResult — likely broken for any real caller.

Verified: 20/20 new tests pass; eslint and tsc --noEmit report nothing
for the new file; full suite before/after is byte-identical in which
20 files/50 tests fail (confirmed via git stash) — pre-existing on
main, unrelated to this change.

Refs ChainLearnOfficial#359
@netlify

netlify Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

❌ Deploy Preview for chainlearn failed.

Name Link
🔨 Latest commit ea766c5
🔍 Latest deploy log https://app.netlify.com/projects/chainlearn/deploys/6ab8ceb9ad32f40008daaf98

@DeFiVC
DeFiVC merged commit b77614b into ChainLearnOfficial:main Sep 27, 2026
1 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants