Skip to content

Commit a3dd631

Browse files
authored
Use TaggedRequest for request and fix sql stores (#158)
* Use TaggedRequest for request to allow for equality check in cache * Fix sql stores syntax
1 parent aaf587d commit a3dd631

File tree

8 files changed

+200
-72
lines changed

8 files changed

+200
-72
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@3loop/transaction-decoder': patch
3+
---
4+
5+
Use TaggedRequest for request to allow for equality check for in-memory caching of requests

.changeset/wet-mirrors-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@3loop/transaction-decoder': minor
3+
---
4+
5+
Fix sql stores syntax

packages/transaction-decoder/src/abi-loader.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1-
import { Context, Effect, Either, RequestResolver, Request, Array, pipe, Data } from 'effect'
1+
import {
2+
Context,
3+
Effect,
4+
Either,
5+
RequestResolver,
6+
Request,
7+
Array,
8+
pipe,
9+
Data,
10+
PrimaryKey,
11+
Schema,
12+
SchemaAST,
13+
} from 'effect'
214
import { ContractABI, ContractAbiResolverStrategy, GetContractABIStrategy } from './abi-strategy/request-model.js'
315
import { Abi } from 'viem'
416

@@ -63,12 +75,22 @@ export class EmptyCalldataError extends Data.TaggedError('DecodeError')<
6375
}
6476
}
6577

66-
export interface AbiLoader extends Request.Request<Abi, MissingABIError>, LoadParameters {
67-
_tag: 'AbiLoader'
78+
class SchemaAbi extends Schema.make<Abi>(SchemaAST.objectKeyword) {}
79+
class AbiLoader extends Schema.TaggedRequest<AbiLoader>()('AbiLoader', {
80+
failure: Schema.instanceOf(MissingABIError),
81+
success: SchemaAbi, // Abi
82+
payload: {
83+
chainID: Schema.Number,
84+
address: Schema.String,
85+
event: Schema.optional(Schema.String),
86+
signature: Schema.optional(Schema.String),
87+
},
88+
}) {
89+
[PrimaryKey.symbol]() {
90+
return `abi::${this.chainID}:${this.address}:${this.event}:${this.signature}`
91+
}
6892
}
6993

70-
const AbiLoader = Request.tagged<AbiLoader>('AbiLoader')
71-
7294
function makeRequestKey(key: AbiLoader) {
7395
return `abi::${key.chainID}:${key.address}:${key.event}:${key.signature}`
7496
}
@@ -191,7 +213,7 @@ const AbiLoaderRequestResolver: Effect.Effect<
191213

192214
// NOTE: Firstly we batch strategies by address because in a transaction most of events and traces are from the same abi
193215
const response = yield* Effect.forEach(remaining, (req) => {
194-
const strategyRequest = GetContractABIStrategy({
216+
const strategyRequest = new GetContractABIStrategy({
195217
address: req.address,
196218
chainID: req.chainID,
197219
})
@@ -216,7 +238,7 @@ const AbiLoaderRequestResolver: Effect.Effect<
216238

217239
// NOTE: Secondly we request strategies to fetch fragments
218240
const fragmentStrategyResults = yield* Effect.forEach(notFound, ({ chainID, address, event, signature }) => {
219-
const strategyRequest = GetContractABIStrategy({
241+
const strategyRequest = new GetContractABIStrategy({
220242
address,
221243
chainID,
222244
event,
@@ -271,7 +293,7 @@ export const getAndCacheAbi = (params: AbiParams) =>
271293
return yield* Effect.fail(new EmptyCalldataError(params))
272294
}
273295

274-
return yield* Effect.request(AbiLoader(params), AbiLoaderRequestResolver)
296+
return yield* Effect.request(new AbiLoader(params), AbiLoaderRequestResolver)
275297
}).pipe(
276298
Effect.withSpan('AbiLoader.GetAndCacheAbi', {
277299
attributes: {

packages/transaction-decoder/src/abi-strategy/request-model.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Request, RequestResolver } from 'effect'
1+
import { PrimaryKey, RequestResolver, Schema, SchemaAST } from 'effect'
22

33
export interface FetchABIParams {
44
readonly chainID: number
@@ -41,16 +41,23 @@ interface AddressABI {
4141

4242
export type ContractABI = FunctionFragmentABI | EventFragmentABI | AddressABI
4343

44-
// NOTE: We might want to return a list of ABIs, this might be helpful when fetching for signature
45-
export interface GetContractABIStrategy
46-
extends Request.Request<ContractABI[], ResolveStrategyABIError>,
47-
FetchABIParams {
48-
readonly _tag: 'GetContractABIStrategy'
49-
}
50-
51-
export const GetContractABIStrategy = Request.tagged<GetContractABIStrategy>('GetContractABIStrategy')
52-
5344
export interface ContractAbiResolverStrategy {
5445
type: 'address' | 'fragment'
5546
resolver: RequestResolver.RequestResolver<GetContractABIStrategy, never>
5647
}
48+
49+
class SchemaContractAbi extends Schema.make<ContractABI>(SchemaAST.objectKeyword) {}
50+
export class GetContractABIStrategy extends Schema.TaggedRequest<GetContractABIStrategy>()('GetContractABIStrategy', {
51+
failure: Schema.instanceOf(ResolveStrategyABIError),
52+
success: Schema.Array(SchemaContractAbi),
53+
payload: {
54+
chainID: Schema.Number,
55+
address: Schema.String,
56+
event: Schema.optional(Schema.String),
57+
signature: Schema.optional(Schema.String),
58+
},
59+
}) {
60+
[PrimaryKey.symbol]() {
61+
return `abi-strategy::${this.chainID}:${this.address}:${this.event}:${this.signature}`
62+
}
63+
}

packages/transaction-decoder/src/contract-meta-loader.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Context, Effect, RequestResolver, Request, Array, Either, pipe } from 'effect'
1+
import { Context, Effect, RequestResolver, Request, Array, Either, pipe, Schema, PrimaryKey, SchemaAST } from 'effect'
22
import { ContractData } from './types.js'
33
import { GetContractMetaStrategy } from './meta-strategy/request-model.js'
44
import { Address } from 'viem'
@@ -50,14 +50,22 @@ export interface ContractMetaStore<Key = ContractMetaParams, Value = ContractMet
5050

5151
export const ContractMetaStore = Context.GenericTag<ContractMetaStore>('@3loop-decoder/ContractMetaStore')
5252

53-
export interface ContractMetaLoader extends Request.Request<ContractData | null, never> {
54-
_tag: 'ContractMetaLoader'
55-
address: Address
56-
chainID: number
53+
class SchemaContractData extends Schema.make<ContractData>(SchemaAST.objectKeyword) {}
54+
class SchemaAddress extends Schema.make<Address>(SchemaAST.stringKeyword) {}
55+
56+
class ContractMetaLoader extends Schema.TaggedRequest<ContractMetaLoader>()('ContractMetaLoader', {
57+
failure: Schema.Never,
58+
success: Schema.NullOr(SchemaContractData),
59+
payload: {
60+
address: SchemaAddress,
61+
chainID: Schema.Number,
62+
},
63+
}) {
64+
[PrimaryKey.symbol]() {
65+
return `contract-meta::${this.chainID}:${this.address}`
66+
}
5767
}
5868

59-
const ContractMetaLoader = Request.tagged<ContractMetaLoader>('ContractMetaLoader')
60-
6169
function makeKey(key: ContractMetaLoader) {
6270
return `contract-meta::${key.chainID}:${key.address}`
6371
}
@@ -148,7 +156,7 @@ const ContractMetaLoaderRequestResolver = RequestResolver.makeBatched((requests:
148156

149157
// Fetch ContractMeta from the strategies
150158
const strategyResults = yield* Effect.forEach(remaining, ({ chainID, address }) => {
151-
const strategyRequest = GetContractMetaStrategy({
159+
const strategyRequest = new GetContractMetaStrategy({
152160
address,
153161
chainID,
154162
})
@@ -186,7 +194,7 @@ export const getAndCacheContractMeta = ({
186194
readonly address: Address
187195
}) => {
188196
return Effect.withSpan(
189-
Effect.request(ContractMetaLoader({ chainID, address }), ContractMetaLoaderRequestResolver),
197+
Effect.request(new ContractMetaLoader({ chainID, address }), ContractMetaLoaderRequestResolver),
190198
'GetAndCacheContractMeta',
191199
{ attributes: { chainID, address } },
192200
)

packages/transaction-decoder/src/meta-strategy/request-model.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { UnknownNetwork } from '../public-client.js'
22
import { ContractData } from '../types.js'
3-
import { Request } from 'effect'
3+
import { PrimaryKey, Schema, SchemaAST } from 'effect'
44
import { Address } from 'viem'
55

66
export interface FetchMetaParams {
@@ -17,11 +17,20 @@ export class ResolveStrategyMetaError {
1717
) {}
1818
}
1919

20-
// TODO: Remove UnknownNetwork
21-
export interface GetContractMetaStrategy
22-
extends Request.Request<ContractData, ResolveStrategyMetaError | UnknownNetwork>,
23-
FetchMetaParams {
24-
readonly _tag: 'GetContractMetaStrategy'
20+
class SchemaAddress extends Schema.make<Address>(SchemaAST.stringKeyword) {}
21+
class SchemaContractData extends Schema.make<ContractData>(SchemaAST.objectKeyword) {}
22+
export class GetContractMetaStrategy extends Schema.TaggedRequest<GetContractMetaStrategy>()(
23+
'GetContractMetaStrategy',
24+
{
25+
failure: Schema.Union(Schema.instanceOf(ResolveStrategyMetaError), Schema.instanceOf(UnknownNetwork)),
26+
success: SchemaContractData,
27+
payload: {
28+
chainID: Schema.Number,
29+
address: SchemaAddress,
30+
},
31+
},
32+
) {
33+
[PrimaryKey.symbol]() {
34+
return `contract-meta-strategy::${this.chainID}:${this.address}`
35+
}
2536
}
26-
27-
export const GetContractMetaStrategy = Request.tagged<GetContractMetaStrategy>('GetContractMetaStrategy')

packages/transaction-decoder/src/sql/abi-store.ts

Lines changed: 60 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ export const make = (strategies: AbiStore['strategies']) =>
88
Effect.gen(function* () {
99
const sql = yield* SqlClient.SqlClient
1010

11+
const table = sql('loop_decoder_contract_abi__')
12+
13+
// TODO; add timestamp to the table
1114
yield* sql`
12-
CREATE TABLE IF NOT EXISTS contractAbi (
15+
CREATE TABLE IF NOT EXISTS ${table} (
1316
type TEXT NOT NULL,
1417
address TEXT,
1518
event TEXT,
@@ -18,7 +21,10 @@ export const make = (strategies: AbiStore['strategies']) =>
1821
abi TEXT,
1922
status TEXT NOT NULL
2023
)
21-
`.pipe(Effect.catchAll(() => Effect.dieMessage('Failed to create contractAbi table')))
24+
`.pipe(
25+
Effect.tapError(Effect.logError),
26+
Effect.catchAll(() => Effect.dieMessage('Failed to create contractAbi table')),
27+
)
2228

2329
return AbiStore.of({
2430
strategies,
@@ -28,33 +34,70 @@ export const make = (strategies: AbiStore['strategies']) =>
2834
if (value.status === 'success' && value.result.type === 'address') {
2935
const result = value.result
3036
yield* sql`
31-
INSERT INTO contractAbi (type, address, chain, abi, status)
32-
VALUES (${result.type}, ${normalizedAddress}, ${result.chainID}, ${result.abi}, "success")
37+
INSERT INTO ${table}
38+
${sql.insert([
39+
{
40+
type: result.type,
41+
address: normalizedAddress,
42+
chain: key.chainID,
43+
abi: result.abi,
44+
status: 'success',
45+
},
46+
])}
3347
`
3448
} else if (value.status === 'success') {
3549
const result = value.result
3650
yield* sql`
37-
INSERT INTO contractAbi (type, event, signature, abi, status)
38-
VALUES (${result.type}, ${'event' in result ? result.event : null}, ${
39-
'signature' in result ? result.signature : null
40-
}, ${result.abi}, "success")
51+
INSERT INTO ${table}
52+
${sql.insert([
53+
{
54+
type: result.type,
55+
event: 'event' in result ? result.event : null,
56+
signature: 'signature' in result ? result.signature : null,
57+
abi: result.abi,
58+
status: 'success',
59+
},
60+
])}
4161
`
4262
} else {
4363
yield* sql`
44-
INSERT INTO contractAbi (type, address, chain, status)
45-
VALUES ("address", ${normalizedAddress}, ${key.chainID}, "not-found")
64+
INSERT INTO ${table}
65+
${sql.insert([
66+
{
67+
type: 'address',
68+
address: normalizedAddress,
69+
chain: key.chainID,
70+
status: 'not-found',
71+
},
72+
])}
4673
`
4774
}
48-
}).pipe(Effect.catchAll(() => Effect.succeed(null))),
75+
}).pipe(
76+
Effect.tapError(Effect.logError),
77+
Effect.catchAll(() => {
78+
return Effect.succeed(null)
79+
}),
80+
),
4981

5082
get: ({ address, signature, event, chainID }) =>
5183
Effect.gen(function* () {
52-
const items = yield* sql`
53-
SELECT * FROM contractAbi
54-
WHERE (address = ${address.toLowerCase()} AND chain = ${chainID} AND type = "address")
55-
${signature ? `OR (signature = ${signature} AND type = "func")` : ''}
56-
${event ? `OR (event = ${event} AND type = "event")` : ''}
57-
`.pipe(Effect.catchAll(() => Effect.succeed([])))
84+
const addressQuery = sql.and([
85+
sql`address = ${address.toLowerCase()}`,
86+
sql`chain = ${chainID}`,
87+
sql`type = 'address'`,
88+
])
89+
90+
const signatureQuery = signature ? sql.and([sql`signature = ${signature}`, sql`type = 'func'`]) : undefined
91+
const eventQuery = event ? sql.and([sql`event = ${event}`, sql`type = 'event'`]) : undefined
92+
const query =
93+
signature == null && event == null
94+
? addressQuery
95+
: sql.or([addressQuery, signatureQuery, eventQuery].filter(Boolean))
96+
97+
const items = yield* sql` SELECT * FROM ${table} WHERE ${query}`.pipe(
98+
Effect.tapError(Effect.logError),
99+
Effect.catchAll(() => Effect.succeed([])),
100+
)
58101

59102
const item =
60103
items.find((item) => {

0 commit comments

Comments
 (0)