@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
107 lines • 4.98 kB
JavaScript
import { ethers } from 'ethers';
import { ProxyAdmin__factory } from '@hyperlane-xyz/core';
import { eqAddress } from '@hyperlane-xyz/utils';
import { transferOwnershipTransactions } from '../contracts/contracts.js';
/**
* Checks if a storage value represents empty/uninitialized storage.
* Some RPC providers (e.g., Somnia) return empty hex strings ('0x' or '')
* instead of the standard '0x0' for uninitialized storage slots.
* @param rawValue - The raw storage value from provider.getStorageAt()
* @returns true if the storage slot is empty/uninitialized
*/
export function isStorageEmpty(rawValue) {
return rawValue === '0x' || rawValue === '' || rawValue === '0x0';
}
async function assertCodeExists(provider, contract) {
const code = await provider.getCode(contract);
if (code === '0x') {
throw new Error(`Contract at ${contract} has no code`);
}
}
export async function proxyImplementation(provider, proxy) {
await assertCodeExists(provider, proxy);
// Hardcoded storage slot for implementation per EIP-1967
const storageValue = await provider.getStorageAt(proxy, '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc');
if (isStorageEmpty(storageValue)) {
return ethers.constants.AddressZero;
}
return ethers.utils.getAddress(storageValue.slice(26));
}
export async function isInitialized(provider, contract) {
await assertCodeExists(provider, contract);
// Using OZ's Initializable 4.9 which keeps it at the 0x0 slot
const storageValue = await provider.getStorageAt(contract, '0x0');
if (isStorageEmpty(storageValue)) {
return false;
}
const value = ethers.BigNumber.from(storageValue);
return value.eq(1) || value.eq(255);
}
export async function proxyAdmin(provider, proxy) {
await assertCodeExists(provider, proxy);
// Hardcoded storage slot for admin per EIP-1967
const storageValue = await provider.getStorageAt(proxy, '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103');
if (isStorageEmpty(storageValue)) {
return ethers.constants.AddressZero;
}
return ethers.utils.getAddress(storageValue.slice(26));
}
export function proxyConstructorArgs(implementation, proxyAdmin, initializeArgs, initializeFnSignature = 'initialize') {
const initData = initializeArgs
? implementation.interface.encodeFunctionData(initializeFnSignature, initializeArgs)
: '0x';
return [implementation.address, proxyAdmin, initData];
}
export async function isProxy(provider, proxy) {
const admin = await proxyAdmin(provider, proxy);
return !eqAddress(admin, ethers.constants.AddressZero);
}
export function proxyAdminUpdateTxs(chainId, proxyAddress, actualConfig, expectedConfig) {
const transactions = [];
const parsedChainId = typeof chainId === 'string' ? parseInt(chainId) : chainId;
if (actualConfig.proxyAdmin?.address &&
expectedConfig.proxyAdmin?.address &&
actualConfig.proxyAdmin.address !== expectedConfig.proxyAdmin.address) {
transactions.push({
chainId: parsedChainId,
annotation: `Updating ProxyAdmin for proxy at "${proxyAddress}" from "${actualConfig.proxyAdmin.address}" to "${expectedConfig.proxyAdmin.address}"`,
to: actualConfig.proxyAdmin.address,
data: ProxyAdmin__factory.createInterface().encodeFunctionData('changeProxyAdmin(address,address)', [proxyAddress, expectedConfig.proxyAdmin.address]),
});
}
else {
const actualOwnershipConfig = actualConfig.proxyAdmin ?? {
owner: actualConfig.owner,
};
const expectedOwnershipConfig = expectedConfig.proxyAdmin ?? {
owner: expectedConfig.owner,
};
transactions.push(
// Internally the createTransferOwnershipTx method already checks if the
// two owner values are the same and produces an empty tx batch if they are
...transferOwnershipTransactions(parsedChainId, actualOwnershipConfig.address, actualOwnershipConfig, expectedOwnershipConfig));
}
return transactions;
}
const requiredProxyAdminFunctionSelectors = [
'owner()',
'getProxyAdmin(address)',
'getProxyImplementation(address)',
'upgrade(address,address)',
'upgradeAndCall(address,address,bytes)',
'changeProxyAdmin(address,address)',
].map((func) => ethers.utils.id(func).substring(2, 10));
/**
* Check if contract bytecode matches ProxyAdmin patterns
* This is more efficient than function calls but less reliable
* @param provider The provider to use
* @param address The contract address
* @returns true if the bytecode suggests it's a ProxyAdmin
*/
export async function isProxyAdminFromBytecode(provider, address) {
const code = await provider.getCode(address);
if (code === '0x')
return false;
return requiredProxyAdminFunctionSelectors.every((selector) => code.includes(selector));
}
//# sourceMappingURL=proxy.js.map