UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

107 lines 4.98 kB
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