From 6b70912db2153fd6d01e83d71d91399378948509 Mon Sep 17 00:00:00 2001 From: Rob Konsdorf Date: Thu, 8 Oct 2026 15:08:19 -0400 Subject: [PATCH] fix(api): refuse an integer parameter above the safe integer range with a 400 Several integer request parameters had no upper bound. A digit string above the bigint range passed the validation, the parser turned it into a float, and the database refused the bound value, so the caller got a 500 and each request wrote a warn log line. Each integer filter of the API now has the safe integer limit that the effective mint parameters already carry. A value inside that range keeps its behavior. Signed-off-by: Rob Konsdorf --- CHANGELOG.md | 13 +++++++ .../atomicassets/handlers/accounts.ts | 2 +- .../handlers/assets-mint-bound.test.ts | 31 +++++++++++++++++ .../atomicassets/handlers/assets.ts | 10 +++--- .../namespaces/atomicassets/handlers/burns.ts | 2 +- .../atomicassets/handlers/collections.ts | 4 +-- .../atomicassets/handlers/offers.ts | 4 +-- .../atomicassets/handlers/schemas.ts | 4 +-- .../atomicassets/handlers/templates.ts | 12 +++---- .../atomicassets/handlers/transfers.ts | 2 +- .../atomicmarket/handlers/auctions.ts | 4 +-- .../atomicmarket/handlers/buyoffers.ts | 4 +-- .../atomicmarket/handlers/prices.ts | 8 ++--- .../atomicmarket/handlers/royalties.ts | 12 +++---- .../namespaces/atomicmarket/handlers/sales.ts | 4 +-- .../handlers/sales2.mint-bound.test.ts | 33 ++++++++++++++++++ .../atomicmarket/handlers/sales2.ts | 12 +++---- .../namespaces/atomicmarket/handlers/stats.ts | 34 +++++++++---------- .../handlers/template-buyoffers.ts | 4 +-- .../atomicmarket/utils.mint-bound.test.ts | 29 ++++++++++++++++ src/api/namespaces/atomicmarket/utils.ts | 16 ++++----- .../namespaces/atomictools/handlers/links.ts | 4 +-- src/api/namespaces/utils.ts | 9 ++--- 23 files changed, 182 insertions(+), 75 deletions(-) create mode 100644 src/api/namespaces/atomicassets/handlers/assets-mint-bound.test.ts create mode 100644 src/api/namespaces/atomicmarket/handlers/sales2.mint-bound.test.ts create mode 100644 src/api/namespaces/atomicmarket/utils.mint-bound.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d62ffa2..f1531c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,19 @@ order; the entry is the editorial text of the version's GitHub Release. The 1.7 maintenance line continues in `CHANGELOG.md` on the `release/1.7` branch. This project follows semantic versioning. +## [2.6.1] + +Refuses an integer request parameter above the safe integer range with a 400 response. + +### Upgrading + +- Image `ghcr.io/atomicassets/atomicassets-api:2.6.1`. The `2.6` and `latest` tags move to it. +- The release has no migration and needs no operator action. + +### Bug fixes + +- An integer request parameter with a value above `Number.MAX_SAFE_INTEGER` now returns 400. Before, such a value passed the validation of the template mint filters of the asset and market endpoints, the asset count filters of the market endpoints, the supply filters of `/v1/templates`, the `before` and `after` filters, the integer forms of `lower_bound` and `upper_bound`, `page`, and the `limit` of the asset sales history. The database then refused the value, and the request ended with a 500 response and a warn log line. + ## [2.6.0] Adds an effective mint filter and sort to the market listing endpoints, so a client can range over the mint of a bridged asset and of a plain asset with one parameter. diff --git a/src/api/namespaces/atomicassets/handlers/accounts.ts b/src/api/namespaces/atomicassets/handlers/accounts.ts index 5dd22573..17876c28 100644 --- a/src/api/namespaces/atomicassets/handlers/accounts.ts +++ b/src/api/namespaces/atomicassets/handlers/accounts.ts @@ -15,7 +15,7 @@ export async function getAccountsAction( ): Promise { // TODO: Use a proper type here - can't be at the moment different return types const maxLimit = ctx.coreArgs.limits?.accounts || 5000; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, match_owner: {type: 'name'}, diff --git a/src/api/namespaces/atomicassets/handlers/assets-mint-bound.test.ts b/src/api/namespaces/atomicassets/handlers/assets-mint-bound.test.ts new file mode 100644 index 00000000..9983d7f5 --- /dev/null +++ b/src/api/namespaces/atomicassets/handlers/assets-mint-bound.test.ts @@ -0,0 +1,31 @@ +import 'mocha'; +import { expect } from 'chai'; + +import { buildAssetQueryCondition } from './assets'; +import QueryBuilder from '../../../builder'; + +describe('buildAssetQueryCondition - template mint bounds', () => { + const tooLarge = String(Number.MAX_SAFE_INTEGER) + '0'; + + for (const key of ['template_mint', 'min_template_mint', 'max_template_mint']) { + it(`${key} above the safe integer range is refused as a 400`, async () => { + let error: any = null; + + try { + await buildAssetQueryCondition({[key]: tooLarge}, new QueryBuilder('SELECT 1'), {assetTable: 'asset'}); + } catch (e) { + error = e; + } + + expect(error).to.not.equal(null); + expect(error.code).to.equal(400); + expect(error.message).to.contain(`Invalid value for parameter ${key}`); + }); + } + + it('template_mint at the safe integer limit is accepted', async () => { + await buildAssetQueryCondition( + {template_mint: String(Number.MAX_SAFE_INTEGER)}, new QueryBuilder('SELECT 1'), {assetTable: 'asset'} + ); + }); +}); diff --git a/src/api/namespaces/atomicassets/handlers/assets.ts b/src/api/namespaces/atomicassets/handlers/assets.ts index 6f02a48e..d84605e6 100644 --- a/src/api/namespaces/atomicassets/handlers/assets.ts +++ b/src/api/namespaces/atomicassets/handlers/assets.ts @@ -45,10 +45,10 @@ export async function buildAssetQueryCondition( has_backed_tokens: {type: 'bool'}, has_template_buyoffer: {type: 'bool'}, - template_mint: {type: 'int', min: 1}, + template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, - min_template_mint: {type: 'int', min: 1}, - max_template_mint: {type: 'int', min: 1}, + min_template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + max_template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, template_blacklist: {type: 'list[id]'}, template_whitelist: {type: 'list[id]'} @@ -286,7 +286,7 @@ export async function getRawAssetsAction( const {search, match, ...params} = values; const maxLimit = ctx.coreArgs.limits?.assets || 1000; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', min: 1}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, @@ -592,7 +592,7 @@ export async function getAssetLogsAction(params: RequestValues, ctx: AtomicAsset const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { asset_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicassets/handlers/burns.ts b/src/api/namespaces/atomicassets/handlers/burns.ts index a2dcb27c..0265dba2 100644 --- a/src/api/namespaces/atomicassets/handlers/burns.ts +++ b/src/api/namespaces/atomicassets/handlers/burns.ts @@ -64,7 +64,7 @@ function burnsQueryNeedsTemplateJoin(params: RequestValues): boolean { export async function getBurnsAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.burns || 5000; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, match_owner: {type: 'name'}, diff --git a/src/api/namespaces/atomicassets/handlers/collections.ts b/src/api/namespaces/atomicassets/handlers/collections.ts index f1de2032..92702286 100644 --- a/src/api/namespaces/atomicassets/handlers/collections.ts +++ b/src/api/namespaces/atomicassets/handlers/collections.ts @@ -10,7 +10,7 @@ import { filterQueryArgs } from '../../validation'; export async function getCollectionsAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.collections || 1000; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', allowedValues: ['created', 'collection_name'], default: 'created'}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, @@ -146,7 +146,7 @@ export async function getCollectionSchemasAction(params: RequestValues, ctx: Ato export async function getCollectionLogsAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicassets/handlers/offers.ts b/src/api/namespaces/atomicassets/handlers/offers.ts index 4311db5a..1d117921 100644 --- a/src/api/namespaces/atomicassets/handlers/offers.ts +++ b/src/api/namespaces/atomicassets/handlers/offers.ts @@ -8,7 +8,7 @@ import { filterQueryArgs } from '../../validation'; export async function getRawOffersAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.offers || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', allowedValues: ['created', 'updated'], default: 'created'}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, @@ -224,7 +224,7 @@ export async function getOfferLogsCountAction(params: RequestValues, ctx: Atomic const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { offer_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicassets/handlers/schemas.ts b/src/api/namespaces/atomicassets/handlers/schemas.ts index 2d082462..515dbefc 100644 --- a/src/api/namespaces/atomicassets/handlers/schemas.ts +++ b/src/api/namespaces/atomicassets/handlers/schemas.ts @@ -10,7 +10,7 @@ import { filterQueryArgs } from '../../validation'; export async function getSchemasAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.schemas || 1000; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', allowedValues: ['created', 'schema_name', 'assets'], default: 'created'}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, @@ -121,7 +121,7 @@ export async function getSchemaStatsAction(params: RequestValues, ctx: AtomicAss export async function getSchemaLogsAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicassets/handlers/templates.ts b/src/api/namespaces/atomicassets/handlers/templates.ts index afac714f..855bd07d 100644 --- a/src/api/namespaces/atomicassets/handlers/templates.ts +++ b/src/api/namespaces/atomicassets/handlers/templates.ts @@ -10,7 +10,7 @@ import { filterQueryArgs } from '../../validation'; export async function getTemplatesAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.templates || 1000; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', allowedValues: ['created', 'name'], default: 'created'}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, @@ -19,13 +19,13 @@ export async function getTemplatesAction(params: RequestValues, ctx: AtomicAsset schema_name: {type: 'list[name]'}, authorized_account: {type: 'name'}, - issued_supply: {type: 'int', min: 0}, - min_issued_supply: {type: 'int', min: 0}, - max_issued_supply: {type: 'int', min: 0}, + issued_supply: {type: 'int', min: 0, max: Number.MAX_SAFE_INTEGER}, + min_issued_supply: {type: 'int', min: 0, max: Number.MAX_SAFE_INTEGER}, + max_issued_supply: {type: 'int', min: 0, max: Number.MAX_SAFE_INTEGER}, has_assets: {type: 'bool'}, include_deleted: {type: 'bool'}, - max_supply: {type: 'int', min: 0}, + max_supply: {type: 'int', min: 0, max: Number.MAX_SAFE_INTEGER}, is_transferable: {type: 'bool'}, is_burnable: {type: 'bool'}, @@ -186,7 +186,7 @@ export async function getTemplateLogsAction(params: RequestValues, ctx: AtomicAs const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { template_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicassets/handlers/transfers.ts b/src/api/namespaces/atomicassets/handlers/transfers.ts index 2b3906d4..fdb99a1c 100644 --- a/src/api/namespaces/atomicassets/handlers/transfers.ts +++ b/src/api/namespaces/atomicassets/handlers/transfers.ts @@ -8,7 +8,7 @@ import { ApiError } from '../../../error'; export async function getRawTransfersAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { const maxLimit = ctx.coreArgs.limits?.transfers || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', allowedValues: ['created'], default: 'created'}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, diff --git a/src/api/namespaces/atomicmarket/handlers/auctions.ts b/src/api/namespaces/atomicmarket/handlers/auctions.ts index ffd23ef3..645e50bb 100644 --- a/src/api/namespaces/atomicmarket/handlers/auctions.ts +++ b/src/api/namespaces/atomicmarket/handlers/auctions.ts @@ -13,7 +13,7 @@ import {filterQueryArgs} from '../../validation'; export async function getAuctionsAction(params: RequestValues, ctx: AtomicMarketContext): Promise { const maxLimit = ctx.coreArgs.limits?.auctions || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: { type: 'string', @@ -139,7 +139,7 @@ export async function getAuctionLogsAction(params: RequestValues, ctx: AtomicMar const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { auction_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicmarket/handlers/buyoffers.ts b/src/api/namespaces/atomicmarket/handlers/buyoffers.ts index 5ab1eec0..9ddb4a89 100644 --- a/src/api/namespaces/atomicmarket/handlers/buyoffers.ts +++ b/src/api/namespaces/atomicmarket/handlers/buyoffers.ts @@ -12,7 +12,7 @@ import { filterQueryArgs } from '../../validation'; export async function getBuyOffersAction(params: RequestValues, ctx: AtomicMarketContext): Promise { const maxLimit = ctx.coreArgs.limits?.buyoffers || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: { type: 'string', @@ -143,7 +143,7 @@ export async function getBuyOfferLogsAction(params: RequestValues, ctx: AtomicMa const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { buyoffer_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicmarket/handlers/prices.ts b/src/api/namespaces/atomicmarket/handlers/prices.ts index 7de11fcc..cc97361b 100644 --- a/src/api/namespaces/atomicmarket/handlers/prices.ts +++ b/src/api/namespaces/atomicmarket/handlers/prices.ts @@ -78,7 +78,7 @@ export async function getAssetSalesAction(params: RequestValues, ctx: AtomicMark seller: {type: 'list[name]'}, buyer: {type: 'list[name]'}, symbol: {type: 'string', min: 1}, - limit: {type: 'int', min: 1, default: 100}, + limit: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 100}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, bundles: {type: 'bool'} }); @@ -145,8 +145,8 @@ export async function getPricesSalesDaysAction(params: RequestValues, ctx: Atomi schema_name: {type: 'list[name]'}, asset_id: {type: 'list[id]'}, symbol: {type: 'string', min: 1}, - after: {type: 'int', min: 1}, - before: {type: 'int', min: 1} + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER} }); const query = new QueryBuilder(` @@ -219,7 +219,7 @@ export async function getPricesTemplatesAction(params: RequestValues, ctx: Atomi schema_name: {type: 'list[name]'}, symbol: {type: 'string', min: 1}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, }); diff --git a/src/api/namespaces/atomicmarket/handlers/royalties.ts b/src/api/namespaces/atomicmarket/handlers/royalties.ts index 4445e2af..4819dea8 100644 --- a/src/api/namespaces/atomicmarket/handlers/royalties.ts +++ b/src/api/namespaces/atomicmarket/handlers/royalties.ts @@ -73,7 +73,7 @@ export async function getRoyaltyTemplateRulesAction(params: RequestValues, ctx: const args = await filterQueryArgs(params, { template_id: { type: 'list[id]' }, - page: { type: 'int', min: 1, default: 1 }, + page: { type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1 }, limit: { type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100) }, }); @@ -97,10 +97,10 @@ export async function getRoyaltyTemplateRulesAction(params: RequestValues, ctx: export async function getRoyaltyAttributeRulesAction(params: RequestValues, ctx: AtomicMarketContext): Promise { const maxLimit = ctx.coreArgs.limits?.royalties || 100; const args = await filterQueryArgs(params, { - source: { type: 'int', min: 0 }, + source: { type: 'int', min: 0, max: Number.MAX_SAFE_INTEGER }, field: { type: 'string', min: 1 }, - page: { type: 'int', min: 1, default: 1 }, + page: { type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1 }, limit: { type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100) }, }); @@ -142,7 +142,7 @@ export async function getRoyaltyPayoutsAction(params: RequestValues, ctx: Atomic listing_id: { type: 'id' }, category: { type: 'list[string]', allowedValues: Object.keys(PAYOUT_CATEGORY_BY_NAME) }, - page: { type: 'int', min: 1, default: 1 }, + page: { type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1 }, limit: { type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100) }, sort: { type: 'string', allowedValues: ['created', 'amount'], default: 'created' }, order: { type: 'string', allowedValues: ['asc', 'desc'], default: 'desc' }, @@ -229,8 +229,8 @@ export async function getRoyaltyAccountAction(params: RequestValues, ctx: Atomic const args = await filterQueryArgs(params, { collection_name: { type: 'list[name]' }, symbol: { type: 'string', min: 1 }, - before: { type: 'int', min: 1 }, - after: { type: 'int', min: 1 }, + before: { type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER }, + after: { type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER }, }); const query = new QueryBuilder(` diff --git a/src/api/namespaces/atomicmarket/handlers/sales.ts b/src/api/namespaces/atomicmarket/handlers/sales.ts index 90b94d74..bc1fbd2d 100644 --- a/src/api/namespaces/atomicmarket/handlers/sales.ts +++ b/src/api/namespaces/atomicmarket/handlers/sales.ts @@ -34,7 +34,7 @@ export async function getSaleLogsAction(params: RequestValues, ctx: AtomicMarket const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { sale_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, @@ -55,7 +55,7 @@ export async function getSaleLogsAction(params: RequestValues, ctx: AtomicMarket export async function getSalesAction(params: RequestValues, ctx: AtomicMarketContext): Promise { const maxLimit = ctx.coreArgs.limits?.sales || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, collection_name: {type: 'list[name]'}, state: {type: 'string', min: 1}, diff --git a/src/api/namespaces/atomicmarket/handlers/sales2.mint-bound.test.ts b/src/api/namespaces/atomicmarket/handlers/sales2.mint-bound.test.ts new file mode 100644 index 00000000..931765d2 --- /dev/null +++ b/src/api/namespaces/atomicmarket/handlers/sales2.mint-bound.test.ts @@ -0,0 +1,33 @@ +import 'mocha'; +import { expect } from 'chai'; + +import { getSalesV2Action } from './sales2'; +import { AtomicMarketContext } from '../index'; + +describe('getSalesV2Action - integer bounds', () => { + const tooLarge = String(Number.MAX_SAFE_INTEGER) + '0'; + const ctx = { + pathParams: {}, + db: { + query: async (): Promise => ({rows: [], rowCount: 0}), + fetchOne: async (): Promise => null, + }, + coreArgs: {atomicmarket_account: 'atomicmarket', atomicassets_account: 'atomicassets', limits: {}}, + } as unknown as AtomicMarketContext; + + for (const key of ['min_template_mint', 'max_template_mint', 'min_assets', 'max_assets']) { + it(`${key} above the safe integer range is refused as a 400`, async () => { + let error: any = null; + + try { + await getSalesV2Action({[key]: tooLarge}, ctx); + } catch (e) { + error = e; + } + + expect(error).to.not.equal(null); + expect(error.code).to.equal(400); + expect(error.message).to.contain(`Invalid value for parameter ${key}`); + }); + } +}); diff --git a/src/api/namespaces/atomicmarket/handlers/sales2.ts b/src/api/namespaces/atomicmarket/handlers/sales2.ts index 39257d91..48ab18cd 100644 --- a/src/api/namespaces/atomicmarket/handlers/sales2.ts +++ b/src/api/namespaces/atomicmarket/handlers/sales2.ts @@ -27,7 +27,7 @@ export async function getSalesV2Action(params: RequestValues, ctx: AtomicMarketC const args = await filterQueryArgs(params, { state: {type: 'string[]', min: 1}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: { type: 'string', @@ -171,8 +171,8 @@ export async function getSalesCountV2Action(params: RequestValues, ctx: AtomicMa async function buildSaleFilterV2(search: SalesSearchOptions): Promise { const {values, query, ctx} = search; const args = await filterQueryArgs(values, { - max_assets: {type: 'int', min: 1}, - min_assets: {type: 'int', min: 1}, + max_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + min_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, symbol: {type: 'string', min: 1}, min_price: {type: 'float', min: 0}, @@ -488,8 +488,8 @@ async function buildListingFilterV2(search: SalesSearchOptions): Promise { taker_marketplace: {type: 'string[]', min: 1}, marketplace: {type: 'string[]', min: 1}, - min_template_mint: {type: 'int', min: 1}, - max_template_mint: {type: 'int', min: 1} + min_template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + max_template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER} }); if (!args.show_seller_contracts) { @@ -643,7 +643,7 @@ export async function getSalesTemplatesV2Action(params: RequestValues, ctx: Atom symbol: {type: 'string', min: 1}, collection_whitelist: {type: 'list[name]'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: { type: 'string', diff --git a/src/api/namespaces/atomicmarket/handlers/stats.ts b/src/api/namespaces/atomicmarket/handlers/stats.ts index fee88739..8dc430a1 100644 --- a/src/api/namespaces/atomicmarket/handlers/stats.ts +++ b/src/api/namespaces/atomicmarket/handlers/stats.ts @@ -14,15 +14,15 @@ export async function getAllCollectionStatsAction(params: RequestValues, ctx: At match: {type: 'name'}, search: {type: 'string', min: 1}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1}, + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, collection_name: {type: 'list[name]'}, collection_whitelist: {type: 'list[name]'}, collection_blacklist: {type: 'list[name]'}, sort: {type: 'string', allowedValues: ['volume', 'sales'], default: 'volume'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: 100, default: 100} }); @@ -110,11 +110,11 @@ export async function getAllAccountStatsAction(params: RequestValues, ctx: Atomi symbol: {type: 'string', min: 1}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1}, + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, sort: {type: 'string', allowedValues: ['sell_volume', 'buy_volume'], default: 'sell_volume'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: 100, default: 100} }); @@ -184,8 +184,8 @@ export async function getSchemaStatsByCollectionV1Action(params: RequestValues, symbol: {type: 'string', min: 1}, match: {type: 'name'}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1}, + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, sort: {type: 'string', allowedValues: ['volume', 'listings'], default: 'volume'} }); @@ -222,8 +222,8 @@ export async function getSchemaStatsByCollectionV2Action(params: RequestValues, const args = await filterQueryArgs(params, { symbol: {type: 'string', min: 1}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1}, + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, sort: {type: 'string', allowedValues: ['volume', 'sales'], default: 'volume'} }); @@ -318,11 +318,11 @@ export async function getTemplateStatsAction(params: RequestValues, ctx: AtomicM search: {type: 'string', min: 1}, sort: {type: 'string', allowedValues: ['volume', 'sales'], default: 'volume'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: 1000, default: 100}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1} + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER} }); const symbol = await fetchSymbol(ctx.db, ctx.coreArgs.atomicmarket_account, args.symbol); @@ -406,8 +406,8 @@ export async function getMarketStatsAction(params: RequestValues, ctx: AtomicMar collection_blacklist: {type: 'list[name]'}, symbol: {type: 'string', min: 1}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1} + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER} }); const symbol = await fetchSymbol(ctx.db, ctx.coreArgs.atomicmarket_account, args.symbol); @@ -443,8 +443,8 @@ export async function getStatsGraphAction(params: RequestValues, ctx: AtomicMark maker_marketplace: {type: 'string'}, symbol: {type: 'string', min: 1}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1} + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER} }); const symbol = await fetchSymbol(ctx.db, ctx.coreArgs.atomicmarket_account, args.symbol); diff --git a/src/api/namespaces/atomicmarket/handlers/template-buyoffers.ts b/src/api/namespaces/atomicmarket/handlers/template-buyoffers.ts index a3fd5af9..fe99a9ec 100644 --- a/src/api/namespaces/atomicmarket/handlers/template-buyoffers.ts +++ b/src/api/namespaces/atomicmarket/handlers/template-buyoffers.ts @@ -12,7 +12,7 @@ import { filterQueryArgs } from '../../validation'; export async function getTemplateBuyOffersAction(params: RequestValues, ctx: AtomicMarketContext): Promise { const maxLimit = ctx.coreArgs.limits?.buyoffers || 100; const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: { type: 'string', @@ -128,7 +128,7 @@ export async function getTemplateBuyOfferLogsAction(params: RequestValues, ctx: const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ...params}, { buyoffer_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/atomicmarket/utils.mint-bound.test.ts b/src/api/namespaces/atomicmarket/utils.mint-bound.test.ts new file mode 100644 index 00000000..0cb9a339 --- /dev/null +++ b/src/api/namespaces/atomicmarket/utils.mint-bound.test.ts @@ -0,0 +1,29 @@ +import 'mocha'; +import { expect } from 'chai'; + +import { buildTemplateMintFilter } from './utils'; +import QueryBuilder from '../../builder'; + +describe('buildTemplateMintFilter - mint bounds', () => { + const tooLarge = String(Number.MAX_SAFE_INTEGER) + '0'; + + for (const key of ['min_template_mint', 'max_template_mint']) { + it(`${key} above the safe integer range is refused as a 400`, async () => { + let error: any = null; + + try { + await buildTemplateMintFilter({[key]: tooLarge}, new QueryBuilder('SELECT 1')); + } catch (e) { + error = e; + } + + expect(error).to.not.equal(null); + expect(error.code).to.equal(400); + expect(error.message).to.contain(`Invalid value for parameter ${key}`); + }); + } + + it('min_template_mint at the safe integer limit is accepted', async () => { + await buildTemplateMintFilter({min_template_mint: String(Number.MAX_SAFE_INTEGER)}, new QueryBuilder('SELECT 1')); + }); +}); diff --git a/src/api/namespaces/atomicmarket/utils.ts b/src/api/namespaces/atomicmarket/utils.ts index 4f41754f..635c36eb 100644 --- a/src/api/namespaces/atomicmarket/utils.ts +++ b/src/api/namespaces/atomicmarket/utils.ts @@ -206,8 +206,8 @@ export async function buildEffectiveMintSort( export async function buildTemplateMintFilter(values: FilterValues, query: QueryBuilder): Promise { const args = await filterQueryArgs(values, { - min_template_mint: {type: 'int', min: 1}, - max_template_mint: {type: 'int', min: 1} + min_template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + max_template_mint: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER} }); if (args.min_template_mint || args.max_template_mint) { @@ -232,8 +232,8 @@ export async function buildSaleFilter(values: FilterValues, query: QueryBuilder) const args = await filterQueryArgs(values, { state: {type: 'string', min: 1}, - max_assets: {type: 'int', min: 1}, - min_assets: {type: 'int', min: 1}, + max_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + min_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, symbol: {type: 'string', min: 1}, min_price: {type: 'float', min: 0}, @@ -344,8 +344,8 @@ export async function buildAuctionFilter(values: FilterValues, query: QueryBuild const args = await filterQueryArgs(values, { state: {type: 'string', min: 1}, - min_assets: {type: 'int', min: 1}, - max_assets: {type: 'int', min: 1}, + min_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + max_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, symbol: {type: 'string', min: 1}, min_price: {type: 'float', min: 0}, @@ -513,8 +513,8 @@ export async function buildBuyofferFilter(values: FilterValues, query: QueryBuil const args = await filterQueryArgs(values, { state: {type: 'string', min: 1}, - min_assets: {type: 'int', min: 1}, - max_assets: {type: 'int', min: 1}, + min_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + max_assets: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, symbol: {type: 'string', min: 1}, min_price: {type: 'float', min: 0}, diff --git a/src/api/namespaces/atomictools/handlers/links.ts b/src/api/namespaces/atomictools/handlers/links.ts index 7d17db90..c6beddcb 100644 --- a/src/api/namespaces/atomictools/handlers/links.ts +++ b/src/api/namespaces/atomictools/handlers/links.ts @@ -19,7 +19,7 @@ export async function getLinksAction(params: RequestValues, ctx: AtomicToolsCont collection_blacklist: {type: 'list[name]'}, collection_whitelist: {type: 'list[name]'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, sort: {type: 'string', allowedValues: ['created'], default: 'created'}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, @@ -131,7 +131,7 @@ export async function getLinkLogsAction(params: RequestValues, ctx: AtomicToolsC const maxLimit = ctx.coreArgs.limits?.logs || 100; const args = await filterQueryArgs({...ctx.pathParams, ... params}, { link_id: {type: 'id'}, - page: {type: 'int', min: 1, default: 1}, + page: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER, default: 1}, limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'asc'}, action_whitelist: {type: 'string[]', min: 1}, diff --git a/src/api/namespaces/utils.ts b/src/api/namespaces/utils.ts index 9002bcc7..58b3a441 100644 --- a/src/api/namespaces/utils.ts +++ b/src/api/namespaces/utils.ts @@ -32,11 +32,12 @@ export async function buildBoundaryFilter( primaryColumn: string, primaryType: 'string' | 'int', dateColumn: string | null ): Promise { + const primaryBound = primaryType === 'int' ? {max: Number.MAX_SAFE_INTEGER} : {}; const filters: FiltersDefinition = { - lower_bound: {type: primaryType, min: 1}, - upper_bound: {type: primaryType, min: 1}, - before: {type: 'int', min: 1}, - after: {type: 'int', min: 1}, + lower_bound: {type: primaryType, min: 1, ...primaryBound}, + upper_bound: {type: primaryType, min: 1, ...primaryBound}, + before: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, + after: {type: 'int', min: 1, max: Number.MAX_SAFE_INTEGER}, ids: {type: 'list[string]'}, }; let primaryColumnName;