diff --git a/.changeset/lazy-scissors-teach.md b/.changeset/lazy-scissors-teach.md new file mode 100644 index 00000000..9bbb87ea --- /dev/null +++ b/.changeset/lazy-scissors-teach.md @@ -0,0 +1,5 @@ +--- +"@delvtech/evm-client": patch +--- + +Added a `cache` property to the `CachedReadContract` type and ensured the factories preserve the prototypes of the contract's they're given. diff --git a/packages/evm-client/src/contract/factories/createCachedReadContract.ts b/packages/evm-client/src/contract/factories/createCachedReadContract.ts index 0407d48b..a2278540 100644 --- a/packages/evm-client/src/contract/factories/createCachedReadContract.ts +++ b/packages/evm-client/src/contract/factories/createCachedReadContract.ts @@ -33,8 +33,16 @@ export function createCachedReadContract({ cache = createLruSimpleCache({ max: DEFAULT_CACHE_SIZE }), namespace, }: CreateCachedReadContractOptions): CachedReadContract { - return { - ...contract, + // Because this is part of the public API, we won't know if the original + // contract is a plain object or a class instance, so we use Object.create to + // preserve the original contract's prototype chain when extending, ensuring + // the new contract includes all the original contract's methods and + // instanceof checks will still work. + const contractPrototype = Object.getPrototypeOf(contract); + const newContract = Object.create(contractPrototype); + + const overrides: Partial> = { + cache, /** * Reads data from the contract. First checks the cache, and if not present, @@ -111,6 +119,8 @@ export function createCachedReadContract({ cache.clear(); }, }; + + return Object.assign(newContract, contract, overrides); } async function getOrSet({ diff --git a/packages/evm-client/src/contract/factories/createCachedReadWriteContract.ts b/packages/evm-client/src/contract/factories/createCachedReadWriteContract.ts index 7773abff..dde16926 100644 --- a/packages/evm-client/src/contract/factories/createCachedReadWriteContract.ts +++ b/packages/evm-client/src/contract/factories/createCachedReadWriteContract.ts @@ -8,7 +8,7 @@ import { ReadWriteContract } from 'src/contract/types/Contract'; export interface CreateCachedReadWriteContractOptions extends CreateCachedReadContractOptions { - contract: ReadWriteContract | CachedReadWriteContract; + contract: ReadWriteContract; } /** @@ -22,18 +22,25 @@ export function createCachedReadWriteContract({ cache, namespace, }: CreateCachedReadWriteContractOptions): CachedReadWriteContract { - const cachedReadContract = isCached(contract) - ? contract - : createCachedReadContract({ contract, cache, namespace }); - - return { - ...contract, - ...cachedReadContract, - }; + // Avoid double-caching if given a contract that already has a cache. + if (isCached(contract)) { + return contract; + } + // Because this is part of the public API, we won't know if the original + // contract is a plain object or a class instance, so we use Object.create to + // preserve the original contract's prototype chain when extending, ensuring + // the new contract includes all the original contract's methods and + // instanceof checks will still work. + const contractPrototype = Object.getPrototypeOf(contract); + const newContract = Object.create(contractPrototype); + return Object.assign( + newContract, + createCachedReadContract({ contract, cache, namespace }), + ); } function isCached( - contract: ReadWriteContract | CachedReadWriteContract, + contract: ReadWriteContract, ): contract is CachedReadWriteContract { return 'clearCache' in contract; } diff --git a/packages/evm-client/src/contract/types/CachedContract.ts b/packages/evm-client/src/contract/types/CachedContract.ts index bfa53739..ba41feea 100644 --- a/packages/evm-client/src/contract/types/CachedContract.ts +++ b/packages/evm-client/src/contract/types/CachedContract.ts @@ -5,9 +5,11 @@ import { ReadWriteContract, } from 'src/contract/types/Contract'; import { FunctionName } from 'src/contract/types/Function'; +import { SimpleCache } from 'src/exports'; export interface CachedReadContract extends ReadContract { + cache: SimpleCache; namespace?: string; clearCache(): void; deleteRead>(