UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

332 lines 16.1 kB
import { constants } from 'ethers'; import { ERC20__factory, ERC721Enumerable__factory, IERC4626__factory, IMessageTransmitter__factory, IXERC20Lockbox__factory, MovableCollateralRouter__factory, OpL1V1NativeTokenBridge__factory, OpL2NativeTokenBridge__factory, TokenBridgeCctp__factory, } from '@hyperlane-xyz/core'; import { ProtocolType, assert, objFilter, objKeys, objMap, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils'; import { GasRouterDeployer } from '../router/GasRouterDeployer.js'; import { resolveRouterMapConfig } from '../router/types.js'; import { TokenMetadataMap } from './TokenMetadataMap.js'; import { TokenType, gasOverhead } from './config.js'; import { hypERC20contracts, hypERC20factories, hypERC721contracts, hypERC721factories, } from './contracts.js'; import { TokenMetadataSchema, isCctpTokenConfig, isCollateralTokenConfig, isMovableCollateralTokenConfig, isNativeTokenConfig, isOpL1TokenConfig, isOpL2TokenConfig, isSyntheticRebaseTokenConfig, isSyntheticTokenConfig, isTokenMetadata, isXERC20TokenConfig, } from './types.js'; // initialize(address _hook, address _owner) const OP_L2_INITIALIZE_SIGNATURE = 'initialize(address,address)'; // initialize(address _owner, string[] memory _urls) const OP_L1_INITIALIZE_SIGNATURE = 'initialize(address,string[])'; // initialize(address _hook, address _owner, string[] memory __urls) const CCTP_INITIALIZE_SIGNATURE = 'initialize(address,address,string[])'; export const TOKEN_INITIALIZE_SIGNATURE = (contractName) => { switch (contractName) { case 'OPL2TokenBridgeNative': assert(OpL2NativeTokenBridge__factory.createInterface().functions[OP_L2_INITIALIZE_SIGNATURE], 'missing expected initialize function'); return OP_L2_INITIALIZE_SIGNATURE; case 'OpL1TokenBridgeNative': assert(OpL1V1NativeTokenBridge__factory.createInterface().functions[OP_L1_INITIALIZE_SIGNATURE], 'missing expected initialize function'); return OP_L1_INITIALIZE_SIGNATURE; case 'TokenBridgeCctp': assert(TokenBridgeCctp__factory.createInterface().functions[CCTP_INITIALIZE_SIGNATURE], 'missing expected initialize function'); return CCTP_INITIALIZE_SIGNATURE; default: return 'initialize'; } }; class TokenDeployer extends GasRouterDeployer { constructor(multiProvider, factories, loggerName, ismFactory, contractVerifier, concurrentDeploy = true) { super(multiProvider, factories, { logger: rootLogger.child({ module: loggerName }), ismFactory, contractVerifier, concurrentDeploy, }); // factories not used in deploy } async constructorArgs(_, config) { // TODO: derive as specified in https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/5296 const scale = config.scale ?? 1; if (isCollateralTokenConfig(config) || isXERC20TokenConfig(config)) { return [config.token, scale, config.mailbox]; } else if (isNativeTokenConfig(config)) { return [scale, config.mailbox]; } else if (isOpL2TokenConfig(config)) { return [config.mailbox, config.l2Bridge]; } else if (isOpL1TokenConfig(config)) { return [config.mailbox, config.portal]; } else if (isSyntheticTokenConfig(config)) { assert(config.decimals, 'decimals is undefined for config'); // decimals must be defined by this point return [config.decimals, scale, config.mailbox]; } else if (isSyntheticRebaseTokenConfig(config)) { const collateralDomain = this.multiProvider.getDomainId(config.collateralChainName); return [config.decimals, scale, config.mailbox, collateralDomain]; } else if (isCctpTokenConfig(config)) { return [ config.token, scale, config.mailbox, config.messageTransmitter, config.tokenMessenger, ]; } else { throw new Error('Unknown token type when constructing arguments'); } } initializeFnSignature(name) { return TOKEN_INITIALIZE_SIGNATURE(name); } async initializeArgs(chain, config) { const signer = await this.multiProvider.getSigner(chain).getAddress(); const defaultArgs = [ config.hook ?? constants.AddressZero, config.interchainSecurityModule ?? constants.AddressZero, // TransferOwnership will happen later in RouterDeployer signer, ]; if (isCollateralTokenConfig(config) || isXERC20TokenConfig(config) || isNativeTokenConfig(config)) { return defaultArgs; } else if (isOpL2TokenConfig(config)) { return [config.hook ?? constants.AddressZero, config.owner]; } else if (isOpL1TokenConfig(config)) { return [config.owner, config.urls]; } else if (isCctpTokenConfig(config)) { return [config.hook ?? constants.AddressZero, config.owner, config.urls]; } else if (isSyntheticTokenConfig(config)) { return [ config.initialSupply ?? 0, config.name, config.symbol, ...defaultArgs, ]; } else if (isSyntheticRebaseTokenConfig(config)) { return [0, config.name, config.symbol, ...defaultArgs]; } else { throw new Error('Unknown collateral type when initializing arguments'); } } static async deriveTokenMetadata(multiProvider, configMap) { const metadataMap = new TokenMetadataMap(); const priorityGetter = (type) => { return ['collateral', 'native'].indexOf(type); }; const sortedEntries = Object.entries(configMap).sort(([, a], [, b]) => priorityGetter(b.type) - priorityGetter(a.type)); for (const [chain, config] of sortedEntries) { if (isTokenMetadata(config)) { metadataMap.set(chain, TokenMetadataSchema.parse(config)); } if (multiProvider.getProtocol(chain) !== ProtocolType.Ethereum) { // If the config didn't specify the token metadata, we can only now // derive it for Ethereum chains. So here we skip non-Ethereum chains. continue; } if (isNativeTokenConfig(config)) { const nativeToken = multiProvider.getChainMetadata(chain).nativeToken; if (nativeToken) { metadataMap.set(chain, TokenMetadataSchema.parse({ ...nativeToken, })); continue; } } if (isCollateralTokenConfig(config) || isXERC20TokenConfig(config) || isCctpTokenConfig(config)) { const provider = multiProvider.getProvider(chain); if (config.isNft) { const erc721 = ERC721Enumerable__factory.connect(config.token, provider); const [name, symbol] = await Promise.all([ erc721.name(), erc721.symbol(), ]); metadataMap.set(chain, TokenMetadataSchema.parse({ name, symbol, })); continue; } let token; switch (config.type) { case TokenType.XERC20Lockbox: token = await IXERC20Lockbox__factory.connect(config.token, provider).callStatic.ERC20(); break; case TokenType.collateralVault: token = await IERC4626__factory.connect(config.token, provider).callStatic.asset(); break; default: token = config.token; break; } const erc20 = ERC20__factory.connect(token, provider); const [name, symbol, decimals] = await Promise.all([ erc20.name(), erc20.symbol(), erc20.decimals(), ]); metadataMap.set(chain, TokenMetadataSchema.parse({ name, symbol, decimals, })); } } metadataMap.finalize(); return metadataMap; } async configureCctpDomains(configMap, deployedContractsMap) { const cctpConfigs = objFilter(configMap, (_, config) => isCctpTokenConfig(config)); const circleDomains = await promiseObjAll(objMap(cctpConfigs, (chain, config) => IMessageTransmitter__factory.connect(config.messageTransmitter, this.multiProvider.getProvider(chain)).localDomain())); const domains = Object.entries(circleDomains).map(([chain, circle]) => ({ hyperlane: this.multiProvider.getDomainId(chain), circle, })); if (domains.length === 0) { return; } await promiseObjAll(objMap(cctpConfigs, async (chain, _config) => { const router = this.router(deployedContractsMap[chain]).address; const tokenBridge = TokenBridgeCctp__factory.connect(router, this.multiProvider.getSigner(chain)); const remoteDomains = domains.filter((domain) => domain.hyperlane !== this.multiProvider.getDomainId(chain)); this.logger.info(`Mapping Circle domains on ${chain}`, { remoteDomains, }); await this.multiProvider.handleTx(chain, tokenBridge.addDomains(remoteDomains)); })); } async setRebalancers(configMap, deployedContractsMap) { await promiseObjAll(objMap(configMap, async (chain, config) => { if (!isMovableCollateralTokenConfig(config)) { return; } const router = this.router(deployedContractsMap[chain]).address; const movableToken = MovableCollateralRouter__factory.connect(router, this.multiProvider.getSigner(chain)); const rebalancers = Array.from(config.allowedRebalancers ?? []); for (const rebalancer of rebalancers) { await this.multiProvider.handleTx(chain, movableToken.addRebalancer(rebalancer)); } })); } async setAllowedBridges(configMap, deployedContractsMap) { await promiseObjAll(objMap(configMap, async (chain, config) => { if (!isMovableCollateralTokenConfig(config)) { return; } const router = this.router(deployedContractsMap[chain]); const movableToken = MovableCollateralRouter__factory.connect(router.address, this.multiProvider.getSigner(chain)); const bridgesToAllow = Object.entries(resolveRouterMapConfig(this.multiProvider, config.allowedRebalancingBridges ?? {})).flatMap(([domain, allowedBridgesToAdd]) => { return allowedBridgesToAdd.map((bridgeToAdd) => { return { domain: Number(domain), bridge: bridgeToAdd.bridge, }; }); }); // Filter out domains that are not enrolled to avoid errors const routerDomains = await router.domains(); const bridgesToAllowOnRouter = bridgesToAllow.filter(({ domain }) => routerDomains.includes(domain)); for (const bridgeConfig of bridgesToAllowOnRouter) { await this.multiProvider.handleTx(chain, movableToken.addBridge(bridgeConfig.domain, bridgeConfig.bridge)); } })); } async setBridgesTokenApprovals(configMap, deployedContractsMap) { await promiseObjAll(objMap(configMap, async (chain, config) => { if (!isMovableCollateralTokenConfig(config)) { return; } const router = this.router(deployedContractsMap[chain]).address; const movableToken = MovableCollateralRouter__factory.connect(router, this.multiProvider.getSigner(chain)); const tokenApprovalTxs = Object.values(config.allowedRebalancingBridges ?? {}).flatMap((allowedBridgesToAdd) => { return allowedBridgesToAdd.flatMap((bridgeToAdd) => { return (bridgeToAdd.approvedTokens ?? []).map((token) => { return { bridge: bridgeToAdd.bridge, token, }; }); }); }); for (const bridgeConfig of tokenApprovalTxs) { await this.multiProvider.handleTx(chain, movableToken.approveTokenForBridge(bridgeConfig.token, bridgeConfig.bridge)); } })); } async deploy(configMap) { let tokenMetadataMap; try { tokenMetadataMap = await TokenDeployer.deriveTokenMetadata(this.multiProvider, configMap); } catch (err) { this.logger.error('Failed to derive token metadata', err, configMap); throw err; } const resolvedConfigMap = await promiseObjAll(objMap(configMap, async (chain, config) => ({ name: tokenMetadataMap.getName(chain), decimals: tokenMetadataMap.getDecimals(chain), symbol: tokenMetadataMap.getSymbol(chain) || tokenMetadataMap.getDefaultSymbol(), scale: tokenMetadataMap.getScale(chain), gas: gasOverhead(config.type), ...config, // override intermediate owner to the signer owner: await this.multiProvider.getSigner(chain).getAddress(), }))); const deployedContractsMap = await super.deploy(resolvedConfigMap); // Configure CCTP domains after all routers are deployed and remotes are enrolled (in super.deploy) await this.configureCctpDomains(configMap, deployedContractsMap); await this.setRebalancers(configMap, deployedContractsMap); await this.setAllowedBridges(configMap, deployedContractsMap); await this.setBridgesTokenApprovals(configMap, deployedContractsMap); await super.transferOwnership(deployedContractsMap, configMap); return deployedContractsMap; } } export class HypERC20Deployer extends TokenDeployer { constructor(multiProvider, ismFactory, contractVerifier, concurrentDeploy = true) { super(multiProvider, hypERC20factories, 'HypERC20Deployer', ismFactory, contractVerifier, concurrentDeploy); } router(contracts) { for (const key of objKeys(hypERC20factories)) { if (contracts[key]) { return contracts[key]; } } throw new Error('No matching contract found'); } routerContractKey(config) { assert(config.type in hypERC20factories, 'Invalid ERC20 token type'); return config.type; } routerContractName(config) { return hypERC20contracts[this.routerContractKey(config)]; } } export class HypERC721Deployer extends TokenDeployer { constructor(multiProvider, ismFactory, contractVerifier) { super(multiProvider, hypERC721factories, 'HypERC721Deployer', ismFactory, contractVerifier); } router(contracts) { for (const key of objKeys(hypERC721factories)) { if (contracts[key]) { return contracts[key]; } } throw new Error('No matching contract found'); } routerContractKey(config) { assert(config.type in hypERC721factories, 'Invalid ERC721 token type'); return config.type; } routerContractName(config) { return hypERC721contracts[this.routerContractKey(config)]; } } //# sourceMappingURL=deploy.js.map