Skip to content

critical: fix vendor-registry client function names, argument types, and status mapping #96

Description

@EmeditWeb

Problem

src/stellar/contracts/clients/vendor-registry.client.ts is desynced from the deployed vendor-registry-contract in four independent ways, so every vendor validation call fails at runtime.

  1. isVendorActive() invokes is_vendor_active. The contract function is is_active (contracts/vendor-registry-contract/src/lib.rs:201).
  2. getVendor() invokes get_vendor. The contract function is get_vendor_info (line 207).
  3. Both encode the vendor identifier with StellarSdk.nativeToScVal(vendorId, { type: 'string' }). Both contract functions take vendor: Address.
  4. getVendor() maps active: Boolean(raw['active'] ?? raw['is_active'] ?? false). Issue [15] Add unit tests for VouchingService #7 replaced active: bool with status: VendorStatus on VendorInfo, so neither key exists and active is always false.

The failure is silent, which is what makes it dangerous. The catch block in getVendor() treats any error containing HostError or Status(ContractError as "vendor not found" and returns null. A wrong-function-name failure is indistinguishable from a genuine miss, so nothing surfaces in logs as an error and no alert fires.

For a real user: loan creation calls into vendor validation, which can never confirm a vendor is approved. Either vendors are rejected wholesale or the check degrades to a no-op depending on the caller's handling of null. The protocol loses its guarantee that funds only flow to verified learning vendors.

Ground Rules

This issue must be solved according to StepFi's established engineering standards. Before writing any code:

  1. Read context/architecture-context.md in full
  2. Read context/code-standards.md in full
  3. Read context/progress-tracker.md to understand what has already been built and why, so you do not duplicate or contradict recent work
  4. Read src/stellar/contracts/clients/reputation.client.ts to see how a working client encodes an Address argument, and follow that pattern rather than inventing one
  5. Read src/stellar/contracts/interfaces/vendor-registry.interface.ts and src/stellar/contracts/mocks/vendor-registry.mock.ts, since both must change with the client
  6. Read contracts/vendor-registry-contract/src/lib.rs and types.rs in the StepFi-Contracts repo to confirm the exact current signatures and the VendorStatus variants

Your PR will be rejected regardless of whether CI passes if it conflicts with anything in these files, introduces patterns inconsistent with what already exists, or duplicates functionality that was solved differently elsewhere in the codebase.

What To Build

  1. Rename the invoked contract function in isVendorActive() from is_vendor_active to is_active.
  2. Rename the invoked contract function in getVendor() from get_vendor to get_vendor_info.
  3. Change argument encoding in both methods from { type: 'string' } to an Address ScVal, matching how reputation.client.ts encodes wallet addresses. Vendor identifiers reaching this client must be Stellar addresses, not database UUIDs. If any caller currently passes a UUID, that caller is also broken and must be fixed in the same PR.
  4. Add a VendorStatus union type to src/stellar/contracts/interfaces/vendor-registry.interface.ts with the four contract variants: Pending, Approved, Suspended, Rejected.
  5. Replace active: boolean on the VendorInfo interface with status: VendorStatus. Do not keep both. A derived active field invites callers to keep reading the stale one.
  6. Update the mapping in getVendor() to read status from the decoded value and validate it against the four known variants. An unrecognized value must throw, not silently default to a passing state.
  7. Narrow the catch block in getVendor(). Return null only for a genuine "vendor not found" contract error. Any other HostError must be logged at error level and rethrown as ContractReadError, so a future function-name drift fails loudly instead of masquerading as a missing vendor.
  8. Update every call site of isVendorActive() and getVendor() for the new status shape.
  9. Update src/stellar/contracts/mocks/vendor-registry.mock.ts to match the new interface, and make the mock return status rather than active.
  10. Add a unit test that would have caught this: assert the exact contract function name passed to simulateContractCall. A test that only mocks the client cannot detect a wrong function name, which is why this shipped.

Files To Touch

  • src/stellar/contracts/clients/vendor-registry.client.ts
  • src/stellar/contracts/interfaces/vendor-registry.interface.ts
  • src/stellar/contracts/mocks/vendor-registry.mock.ts
  • src/modules/vendors/vendors.service.ts (call sites)
  • src/modules/loans/loans.service.ts (call sites, if vendor validation runs there)
  • src/stellar/contracts/clients/vendor-registry.client.spec.ts (new)

Acceptance Criteria

  • isVendorActive() invokes is_active and getVendor() invokes get_vendor_info
  • Both encode the vendor identifier as an Address ScVal
  • VendorInfo exposes status: VendorStatus and no active boolean
  • An unrecognized status value throws rather than defaulting to a passing state
  • A non-"not found" contract error is logged and rethrown, not swallowed into null
  • A test asserts the exact contract function name and argument type passed to simulateContractCall
  • All existing vendor call sites compile and pass against the new shape
  • All state-changing operations validated and safe against invalid input

Mandatory Checks Before Opening PR

  • All context/ files read and understood
  • Code follows context/code-standards.md exactly
  • context/progress-tracker.md updated with an accurate entry describing the change and why
  • Build passes with zero errors (npm run build)
  • All existing tests still pass, test count has not decreased
  • New tests written covering the new functionality
  • No new any types
  • Full Swagger decorators on any new or changed endpoint
  • PR template filled out completely, including the "problem this solves" and "how it was tested" sections
  • PR references this issue number exactly

PRs that fail any check above will be closed without review, regardless of how much work went into them. No exceptions. This is a Grantfox-funded, quality-first project. Code that does not meet these standards will not be merged and will not be paid.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions