diff --git a/packages/drift/src/adapter/types/Adapter.ts b/packages/drift/src/adapter/types/Adapter.ts index d58d1cd0..acfd5c31 100644 --- a/packages/drift/src/adapter/types/Adapter.ts +++ b/packages/drift/src/adapter/types/Adapter.ts @@ -61,6 +61,11 @@ export interface WriteAdapter { export interface ReadWriteAdapter extends ReadAdapter, WriteAdapter {} +export interface ContractParams { + abi: TAbi; + address: Address; +} + export type FunctionArgsParam< TAbi extends Abi = Abi, TFunctionName extends FunctionName = FunctionName, @@ -82,9 +87,7 @@ export type ReadParams< TAbi, "pure" | "view" >, -> = { - abi: TAbi; - address: Address; +> = ContractParams & { fn: TFunctionName; } & FunctionArgsParam & ContractReadOptions; @@ -92,9 +95,8 @@ export type ReadParams< export interface GetEventsParams< TAbi extends Abi = Abi, TEventName extends EventName = EventName, -> extends ContractGetEventsOptions { - abi: TAbi; - address: Address; +> extends ContractParams, + ContractGetEventsOptions { event: TEventName; } @@ -104,9 +106,7 @@ export type SimulateWriteParams< TAbi, "nonpayable" | "payable" > = FunctionName, -> = { - abi: TAbi; - address: Address; +> = ContractParams & { fn: TFunctionName; } & FunctionArgsParam & ContractWriteOptions; diff --git a/packages/drift/src/cache/ClientCache.test.ts b/packages/drift/src/cache/ClientCache.test.ts index cb953580..346760cc 100644 --- a/packages/drift/src/cache/ClientCache.test.ts +++ b/packages/drift/src/cache/ClientCache.test.ts @@ -1,10 +1,11 @@ -import type { GetEventsParams } from "src/adapter/types/Adapter"; +import type { GetEventsParams, ReadParams } from "src/adapter/types/Adapter"; import type { EventLog } from "src/adapter/types/Event"; import { createStubTransaction } from "src/adapter/utils/testing/createStubTransaction"; import { createStubTransactionReceipt } from "src/adapter/utils/testing/createStubTransactionReceipt"; import { ClientCache } from "src/cache/ClientCache"; import { LruSimpleCache } from "src/cache/LruSimpleCache"; import { ZERO_ADDRESS } from "src/constants"; +import { ALICE, BOB, NANCY } from "src/utils/testing/accounts"; import { erc20 } from "src/utils/testing/erc20"; import { describe, expect, it } from "vitest"; @@ -13,159 +14,252 @@ type Erc20Abi = typeof erc20.abi; describe("ClientCache", () => { // Balance // - describe("balanceKey", () => { - it("Namespaces keys", async () => { - const store = new LruSimpleCache({ max: 100 }); - const cache1 = new ClientCache({ store, namespace: "ns1" }); - const cache2 = new ClientCache({ store, namespace: "ns2" }); + it("Namespaces balance keys", async () => { + const store = new LruSimpleCache({ max: 100 }); + const cache1 = new ClientCache({ store, namespace: "ns1" }); + const cache2 = new ClientCache({ store, namespace: "ns2" }); - const key1 = await cache1.balanceKey({ address: "0xAlice" }); - const key2 = await cache2.balanceKey({ address: "0xAlice" }); + const key1 = await cache1.balanceKey({ address: ALICE }); + const key2 = await cache2.balanceKey({ address: ALICE }); - expect(key1).not.toBe(key2); - expect(JSON.stringify(key1)).toContain("ns1"); - expect(JSON.stringify(key2)).toContain("ns2"); - }); + expect(key1).not.toBe(key2); + expect(JSON.stringify(key1)).toContain("ns1"); + expect(JSON.stringify(key2)).toContain("ns2"); }); - describe("preloadBalance", () => { - it("Preloads the balance", async () => { - const cache = new ClientCache({ namespace: "test" }); - await cache.preloadBalance({ address: "0xAlice", value: 123n }); - const key = await cache.balanceKey({ address: "0xAlice" }); - const value = await cache.get(key); - expect(value).toBe(123n); - }); + it("Preloads balances", async () => { + const cache = new ClientCache({ namespace: "test" }); + await cache.preloadBalance({ address: ALICE, value: 123n }); + const key = await cache.balanceKey({ address: ALICE }); + const value = await cache.get(key); + expect(value).toBe(123n); }); - describe("invalidateBalance", () => { - it("Invalidates the balance", async () => { - const cache = new ClientCache({ namespace: "test" }); - const key = await cache.balanceKey({ address: "0xAlice" }); + it("Invalidates balances", async () => { + const cache = new ClientCache({ namespace: "test" }); + const key = await cache.balanceKey({ address: ALICE }); - await cache.preloadBalance({ address: "0xAlice", value: 123n }); - let value = await cache.get(key); - expect(value).toBe(123n); + await cache.preloadBalance({ address: ALICE, value: 123n }); + let value = await cache.get(key); + expect(value).toBe(123n); - await cache.invalidateBalance({ address: "0xAlice" }); - value = await cache.get(key); - expect(value).toBeUndefined(); - }); + await cache.invalidateBalance({ address: ALICE }); + value = await cache.get(key); + expect(value).toBeUndefined(); }); // Transaction // - describe("transactionKey", () => { - it("Namespaces keys", async () => { - const store = new LruSimpleCache({ max: 100 }); - const cache1 = new ClientCache({ store, namespace: "ns1" }); - const cache2 = new ClientCache({ store, namespace: "ns2" }); + it("Namespaces transaction keys", async () => { + const store = new LruSimpleCache({ max: 100 }); + const cache1 = new ClientCache({ store, namespace: "ns1" }); + const cache2 = new ClientCache({ store, namespace: "ns2" }); - const key1 = await cache1.transactionKey({ hash: "0x123" }); - const key2 = await cache2.transactionKey({ hash: "0x123" }); + const key1 = await cache1.transactionKey({ hash: "0x123" }); + const key2 = await cache2.transactionKey({ hash: "0x123" }); - expect(key1).not.toBe(key2); - expect(JSON.stringify(key1)).toContain("ns1"); - expect(JSON.stringify(key2)).toContain("ns2"); - }); + expect(key1).not.toBe(key2); + expect(JSON.stringify(key1)).toContain("ns1"); + expect(JSON.stringify(key2)).toContain("ns2"); }); - describe("preloadTransaction", () => { - it("Preloads transactions", async () => { - const cache = new ClientCache({ namespace: "test" }); - const tx = createStubTransaction(); - await cache.preloadTransaction({ hash: "0x123", value: tx }); - const key = await cache.transactionKey({ hash: "0x123" }); - const value = await cache.get(key); - expect(value).toStrictEqual(tx); - }); + it("Preloads transactions", async () => { + const cache = new ClientCache({ namespace: "test" }); + const tx = createStubTransaction(); + await cache.preloadTransaction({ hash: "0x123", value: tx }); + const key = await cache.transactionKey({ hash: "0x123" }); + const value = await cache.get(key); + expect(value).toStrictEqual(tx); }); // Transaction Receipt // - describe("transactionReceiptKey", () => { - it("Namespaces keys", async () => { - const store = new LruSimpleCache({ max: 100 }); - const cache1 = new ClientCache({ store, namespace: "ns1" }); - const cache2 = new ClientCache({ store, namespace: "ns2" }); + it("Namespaces trandaction receipt keys", async () => { + const store = new LruSimpleCache({ max: 100 }); + const cache1 = new ClientCache({ store, namespace: "ns1" }); + const cache2 = new ClientCache({ store, namespace: "ns2" }); - const key1 = await cache1.transactionReceiptKey({ hash: "0x123" }); - const key2 = await cache2.transactionReceiptKey({ hash: "0x123" }); + const key1 = await cache1.transactionReceiptKey({ hash: "0x123" }); + const key2 = await cache2.transactionReceiptKey({ hash: "0x123" }); - expect(key1).not.toBe(key2); - expect(JSON.stringify(key1)).toContain("ns1"); - expect(JSON.stringify(key2)).toContain("ns2"); - }); + expect(key1).not.toBe(key2); + expect(JSON.stringify(key1)).toContain("ns1"); + expect(JSON.stringify(key2)).toContain("ns2"); }); - describe("preloadTransactionReceipt", () => { - it("Preloads transaction receipts", async () => { - const cache = new ClientCache({ namespace: "test" }); - const receipt = createStubTransactionReceipt(); - await cache.preloadTransactionReceipt({ hash: "0x123", value: receipt }); - const key = await cache.transactionReceiptKey({ hash: "0x123" }); - const value = await cache.get(key); - expect(value).toStrictEqual(receipt); - }); + it("Preloads transaction receipts", async () => { + const cache = new ClientCache({ namespace: "test" }); + const receipt = createStubTransactionReceipt(); + await cache.preloadTransactionReceipt({ hash: "0x123", value: receipt }); + const key = await cache.transactionReceiptKey({ hash: "0x123" }); + const value = await cache.get(key); + expect(value).toStrictEqual(receipt); }); // Events // - describe("eventsKey", () => { - it("Namespaces keys", async () => { - const store = new LruSimpleCache({ max: 100 }); - const cache1 = new ClientCache({ store, namespace: "ns1" }); - const cache2 = new ClientCache({ store, namespace: "ns2" }); - - const params: GetEventsParams = { - abi: erc20.abi, - address: ZERO_ADDRESS, - event: "Approval", - }; - const key1 = await cache1.eventsKey(params); - const key2 = await cache2.eventsKey(params); - - expect(key1).not.toBe(key2); - expect(JSON.stringify(key1)).toContain("ns1"); - expect(JSON.stringify(key2)).toContain("ns2"); - }); + it("Namespaces event keys", async () => { + const store = new LruSimpleCache({ max: 100 }); + const cache1 = new ClientCache({ store, namespace: "ns1" }); + const cache2 = new ClientCache({ store, namespace: "ns2" }); + + const params: GetEventsParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + event: "Approval", + }; + const key1 = await cache1.eventsKey(params); + const key2 = await cache2.eventsKey(params); + + expect(key1).not.toBe(key2); + expect(JSON.stringify(key1)).toContain("ns1"); + expect(JSON.stringify(key2)).toContain("ns2"); }); - describe("preloadEvents", () => { - it("Preloads events", async () => { - const cache = new ClientCache({ namespace: "test" }); + it("Preloads events", async () => { + const cache = new ClientCache({ namespace: "test" }); - const params: GetEventsParams = { - abi: erc20.abi, - address: ZERO_ADDRESS, - event: "Approval", - filter: { - owner: "0xAlice", - }, - }; - const events: EventLog[] = [ - { - eventName: "Approval", - args: { - owner: "0xAlice", - spender: "0xBob", - value: 100n, - }, + const params: GetEventsParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + event: "Approval", + filter: { + owner: ALICE, + }, + }; + const events: EventLog[] = [ + { + eventName: "Approval", + args: { + owner: ALICE, + spender: BOB, + value: 100n, }, - { - eventName: "Approval", - args: { - owner: "0xAlice", - spender: "0xCharlie", - value: 120n, - }, + }, + { + eventName: "Approval", + args: { + owner: ALICE, + spender: NANCY, + value: 120n, }, - ]; + }, + ]; + + await cache.preloadEvents({ ...params, value: events }); + const key = await cache.eventsKey(params); + const value = await cache.get(key); + expect(value).toStrictEqual(events); + }); - await cache.preloadEvents({ ...params, value: events }); - const key = await cache.eventsKey(params); - const value = await cache.get(key); - expect(value).toStrictEqual(events); + // Reads // + + it("Namespaces read keys", async () => { + const store = new LruSimpleCache({ max: 100 }); + const cache1 = new ClientCache({ store, namespace: "ns1" }); + const cache2 = new ClientCache({ store, namespace: "ns2" }); + + const params: ReadParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + fn: "allowance", + args: { + owner: ALICE, + spender: BOB, + }, + }; + const key1 = await cache1.readKey(params); + const key2 = await cache2.readKey(params); + + expect(key1).not.toBe(key2); + expect(JSON.stringify(key1)).toContain("ns1"); + expect(JSON.stringify(key2)).toContain("ns2"); + }); + + it("Preloads reads", async () => { + const cache = new ClientCache({ namespace: "test" }); + + const params: ReadParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + fn: "allowance", + args: { + owner: ALICE, + spender: BOB, + }, + }; + + await cache.preloadRead({ ...params, value: 123n }); + const key = await cache.readKey(params); + const value = await cache.get(key); + expect(value).toStrictEqual(123n); + }); + + it("Invalidates reads", async () => { + const cache = new ClientCache({ namespace: "test" }); + + const params: ReadParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + fn: "allowance", + args: { + owner: ALICE, + spender: BOB, + }, + }; + const key = await cache.readKey(params); + + await cache.preloadRead({ ...params, value: 123n }); + let value = await cache.get(key); + expect(value).toBe(123n); + + await cache.invalidateRead(params); + value = await cache.get(key); + expect(value).toBeUndefined(); + }); + + it("Invalidates reads matching partial params", async () => { + const cache = new ClientCache({ namespace: "test" }); + + const params1: ReadParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + fn: "allowance", + args: { + owner: ALICE, + spender: BOB, + }, + }; + const params2: ReadParams = { + abi: erc20.abi, + address: ZERO_ADDRESS, + fn: "allowance", + args: { + owner: ALICE, + spender: NANCY, + }, + }; + + await cache.preloadRead({ ...params1, value: 123n }); + await cache.preloadRead({ ...params2, value: 456n }); + + const key1 = await cache.readKey(params1); + const key2 = await cache.readKey(params2); + let value1 = await cache.get(key1); + let value2 = await cache.get(key2); + + expect(value1).toBe(123n); + expect(value2).toBe(456n); + + await cache.invalidateReadsMatching({ + abi: erc20.abi, + address: ZERO_ADDRESS, + fn: "allowance", }); + + value1 = await cache.get(key1); + value2 = await cache.get(key2); + expect(value1).toBeUndefined(); + expect(value2).toBeUndefined(); }); }); diff --git a/packages/drift/src/cache/ClientCache.ts b/packages/drift/src/cache/ClientCache.ts index 2fba231b..bcf213b7 100644 --- a/packages/drift/src/cache/ClientCache.ts +++ b/packages/drift/src/cache/ClientCache.ts @@ -1,9 +1,18 @@ import type { Abi } from "abitype"; import isMatch from "lodash.ismatch"; -import type { GetEventsParams, ReadParams } from "src/adapter/types/Adapter"; +import type { + ContractParams, + GetEventsParams, + ReadParams, +} from "src/adapter/types/Adapter"; import type { Block } from "src/adapter/types/Block"; +import type { ContractReadOptions } from "src/adapter/types/Contract"; import type { EventLog, EventName } from "src/adapter/types/Event"; -import type { FunctionName, FunctionReturn } from "src/adapter/types/Function"; +import type { + FunctionArgs, + FunctionName, + FunctionReturn, +} from "src/adapter/types/Function"; import type { GetBalanceParams, GetBlockParams, @@ -182,7 +191,7 @@ export class ClientCache async partialReadKey< TAbi extends Abi, TFunctionName extends FunctionName, - >({ address, args, block, fn }: Partial>) { + >({ address, args, block, fn }: PartialReadParams) { return this.createNamespacedKey("read", { address, args, @@ -194,7 +203,7 @@ export class ClientCache async readKey>( params: ReadParams, ) { - return this.partialReadKey(params); + return this.partialReadKey(params as PartialReadParams); } async preloadRead< @@ -221,7 +230,7 @@ export class ClientCache async invalidateReadsMatching< TAbi extends Abi, TFunctionName extends FunctionName, - >(params: Partial>): Promise { + >(params: PartialReadParams): Promise { const matchKey = await this.partialReadKey(params); const operations: MaybePromise[] = []; @@ -275,4 +284,15 @@ export class ClientCache } } +// Required due to incompatibility between the conditional `FunctionArgsParam` +// type and `Partial` type. +export interface PartialReadParams< + TAbi extends Abi = Abi, + TFunctionName extends FunctionName = FunctionName, +> extends ContractParams, + ContractReadOptions { + fn?: TFunctionName; + args?: FunctionArgs; +} + export class ClientCacheError extends DriftError {} diff --git a/packages/drift/src/client/contract/Contract.ts b/packages/drift/src/client/contract/Contract.ts index 4182018e..4adc0909 100644 --- a/packages/drift/src/client/contract/Contract.ts +++ b/packages/drift/src/client/contract/Contract.ts @@ -2,6 +2,7 @@ import type { Abi } from "abitype"; import type { Address, Bytes, Hash } from "src/adapter/types/Abi"; import type { Adapter, + ContractParams, GetEventsParams, OnMinedParam, ReadParams, @@ -36,7 +37,7 @@ export type ContractConfig< TCache extends SimpleCache = SimpleCache, TClient extends BaseClient = BaseClient, > = Pretty< - ContractOptions & ContractClientOptions + ContractParams & ContractClientOptions >; export class Contract< @@ -168,7 +169,7 @@ export class Contract< } preloadRead>( - params: Omit, keyof ContractConfig> & { + params: Omit, keyof ContractParams> & { value: FunctionReturn; }, ) { @@ -285,11 +286,6 @@ export type ReadWriteContract< TClient extends ReadWriteClient = ReadWriteClient, > = Contract; -export interface ContractOptions { - abi: TAbi; - address: Address; -} - export type ContractClientOptions< TAdapter extends Adapter = Adapter, TCache extends SimpleCache = SimpleCache, diff --git a/packages/drift/src/client/contract/MockContract.ts b/packages/drift/src/client/contract/MockContract.ts index ff2a5e1d..59d912c1 100644 --- a/packages/drift/src/client/contract/MockContract.ts +++ b/packages/drift/src/client/contract/MockContract.ts @@ -2,6 +2,7 @@ import type { Abi } from "abitype"; import type { MockAdapter } from "src/adapter/MockAdapter"; import type { Bytes } from "src/adapter/types/Abi"; import type { + ContractParams, EncodeFunctionDataParams, ReadParams, ReadWriteAdapter, @@ -19,7 +20,6 @@ import { MockClient } from "src/client/MockClient"; import { Contract, type ContractGetEventsArgs, - type ContractOptions, } from "src/client/contract/Contract"; import { ZERO_ADDRESS } from "src/constants"; import type { FunctionKey, OneOf, OptionalKeys, Pretty } from "src/utils/types"; @@ -30,7 +30,7 @@ export type MockContractConfig< TCache extends SimpleCache = SimpleCache, TClient extends MockClient = MockClient, > = Pretty< - Partial> & + Partial> & MockContractClientOptions >; diff --git a/packages/drift/src/client/drift/Drift.ts b/packages/drift/src/client/drift/Drift.ts index fa83772f..55149351 100644 --- a/packages/drift/src/client/drift/Drift.ts +++ b/packages/drift/src/client/drift/Drift.ts @@ -1,8 +1,12 @@ import type { Abi } from "abitype"; -import type { Adapter, ReadWriteAdapter } from "src/adapter/types/Adapter"; +import type { + Adapter, + ContractParams, + ReadWriteAdapter, +} from "src/adapter/types/Adapter"; import type { SimpleCache } from "src/cache/types"; import { BaseClient } from "src/client/BaseClient"; -import { Contract, type ContractOptions } from "src/client/contract/Contract"; +import { Contract } from "src/client/contract/Contract"; export class Drift< TAdapter extends Adapter = ReadWriteAdapter, @@ -11,7 +15,7 @@ export class Drift< contract({ abi, address, - }: ContractOptions): Contract< + }: ContractParams): Contract< TAbi, TAdapter, TCache,