Skip to content

Commit 390e009

Browse files
committed
feat: detect and cache 7702 delegations
1 parent 555c687 commit 390e009

File tree

2 files changed

+140
-19
lines changed

2 files changed

+140
-19
lines changed

src/routes/contract/controller.ts

Lines changed: 139 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
PublicClient,
1212
createPublicClient,
1313
http,
14+
size,
15+
slice,
1416
toEventSelector,
1517
toFunctionSelector,
1618
} from 'viem';
@@ -33,6 +35,8 @@ const DAY = 1000 * 60 * 60 * 24;
3335
const NO_SOURCE_CACHE_DURATION = 7 * DAY;
3436
const IMPLEMENTATION_CACHE_DURATION = DAY;
3537
const NO_IMPLEMENTATION_CACHE_DURATION = 30 * DAY;
38+
const DELEGATION_CACHE_DURATION = DAY;
39+
const NO_DELEGATION_CACHE_DURATION = DAY;
3640
const NO_DEPLOYMENT_CACHE_DURATION = 30 * DAY;
3741

3842
interface SourceCodeResponse {
@@ -43,6 +47,11 @@ interface SourceCodeResponse {
4347
abi: Abi | null;
4448
source: SourceCode | null;
4549
} | null;
50+
delegation: {
51+
address: Address;
52+
abi: Abi | null;
53+
source: SourceCode | null;
54+
} | null;
4655
}
4756

4857
interface DeploymentResponse {
@@ -122,7 +131,41 @@ async function getSource(
122131
if (!contract.value) {
123132
return null;
124133
}
125-
// Fetch impl address if there's no contract or if there is a contract but there is no implementation cached (unless we did that already recently)
134+
const implementation = await fetchImplementation(
135+
chain,
136+
client,
137+
contract,
138+
address,
139+
);
140+
const delegation = await fetchDelegation(chain, client, contract, address);
141+
return {
142+
abi: contract.value.abi,
143+
source: contract.value.source,
144+
implementation,
145+
delegation,
146+
};
147+
}
148+
149+
// Fetch implementation if there's no contract or if there is a contract but there is no implementation cached (unless we did that already recently)
150+
async function fetchImplementation(
151+
chain: ChainId,
152+
client: PublicClient,
153+
contract: OptionalContractSourceCache,
154+
address: Address,
155+
): Promise<{
156+
address: Address;
157+
abi: Abi | null;
158+
source: SourceCode | null;
159+
} | null> {
160+
const minioService = new MinioService(
161+
minioPublicEndpoint,
162+
minioAccessKey,
163+
minioSecretKey,
164+
minioBucket,
165+
);
166+
if (!contract.value) {
167+
return null;
168+
}
126169
const useCachedImplementation =
127170
contract.timestamp === null
128171
? contract.value.implementation !== null
@@ -150,17 +193,84 @@ async function getSource(
150193
implementationContract && implementationContract.value
151194
? implementationContract.value.source
152195
: null;
153-
return {
154-
abi: contract.value.abi,
155-
source: contract.value.source,
156-
implementation: implementation
157-
? {
158-
address: implementation,
159-
abi: implementationAbi,
160-
source: implementationSource,
161-
}
162-
: null,
163-
};
196+
return implementation
197+
? {
198+
address: implementation,
199+
abi: implementationAbi,
200+
source: implementationSource,
201+
}
202+
: null;
203+
}
204+
205+
// Fetch delegation if there's no contract or if there is a contract but there is no delegation cached (unless we did that already recently)
206+
async function fetchDelegation(
207+
chain: ChainId,
208+
client: PublicClient,
209+
contract: OptionalContractSourceCache,
210+
address: Address,
211+
): Promise<{
212+
address: Address;
213+
abi: Abi | null;
214+
source: SourceCode | null;
215+
} | null> {
216+
async function getDelegation(
217+
client: PublicClient,
218+
address: Address,
219+
): Promise<Address | null> {
220+
const code = await client.getCode({
221+
address,
222+
});
223+
if (code === undefined) {
224+
return null;
225+
}
226+
if (code.length === 0) {
227+
return null;
228+
}
229+
if (size(code) !== 23) {
230+
return null;
231+
}
232+
return slice(code, 3);
233+
}
234+
235+
const minioService = new MinioService(
236+
minioPublicEndpoint,
237+
minioAccessKey,
238+
minioSecretKey,
239+
minioBucket,
240+
);
241+
if (!contract.value) {
242+
return null;
243+
}
244+
const useCachedDelegation =
245+
contract.timestamp === null
246+
? contract.value.delegation !== null
247+
: contract.value.delegation === null
248+
? contract.timestamp > Date.now() - NO_DELEGATION_CACHE_DURATION
249+
: contract.timestamp > Date.now() - DELEGATION_CACHE_DURATION;
250+
const delegation = useCachedDelegation
251+
? contract.value.delegation
252+
: await getDelegation(client, address);
253+
// Store the delegation in the cache
254+
if (!useCachedDelegation) {
255+
await minioService.setSource(chain, address, {
256+
...contract.value,
257+
delegation,
258+
});
259+
}
260+
const delegationContract = delegation
261+
? await fetchContract(minioService, chain, delegation)
262+
: null;
263+
const delegationAbi =
264+
delegationContract && delegationContract.value
265+
? delegationContract.value.abi
266+
: null;
267+
const delegationSource =
268+
delegationContract && delegationContract.value
269+
? delegationContract.value.source
270+
: null;
271+
return delegation
272+
? { address: delegation, abi: delegationAbi, source: delegationSource }
273+
: null;
164274
}
165275

166276
async function getAbi(
@@ -335,16 +445,25 @@ async function extractContractAbi(
335445
return null;
336446
}
337447
const implementation = contract.implementation;
338-
if (!implementation) {
339-
if (contract.abi !== null) {
340-
return contract.abi;
448+
if (implementation) {
449+
if (implementation.abi !== null) {
450+
return implementation.abi;
341451
}
342-
return guessAbi(chain, address);
452+
return guessAbi(chain, implementation.address);
343453
}
344-
if (implementation.abi !== null) {
345-
return implementation.abi;
454+
455+
const delegation = contract.delegation;
456+
if (delegation) {
457+
if (delegation.abi !== null) {
458+
return delegation.abi;
459+
}
460+
return guessAbi(chain, delegation.address);
461+
}
462+
463+
if (contract.abi !== null) {
464+
return contract.abi;
346465
}
347-
return guessAbi(chain, implementation.address);
466+
return guessAbi(chain, address);
348467
}
349468

350469
async function fetchContract(
@@ -379,6 +498,7 @@ async function fetchContract(
379498
// Don't use the implementation address from etherscan
380499
// Too many false positives
381500
implementation: null,
501+
delegation: null,
382502
};
383503
await minioService.setSource(chain, address, contract);
384504
return {

src/services/minio.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface ContractSource {
1010
source: SourceCode | null;
1111
abi: Abi | null;
1212
implementation: Address | null;
13+
delegation: Address | null;
1314
}
1415

1516
interface ContractSourceCache {

0 commit comments

Comments
 (0)