Skip to content

Commit

Permalink
feat(cache): Add redis cache
Browse files Browse the repository at this point in the history
  • Loading branch information
lukas authored and mindrunner committed Sep 6, 2023
1 parent 1a93449 commit b9b4ff9
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 18 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@lifi/types": "^8.3.0",
"dotenv": "^16.3.1",
"ethers": "^6.7.0",
"ioredis": "^5.3.2",
"pretty-error": "^4.0.0",
"superagent": "^8.0.9",
"superagent-throttle": "^1.0.1",
Expand Down
58 changes: 58 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/abi-cache/abi-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { CacheType, initCache } from './cache'
let cache: AbiCache

describe('abi-loader', () => {
beforeAll(() => {
cache = initCache(CacheType.MEMORY)
beforeAll(async () => {
cache = await initCache(CacheType.MEMORY)
})

it('load ABIs from the "abis" directory of the file system', () => {
Expand Down
17 changes: 16 additions & 1 deletion src/abi-cache/abi-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ type CachedFunctionFragmentsBySighash = {
}
export type ContractLocation = { address: string; chain: ChainId }

export class AbiCache {
export abstract class AbiCache {
protected cachedAbis: Map<string, Interface> = new Map()
protected functionFragments: CachedFunctionFragmentsBySighash = {}

constructor() {
this.loadAbiDirectory(path.join(__dirname, '../../abis'))
}

public abstract init(): Promise<AbiCache>

private groupFunctionFragmentsBySighash = (): void => {
this.functionFragments = {}
for (const abi of this.cachedAbis.values()) {
Expand Down Expand Up @@ -70,6 +72,19 @@ export class AbiCache {
})
}

protected loadFromString(key: string, abiString: string): void {
try {
const ethersInterface = new Interface(abiString)

const location: ContractLocation = this.fromKey(key)

this.cachedAbis.set(this.toKey(location), ethersInterface)
this.groupFunctionFragmentsBySighash()
} catch (error) {
log().error(`Error parsing string: ${(error as Error).message}`)
}
}

protected loadFromFile(fileName: string): void {
try {
const fileContent = fs.readFileSync(fileName).toString()
Expand Down
15 changes: 14 additions & 1 deletion src/abi-cache/cache.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { RedisOptions } from 'ioredis'

import { AbiCache } from './abi-cache'
import { FileSystemAbiCache } from './file-system-abi-cache'
import { MemoryAbiCache } from './memory-abi-cache'
import { RedisAbiCache } from './redis-abi-cache'

export enum CacheType {
MEMORY,
Expand All @@ -10,17 +13,27 @@ export enum CacheType {

let cache: AbiCache

export const initCache = (cacheType: CacheType): AbiCache => {
export const initCache = async (
cacheType: CacheType,
redisOptions?: RedisOptions
): Promise<AbiCache> => {
if (!cache) {
switch (cacheType) {
case CacheType.FILE_SYSTEM: {
cache = new FileSystemAbiCache(
process.env.ABI_DIRECTORY ?? '/tmp/abi-parser'
)
await cache.init()
break
}
case CacheType.MEMORY: {
cache = new MemoryAbiCache()
await cache.init()
break
}
case CacheType.REDIS: {
cache = new RedisAbiCache(redisOptions || {})
await cache.init()
break
}
default: {
Expand Down
7 changes: 6 additions & 1 deletion src/abi-cache/file-system-abi-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import { stringify } from '../lib/stringify'
import { AbiCache, AbiInformation } from './abi-cache'

export class FileSystemAbiCache extends AbiCache {
private abiDirectory: string
private readonly abiDirectory: string

constructor(abiDirectory: string) {
super()
this.abiDirectory = abiDirectory || '/tmp/abi-parser'
}

public override init(): Promise<AbiCache> {
this.loadAbiDirectory(this.abiDirectory)

return Promise.resolve(this)
}

protected override persist(key: string, abi: AbiInformation): void {
Expand Down
4 changes: 4 additions & 0 deletions src/abi-cache/memory-abi-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@ export class MemoryAbiCache extends AbiCache {
protected override persist(key: string, _: AbiInformation): void {
log().debug(`Using Memory cache, not persisting ABI ${key}`)
}

public override init(): Promise<AbiCache> {
return Promise.resolve(this)
}
}
39 changes: 39 additions & 0 deletions src/abi-cache/redis-abi-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Redis, RedisOptions } from 'ioredis'

import { stringify } from '../lib/stringify'

import { AbiCache, AbiInformation } from './abi-cache'

export class RedisAbiCache extends AbiCache {
private redis: Redis
private cachePrefix = 'ABICACHE:'

constructor(redisOptions: RedisOptions) {
super()
this.redis = new Redis({ ...redisOptions, keyPrefix: this.cachePrefix })
}

public override async init(): Promise<AbiCache> {
const keys = await this.redis.keys(`${this.cachePrefix}*`)

await Promise.all(
keys.map(async (key: string) => {
const abiKey = key.replace(`${this.cachePrefix}`, '')
const abi = await this.redis.get(abiKey)

if (abi) {
this.loadFromString(abiKey, abi)
}
})
)

return Promise.resolve(this)
}

protected override async persist(
key: string,
abi: AbiInformation
): Promise<void> {
await this.redis.set(key, stringify(abi))
}
}
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
dotenv.config()

const run = async () => {
const cache = initCache(CacheType.FILE_SYSTEM)
const cache = await initCache(CacheType.FILE_SYSTEM)

const callDataStrings =
process.argv.length === 2
Expand Down
10 changes: 6 additions & 4 deletions src/parse-call-data-string.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { AbiCache } from './abi-cache/abi-cache'
import { CacheType, initCache } from './abi-cache/cache'
import { cacheCandidates } from './abi-cache/cache-candidates'
import { AbiCache, CacheType, initCache, cacheCandidates } from './abi-cache'
import { CallDataInformation } from './parser'
import { parseCallData } from './parser/calldata-parsers/parse-call-data'

export const parseCallDataString = async (
callDataString: string,
cache: AbiCache = initCache(CacheType.MEMORY)
abiCache?: AbiCache
): Promise<CallDataInformation[]> => {
const cache: AbiCache = abiCache
? abiCache
: await initCache(CacheType.MEMORY)

const parsedCandidates: CallDataInformation[] = parseCallData(
callDataString,
cache
Expand Down
7 changes: 3 additions & 4 deletions src/parser/parser.acceptance.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { AbiCoder } from 'ethers'

import { AbiCache } from '../abi-cache/abi-cache'
import { CacheType, initCache } from '../abi-cache/cache'
import { AbiCache, CacheType, initCache } from '../abi-cache'
import {
bridge,
bridgeSwap,
Expand Down Expand Up @@ -56,8 +55,8 @@ const validateAndExtractSwapData = (
let cache: AbiCache

describe('Acceptance tests', () => {
beforeAll(() => {
cache = initCache(CacheType.MEMORY)
beforeAll(async () => {
cache = await initCache(CacheType.MEMORY)
})

it('should parse a swap transfer', () => {
Expand Down
7 changes: 3 additions & 4 deletions src/parser/parser.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { FunctionFragment } from 'ethers'

import { AbiCache } from '../abi-cache/abi-cache'
import { CacheType, initCache } from '../abi-cache/cache'
import { AbiCache, CacheType, initCache } from '../abi-cache'

import { parseCallData } from './calldata-parsers/parse-call-data'

Expand All @@ -15,8 +14,8 @@ const wrappedCallFunctionFragment: FunctionFragment = FunctionFragment.from(
let cache: AbiCache

describe('parseCallData', () => {
beforeAll(() => {
cache = initCache(CacheType.MEMORY)
beforeAll(async () => {
cache = await initCache(CacheType.MEMORY)
})

afterEach(() => {
Expand Down

0 comments on commit b9b4ff9

Please sign in to comment.