Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions backend/__tests__/errorCodes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* - Every error code has a non-empty httpStatus, message, and code.
* - getError() returns the correct entry and falls back to GEN_UNKNOWN.
* - formatErrorResponse() produces the canonical shape.
* - CONTRACT_ERROR_MAP maps all 17 contract error codes.
* - CONTRACT_ERROR_MAP maps all 29 contract error codes.
* - getContractErrorCode() returns the correct key.
*/

Expand Down Expand Up @@ -198,19 +198,19 @@ describe("formatErrorResponse()", () => {
});

describe("CONTRACT_ERROR_MAP", () => {
it("maps all 17 contract error codes (1–17)", () => {
for (let i = 1; i <= 17; i++) {
it("maps all 29 contract error codes (1-29)", () => {
for (let i = 1; i <= 29; i++) {
expect(CONTRACT_ERROR_MAP[i]).toBeDefined();
expect(typeof CONTRACT_ERROR_MAP[i]).toBe("string");
expect(CONTRACT_ERROR_MAP[i].startsWith("CONTRACT_")).toBe(true);
}
});

it("has no extra keys beyond 1–17", () => {
it("has no extra keys beyond 1-29", () => {
const keys = Object.keys(CONTRACT_ERROR_MAP).map(Number);
expect(Math.max(...keys)).toBe(17);
expect(Math.max(...keys)).toBe(29);
expect(Math.min(...keys)).toBe(1);
expect(keys.length).toBe(17);
expect(keys.length).toBe(29);
});

it("maps contract code 2 (Unauthorized) to CONTRACT_UNAUTHORIZED", () => {
Expand All @@ -224,18 +224,27 @@ describe("CONTRACT_ERROR_MAP", () => {
it("maps contract code 17 (TransferFailed) to CONTRACT_TRANSFER_FAILED", () => {
expect(CONTRACT_ERROR_MAP[17]).toBe("CONTRACT_TRANSFER_FAILED");
});

it("maps swap hardening errors to specific contract codes", () => {
expect(CONTRACT_ERROR_MAP[21]).toBe("CONTRACT_INVALID_PATH");
expect(CONTRACT_ERROR_MAP[22]).toBe("CONTRACT_SLIPPAGE_EXCEEDED");
expect(CONTRACT_ERROR_MAP[23]).toBe("CONTRACT_EXCESSIVE_AMOUNT_IN");
expect(CONTRACT_ERROR_MAP[24]).toBe("CONTRACT_INVALID_FEE_BPS");
expect(CONTRACT_ERROR_MAP[29]).toBe("CONTRACT_STALE_PATH");
});
});

describe("getContractErrorCode()", () => {
it("returns the correct code for a known numeric contract error", () => {
expect(getContractErrorCode(2)).toBe("CONTRACT_UNAUTHORIZED");
expect(getContractErrorCode(5)).toBe("CONTRACT_NOT_FOUND");
expect(getContractErrorCode(12)).toBe("CONTRACT_PAUSED");
expect(getContractErrorCode(29)).toBe("CONTRACT_STALE_PATH");
});

it("returns GEN_UNKNOWN for an out-of-range code", () => {
expect(getContractErrorCode(0)).toBe("GEN_UNKNOWN");
expect(getContractErrorCode(18)).toBe("GEN_UNKNOWN");
expect(getContractErrorCode(30)).toBe("GEN_UNKNOWN");
expect(getContractErrorCode(999)).toBe("GEN_UNKNOWN");
});

Expand Down
2 changes: 1 addition & 1 deletion backend/src/utils/errorResponse.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ function sendError(res, code, options = {}) {
* Send a canonical error response built from a numeric Soroban ContractError.
*
* @param {import('express').Response} res
* @param {number} contractErrorCode - The numeric ContractError value (1–17).
* @param {number} contractErrorCode - The numeric ContractError value (1–29).
* @param {{ details?: *, message?: string, status?: number }} [options]
* @returns {import('express').Response}
*/
Expand Down
3 changes: 2 additions & 1 deletion contracts/finchippay-contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ transfers) are fully supported: the cached `LastContractBalance` matches the
real on-chain balance, and every deposit/claim settles exactly.

**Fee-on-transfer / taxed / deflationary tokens** (where `transfer` moves
*less* than `amount` into the recipient) are **not** supported for deposits:
*less* than `amount` into the recipient) are supported by the measured contract
swap entry points and remain rejected by other deposit-style flows:
the phantom-deposit check in `require_transfer_succeeded` compares the actual
balance deltas and rejects the operation (`TransferFailed`) rather than locking
a balance that never fully arrived. This is deliberate — it guarantees **no
Expand Down
1 change: 1 addition & 0 deletions contracts/finchippay-contract/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
//! | `admin_action_approved` | (id, approver, count, threshold) | Gov action approved |
//! | `balance_reconciled` | (token, old, new) | Admin resynced cached contract balance |
//! | `balance_drift_detected` | (token, cached, actual) | Cached vs actual balance drift surfaced |
//! | `swap` | (requested_in, actual_in, amount_out, fee, path_len) | Contract-reserve swap settled |

use soroban_sdk::Symbol;

Expand Down
Loading
Loading