Skip to content

Commit fa7fe73

Browse files
committed
fix: Beacon proxy resolution
1 parent 567ac95 commit fa7fe73

File tree

1 file changed

+69
-28
lines changed

1 file changed

+69
-28
lines changed

src/utils/proxy.ts

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { padHex, zeroAddress, zeroHash } from 'viem';
22
import type { Address, Hex, PublicClient } from 'viem';
3+
import { getStorageAt, multicall } from 'viem/actions';
34

5+
import eip897ProxyAbi from '@/abi/eip897Proxy.js';
46
import safeProxyAbi from '@/abi/safeProxy.js';
57

68
// Storage slots for common proxy implementations
@@ -40,17 +42,6 @@ const slotMap: Record<string, Hex> = {
4042
// DIAMOND_STORAGE: '0xc8fcad8db84d3cc18b4c41d551ea0ee66dd599cde068d998e57d5e09332c131b',
4143
};
4244

43-
async function getStorage(
44-
client: PublicClient,
45-
address: Address,
46-
slots: Hex[],
47-
): Promise<Hex[]> {
48-
const results = await Promise.all(
49-
slots.map((slot) => client.getStorageAt({ address, slot })),
50-
);
51-
return results.filter((result) => !!result);
52-
}
53-
5445
function toAddress(slotValue: Hex | null): Address | null {
5546
if (!slotValue) {
5647
return null;
@@ -66,40 +57,90 @@ function toAddress(slotValue: Hex | null): Address | null {
6657
return address;
6758
}
6859

69-
// Attempts to get the proxy implementation address for a given address
70-
// Note that this may not succeed even if the provided address is a proxy
71-
async function getImplementation(
60+
async function getByImplementationCall(
7261
client: PublicClient,
7362
address: Address,
7463
): Promise<Address | null> {
75-
// Call-based detection (Safe-like proxies)
76-
const callResults = await client.multicall({
64+
const results = await multicall(client, {
65+
contracts: [
66+
{
67+
address,
68+
abi: eip897ProxyAbi,
69+
functionName: 'implementation',
70+
},
71+
],
72+
});
73+
const result = results[0];
74+
if (result.status === 'failure') {
75+
return null;
76+
}
77+
const implementation = result.result.toLowerCase() as Address;
78+
if (implementation === zeroAddress) {
79+
return null;
80+
}
81+
return implementation;
82+
}
83+
84+
async function getByMasterCopyCall(
85+
client: PublicClient,
86+
address: Address,
87+
): Promise<Address | null> {
88+
const results = await multicall(client, {
7789
contracts: [
7890
{
91+
address,
7992
abi: safeProxyAbi,
80-
address: address,
8193
functionName: 'masterCopy',
82-
args: [],
8394
},
8495
],
8596
});
86-
const masterCopyResult = callResults[0];
87-
if (masterCopyResult.status === 'success') {
88-
const address = masterCopyResult.result.toLowerCase() as Address;
89-
if (address !== zeroAddress) {
90-
return address;
91-
}
97+
const result = results[0];
98+
if (result.status === 'failure') {
99+
return null;
100+
}
101+
const implementation = result.result.toLowerCase() as Address;
102+
if (implementation === zeroAddress) {
103+
return null;
104+
}
105+
return implementation;
106+
}
107+
108+
// Attempts to get the proxy implementation address for a given address
109+
// Note that this may not succeed even if the provided address is a proxy
110+
async function getImplementation(
111+
client: PublicClient,
112+
address: Address,
113+
): Promise<Address | null> {
114+
// Call-based detection (Safe-like proxies)
115+
const masterCopy = await getByMasterCopyCall(client, address);
116+
if (masterCopy) {
117+
return masterCopy;
92118
}
93119
// Slot-based detection
94120
const slots = Object.values(slotMap);
95121
const addressSlot = padHex(address);
96122
slots.push(addressSlot);
97-
const slotValues = await getStorage(client, address, slots);
98-
for (const slot of slotValues) {
99-
const slotAddress = toAddress(slot);
100-
if (slotAddress) {
123+
const slotValues = await Promise.all(
124+
slots.map((slot) => getStorageAt(client, { address, slot })),
125+
);
126+
for (const slot of slots) {
127+
const slotIndex = slots.indexOf(slot);
128+
const slotValue = slotValues[slotIndex];
129+
if (!slotValue) {
130+
continue;
131+
}
132+
const slotAddress = toAddress(slotValue);
133+
if (!slotAddress) {
134+
continue;
135+
}
136+
if (slot !== slotMap['EIP1967_BEACON']) {
101137
return slotAddress;
102138
}
139+
// Beacon proxies require a 2-step resolution
140+
const implementation = await getByImplementationCall(client, slotAddress);
141+
if (implementation) {
142+
return implementation;
143+
}
103144
}
104145
// Bytecode-based detection
105146
// Credit to @banteg for the original list

0 commit comments

Comments
 (0)