UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

182 lines 8.49 kB
import { constants } from 'ethers'; import { RoutingFee__factory } from '@hyperlane-xyz/core'; import { assert, deepEquals, eqAddress, isZeroishAddress, objMap, objMerge, objOmit, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils'; import { transferOwnershipTransactions } from '../contracts/contracts.js'; import { HyperlaneModule, } from '../core/AbstractHyperlaneModule.js'; import { normalizeConfig } from '../utils/ism.js'; import { EvmTokenFeeDeployer } from './EvmTokenFeeDeployer.js'; import { EvmTokenFeeReader, } from './EvmTokenFeeReader.js'; import { ImmutableTokenFeeType, TokenFeeConfigSchema, TokenFeeType, } from './types.js'; export class EvmTokenFeeModule extends HyperlaneModule { multiProvider; contractVerifier; logger = rootLogger.child({ module: 'EvmTokenFeeModule' }); deployer; reader; chainName; chainId; constructor(multiProvider, params, contractVerifier) { super(params); this.multiProvider = multiProvider; this.contractVerifier = contractVerifier; this.chainName = multiProvider.getChainName(params.chain); this.chainId = multiProvider.getDomainId(this.chainName); this.deployer = new EvmTokenFeeDeployer(multiProvider, this.chainName, { logger: this.logger, contractVerifier: contractVerifier, }); this.reader = new EvmTokenFeeReader(multiProvider, this.chainName); } static async create({ multiProvider, chain, config, contractVerifier, }) { const chainName = multiProvider.getChainName(chain); const module = new EvmTokenFeeModule(multiProvider, { addresses: { deployedFee: constants.AddressZero, }, chain, config, }, contractVerifier); const contracts = await module.deploy({ multiProvider, chainName, contractVerifier, config, }); module.args.addresses.deployedFee = contracts[chainName][config.type].address; return module; } // Processes the Input config to the Final config // For LinearFee, it converts the bps to maxFee and halfAmount static async expandConfig(params) { const { config, multiProvider, chainName } = params; let intermediaryConfig; if (config.type === TokenFeeType.LinearFee) { const reader = new EvmTokenFeeReader(params.multiProvider, params.chainName); let { maxFee, halfAmount } = config; if (!isZeroishAddress(config.token)) { const { maxFee: convertedMaxFee, halfAmount: convertedHalfAmount } = await reader.convertFromBps(config.bps, config.token); maxFee = convertedMaxFee; halfAmount = convertedHalfAmount; } assert(maxFee && halfAmount, 'Config properties "maxFee" and "halfAmount" must be supplied when "token" is not supplied'); intermediaryConfig = { type: TokenFeeType.LinearFee, token: config.token, owner: config.owner, bps: config.bps, maxFee, halfAmount, }; } else if (config.type === TokenFeeType.RoutingFee) { const feeContracts = config.feeContracts ? await promiseObjAll(objMap(config.feeContracts, async (_, innerConfig) => { return EvmTokenFeeModule.expandConfig({ config: innerConfig, multiProvider, chainName, }); })) : undefined; intermediaryConfig = { type: TokenFeeType.RoutingFee, token: config.token, owner: config.owner, maxFee: constants.MaxUint256.toBigInt(), halfAmount: constants.MaxUint256.toBigInt(), feeContracts, }; } else { intermediaryConfig = config; } return TokenFeeConfigSchema.parse(intermediaryConfig); } async deploy(params) { const deployer = new EvmTokenFeeDeployer(params.multiProvider, params.chainName, { contractVerifier: params.contractVerifier, }); return deployer.deploy({ [params.chainName]: params.config }); } async read(params) { const address = params?.address ?? this.args.addresses.deployedFee; const routingDestinations = params?.routingDestinations; return this.reader.deriveTokenFeeConfig({ address: address, routingDestinations, }); } async update(targetConfig, params) { let updateTransactions = []; const normalizedTargetConfig = normalizeConfig(await EvmTokenFeeModule.expandConfig({ config: targetConfig, multiProvider: this.multiProvider, chainName: this.chainName, })); const actualConfig = await this.read(params); const normalizedActualConfig = normalizeConfig(actualConfig); //If configs are the same, return empty array if (deepEquals(normalizedActualConfig, normalizedTargetConfig)) { this.logger.debug(`Same config for ${normalizedTargetConfig.type}, no update needed`); return []; } // Redeploy immutable fee types, if owner is the same, but the rest of the config is different const nonOwnerDiffers = !deepEquals(objOmit(normalizedActualConfig, { owner: true }), objOmit(normalizedTargetConfig, { owner: true })); if (ImmutableTokenFeeType.includes(normalizedTargetConfig.type) && nonOwnerDiffers) { this.logger.info(`Immutable fee type ${normalizedTargetConfig.type}, redeploying`); const contracts = await this.deploy({ config: normalizedTargetConfig, multiProvider: this.multiProvider, chainName: this.chainName, contractVerifier: this.contractVerifier, }); this.args.addresses.deployedFee = contracts[this.chainName][normalizedTargetConfig.type].address; return []; } // if the type is a mutable (for now, only routing fee), then update updateTransactions = [ ...(await this.updateRoutingFee(objMerge(actualConfig, normalizedTargetConfig, 10, true))), ...this.createOwnershipUpdateTxs(normalizedActualConfig, normalizedTargetConfig), ]; return updateTransactions; } async updateRoutingFee(targetConfig) { const updateTransactions = []; if (!targetConfig.feeContracts) return []; const currentRoutingAddress = this.args.addresses.deployedFee; await promiseObjAll(objMap(targetConfig.feeContracts, async (chainName, config) => { const address = config.address; const subFeeModule = new EvmTokenFeeModule(this.multiProvider, { addresses: { deployedFee: address, }, chain: this.chainName, config, }, this.contractVerifier); const subFeeUpdateTransactions = await subFeeModule.update(config, { address, }); const { deployedFee: deployedSubFee } = subFeeModule.serialize(); updateTransactions.push(...subFeeUpdateTransactions); if (!eqAddress(deployedSubFee, address)) { const annotation = `Sub fee contract redeployed. Updating contract for ${chainName} to ${deployedSubFee}`; this.logger.debug(annotation); updateTransactions.push({ annotation: annotation, chainId: this.chainId, to: currentRoutingAddress, data: RoutingFee__factory.createInterface().encodeFunctionData('setFeeContract(uint32,address)', [this.multiProvider.getDomainId(chainName), deployedSubFee]), }); } })); return updateTransactions; } createOwnershipUpdateTxs(actualConfig, expectedConfig) { return transferOwnershipTransactions(this.multiProvider.getEvmChainId(this.args.chain), this.args.addresses.deployedFee, actualConfig, expectedConfig, `${expectedConfig.type} Warp Route`); } } //# sourceMappingURL=EvmTokenFeeModule.js.map