From 09f4c8b077c06322855a99c722fd87aadee2feac Mon Sep 17 00:00:00 2001 From: Or Noyman <17881579+fullkomnun@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:08:05 +0000 Subject: [PATCH] Fix validation uint int sizes (#6434) * add ABI test cases with single numeric parameter that have a size that is not a power of 2 * fail-fast by throwing an error if during conversion of JSON shceme to Zod 'check' function is undefined (due to unsupported type) * make sure validators are defined for all valid sized of numeric types * prettify * updated changelog * include all newly generated single param test cases in validator test suite * add schemaError and unit test * update errors * format * test converage * address feedback --------- Co-authored-by: Alex Co-authored-by: Muhammad Altabba <24407834+Muhammad-Altabba@users.noreply.github.com> --- packages/web3-errors/CHANGELOG.md | 4 ++- packages/web3-errors/src/error_codes.ts | 4 +++ .../web3-errors/src/errors/schema_errors.ts | 32 +++++++++++++++++ packages/web3-errors/src/index.ts | 1 + .../unit/__snapshots__/errors.test.ts.snap | 10 ++++++ packages/web3-errors/test/unit/errors.test.ts | 11 ++++++ packages/web3-validator/CHANGELOG.md | 2 ++ packages/web3-validator/src/formats.ts | 3 +- packages/web3-validator/src/validator.ts | 5 +++ .../web3-validator/test/config/jest.config.js | 2 +- .../test/fixtures/abi_to_json_schema.ts | 35 ++++++++++++++++++- .../web3-validator/test/unit/load.test.ts | 2 +- .../test/unit/web3_validator.test.ts | 31 ++++++++++++---- 13 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 packages/web3-errors/src/errors/schema_errors.ts diff --git a/packages/web3-errors/CHANGELOG.md b/packages/web3-errors/CHANGELOG.md index 6c242768467..303bff74b62 100644 --- a/packages/web3-errors/CHANGELOG.md +++ b/packages/web3-errors/CHANGELOG.md @@ -154,4 +154,6 @@ Documentation: - Dependencies updated -## [Unreleased] \ No newline at end of file +## [Unreleased] + +- Added new SchemaFormatError (#6434) \ No newline at end of file diff --git a/packages/web3-errors/src/error_codes.ts b/packages/web3-errors/src/error_codes.ts index 8d077e6a040..c9ff68f6d7f 100644 --- a/packages/web3-errors/src/error_codes.ts +++ b/packages/web3-errors/src/error_codes.ts @@ -158,10 +158,14 @@ export const ERR_INVALID_NIBBLE_WIDTH = 1014; // Validation error codes export const ERR_VALIDATION = 1100; + // Core error codes export const ERR_CORE_HARDFORK_MISMATCH = 1101; export const ERR_CORE_CHAIN_MISMATCH = 1102; +// Schema error codes +export const ERR_SCHEMA_FORMAT = 1200; + // RPC error codes (EIP-1474) // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md export const ERR_RPC_INVALID_JSON = -32700; diff --git a/packages/web3-errors/src/errors/schema_errors.ts b/packages/web3-errors/src/errors/schema_errors.ts new file mode 100644 index 00000000000..4d61684c616 --- /dev/null +++ b/packages/web3-errors/src/errors/schema_errors.ts @@ -0,0 +1,32 @@ +/* +This file is part of web3.js. + +web3.js is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +web3.js is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with web3.js. If not, see . +*/ + +import { ERR_SCHEMA_FORMAT } from '../error_codes.js'; +import { BaseWeb3Error } from '../web3_error_base.js'; + +export class SchemaFormatError extends BaseWeb3Error { + public code = ERR_SCHEMA_FORMAT; + + public constructor(public type: string) { + super(`Format for the type ${type} is unsupported`); + } + + public toJSON() { + return { ...super.toJSON(), type: this.type }; + } + +} diff --git a/packages/web3-errors/src/index.ts b/packages/web3-errors/src/index.ts index 5916321f8a6..2e4ef7abca6 100644 --- a/packages/web3-errors/src/index.ts +++ b/packages/web3-errors/src/index.ts @@ -30,3 +30,4 @@ export * from './errors/response_errors.js'; export * from './errors/core_errors.js'; export * from './errors/rpc_errors.js'; export * from './errors/rpc_error_messages.js'; +export * from './errors/schema_errors.js'; \ No newline at end of file diff --git a/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap b/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap index a0f890a4b88..ab49d6aaa0a 100644 --- a/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap +++ b/packages/web3-errors/test/unit/__snapshots__/errors.test.ts.snap @@ -333,6 +333,16 @@ Object { } `; +exports[`errors SchemaFormatError should have valid json structure 1`] = ` +Object { + "code": 1200, + "innerError": undefined, + "message": "Format for the type unsupported is unsupported", + "name": "SchemaFormatError", + "type": "unsupported", +} +`; + exports[`errors TransactionError should have valid json structure 1`] = ` Object { "code": 400, diff --git a/packages/web3-errors/test/unit/errors.test.ts b/packages/web3-errors/test/unit/errors.test.ts index f8dd8466fcd..7428cc9b6b3 100644 --- a/packages/web3-errors/test/unit/errors.test.ts +++ b/packages/web3-errors/test/unit/errors.test.ts @@ -26,6 +26,7 @@ import * as signatureErrors from '../../src/errors/signature_errors'; import * as transactionErrors from '../../src/errors/transaction_errors'; import * as utilsErrors from '../../src/errors/utils_errors'; import * as responseErrors from '../../src/errors/response_errors'; +import * as schemaErrors from '../../src/errors/schema_errors'; import { ConvertValueToString } from '../fixtures/errors'; import { BaseWeb3Error } from '../../src/web3_error_base'; @@ -50,6 +51,7 @@ describe('errors', () => { ...signatureErrors, ...transactionErrors, ...utilsErrors, + ...schemaErrors, })) { if (ErrorClass === transactionErrors.InvalidPropertiesForTransactionTypeError) break; // To disable error for the abstract class @@ -379,4 +381,13 @@ describe('errors', () => { ).toMatchSnapshot(); }); }); + + describe('SchemaFormatError', () => { + it('should have valid json structure', () => { + expect( + new schemaErrors.SchemaFormatError("unsupported" + ).toJSON(), + ).toMatchSnapshot(); + }); + }); }); diff --git a/packages/web3-validator/CHANGELOG.md b/packages/web3-validator/CHANGELOG.md index 44852713656..94cda5fb867 100644 --- a/packages/web3-validator/CHANGELOG.md +++ b/packages/web3-validator/CHANGELOG.md @@ -153,3 +153,5 @@ Documentation: - Multi-dimensional arrays are now handled properly when parsing ABIs (#6435) - Fix issue with default config with babel (and React): "TypeError: Cannot convert a BigInt value to a number #6187" (#6506) +- Validator will now properly handle all valid numeric type sizes: intN / uintN where 8 <= N <= 256 and N % 8 == 0 (#6434) +- Will now throw SchemaFormatError when unsupported format is passed to `convertToZod` method (#6434) diff --git a/packages/web3-validator/src/formats.ts b/packages/web3-validator/src/formats.ts index da0c0ba6564..69d6fa9cf1f 100644 --- a/packages/web3-validator/src/formats.ts +++ b/packages/web3-validator/src/formats.ts @@ -41,8 +41,7 @@ const formats: { [key: string]: (data: unknown) => boolean } = { string: (data: unknown) => isString(data as ValidInputTypes), }; // generate formats for all numbers types -for (let i = 3; i <= 8; i += 1) { - const bitSize = 2 ** i; +for (let bitSize = 8; bitSize <= 256; bitSize += 8) { formats[`int${bitSize}`] = data => isInt(data as ValidInputTypes, { bitSize }); formats[`uint${bitSize}`] = data => isUInt(data as ValidInputTypes, { bitSize }); } diff --git a/packages/web3-validator/src/validator.ts b/packages/web3-validator/src/validator.ts index c1779992125..d477a06d96b 100644 --- a/packages/web3-validator/src/validator.ts +++ b/packages/web3-validator/src/validator.ts @@ -14,6 +14,7 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import { SchemaFormatError } from 'web3-errors'; import { Web3ValidationErrorObject } from 'web3-types'; import { z, ZodType, ZodIssue, ZodIssueCode, ZodTypeAny } from 'zod'; @@ -67,6 +68,10 @@ const convertToZod = (schema: JsonSchema): ZodType => { } if (schema?.format) { + if (!formats[schema.format]) { + throw new SchemaFormatError(schema.format); + } + return z.any().refine(formats[schema.format], (value: unknown) => ({ params: { value, format: schema.format }, })); diff --git a/packages/web3-validator/test/config/jest.config.js b/packages/web3-validator/test/config/jest.config.js index 4a60f95b353..c8c21e06e6a 100644 --- a/packages/web3-validator/test/config/jest.config.js +++ b/packages/web3-validator/test/config/jest.config.js @@ -12,7 +12,7 @@ module.exports = { }, moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', - }, + }, verbose: false, collectCoverage: false, coverageReporters: ['json'], diff --git a/packages/web3-validator/test/fixtures/abi_to_json_schema.ts b/packages/web3-validator/test/fixtures/abi_to_json_schema.ts index dcac24e2046..1a2cb7bd36f 100644 --- a/packages/web3-validator/test/fixtures/abi_to_json_schema.ts +++ b/packages/web3-validator/test/fixtures/abi_to_json_schema.ts @@ -30,7 +30,7 @@ export type AbiToJsonSchemaCase = { data: Record | Array; }; }; -export const abiToJsonSchemaCases: AbiToJsonSchemaCase[] = [ +const abiToJsonSchemaCases: AbiToJsonSchemaCase[] = [ { title: 'single param uint', abi: { @@ -1659,3 +1659,36 @@ export const abiToJsonSchemaCases: AbiToJsonSchemaCase[] = [ }, }, ]; + +function generateSingleParamNumericCase(type: string, bitSize: number) { + return { + title: `single param ${type}${bitSize}`, + abi: { + fullSchema: [{ name: 'a', type: `${type}${bitSize}` }], + shortSchema: [`${type}${bitSize}`], + data: [12], + }, + json: { + fullSchema: { + type: 'array', + items: [{ $id: 'a', format: `${type}${bitSize}`, required: true }], + minItems: 1, + maxItems: 1, + }, + shortSchema: { + type: 'array', + items: [{ $id: '/0/0', format: `${type}${bitSize}`, required: true }], + minItems: 1, + maxItems: 1, + }, + data: [12], + }, + }; +} + +for (let i = 256; i >= 8; i -= 8) { + abiToJsonSchemaCases.unshift(generateSingleParamNumericCase('int', i)); + abiToJsonSchemaCases.unshift(generateSingleParamNumericCase('uint', i)); +} + +export { abiToJsonSchemaCases }; diff --git a/packages/web3-validator/test/unit/load.test.ts b/packages/web3-validator/test/unit/load.test.ts index 919214ef7af..9053be7e8f0 100644 --- a/packages/web3-validator/test/unit/load.test.ts +++ b/packages/web3-validator/test/unit/load.test.ts @@ -16,7 +16,7 @@ along with web3.js. If not, see . */ import { Web3Validator } from '../../src/web3_validator'; -import { Json, JsonSchema, ValidationSchemaInput } from '../..'; +import { Json, JsonSchema, ValidationSchemaInput } from '../../src/types'; const abi = [ { indexed: true, internalType: 'address', name: 'from', type: 'address' }, diff --git a/packages/web3-validator/test/unit/web3_validator.test.ts b/packages/web3-validator/test/unit/web3_validator.test.ts index e1a49fa6e3a..0b910c31f17 100644 --- a/packages/web3-validator/test/unit/web3_validator.test.ts +++ b/packages/web3-validator/test/unit/web3_validator.test.ts @@ -14,6 +14,7 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +import { SchemaFormatError } from 'web3-errors' import { abiToJsonSchemaCases } from '../fixtures/abi_to_json_schema'; import { Web3Validator } from '../../src/web3_validator'; import { Web3ValidatorError } from '../../src/errors'; @@ -101,14 +102,32 @@ describe('web3-validator', () => { ), ).toBeUndefined(); }); + + it('should throw due to unsupported format', () => { + expect(() => { + validator.validateJSONSchema( + { + type: 'array', + items: [{ $id: 'a', format: 'unsupportedFormat', required: true }], + minItems: 1, + maxItems: 1, + }, + ['0x2df0879f1ee2b2b1f2448c64c089c29e3ad7ccc5'], + ); + }).toThrow(SchemaFormatError); + }); }); describe('validateJsonSchema', () => { - it.each(abiToJsonSchemaCases.slice(0, 5))('should pass for valid data', abi => { - const jsonSchema = abi.json; - expect( - validator.validateJSONSchema(jsonSchema.fullSchema, jsonSchema.data), - ).toBeUndefined(); - }); + // only single param test cases + it.each(abiToJsonSchemaCases.slice(0, 69))( + `$title - should pass for valid data`, + abi => { + const jsonSchema = abi.json; + expect( + validator.validateJSONSchema(jsonSchema.fullSchema, jsonSchema.data), + ).toBeUndefined(); + }, + ); it('should throw', () => { expect(() => {