-
Notifications
You must be signed in to change notification settings - Fork 544
Expand file tree
/
Copy pathproxy-contract-util.ts
More file actions
141 lines (126 loc) · 4.66 KB
/
Copy pathproxy-contract-util.ts
File metadata and controls
141 lines (126 loc) · 4.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { whatsabi } from "@shazow/whatsabi";
import type { SourcifyChain } from "@ethereum-sourcify/lib-sourcify";
import { AbiCoder } from "ethers";
export type ProxyType =
| "EIP1167Proxy"
| "FixedProxy"
| "EIP1967Proxy"
| "GnosisSafeProxy"
| "DiamondProxy"
| "PROXIABLEProxy"
| "ZeppelinOSProxy"
| "MaticProxy"
| "SequenceWalletProxy"
| "LivepeerManagerProxy";
export type Implementation = { address: string; name?: string };
export interface ProxyDetectionResult {
isProxy: boolean;
proxyType: ProxyType | null;
implementations: Implementation[];
}
export async function detectAndResolveProxy(
bytecode: string,
address: string,
sourcifyChain: SourcifyChain,
): Promise<ProxyDetectionResult> {
// Pass our bytecode to whatsabi so it does not need to fetch it from an rpc
const codeCache = {
[address]: bytecode,
};
const cachedCodeProvider = whatsabi.providers.WithCachedCode(
// This object mocks a provider as it is not needed for detection only
{
request: () => {},
},
codeCache,
);
// Detect proxies but skip other functionalities of whatsabi
const detectionResult = await whatsabi.autoload(address, {
provider: cachedCodeProvider,
abiLoader: false,
signatureLookup: false,
followProxies: false,
});
// Ignore contracts that have CREATE opcodes as they could falsely be detected as proxies.
if (detectionResult.isFactory) {
return { isProxy: false, proxyType: null, implementations: [] };
}
const proxyResolvers = detectionResult.proxies;
// In the following, we check the returned proxy resolvers
// and resolve the implementation address for the first valid proxy resolver.
// We first handle FixedProxies and DiamondProxies, as their implementations are resolved differently.
// Most of the assumptions here are based on the proxy detection experiments in:
// https://github.com/sourcifyeth/data-analysis-scripts
const fixedProxy = proxyResolvers.find(
(proxy) => proxy instanceof whatsabi.proxies.FixedProxyResolver,
);
// Only return EIP1167Proxies because whatsabi can falsely detect non-proxy contracts as FixedProxies (e.g. libraries)
if (fixedProxy && isEIP1167Proxy(bytecode, fixedProxy.resolvedAddress)) {
return {
isProxy: true,
proxyType: "EIP1167Proxy",
implementations: [{ address: fixedProxy.resolvedAddress }],
};
}
if (
proxyResolvers.some(
(proxy) => proxy instanceof whatsabi.proxies.DiamondProxyResolver,
)
) {
try {
// Call facetAddresses()
const encodedFacets = await sourcifyChain.call({
to: address,
data: "0x52ef6b2c",
});
const facets = AbiCoder.defaultAbiCoder().decode(
["address[]"],
encodedFacets,
)[0];
return {
isProxy: true,
proxyType: "DiamondProxy",
implementations: facets.map((facet: string) => ({ address: facet })),
};
} catch (error) {
// Falsely detected as a diamond proxy,
// ignore and check if there are other proxy resolvers
}
}
// In the following we check all remaining proxy types.
// As whatsabi might return multiple instances of the same proxy resolver,
// we keep track of the already checked proxy types to avoid unnecessary rpc calls.
const checkedProxyTypes: Set<ProxyType> = new Set([
"FixedProxy",
"DiamondProxy",
]);
for (const proxy of proxyResolvers) {
if (checkedProxyTypes.has(proxy.name as ProxyType)) {
continue;
}
// Tries to resolve the implementation address by querying a specific storage slot from the rpc.
// The storage slot depends on the proxy type.
const resolvedAddress = await proxy.resolve(sourcifyChain, address);
if (resolvedAddress !== "0x0000000000000000000000000000000000000000") {
return {
isProxy: true,
proxyType: proxy.name as ProxyType,
implementations: [{ address: resolvedAddress }],
};
}
checkedProxyTypes.add(proxy.name as ProxyType);
}
return { isProxy: false, proxyType: null, implementations: [] };
}
function isEIP1167Proxy(bytecode: string, resolvedAddress: string): boolean {
// The EIP-1167 minimal proxy stub sits at the *start* of the runtime bytecode.
// We must anchor the match at the beginning (not the end): "clones with immutable
// args" (e.g. OpenZeppelin `Clones.cloneWithImmutableArgs`) append per-clone
// argument bytes *after* the stub, so those clones do not end with the stub.
const normalizedBytecode = (
bytecode.startsWith("0x") ? bytecode.slice(2) : bytecode
).toLowerCase();
return normalizedBytecode.startsWith(
`363d3d373d3d3d363d73${resolvedAddress.slice(2).toLowerCase()}5af43d82803e903d91602b57fd5bf3`,
);
}