diff --git a/src/ts/utils.ts b/src/ts/utils.ts index cd337bb1..de44e281 100644 --- a/src/ts/utils.ts +++ b/src/ts/utils.ts @@ -1,7 +1,7 @@ -import {Address, getAddress} from 'viem'; +import { Address, getAddress } from 'viem'; import * as addresses from './AaveAddressBook'; -type AnyObject = {[key: string]: any}; +type AnyObject = { [key: string]: any }; function flattenObject(obj: AnyObject, parentKey = '', result: AnyObject = {}): AnyObject { for (const [key, value] of Object.entries(obj)) { @@ -28,12 +28,12 @@ export function getAddressBookReferences(value: Address, chainId: number): strin const transformedAddresses = Object.keys(addresses).reduce( (acc, key) => { if (addresses[key as keyof typeof addresses].CHAIN_ID === chainId) { - const chainAddresses = {...addresses[key as keyof typeof addresses]}; + const chainAddresses = { ...addresses[key as keyof typeof addresses] }; // deleting eModes as they only contain duplicates and on lookup should always be deprioritized if ((chainAddresses as any).E_MODES) { delete (chainAddresses as any).E_MODES; } - return {...acc, ...flattenObject({[key]: chainAddresses})}; + return { ...acc, ...flattenObject({ [key]: chainAddresses }) }; } return acc; }, @@ -43,3 +43,54 @@ export function getAddressBookReferences(value: Address, chainId: number): strin (key) => transformedAddresses[key] === getAddress(value), ); } + +type Deliteralize = { + [K in keyof T]: T[K] extends string ? string : T[K]; +}; +type MakePropertyRequired = T & { + [P in K]-?: T[P]; +}; + +type Addresses = Extract<(typeof addresses)[keyof typeof addresses], { CHAIN_ID: number }>; +type AddressFilter = MakePropertyRequired>, 'CHAIN_ID'>; + +/** + * Searches the address book for entries matching the filter + * @param filter an object with properties to filter by + * @returns list of matching entries + */ +export const filterAddresses = (filter: F) => { + const result = Object.values(addresses).filter((v) => { + return Object.entries(filter).every(([key, left]) => { + const right = v[key as keyof Addresses]; + + if (typeof left === 'string') { + return left.toLowerCase() === String(right).toLowerCase(); + } + return left === v[key as keyof Addresses]; + }); + }) as Extract[]; + + return result; +}; + +/** + * Finds a single entry in the address book matching the filter + * @param filter a filter object, that should match exactly one entry + * @returns exactly one matching entry + * @throws if no or multiple entries are found + */ +export const findAddress = (filter: F) => { + const results = filterAddresses(filter); + if (!results[0]) { + throw new Error(`No entry found in address book for filter ${JSON.stringify(filter)}`); + } + if (results[1]) { + throw new Error( + `Multiple entries found in address book for filter ${JSON.stringify( + filter, + )}: ${results.map((r) => JSON.stringify(r)).join(', ')}`, + ); + } + return results[0]; +}; diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts index 0a4c0e3f..32ddf109 100644 --- a/tests/utils.spec.ts +++ b/tests/utils.spec.ts @@ -1,9 +1,48 @@ -import {describe, expect, it} from 'vitest'; -import {POOL} from '../src/ts/AaveV3Ethereum'; -import {getAddressBookReferences} from '../src/ts/utils'; +import { describe, expect, it } from 'vitest'; +import { POOL } from '../src/ts/AaveV3Ethereum'; +import { filterAddresses, findAddress, getAddressBookReferences } from '../src/ts/utils'; +import { AaveV3Ethereum } from '../src/ts/AaveAddressBook'; describe('getAddressBookReferences', () => { it('should return correct response', () => { expect(getAddressBookReferences(POOL, 1)).toEqual(['AaveV3Ethereum.POOL']); }); }); + +describe("findAddress", () => { + it("finds a pool for a pool-configurator", () => { + const result = findAddress({ + CHAIN_ID: 1, + POOL_CONFIGURATOR: AaveV3Ethereum.POOL_CONFIGURATOR, + }); + + expect(result.POOL).toBe(AaveV3Ethereum.POOL); + }); + + it("throws if no entry is found", () => { + expect(() => + findAddress({ + CHAIN_ID: 1, + POOL_CONFIGURATOR: "0x0000000000000000000000000000000000000000", + }), + ).toThrow(); + }); + + it("throws if multiple entries are found", () => { + expect(() => + findAddress({ + CHAIN_ID: 1, + }), + ).toThrow(); + }); +}); + +describe("filterAddress", () => { + it("finds all entries for chain id", () => { + const results = filterAddresses({ + CHAIN_ID: 1, + }); + + expect(results.length).toMatchInlineSnapshot(`14`); + }); +}) \ No newline at end of file