From 5b5ade2c92b92bb0ed8899195d765df808f5a9fc Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 27 Jun 2024 12:54:52 +0100 Subject: [PATCH] Fix at+get fns (#1290) * combine get methods to enforce key as number | string | symbol * adjust `at()` method to infer array type properly, and handle element types better. Also add readonly array support * fix test for undefined array type and add readonly test * remove generic from `at` call sites * is finite check for index * is finite index test * fix array of lookup type handling * missing license * lint --- packages/contract/src/contract/storage.ts | 2 +- packages/detector/src/index.d.ts | 13 +++++++++++++ packages/procaptcha/src/modules/Manager.ts | 4 ++-- packages/util/src/at.ts | 20 ++++++++++++++------ packages/util/src/get.ts | 7 +++---- packages/util/src/tests/at.test.ts | 10 +++++++++- 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/packages/contract/src/contract/storage.ts b/packages/contract/src/contract/storage.ts index 5a3365b0da..b6e18d0287 100644 --- a/packages/contract/src/contract/storage.ts +++ b/packages/contract/src/contract/storage.ts @@ -140,7 +140,7 @@ export function getStorageKeyAndType( } const rootKeyReversed = reverseHexString(rootKey.slice(2)) - const item = get(abi.registry.lookup.types, storage.layout.leaf.ty) + const item = at(abi.registry.lookup.types, parseInt(storage.layout.leaf.ty)) return { storageType: item, storageKey: rootKeyReversed, diff --git a/packages/detector/src/index.d.ts b/packages/detector/src/index.d.ts index f95b61ee9e..8cab82e098 100644 --- a/packages/detector/src/index.d.ts +++ b/packages/detector/src/index.d.ts @@ -1,3 +1,16 @@ +// Copyright 2021-2024 Prosopo (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. export function isBot(): Promise<{ fingerprint: { resistance: diff --git a/packages/procaptcha/src/modules/Manager.ts b/packages/procaptcha/src/modules/Manager.ts index d97e6acef1..e00e1399b4 100644 --- a/packages/procaptcha/src/modules/Manager.ts +++ b/packages/procaptcha/src/modules/Manager.ts @@ -308,7 +308,7 @@ export function Manager( const blockNumber = getBlockNumber() const signer = getExtension(account).signer - const first = at(challenge.captchas, 0) + const first = at(challenge.captchas, 0) if (!first.captcha.datasetId) { throw new ProsopoDatasetError('CAPTCHA.INVALID_CAPTCHA_ID', { context: { error: 'No datasetId set for challenge' }, @@ -384,7 +384,7 @@ export function Manager( } const index = state.index const solutions = state.solutions - const solution = at(solutions, index) + const solution = at(solutions, index) if (solution.includes(hash)) { // remove the hash from the solution solution.splice(solution.indexOf(hash), 1) diff --git a/packages/util/src/at.ts b/packages/util/src/at.ts index b669e7e806..753280ae3b 100644 --- a/packages/util/src/at.ts +++ b/packages/util/src/at.ts @@ -16,20 +16,28 @@ export type AtOptions = { noWrap?: boolean // whether to wrap the index around the bounds of the array (true == no wrap, false == wrap indices) } // Get an element from an array, throwing an error if it's index is out of bounds or if the element is undefined or null (can be overridden with the options) + export function at(str: string, index: number, options: AtOptions & { optional: true }): string | undefined export function at(str: string, index: number, options?: AtOptions): string -export function at(items: T[] | string, index: number, options: AtOptions & { optional: false }): T -export function at( - items: (T | undefined)[] | string, +export function at( + items: T, index: number, options: AtOptions & { optional: true } -): T | undefined -export function at(items: T[], index: number, options?: AtOptions): T -export function at(items: T[] | string, index: number, options?: AtOptions): T { +): T[number] | undefined +export function at( + items: T, + index: number, + options?: AtOptions +): Exclude +export function at(items: T | string, index: number, options?: AtOptions): T[number] { if (items.length === 0) { throw new Error('Array is empty') } + if (!isFinite(index)) { + throw new Error(`Index ${index} is not a finite number`) + } + if (!options?.noWrap) { if (index > 0) { index = index % items.length diff --git a/packages/util/src/get.ts b/packages/util/src/get.ts index ef28f5df50..803e5ffd67 100644 --- a/packages/util/src/get.ts +++ b/packages/util/src/get.ts @@ -11,10 +11,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -export function get(obj: T, key: unknown, required?: true): Exclude -export function get(obj: T, key: unknown, required: false): T[keyof T] | undefined -export function get(obj: unknown, key: string | number | symbol, required?: true): Exclude -export function get(obj: unknown, key: string | number | symbol, required: false): T | undefined + +export function get(obj: T, key: string | number | symbol, required?: true): Exclude +export function get(obj: T, key: string | number | symbol, required: false): T[keyof T] | undefined export function get(obj: T, key: unknown, required = true): V { const value = obj[key as unknown as keyof T] if (required && value === undefined) { diff --git a/packages/util/src/tests/at.test.ts b/packages/util/src/tests/at.test.ts index abaa809bbd..5b07df313e 100644 --- a/packages/util/src/tests/at.test.ts +++ b/packages/util/src/tests/at.test.ts @@ -34,9 +34,17 @@ describe('at', () => { const a5: string = at('abc', 0) const a10: string = at('abc', 0, { optional: false }) const a11: string | undefined = at('abc', 0, { optional: true }) - const a12: undefined = at([undefined, undefined, undefined], 0) + const a12: never = at([undefined, undefined, undefined], 0) const a13: undefined = at([undefined, undefined, undefined], 0, { optional: true }) const a14: undefined = at([undefined, undefined, undefined], 0, { optional: false }) + + const a15: string = at(['a', 'b', 'c'] as readonly string[], 0) + }) + + test('infinite index', () => { + expect(() => at([1, 2, 3], Infinity)).to.throw() + expect(() => at([1, 2, 3], -Infinity)).to.throw() + expect(() => at([1, 2, 3], NaN)).to.throw() }) test('compatible with string', () => {