@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
159 lines • 7.73 kB
JavaScript
import { constants } from 'ethers';
import { Ownable__factory } from '@hyperlane-xyz/core';
import { ProtocolType, assert, eqAddress, hexOrBase58ToHex, objFilter, objMap, pick, rootLogger, } from '@hyperlane-xyz/utils';
export function serializeContractsMap(contractsMap) {
return objMap(contractsMap, (_, contracts) => {
return serializeContracts(contracts);
});
}
export function serializeContracts(contracts) {
return objMap(contracts, (_, contract) => contract.address ? contract.address : serializeContracts(contract));
}
function getFactory(key, factories) {
if (!(key in factories)) {
throw new Error(`Factories entry missing for ${key.toString()}`);
}
return factories[key];
}
export function filterAddressesMap(addressesMap, factories) {
const factoryKeys = Object.keys(factories);
// Filter out addresses that we do not have factories for
const pickedAddressesMap = objMap(addressesMap, (_, addresses) => pick(addresses, factoryKeys));
const chainsWithMissingAddresses = new Set();
const filledAddressesMap = objMap(pickedAddressesMap, (chainName, addresses) => objMap(addresses, (key, value) => {
if (!value) {
rootLogger.warn(`Missing address for contract "${key}" on chain ${chainName}`);
chainsWithMissingAddresses.add(chainName);
return constants.AddressZero;
}
return value;
}));
// Add summary warning if any addresses were missing
if (chainsWithMissingAddresses.size > 0) {
rootLogger.warn(`Warning: Core deployment incomplete for chain(s): ${Array.from(chainsWithMissingAddresses).join(', ')}. ` +
`Please run 'core deploy' again for these chains to fix the deployment.`);
}
// Filter out chains for which we do not have a complete set of addresses
return objFilter(filledAddressesMap, (_, addresses) => {
return Object.keys(addresses).every((a) => factoryKeys.includes(a));
});
}
export function filterChainMapToProtocol(contractsMap, protocolType, metadataManager) {
return objFilter(contractsMap, (c, _addrs) => metadataManager.tryGetChainMetadata(c)?.protocol === protocolType);
}
export function filterChainMapExcludeProtocol(contractsMap, protocolType, metadataManager) {
return objFilter(contractsMap, (c, _addrs) => metadataManager.tryGetChainMetadata(c)?.protocol !== protocolType);
}
export function attachContracts(addresses, factories) {
return objMap(addresses, (key, address) => {
const factory = getFactory(key, factories);
return factory.attach(address);
});
}
export function attachContractsMap(addressesMap, factories) {
const filteredAddressesMap = filterAddressesMap(addressesMap, factories);
return objMap(filteredAddressesMap, (_, addresses) => attachContracts(addresses, factories));
}
export function attachContractsMapAndGetForeignDeployments(addressesMap, factories, metadataManager) {
const contractsMap = attachContractsMap(filterChainMapToProtocol(addressesMap, ProtocolType.Ethereum, metadataManager), factories);
// TODO: This function shouldn't need to be aware of application types like collateral / synthetic / native etc. Ideally this should work for any app, not just warp routes. is it safe to assume this is always an object containing 1 key/value pair, and that the value will always be an address?
const foreignDeployments = objMap(filterChainMapExcludeProtocol(addressesMap, ProtocolType.Ethereum, metadataManager), (chain, addresses) => {
const router = addresses.router ||
addresses.collateral ||
addresses.synthetic ||
addresses.native;
const protocolType = metadataManager.tryGetChainMetadata(chain)?.protocol;
if (!router || typeof router !== 'string') {
throw new Error(`Router address not found for ${chain}`);
}
if (!protocolType) {
throw new Error(`Protocol type not found for ${chain}`);
}
switch (protocolType) {
case ProtocolType.Ethereum:
throw new Error('Ethereum chain should not have foreign deployments');
case ProtocolType.Cosmos:
case ProtocolType.CosmosNative:
case ProtocolType.Starknet:
return router;
case ProtocolType.Sealevel:
return hexOrBase58ToHex(router);
default:
throw new Error(`Unsupported protocol type: ${protocolType}`);
}
});
return {
contractsMap,
foreignDeployments,
};
}
export function attachAndConnectContracts(addresses, factories, connection) {
const contracts = attachContracts(addresses, factories);
return connectContracts(contracts, connection);
}
export function connectContracts(contracts, connection) {
const connectedContracts = objMap(contracts, (_, contract) => {
if (!contract.connect) {
return undefined;
}
return contract.connect(connection);
});
return Object.fromEntries(Object.entries(connectedContracts).filter(([_, contract]) => !!contract));
}
export function connectContractsMap(contractsMap, multiProvider) {
return objMap(contractsMap, (chain, contracts) => connectContracts(contracts, multiProvider.getSignerOrProvider(chain)));
}
// NOTE: does not perform any onchain checks
export function filterOwnableContracts(contracts) {
return objFilter(contracts, (_, contract) => 'owner' in contract.functions);
}
export function appFromAddressesMapHelper(addressesMap, factories, multiProvider) {
// Filter out non-Ethereum chains from the addressesMap
const ethereumAddressesMap = objFilter(addressesMap, (chain, _) => multiProvider.getProtocol(chain) === ProtocolType.Ethereum);
// Attaches contracts for each Ethereum chain for which we have a complete set of addresses
const contractsMap = attachContractsMap(ethereumAddressesMap, factories);
// Filters out providers for chains for which we don't have a complete set
// of addresses
const intersection = multiProvider.intersect(Object.keys(contractsMap));
// Filters out contracts for chains for which we don't have a provider
const filteredContractsMap = pick(contractsMap, intersection.intersection);
return {
contractsMap: filteredContractsMap,
multiProvider,
};
}
export function transferOwnershipTransactions(chainId, contract, actual, expected, label) {
if (eqAddress(actual.owner, expected.owner)) {
return [];
}
return [
{
chainId,
annotation: `Transferring ownership of ${label ?? contract} from ${actual.owner} to ${expected.owner}`,
to: contract,
data: Ownable__factory.createInterface().encodeFunctionData('transferOwnership', [expected.owner]),
},
];
}
export async function isAddressActive(provider, address) {
const [code, txnCount] = await Promise.all([
provider.getCode(address),
provider.getTransactionCount(address),
]);
return code !== '0x' || txnCount > 0;
}
/**
* Checks if the provided address is a contract
*/
export async function isContractAddress(multiProvider, chain, address, blockNumber) {
const provider = multiProvider.getProvider(chain);
const code = await provider.getCode(address, blockNumber);
return code !== '0x';
}
/**
* Checks if the provided address is a contract and throws if it isn't
*/
export async function assertIsContractAddress(multiProvider, chain, address) {
assert(await isContractAddress(multiProvider, chain, address), `Address "${address}" on chain "${chain}" is not a contract`);
}
//# sourceMappingURL=contracts.js.map