Repository navigation
Fix/1530 1531 1545 1546 - #1647
Merged
Merged
Conversation
… approvals - vault: validate_strategy_response rejects the zero strategy address, asset tokens with more than 30 decimals and values outside 0..=MAX_STRATEGY_VALUE; harvested Korean yield is bounded and rebalance checks the target strategy's deposit delta with StrategyValidator (Junirezz#1530) - permissions: MultiSignerValidator returns VaultError, uses Map-backed sets for duplicate / membership checks and calls require_auth on every approver; require_governance_threshold picks the signer set without auth so each approver signs once (Junirezz#1531) - fix pre-existing compile errors in rounding.rs, total_assets, the wasm zero-address check and vault lib tests so the tests run Closes Junirezz#1530 Closes Junirezz#1531
Same repairs as the OpenAPI branch: decode base64-encoded healthProbe.ts and apiKeyAuth.ts, drop duplicated merge blocks in eventOutbox, idempotency, apiError, pagination, tracing, outboxPoller, vaultEndpoints and routes/vaults, fix the @yieldvault/api-schemas dependency name, sync the lockfile and remove a trailing comma in the root package.json.
- prisma/postgres-type-mapping.json maps every PostgreSQL NUMERIC / JSONB / TIMESTAMPTZ / DATE column to its Prisma (SQLite) field or null - scripts/check-type-mapping.js (npm run db:check-types) fails when a divergent column is unmapped, a mapped column/field disappears, or a Prisma type no longer matches; runs in Backend Governance CI - src/numericColumns.ts: NUMERIC(38, 18) string encoding with 38-digit Decimal precision, plus precision and drift tests - docs/DB_TYPE_MAPPING.md Closes Junirezz#1546
Move route handlers out of the 5.6k-line src/index.ts into feature routers: - routes/vault.ts: vault summary / metrics / APY / health (legacy, v1, v2) - routes/transactions.ts: transaction exports and admin backfill - routes/webhooks.ts: webhook verification, /api/v1/webhooks and /admin/webhooks/* (incl. deduplication) - routes/exports.ts: /admin/exports/* and /admin/reports/exports/* - routes/admin.ts: remaining /admin/* operational endpoints - routes/shared.ts: helpers and the health cache used by several modules Handler bodies are moved verbatim. index.ts (now ~880 lines) keeps the middleware stack, health/ops routes and startup wiring, and mounts the routers where the routes used to be registered, so the effective route table, handler chains and ordering of overlapping routes are unchanged; the full Jest suite gives identical per-test results before and after. Closes Junirezz#1545
|
@prizncezz-cell Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Comment on lines
+338
to
+364
| router.post('/admin/reports/exports/manifests/:id/verify', validateApiKey, validate({ params: IdParamSchema, body: ChecksumVerifyBodySchema }), async (req: Request, res: Response) => { | ||
| const checksum = String(req.body?.checksum || '').trim(); | ||
| if (!checksum) { | ||
| res.status(400).json({ | ||
| error: 'Bad Request', | ||
| status: 400, | ||
| message: 'checksum is required', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const result = await verifyExportManifestChecksum(String(req.params.id), checksum); | ||
| if (!result.manifest) { | ||
| res.status(404).json({ | ||
| error: 'Not Found', | ||
| status: 404, | ||
| message: 'Export manifest not found', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| res.status(200).json({ | ||
| match: result.match, | ||
| manifest: result.manifest, | ||
| timestamp: new Date().toISOString(), | ||
| }); | ||
| }); |
| const walletAddress = | ||
| typeof req.query.walletAddress === 'string' ? normalizeWalletAddress(req.query.walletAddress) : undefined; | ||
|
|
||
| const apiKeyMatch = authHeader.match(/^ApiKey\s+(.+)$/i); |
| }; | ||
| } | ||
|
|
||
| const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i); |
| * POST /api/v1/webhooks — authenticated clients register an HTTPS callback | ||
| * instead of polling for transaction/vault events. | ||
| */ | ||
| apiV1WebhookRoutes.post('/webhooks', requireAuth, validate({ body: WebhookRegisterSchema }), (req: Request, res: Response) => { |
| } | ||
| }); | ||
|
|
||
| apiV1WebhookRoutes.get('/webhooks', requireAuth, validate({ query: PaginationQuerySchema }), (req: Request, res: Response) => { |
Comment on lines
+58
to
+89
| router.post('/webhooks/verify', validate({ body: WebhookVerifyBodySchema }), (req: Request, res: Response) => { | ||
| const { secret, payload, signature } = req.body || {}; | ||
| if (typeof secret !== 'string' || !secret.trim()) { | ||
| res.status(400).json({ | ||
| error: 'Bad Request', | ||
| status: 400, | ||
| message: 'secret is required and must be a non-empty string', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| if (typeof payload === 'undefined') { | ||
| res.status(400).json({ | ||
| error: 'Bad Request', | ||
| status: 400, | ||
| message: 'payload is required', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const computedSignature = createWebhookSignature(secret, payload); | ||
| const verified = | ||
| typeof signature === 'string' && signature.length > 0 | ||
| ? verifyWebhookSignature(secret, payload, signature) | ||
| : null; | ||
|
|
||
| res.status(200).json({ | ||
| algorithm: 'HMAC-SHA256', | ||
| signature: computedSignature, | ||
| verified, | ||
| }); | ||
| }); |
Comment on lines
+113
to
+145
| router.post('/admin/webhooks/:id/verify', validateApiKey, validate({ params: IdParamSchema }), async (req: Request, res: Response) => { | ||
| try { | ||
| const endpoint = await verifyWebhookEndpoint(req.params.id); | ||
| if (!endpoint) { | ||
| res.status(404).json({ | ||
| error: 'Not Found', | ||
| status: 404, | ||
| message: 'Webhook endpoint not found', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| await recordAdminAuditLog(req, 'webhook.verify', endpoint.verificationStatus === 'verified' ? 200 : 422, { | ||
| endpointId: endpoint.id, | ||
| verificationStatus: endpoint.verificationStatus, | ||
| lastVerificationError: endpoint.lastVerificationError, | ||
| }); | ||
|
|
||
| res.status(endpoint.verificationStatus === 'verified' ? 200 : 422).json({ | ||
| message: | ||
| endpoint.verificationStatus === 'verified' | ||
| ? 'Webhook endpoint verified' | ||
| : 'Webhook endpoint verification failed', | ||
| endpoint, | ||
| }); | ||
| } catch (error) { | ||
| res.status(500).json({ | ||
| error: 'Internal Server Error', | ||
| status: 500, | ||
| message: error instanceof Error ? error.message : 'Failed to verify webhook endpoint', | ||
| }); | ||
| } | ||
| }); |
Comment on lines
+346
to
+377
| router.post('/api/v1/webhooks/verify', validate({ body: WebhookVerifyBodySchema }), (req: Request, res: Response) => { | ||
| const { secret, payload, signature } = req.body || {}; | ||
| if (typeof secret !== 'string' || !secret.trim()) { | ||
| res.status(400).json({ | ||
| error: 'Bad Request', | ||
| status: 400, | ||
| message: 'secret is required and must be a non-empty string', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| if (typeof payload === 'undefined') { | ||
| res.status(400).json({ | ||
| error: 'Bad Request', | ||
| status: 400, | ||
| message: 'payload is required', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const computedSignature = createWebhookSignature(secret, payload); | ||
| const verified = | ||
| typeof signature === 'string' && signature.length > 0 | ||
| ? verifyWebhookSignature(secret, payload, signature) | ||
| : null; | ||
|
|
||
| res.status(200).json({ | ||
| algorithm: 'HMAC-SHA256', | ||
| signature: computedSignature, | ||
| verified, | ||
| }); | ||
| }); |
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.
🚀 Overview
This Pull Request delivers comprehensive smart contract security hardening, automated database schema drift validation, and Express router refactoring for Junirezz/YieldVault-RWA:
validate_strategy_response(rejecting zero-address strategies, asset tokens exceeding 30 decimals, and strategy valuations outsideMultiSignerValidatorto return typedVaultErrorvariants, enforces Sorobanapprover.require_auth()cryptographic signatures per signer, and replacesprisma/postgres-type-mapping.json), documents numeric precision constraints (docs/DB_TYPE_MAPPING.md), adds high-precision decimal helpers (src/numericColumns.ts), and wires an automated CI schema drift check (npm run db:check-types).backend/src/index.tsinto isolated feature routers (routes/vault.ts,transactions.ts,webhooks.ts,exports.ts,admin.ts,shared.ts) with zero route table or middleware chain behavioral drift.🔑 Linked Issues
🛠️ Summary of Changes
🛡️ 1. Strategy Valuation Bounds & Consistency (
contracts/vault/src/) (#1530)validate_strategy_responsenow explicitly validates against zero-address strategy targets, asset token decimalsMAX_STRATEGY_VALUE(returningVaultError::MathOverflow).MAX_STRATEGY_VALUE,MAX_STRATEGY_VALUE + 1,i128::MAX,total_assetsevaluations.🔐 2. Multi-Signer Authentication & Set-Based Dedup (
contracts/vault/src/permissions.rs) (#1531)approver.require_auth()per signer to ensure authorization payloads cannot be spoofed.GovernanceThresholdNotMeterrors with specificVaultError::InvalidGovernanceThresholdandVaultError::DuplicateVotevariants.🗄️ 3. Postgres/Prisma Type Mapping & Drift Check (
backend/) (#1546)prisma/postgres-type-mapping.jsonanddocs/DB_TYPE_MAPPING.mdmapping every Postgres nativeDECIMAL,JSONB, andTIMESTAMPTZcolumn to its corresponding Prisma field.npm run db:check-types(running in the Backend Governance CI pipeline) to verify schema alignment without requiring an active database instance.src/numericColumns.tsand unit tests ensuring Decimal.js default precision caps (20 digits) are extended to handle full 38-digit integer and 18-decimal Postgres precision without truncation.🧩 4. Express Monolith Router Decomposition (
backend/src/) (#1545)index.tsfrom ~5,600 to ~880 lines by extracting handlers into:routes/vault.ts: Vault management endpoints.routes/transactions.ts: Deposit/withdrawal flows.routes/webhooks.ts: Off-chain and bridge webhook event handlers.routes/exports.ts: Report generation routes.routes/admin.ts: Administrative and impersonation routes.routes/shared.ts: Shared middleware and route utility helpers.📁 Key Files Touched
contracts/vault/src/lib.rscontracts/vault/src/permissions.rsapprover.require_auth(), map-based dedup, and granularVaultErrorreturns.backend/src/index.tsbackend/src/routes/vault.ts,transactions.ts,webhooks.ts,exports.ts,admin.ts,shared.ts).backend/prisma/postgres-type-mapping.jsonbackend/docs/DB_TYPE_MAPPING.mdbackend/src/numericColumns.ts🧪 Verification Logs
cargo test -p vault). Existing baseline test failures onmain(36 tests) remain unchanged.npm run db:check-typespassed cleanly.