@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
143 lines • 7.44 kB
JavaScript
import { ethers } from 'ethers';
import { IERC20__factory, InterchainAccountRouter__factory, } from '@hyperlane-xyz/core';
import { addressToBytes32, bytes32ToAddress, difference, rootLogger, } from '@hyperlane-xyz/utils';
import { serializeContracts } from '../contracts/contracts.js';
import { EvmIcaRouterReader } from '../ica/EvmIcaReader.js';
import { InterchainAccountDeployer } from '../middleware/account/InterchainAccountDeployer.js';
import { HyperlaneModule, } from './AbstractHyperlaneModule.js';
export class EvmIcaModule extends HyperlaneModule {
multiProvider;
logger = rootLogger.child({ module: 'EvmIcaModule' });
icaRouterReader;
domainId;
chainId;
constructor(multiProvider, args) {
super(args);
this.multiProvider = multiProvider;
this.icaRouterReader = new EvmIcaRouterReader(multiProvider, this.args.chain);
this.domainId = multiProvider.getDomainId(args.chain);
this.chainId = multiProvider.getEvmChainId(args.chain);
}
async read() {
return this.icaRouterReader.deriveConfig(this.args.addresses.interchainAccountRouter);
}
async update(expectedConfig) {
const actualConfig = await this.read();
const transactions = [
...(await this.updateRemoteRoutersEnrollment(actualConfig.remoteRouters, expectedConfig.remoteRouters)),
...(await this.getFeeTokenApprovalTxs(expectedConfig.feeTokenApprovals)),
];
return transactions;
}
async updateRemoteRoutersEnrollment(actualConfig, expectedConfig = {}) {
const transactions = [
...(await this.getEnrollRemoteRoutersTxs(actualConfig, expectedConfig)),
...(await this.getUnenrollRemoteRoutersTxs(actualConfig, expectedConfig)),
];
return transactions;
}
/**
* Generates transactions to approve fee tokens for hooks.
* Only generates transactions for approvals that are not already set to max.
*/
async getFeeTokenApprovalTxs(feeTokenApprovals = []) {
if (feeTokenApprovals.length === 0) {
return [];
}
const transactions = [];
const routerAddress = this.args.addresses.interchainAccountRouter;
const provider = this.multiProvider.getProvider(this.args.chain);
for (const approval of feeTokenApprovals) {
// Check if approval is already set to max
const token = IERC20__factory.connect(approval.feeToken, provider);
const currentAllowance = await token.allowance(routerAddress, approval.hook);
if (currentAllowance.toBigInt() !== ethers.constants.MaxUint256.toBigInt()) {
this.logger.debug(`Generating approval tx for fee token ${approval.feeToken} to hook ${approval.hook}`);
transactions.push({
chainId: this.chainId,
annotation: `Approving hook ${approval.hook} to spend fee token ${approval.feeToken} on behalf of ICA router ${routerAddress}`,
to: routerAddress,
data: InterchainAccountRouter__factory.createInterface().encodeFunctionData('approveFeeTokenForHook', [approval.feeToken, approval.hook]),
});
}
}
return transactions;
}
async getEnrollRemoteRoutersTxs(actualConfig, expectedConfig = {}) {
if (!actualConfig) {
return [];
}
const transactions = [];
const routesToEnroll = Array.from(difference(new Set(Object.keys(expectedConfig)), new Set(Object.keys(actualConfig))));
if (routesToEnroll.length === 0) {
return transactions;
}
const domainsToEnroll = [];
const remoteDomainIca = [];
const remoteIsm = [];
for (const domainId of routesToEnroll) {
domainsToEnroll.push(domainId);
remoteDomainIca.push(addressToBytes32(expectedConfig[domainId].address));
remoteIsm.push(ethers.utils.hexZeroPad('0x', 32));
}
const remoteTransactions = domainsToEnroll.map((domainId) => ({
annotation: `Enrolling InterchainAccountRouter on domain ${this.domainId} on InterchainAccountRouter at ${expectedConfig[domainId].address} on domain ${domainId}`,
chainId: this.multiProvider.getEvmChainId(domainId),
to: expectedConfig[domainId].address,
data: InterchainAccountRouter__factory.createInterface().encodeFunctionData('enrollRemoteRouter(uint32,bytes32)', [
this.domainId,
addressToBytes32(this.args.addresses.interchainAccountRouter),
]),
}));
transactions.push({
annotation: `Enrolling remote InterchainAccountRouters on domain ${this.domainId}`,
chainId: this.chainId,
to: this.args.addresses.interchainAccountRouter,
data: InterchainAccountRouter__factory.createInterface().encodeFunctionData('enrollRemoteRouterAndIsms(uint32[],bytes32[],bytes32[])', [domainsToEnroll, remoteDomainIca, remoteIsm]),
});
transactions.push(...remoteTransactions);
return transactions;
}
async getUnenrollRemoteRoutersTxs(actualConfig, expectedConfig = {}) {
if (!actualConfig) {
return [];
}
const transactions = [];
const routesToUnenroll = Array.from(difference(new Set(Object.keys(actualConfig)), new Set(Object.keys(expectedConfig))));
if (routesToUnenroll.length === 0) {
return transactions;
}
transactions.push({
annotation: `Unenrolling remote InterchainAccountRouters from chain ${this.domainId}`,
chainId: this.chainId,
to: this.args.addresses.interchainAccountRouter,
data: InterchainAccountRouter__factory.createInterface().encodeFunctionData('unenrollRemoteRouters(uint32[])', [routesToUnenroll]),
});
const remoteTransactions = routesToUnenroll.map((domainId) => ({
annotation: `Removing InterchainAccountRouter on domain ${this.domainId} from InterchainAccountRouter at ${actualConfig[domainId].address} on domain ${domainId}`,
chainId: this.multiProvider.getEvmChainId(domainId),
to: bytes32ToAddress(actualConfig[domainId].address),
data: InterchainAccountRouter__factory.createInterface().encodeFunctionData('unenrollRemoteRouter(uint32)', [this.domainId]),
}));
transactions.push(...remoteTransactions);
return transactions;
}
/**
* Creates a new EvmIcaModule instance by deploying an ICA with an ICA ISM.
*
* @param chain - The chain on which to deploy the ICA.
* @param config - The configuration for the ICA.
* @param multiProvider - The MultiProvider instance to use for deployment.
* @returns {Promise<EvmIcaModule>} - A new EvmIcaModule instance.
*/
static async create({ chain, config, multiProvider, contractVerifier, }) {
const interchainAccountDeployer = new InterchainAccountDeployer(multiProvider, contractVerifier);
const deployedContracts = await interchainAccountDeployer.deployContracts(multiProvider.getChainName(chain), config);
return new EvmIcaModule(multiProvider, {
addresses: serializeContracts(deployedContracts),
chain,
config,
});
}
}
//# sourceMappingURL=EvmIcaModule.js.map