diff --git a/.github/workflows/node.yml b/.github/workflows/node.yml index 26941af52..fcf630fad 100644 --- a/.github/workflows/node.yml +++ b/.github/workflows/node.yml @@ -115,7 +115,7 @@ jobs: strategy: matrix: environment: [testnet3, mainnet2] - module: [core, igp] + module: [core, igp, ica, iqs] steps: - uses: actions/checkout@v3 diff --git a/typescript/helloworld b/typescript/helloworld index 3b443c79b..591edb07f 160000 --- a/typescript/helloworld +++ b/typescript/helloworld @@ -1 +1 @@ -Subproject commit 3b443c79bcb7671ef3d49ba7d6765c9adb139789 +Subproject commit 591edb07f44e67d95220e6c89d669f11cde68501 diff --git a/typescript/infra/config/environments/testnet3/middleware/queries/verification.json b/typescript/infra/config/environments/testnet3/middleware/queries/verification.json index 28c527370..5689bf0e4 100644 --- a/typescript/infra/config/environments/testnet3/middleware/queries/verification.json +++ b/typescript/infra/config/environments/testnet3/middleware/queries/verification.json @@ -2,65 +2,127 @@ "alfajores": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "fuji": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "mumbai": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "bsctestnet": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "goerli": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "moonbasealpha": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "optimismgoerli": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ], "arbitrumgoerli": [ { "name": "InterchainQueryRouter", - "address": "0xF782C6C4A02f2c71BB8a1Db0166FAB40ea956818", - "isProxy": false, - "constructorArguments": "0x" + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true + } + ], + "sepolia": [ + { + "name": "InterchainQueryRouter", + "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", + "constructorArguments": "0x", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", + "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true } ] } diff --git a/typescript/infra/scripts/check-deploy.ts b/typescript/infra/scripts/check-deploy.ts index cb15e086e..35c20700f 100644 --- a/typescript/infra/scripts/check-deploy.ts +++ b/typescript/infra/scripts/check-deploy.ts @@ -3,23 +3,31 @@ import { HyperlaneCoreChecker, HyperlaneIgp, HyperlaneIgpChecker, + InterchainAccount, + InterchainAccountChecker, + InterchainQuery, + InterchainQueryChecker, } from '@hyperlane-xyz/sdk'; import { deployEnvToSdkEnv } from '../src/config/environment'; import { HyperlaneCoreGovernor } from '../src/core/govern'; import { HyperlaneIgpGovernor } from '../src/gas/govern'; import { HyperlaneAppGovernor } from '../src/govern/HyperlaneAppGovernor'; +import { InterchainAccountGovernor } from '../src/middleware/account/govern'; +import { InterchainQueryGovernor } from '../src/middleware/query/govern'; import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; import { Modules, getArgsWithModuleAndFork, getEnvironmentConfig, + getRouterConfig, } from './utils'; async function check() { const { fork, govern, module, environment } = await getArgsWithModuleAndFork() .boolean('govern') + .default('govern', false) .alias('g', 'govern').argv; const config = await getEnvironmentConfig(); const multiProvider = await config.getMultiProvider(); @@ -44,6 +52,16 @@ async function check() { const igp = HyperlaneIgp.fromEnvironment(env, multiProvider); const checker = new HyperlaneIgpChecker(multiProvider, igp, config.igp); governor = new HyperlaneIgpGovernor(checker, config.owners); + } else if (module === Modules.INTERCHAIN_ACCOUNTS) { + const config = await getRouterConfig(environment, multiProvider); + const ica = InterchainAccount.fromEnvironment(env, multiProvider); + const checker = new InterchainAccountChecker(multiProvider, ica, config); + governor = new InterchainAccountGovernor(checker, config.owners); + } else if (module === Modules.INTERCHAIN_QUERY_SYSTEM) { + const config = await getRouterConfig(environment, multiProvider); + const iqs = InterchainQuery.fromEnvironment(env, multiProvider); + const checker = new InterchainQueryChecker(multiProvider, iqs, config); + governor = new InterchainQueryGovernor(checker, config.owners); } else { console.log(`Skipping ${module}, checker or governor unimplemented`); return; diff --git a/typescript/infra/src/middleware/account/govern.ts b/typescript/infra/src/middleware/account/govern.ts new file mode 100644 index 000000000..6d0234dc4 --- /dev/null +++ b/typescript/infra/src/middleware/account/govern.ts @@ -0,0 +1,25 @@ +import { + ChainMap, + InterchainAccount, + InterchainAccountChecker, + InterchainAccountConfig, +} from '@hyperlane-xyz/sdk'; +import { types } from '@hyperlane-xyz/utils'; + +import { HyperlaneAppGovernor } from '../../govern/HyperlaneAppGovernor'; + +export class InterchainAccountGovernor extends HyperlaneAppGovernor< + InterchainAccount, + InterchainAccountConfig +> { + constructor( + checker: InterchainAccountChecker, + owners: ChainMap, + ) { + super(checker, owners); + } + + protected async mapViolationsToCalls() { + throw new Error('governor not implemented for account middleware'); + } +} diff --git a/typescript/infra/src/middleware/query/govern.ts b/typescript/infra/src/middleware/query/govern.ts new file mode 100644 index 000000000..382ca90d8 --- /dev/null +++ b/typescript/infra/src/middleware/query/govern.ts @@ -0,0 +1,25 @@ +import { + ChainMap, + InterchainQuery, + InterchainQueryChecker, + InterchainQueryConfig, +} from '@hyperlane-xyz/sdk'; +import { types } from '@hyperlane-xyz/utils'; + +import { HyperlaneAppGovernor } from '../../govern/HyperlaneAppGovernor'; + +export class InterchainQueryGovernor extends HyperlaneAppGovernor< + InterchainQuery, + InterchainQueryConfig +> { + constructor( + checker: InterchainQueryChecker, + owners: ChainMap, + ) { + super(checker, owners); + } + + protected async mapViolationsToCalls() { + throw new Error('governor not implemented for query middleware'); + } +} diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index f4a6d8ef3..bcad609b3 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -179,9 +179,6 @@ export function writeJsonAtPath(filepath: string, obj: any) { } export function writeJSON(directory: string, filename: string, obj: any) { - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory, { recursive: true }); - } writeJsonAtPath(path.join(directory, filename), obj); } diff --git a/typescript/sdk/src/HyperlaneApp.ts b/typescript/sdk/src/HyperlaneApp.ts index 200b94f26..219c3ca77 100644 --- a/typescript/sdk/src/HyperlaneApp.ts +++ b/typescript/sdk/src/HyperlaneApp.ts @@ -1,5 +1,6 @@ import { HyperlaneAddresses, + HyperlaneContract, HyperlaneContracts, HyperlaneFactories, buildContracts, @@ -7,9 +8,10 @@ import { serializeContracts, } from './contracts'; import { MultiProvider } from './providers/MultiProvider'; +import { isProxiedContract } from './proxy'; import { ChainMap, ChainName } from './types'; import { MultiGeneric } from './utils/MultiGeneric'; -import { objMap, pick } from './utils/objects'; +import { objFilter, objMap, pick } from './utils/objects'; export class HyperlaneApp< Contracts extends HyperlaneContracts, @@ -45,6 +47,17 @@ export class HyperlaneApp< return this.get(chain); } + getFlattenedFilteredContracts( + chain: ChainName, + filter: (k: ChainName, v: HyperlaneContract) => v is K, + ): { [key: string]: K } { + const filtered = objFilter(this.getContracts(chain), filter); + const flattened = objMap(filtered, (name, contract) => + isProxiedContract(contract) ? contract.contract : contract, + ); + return flattened; + } + getAddresses(chain: ChainName): HyperlaneAddresses { return serializeContracts(this.get(chain)); } diff --git a/typescript/sdk/src/consts/environments/mainnet.json b/typescript/sdk/src/consts/environments/mainnet.json index f30d33fa1..507d67865 100644 --- a/typescript/sdk/src/consts/environments/mainnet.json +++ b/typescript/sdk/src/consts/environments/mainnet.json @@ -17,7 +17,6 @@ "multisigIsm": "0x9bDE63104EE030d9De419EEd6bA7D14b86D6fE3f", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "ethereum": { @@ -38,7 +37,6 @@ "multisigIsm": "0xec48E52D960E54a179f70907bF28b105813877ee", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "avalanche": { @@ -59,7 +57,6 @@ "multisigIsm": "0xeE80ab5B563cB3825133f29502bA34eD3707cb8C", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "polygon": { @@ -80,7 +77,6 @@ "multisigIsm": "0x61A80297e77FC5395bd6Ff60EEacf7CD4f18d4a4", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "bsc": { @@ -101,7 +97,6 @@ "multisigIsm": "0x3a579C0bd04FC4C98A8D70EEABD9094e7be4B26D", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "arbitrum": { @@ -122,7 +117,6 @@ "multisigIsm": "0x32B92bd3e5045B67FDD8dbb7A58D25980836d04C", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "optimism": { @@ -143,7 +137,6 @@ "multisigIsm": "0xAab1D11E2063Bae5EB01fa946cA8d2FDe3db05D5", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "moonbeam": { @@ -164,7 +157,6 @@ "multisigIsm": "0xf3b1F415740A26568C45b1c771A737E31C198F09", "create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "interchainAccountRouter": "0xE0Be420779cAd6E2bEA1E4F7C02F996D9ED1fCB5", "interchainQueryRouter": "0x234b19282985882d6d6fd54dEBa272271f4eb784" }, "gnosis": { diff --git a/typescript/sdk/src/consts/environments/test.json b/typescript/sdk/src/consts/environments/test.json index d12a04dd8..5e09730b3 100644 --- a/typescript/sdk/src/consts/environments/test.json +++ b/typescript/sdk/src/consts/environments/test.json @@ -1,53 +1,53 @@ { "test1": { - "storageGasOracle": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", - "validatorAnnounce": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "proxyAdmin": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + "storageGasOracle": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1", + "validatorAnnounce": "0x0165878A594ca255338adfa4d48449f69242Eb8F", + "proxyAdmin": "0x59b670e9fA9D0A427751Af201D676719a970857b", "mailbox": { "kind": "Transparent", - "proxy": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "implementation": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" + "proxy": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707", + "implementation": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" }, "interchainGasPaymaster": { "kind": "Transparent", - "proxy": "0x0165878A594ca255338adfa4d48449f69242Eb8F", - "implementation": "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" + "proxy": "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", + "implementation": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" }, - "defaultIsmInterchainGasPaymaster": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6", + "defaultIsmInterchainGasPaymaster": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", "multisigIsm": "0x5FbDB2315678afecb367f032d93F642f64180aa3" }, "test2": { - "storageGasOracle": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed", - "validatorAnnounce": "0x09635F643e140090A9A8Dcd712eD6285858ceBef", - "proxyAdmin": "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE", + "storageGasOracle": "0xE6E340D132b5f46d1e472DebcD681B2aBc16e57E", + "validatorAnnounce": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "proxyAdmin": "0x67d269191c92Caf3cD7723F116c85e6E9bf55933", "mailbox": { "kind": "Transparent", - "proxy": "0x7a2088a1bFc9d81c55368AE168C2C02570cB814F", - "implementation": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + "proxy": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "implementation": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e" }, "interchainGasPaymaster": { "kind": "Transparent", - "proxy": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", - "implementation": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" + "proxy": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB", + "implementation": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690" }, - "defaultIsmInterchainGasPaymaster": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1", - "multisigIsm": "0x9A676e781A523b5d0C0e43731313A708CB607508" + "defaultIsmInterchainGasPaymaster": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9", + "multisigIsm": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" }, "test3": { - "storageGasOracle": "0x84eA74d481Ee0A5332c457a4d796187F6Ba67fEB", - "validatorAnnounce": "0x4826533B4897376654Bb4d4AD88B7faFD0C98528", - "proxyAdmin": "0xc3e53F4d16Ae77Db1c982e75a937B9f60FE63690", + "storageGasOracle": "0x95401dc811bb5740090279Ba06cfA8fcF6113778", + "validatorAnnounce": "0xc6e7DF5E7b4f2A278906862b61205850344D4e7d", + "proxyAdmin": "0xf5059a5D33d5853360D16C683c16e67980206f36", "mailbox": { "kind": "Transparent", - "proxy": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49", - "implementation": "0x998abeb3E57409262aE5b751f60747921B33613E" + "proxy": "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c", + "implementation": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" }, "interchainGasPaymaster": { "kind": "Transparent", - "proxy": "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9", - "implementation": "0x9E545E3C0baAB3E08CdfD552C960A1050f373042" + "proxy": "0x70e0bA845a1A0F2DA3359C97E0285013525FFC49", + "implementation": "0x998abeb3E57409262aE5b751f60747921B33613E" }, - "defaultIsmInterchainGasPaymaster": "0x851356ae760d987E095750cCeb3bC6014560891C", - "multisigIsm": "0xc5a5C42992dECbae36851359345FE25997F5C42d" + "defaultIsmInterchainGasPaymaster": "0x99bbA657f2BbC93c02D617f8bA121cB8Fc104Acf", + "multisigIsm": "0x9A676e781A523b5d0C0e43731313A708CB607508" } } diff --git a/typescript/sdk/src/core/HyperlaneCoreChecker.ts b/typescript/sdk/src/core/HyperlaneCoreChecker.ts index e62ecd619..0abbd4bf8 100644 --- a/typescript/sdk/src/core/HyperlaneCoreChecker.ts +++ b/typescript/sdk/src/core/HyperlaneCoreChecker.ts @@ -40,13 +40,7 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< async checkDomainOwnership(chain: ChainName): Promise { const config = this.configMap[chain]; if (config.owner) { - const contracts = this.app.getContracts(chain); - const ownables = [ - contracts.proxyAdmin, - contracts.mailbox.contract, - contracts.multisigIsm, - ]; - return this.checkOwnership(chain, config.owner, ownables); + return this.checkOwnership(chain, config.owner); } } @@ -118,16 +112,6 @@ export class HyperlaneCoreChecker extends HyperlaneAppChecker< ); } - async checkProxiedContracts(chain: ChainName): Promise { - const contracts = this.app.getContracts(chain); - await this.checkProxiedContract( - chain, - 'Mailbox', - contracts.mailbox.addresses, - contracts.proxyAdmin.address, - ); - } - async checkValidatorAnnounce(chain: ChainName): Promise { const expectedValidators = new Set(); const remotes = Object.keys(this.configMap).filter((c) => c !== chain); diff --git a/typescript/sdk/src/deploy/HyperlaneAppChecker.ts b/typescript/sdk/src/deploy/HyperlaneAppChecker.ts index ca50a53e3..62af84f54 100644 --- a/typescript/sdk/src/deploy/HyperlaneAppChecker.ts +++ b/typescript/sdk/src/deploy/HyperlaneAppChecker.ts @@ -6,11 +6,11 @@ import { utils } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../HyperlaneApp'; import { MultiProvider } from '../providers/MultiProvider'; -import { TransparentProxyAddresses } from '../proxy'; +import { ProxiedContract, isProxiedContract } from '../proxy'; import { ChainMap, ChainName } from '../types'; -import { objMap } from '../utils/objects'; +import { objMap, promiseObjAll } from '../utils/objects'; -import { proxyAdmin, proxyImplementation, proxyViolation } from './proxy'; +import { proxyAdmin } from './proxy'; import { BytecodeMismatchViolation, CheckerViolation, @@ -62,34 +62,36 @@ export abstract class HyperlaneAppChecker< this.violations.push(violation); } - async checkProxiedContract( - chain: ChainName, - name: string, - proxiedAddress: TransparentProxyAddresses, - proxyAdminAddress?: types.Address, - ): Promise { - const provider = this.multiProvider.getProvider(chain); - const implementation = await proxyImplementation( - provider, - proxiedAddress.proxy, - ); - if (implementation !== proxiedAddress.implementation) { - this.addViolation( - proxyViolation(chain, name, proxiedAddress, implementation), + async checkProxiedContracts(chain: ChainName): Promise { + const expectedAdmin = this.app.getContracts(chain).proxyAdmin.address; + if (!expectedAdmin) { + throw new Error( + `Checking proxied contracts for ${chain} with no admin provided`, ); } - if (proxyAdminAddress) { - const admin = await proxyAdmin(provider, proxiedAddress.proxy); - if (admin !== proxyAdminAddress) { - this.addViolation({ - type: ViolationType.ProxyAdmin, - chain, - name, - expected: proxyAdminAddress, - actual: admin, - } as ProxyAdminViolation); - } - } + const provider = this.multiProvider.getProvider(chain); + const isProxied = ( + _: string, + contract: any, + ): contract is ProxiedContract => { + return isProxiedContract(contract); + }; + const proxied = this.app.getFlattenedFilteredContracts(chain, isProxied); + await promiseObjAll( + objMap(proxied, async (name, contract) => { + // Check the ProxiedContract's admin matches expectation + const actualAdmin = await proxyAdmin(provider, contract.address); + if (!utils.eqAddress(actualAdmin, expectedAdmin)) { + this.addViolation({ + type: ViolationType.ProxyAdmin, + chain, + name, + expected: expectedAdmin, + actual: actualAdmin, + } as ProxyAdminViolation); + } + }), + ); } private removeBytecodeMetadata(bytecode: string): string { @@ -122,17 +124,24 @@ export abstract class HyperlaneAppChecker< } } - async checkOwnership( - chain: ChainName, - owner: types.Address, - ownables: Ownable[], - ): Promise { - await Promise.all( - ownables.map(async (contract) => { + // TODO: Require owner in config if ownables is non-empty + async checkOwnership(chain: ChainName, owner: types.Address): Promise { + const isOwnable = (_: string, contract: any): contract is Ownable => { + return ( + contract !== null && + typeof contract === 'object' && + contract.owner && + contract.transferOwnership + ); + }; + const ownables = this.app.getFlattenedFilteredContracts(chain, isOwnable); + await promiseObjAll( + objMap(ownables, async (name, contract) => { const actual = await contract.owner(); - if (actual.toLowerCase() != owner.toLowerCase()) { + if (!utils.eqAddress(actual, owner)) { const violation: OwnerViolation = { chain, + name, type: ViolationType.Owner, actual, expected: owner, diff --git a/typescript/sdk/src/deploy/types.ts b/typescript/sdk/src/deploy/types.ts index ed992b906..fbb1edf54 100644 --- a/typescript/sdk/src/deploy/types.ts +++ b/typescript/sdk/src/deploy/types.ts @@ -22,6 +22,7 @@ export enum ViolationType { export interface OwnerViolation extends CheckerViolation { type: ViolationType.Owner; contract: Ownable; + name: string; } export interface ProxyAdminViolation extends CheckerViolation { diff --git a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts index 19dadb77a..3252dbd8b 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts @@ -31,13 +31,7 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< async checkDomainOwnership(chain: ChainName): Promise { const config = this.configMap[chain]; if (config.owner) { - const contracts = this.app.getContracts(chain); - const ownables = [ - contracts.proxyAdmin, - contracts.interchainGasPaymaster.contract, - contracts.defaultIsmInterchainGasPaymaster, - ]; - return this.checkOwnership(chain, config.owner, ownables); + return this.checkOwnership(chain, config.owner); } } @@ -78,16 +72,6 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< ); } - async checkProxiedContracts(chain: ChainName): Promise { - const contracts = this.app.getContracts(chain); - await this.checkProxiedContract( - chain, - 'InterchainGasPaymaster', - contracts.interchainGasPaymaster.addresses, - contracts.proxyAdmin.address, - ); - } - async checkOverheadInterchainGasPaymaster(local: ChainName): Promise { const coreContracts = this.app.getContracts(local); const defaultIsmIgp = coreContracts.defaultIsmInterchainGasPaymaster; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 4924e5fc5..e141c0f1b 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -72,9 +72,9 @@ export { } from './core/types'; export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker'; export { - HyperlaneDeployer, - DeployOptions, DeployerOptions, + DeployOptions, + HyperlaneDeployer, } from './deploy/HyperlaneDeployer'; export { ProxyViolation } from './deploy/proxy'; export { @@ -110,12 +110,13 @@ export { OverheadIgpConfig, } from './gas/types'; export { HyperlaneApp } from './HyperlaneApp'; +export { interchainAccountFactories } from './middleware/account/contracts'; +export { InterchainAccount } from './middleware/account/InterchainAccount'; +export { InterchainAccountChecker } from './middleware/account/InterchainAccountChecker'; export { + InterchainAccountConfig, InterchainAccountDeployer, - interchainAccountFactories, - InterchainQueryDeployer, - interchainQueryFactories, -} from './middleware/deploy'; +} from './middleware/account/InterchainAccountDeployer'; export { LiquidityLayerContracts, liquidityLayerFactories, @@ -128,6 +129,13 @@ export { LiquidityLayerDeployer, PortalAdapterConfig, } from './middleware/liquidity-layer/LiquidityLayerRouterDeployer'; +export { interchainQueryFactories } from './middleware/query/contracts'; +export { InterchainQuery } from './middleware/query/InterchainQuery'; +export { InterchainQueryChecker } from './middleware/query/InterchainQueryChecker'; +export { + InterchainQueryConfig, + InterchainQueryDeployer, +} from './middleware/query/InterchainQueryDeployer'; export { MultiProvider, providerBuilder } from './providers/MultiProvider'; export { RetryJsonRpcProvider, RetryProvider } from './providers/RetryProvider'; export { @@ -140,12 +148,7 @@ export { GasRouterDeployer } from './router/GasRouterDeployer'; export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker'; export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer'; export { GasRouterApp, Router, RouterApp } from './router/RouterApps'; -export { - GasRouterConfig, - RouterConfig, - RouterContracts, - RouterFactories, -} from './router/types'; +export { GasRouterConfig, RouterConfig } from './router/types'; export { createRouterConfigMap, deployTestIgpsAndGetRouterConfig, diff --git a/typescript/sdk/src/middleware/MiddlewareRouterChecker.ts b/typescript/sdk/src/middleware/MiddlewareRouterChecker.ts new file mode 100644 index 000000000..c4e24d29b --- /dev/null +++ b/typescript/sdk/src/middleware/MiddlewareRouterChecker.ts @@ -0,0 +1,20 @@ +import { HyperlaneContracts } from '../contracts'; +import { HyperlaneRouterChecker } from '../router/HyperlaneRouterChecker'; +import { RouterApp } from '../router/RouterApps'; +import { RouterConfig } from '../router/types'; +import { ChainName } from '../types'; + +export abstract class MiddlewareRouterChecker< + MiddlewareRouterApp extends RouterApp, + MiddlewareRouterConfig extends RouterConfig, + MiddlewareRouterContracts extends HyperlaneContracts, +> extends HyperlaneRouterChecker< + MiddlewareRouterApp, + MiddlewareRouterConfig, + MiddlewareRouterContracts +> { + async checkChain(chain: ChainName): Promise { + await super.checkChain(chain); + await this.checkProxiedContracts(chain); + } +} diff --git a/typescript/sdk/src/middleware/MiddlewareRouterDeployer.ts b/typescript/sdk/src/middleware/MiddlewareRouterDeployer.ts new file mode 100644 index 000000000..d22613063 --- /dev/null +++ b/typescript/sdk/src/middleware/MiddlewareRouterDeployer.ts @@ -0,0 +1,96 @@ +import { ContractFactory, ethers } from 'ethers'; + +import { ProxyAdmin, Router } from '@hyperlane-xyz/core'; + +import { MultiProvider } from '../providers/MultiProvider'; +import { HyperlaneRouterDeployer } from '../router/HyperlaneRouterDeployer'; +import { + ProxiedContracts, + ProxiedFactories, + RouterConfig, +} from '../router/types'; +import { ChainMap, ChainName } from '../types'; + +export abstract class MiddlewareRouterDeployer< + MiddlewareRouterConfig extends RouterConfig, + MiddlewareRouterContracts extends ProxiedContracts, + MiddlewareFactories extends ProxiedFactories, + RouterFactory extends ContractFactory, +> extends HyperlaneRouterDeployer< + MiddlewareRouterConfig, + MiddlewareRouterContracts, + MiddlewareFactories +> { + constructor( + multiProvider: MultiProvider, + configMap: ChainMap, + factories: MiddlewareFactories, + protected create2salt = 'middlewarerouter', + ) { + super(multiProvider, configMap, factories); + } + + constructorArgs( + _chain: ChainName, + _config: MiddlewareRouterConfig, + ): Parameters { + return [] as any; + } + + abstract readonly routerContractName: string; + + router(contracts: MiddlewareRouterContracts): Router { + return contracts[this.routerContractName].contract; + } + + async initializeArgs( + chain: ChainName, + config: MiddlewareRouterConfig, + ): Promise<[string, string, string, string]> { + // configure owner as signer for additional initialization steps + // ownership is transferred to config.owner in HyperlaneRouterDeployer.deploy + const owner = await this.multiProvider.getSignerAddress(chain); + return [ + config.mailbox, + config.interchainGasPaymaster, + config.interchainSecurityModule ?? ethers.constants.AddressZero, + owner, + ]; + } + + async deployContracts( + chain: ChainName, + config: MiddlewareRouterConfig, + ): Promise { + const proxyAdmin = (await this.deployContract( + chain, + 'proxyAdmin', + [] as any, // generic type inference fails here + )) as ProxyAdmin; + + const initArgs = await this.initializeArgs(chain, config); + const proxiedRouter = await this.deployProxiedContract( + chain, + this.routerContractName, + this.constructorArgs(chain, config), + initArgs as any, // generic type inference fails here + proxyAdmin.address, + { + create2Salt: this.create2salt, + }, + ); + + this.logger(`Transferring ownership of proxy admin to ${config.owner}`); + await super.runIfOwner(chain, proxyAdmin, () => + this.multiProvider.handleTx( + chain, + proxyAdmin.transferOwnership(config.owner), + ), + ); + const ret: MiddlewareRouterContracts = { + [this.routerContractName]: proxiedRouter, + proxyAdmin, + } as MiddlewareRouterContracts; + return ret; + } +} diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts new file mode 100644 index 000000000..d5debed7f --- /dev/null +++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts @@ -0,0 +1,59 @@ +import { InterchainAccountRouter } from '@hyperlane-xyz/core'; + +import { + HyperlaneEnvironment, + hyperlaneEnvironments, +} from '../../consts/environments'; +import { HyperlaneAddresses } from '../../contracts'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterApp } from '../../router/RouterApps'; +import { ChainMap, ChainName } from '../../types'; + +import { + InterchainAccountContracts, + interchainAccountFactories, +} from './contracts'; + +export type InterchainAccountContractsMap = + ChainMap; + +export class InterchainAccount extends RouterApp { + constructor( + contractsMap: InterchainAccountContractsMap, + multiProvider: MultiProvider, + ) { + super(contractsMap, multiProvider); + } + + router(contracts: InterchainAccountContracts): InterchainAccountRouter { + return contracts.interchainAccountRouter.contract; + } + + static fromAddresses( + addresses: ChainMap, + multiProvider: MultiProvider, + ): InterchainAccount { + const { contracts, intersectionProvider } = + this.buildContracts( + addresses, + interchainAccountFactories, + multiProvider, + ); + return new InterchainAccount(contracts, intersectionProvider); + } + + static fromEnvironment( + env: Env, + multiProvider: MultiProvider, + ): InterchainAccount { + const envAddresses = hyperlaneEnvironments[env]; + if (!envAddresses) { + throw new Error(`No addresses found for ${env}`); + } + return InterchainAccount.fromAddresses(envAddresses, multiProvider); + } + + getContracts(chain: ChainName): InterchainAccountContracts { + return super.getContracts(chain); + } +} diff --git a/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts b/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts new file mode 100644 index 000000000..8fb1c709a --- /dev/null +++ b/typescript/sdk/src/middleware/account/InterchainAccountChecker.ts @@ -0,0 +1,11 @@ +import { MiddlewareRouterChecker } from '../MiddlewareRouterChecker'; + +import { InterchainAccount } from './InterchainAccount'; +import { InterchainAccountConfig } from './InterchainAccountDeployer'; +import { InterchainAccountContracts } from './contracts'; + +export class InterchainAccountChecker extends MiddlewareRouterChecker< + InterchainAccount, + InterchainAccountConfig, + InterchainAccountContracts +> {} diff --git a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts new file mode 100644 index 000000000..b35f66538 --- /dev/null +++ b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts @@ -0,0 +1,113 @@ +import { + InterchainAccountRouter, + InterchainAccountRouter__factory, + ProxyAdmin, + TransparentUpgradeableProxy__factory, +} from '@hyperlane-xyz/core'; + +import { MultiProvider } from '../../providers/MultiProvider'; +import { ProxiedContract, ProxyKind } from '../../proxy'; +import { RouterConfig } from '../../router/types'; +import { ChainMap, ChainName } from '../../types'; +import { MiddlewareRouterDeployer } from '../MiddlewareRouterDeployer'; + +import { + InterchainAccountContracts, + InterchainAccountFactories, + interchainAccountFactories, +} from './contracts'; + +export type InterchainAccountConfig = RouterConfig; + +export class InterchainAccountDeployer extends MiddlewareRouterDeployer< + InterchainAccountConfig, + InterchainAccountContracts, + InterchainAccountFactories, + InterchainAccountRouter__factory +> { + readonly routerContractName = 'interchainAccountRouter'; + + constructor( + multiProvider: MultiProvider, + configMap: ChainMap, + create2salt = 'accountsrouter', + ) { + super(multiProvider, configMap, interchainAccountFactories, create2salt); + } + + // The OwnableMulticall implementation has an immutable owner address that + // must be set to the InterchainAccountRouter proxy address. To achieve this, we + // 1. deploy the proxy first with a dummy implementation + // 2. deploy the real InterchainAccountRouter and OwnableMulticall implementation with proxy address + // 3. upgrade the proxy to the real implementation and initialize + async deployContracts( + chain: ChainName, + config: InterchainAccountConfig, + ): Promise { + const proxyAdmin = (await this.deployContract( + chain, + 'proxyAdmin', + [], + )) as ProxyAdmin; + + // adapted from HyperlaneDeployer.deployProxiedContract + const cached = this.deployedContracts[chain] + ?.interchainAccountRouter as ProxiedContract; + if (cached && cached.addresses.proxy && cached.addresses.implementation) { + this.logger('Recovered full InterchainAccountRouter'); + return { + proxyAdmin, + interchainAccountRouter: cached, + }; + } + + const deployer = await this.multiProvider.getSignerAddress(chain); + + // 1. deploy the proxy first with a dummy implementation (proxy admin contract) + const proxy = await this.deployContractFromFactory( + chain, + new TransparentUpgradeableProxy__factory(), + 'TransparentUpgradeableProxy', + [proxyAdmin.address, deployer, '0x'], + { create2Salt: this.create2salt }, + ); + + // 2. deploy the real InterchainAccountRouter and OwnableMulticall implementation with proxy address + const domainId = this.multiProvider.getDomainId(chain); + const implementation = await this.deployContract( + chain, + 'interchainAccountRouter', + [domainId, proxy.address], + ); + + // 3. upgrade the proxy to the real implementation and initialize + // adapted from HyperlaneDeployer.deployProxy.useCreate2 + const initArgs = await this.initializeArgs(chain, config); + const initData = + this.factories.interchainAccountRouter.interface.encodeFunctionData( + 'initialize', + initArgs, + ); + await super.upgradeAndInitialize( + chain, + proxy, + implementation.address, + initData, + ); + await super.changeAdmin(chain, proxy, proxyAdmin.address); + + const proxiedRouter = new ProxiedContract( + implementation.attach(proxy.address), + { + kind: ProxyKind.Transparent, + implementation: implementation.address, + proxy: proxy.address, + }, + ); + + return { + proxyAdmin, + interchainAccountRouter: proxiedRouter, // for serialization + }; + } +} diff --git a/typescript/sdk/src/middleware/accounts.hardhat-test.ts b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts similarity index 68% rename from typescript/sdk/src/middleware/accounts.hardhat-test.ts rename to typescript/sdk/src/middleware/account/accounts.hardhat-test.ts index 0d43caa0d..4fbcf18d4 100644 --- a/typescript/sdk/src/middleware/accounts.hardhat-test.ts +++ b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts @@ -7,19 +7,18 @@ import { TestRecipient__factory, } from '@hyperlane-xyz/core'; -import { Chains } from '../consts/chains'; -import { TestCoreApp } from '../core/TestCoreApp'; -import { TestCoreDeployer } from '../core/TestCoreDeployer'; -import { MultiProvider } from '../providers/MultiProvider'; -import { RouterConfig } from '../router/types'; -import { deployTestIgpsAndGetRouterConfig } from '../test/testUtils'; -import { ChainMap } from '../types'; -import { objMap, promiseObjAll } from '../utils/objects'; +import { Chains } from '../../consts/chains'; +import { TestCoreApp } from '../../core/TestCoreApp'; +import { TestCoreDeployer } from '../../core/TestCoreDeployer'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterConfig } from '../../router/types'; +import { deployTestIgpsAndGetRouterConfig } from '../../test/testUtils'; +import { ChainMap } from '../../types'; -import { - InterchainAccountContracts, - InterchainAccountDeployer, -} from './deploy'; +import { InterchainAccount } from './InterchainAccount'; +import { InterchainAccountChecker } from './InterchainAccountChecker'; +import { InterchainAccountDeployer } from './InterchainAccountDeployer'; +import { InterchainAccountContracts } from './contracts'; describe('InterchainAccounts', async () => { const localChain = Chains.test1; @@ -54,21 +53,15 @@ describe('InterchainAccounts', async () => { beforeEach(async () => { const deployer = new InterchainAccountDeployer(multiProvider, config); contracts = await deployer.deploy(); - - local = contracts[localChain].router; - remote = contracts[remoteChain].router; + local = contracts[localChain].interchainAccountRouter.contract; + remote = contracts[remoteChain].interchainAccountRouter.contract; }); - it('deploys and sets configured ISMs', async () => { - const deployedIsms = await promiseObjAll( - objMap(contracts, (_, c) => c.router.interchainSecurityModule()), - ); - expect(deployedIsms).to.eql( - objMap( - config, - (_, c) => c.interchainSecurityModule ?? ethers.constants.AddressZero, - ), - ); + it('checks', async () => { + const app = new InterchainAccount(contracts, multiProvider); + const checker = new InterchainAccountChecker(multiProvider, app, config); + await checker.check(); + expect(checker.violations.length).to.eql(0); }); it('forwards calls from interchain account', async () => { diff --git a/typescript/sdk/src/middleware/account/contracts.ts b/typescript/sdk/src/middleware/account/contracts.ts new file mode 100644 index 000000000..04d3e34b9 --- /dev/null +++ b/typescript/sdk/src/middleware/account/contracts.ts @@ -0,0 +1,23 @@ +import { + InterchainAccountRouter, + InterchainAccountRouter__factory, + ProxyAdmin, + ProxyAdmin__factory, +} from '@hyperlane-xyz/core'; + +import { ProxiedContract } from '../../proxy'; + +export type InterchainAccountFactories = { + interchainAccountRouter: InterchainAccountRouter__factory; + proxyAdmin: ProxyAdmin__factory; +}; + +export const interchainAccountFactories = { + interchainAccountRouter: new InterchainAccountRouter__factory(), + proxyAdmin: new ProxyAdmin__factory(), +}; + +export type InterchainAccountContracts = { + interchainAccountRouter: ProxiedContract; + proxyAdmin: ProxyAdmin; +}; diff --git a/typescript/sdk/src/middleware/deploy.ts b/typescript/sdk/src/middleware/deploy.ts deleted file mode 100644 index eb70ca980..000000000 --- a/typescript/sdk/src/middleware/deploy.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { ethers } from 'ethers'; - -import { - InterchainAccountRouter, - InterchainAccountRouter__factory, - InterchainQueryRouter, - InterchainQueryRouter__factory, - ProxyAdmin__factory, - TransparentUpgradeableProxy__factory, -} from '@hyperlane-xyz/core'; - -import { MultiProvider } from '../providers/MultiProvider'; -import { ProxiedContract, ProxyKind } from '../proxy'; -import { HyperlaneRouterDeployer } from '../router/HyperlaneRouterDeployer'; -import { - ProxiedRouterContracts, - ProxiedRouterFactories, - RouterConfig, -} from '../router/types'; -import { ChainMap, ChainName } from '../types'; - -export type InterchainAccountFactories = - ProxiedRouterFactories; - -export const interchainAccountFactories: InterchainAccountFactories = { - router: new InterchainAccountRouter__factory(), - proxyAdmin: new ProxyAdmin__factory(), -}; - -export type InterchainAccountContracts = - ProxiedRouterContracts; - -export type InterchainQueryFactories = - ProxiedRouterFactories; - -export const interchainQueryFactories: InterchainQueryFactories = { - router: new InterchainQueryRouter__factory(), - proxyAdmin: new ProxyAdmin__factory(), -}; - -export type InterchainQueryContracts = - ProxiedRouterContracts; - -export abstract class MiddlewareRouterDeployer< - MiddlewareRouterConfig extends RouterConfig, - MiddlewareRouterContracts extends ProxiedRouterContracts, - MiddlewareFactories extends ProxiedRouterFactories, -> extends HyperlaneRouterDeployer< - MiddlewareRouterConfig, - MiddlewareRouterContracts, - MiddlewareFactories -> { - constructor( - multiProvider: MultiProvider, - configMap: ChainMap, - factories: MiddlewareFactories, - protected create2salt = 'middlewarerouter', - ) { - super(multiProvider, configMap, factories); - } - - async initializeArgs( - chain: ChainName, - config: MiddlewareRouterConfig, - ): Promise<[string, string, string, string]> { - // configure owner as signer for additional initialization steps - // ownership is transferred to config.owner in HyperlaneRouterDeployer.deploy - const owner = await this.multiProvider.getSignerAddress(chain); - return [ - config.mailbox, - config.interchainGasPaymaster, - config.interchainSecurityModule ?? ethers.constants.AddressZero, - owner, - ]; - } - - async deployContracts( - chain: ChainName, - config: MiddlewareRouterConfig, - ): Promise { - const proxyAdmin = await this.deployContract( - chain, - 'proxyAdmin', - [] as any, // generic type inference fails here - ); - - const initArgs = await this.initializeArgs(chain, config); - const proxiedRouter = await this.deployProxiedContract( - chain, - 'router', - [] as any, // generic type inference fails here - initArgs as any, // generic type inference fails here - proxyAdmin.address, - { - create2Salt: this.create2salt, - }, - ); - return { - proxyAdmin, - proxiedRouter, - router: proxiedRouter.contract, // for backwards compatibility - } as any; // generic type inference fails here - } -} - -type InterchainAccountConfig = RouterConfig; - -export class InterchainAccountDeployer extends MiddlewareRouterDeployer< - InterchainAccountConfig, - InterchainAccountContracts, - InterchainAccountFactories -> { - constructor( - multiProvider: MultiProvider, - configMap: ChainMap, - create2Salt = 'accountsrouter', - ) { - super(multiProvider, configMap, interchainAccountFactories, create2Salt); - } - - // The OwnableMulticall implementation has an immutable owner address that - // must be set to the InterchainAccountRouter proxy address. To achieve this, we - // 1. deploy the proxy first with a dummy implementation - // 2. deploy the real InterchainAccountRouter and OwnableMulticall implementation with proxy address - // 3. upgrade the proxy to the real implementation and initialize - async deployContracts( - chain: ChainName, - config: InterchainAccountConfig, - ): Promise { - const proxyAdmin = await this.deployContract(chain, 'proxyAdmin', []); - - // manually recover from cache because cannot use HyperlaneDeployer.deployProxiedContract - const cached = this.deployedContracts[chain]?.proxiedRouter; - if (cached && cached.addresses.proxy && cached.addresses.implementation) { - this.logger('Recovered full InterchainAccountRouter'); - return { - proxyAdmin, - proxiedRouter: cached, - router: cached.contract, - }; - } - - const deployer = await this.multiProvider.getSignerAddress(chain); - - // 1. deploy the proxy first with a dummy implementation (proxy admin contract) - const proxy = await this.deployContractFromFactory( - chain, - new TransparentUpgradeableProxy__factory(), - 'TransparentUpgradeableProxy', - [proxyAdmin.address, deployer, '0x'], - { create2Salt: this.create2salt }, - ); - - // 2. deploy the real InterchainAccountRouter and OwnableMulticall implementation with proxy address - const domainId = this.multiProvider.getDomainId(chain); - const implementation = await this.deployContract(chain, 'router', [ - domainId, - proxy.address, - ]); - - // 3. upgrade the proxy to the real implementation and initialize - // adapted from HyperlaneDeployer.deployProxy.useCreate2 - const initArgs = await this.initializeArgs(chain, config); - const initData = this.factories.router.interface.encodeFunctionData( - 'initialize', - initArgs, - ); - await super.upgradeAndInitialize( - chain, - proxy, - implementation.address, - initData, - ); - await super.changeAdmin(chain, proxy, proxyAdmin.address); - - const proxiedRouter = new ProxiedContract( - implementation.attach(proxy.address), - { - kind: ProxyKind.Transparent, - implementation: implementation.address, - proxy: proxy.address, - }, - ); - - return { - proxyAdmin, - proxiedRouter, - router: proxiedRouter.contract, - }; - } -} - -type InterchainQueryConfig = RouterConfig; - -export class InterchainQueryDeployer extends MiddlewareRouterDeployer< - InterchainQueryConfig, - InterchainQueryContracts, - InterchainQueryFactories -> { - constructor( - multiProvider: MultiProvider, - configMap: ChainMap, - create2salt = 'queryrouter2', - ) { - super(multiProvider, configMap, interchainQueryFactories, create2salt); - } -} diff --git a/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts b/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts index 67fe0c5c8..064943453 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts @@ -2,6 +2,7 @@ import { CircleBridgeAdapter, CircleBridgeAdapter__factory, LiquidityLayerRouter, + LiquidityLayerRouter__factory, PortalAdapter, PortalAdapter__factory, } from '@hyperlane-xyz/core'; @@ -10,8 +11,8 @@ import { utils } from '@hyperlane-xyz/utils'; import { MultiProvider } from '../../providers/MultiProvider'; import { RouterConfig } from '../../router/types'; import { ChainMap, ChainName } from '../../types'; -import { objFilter, objMap } from '../../utils/objects'; -import { MiddlewareRouterDeployer } from '../deploy'; +import { objMap } from '../../utils/objects'; +import { MiddlewareRouterDeployer } from '../MiddlewareRouterDeployer'; import { LiquidityLayerContracts, @@ -54,8 +55,11 @@ export type LiquidityLayerConfig = RouterConfig & BridgeAdapterConfig; export class LiquidityLayerDeployer extends MiddlewareRouterDeployer< LiquidityLayerConfig, LiquidityLayerContracts, - LiquidityLayerFactories + LiquidityLayerFactories, + LiquidityLayerRouter__factory > { + readonly routerContractName = 'liquidityLayerRouter'; + constructor( multiProvider: MultiProvider, configMap: ChainMap, @@ -71,23 +75,23 @@ export class LiquidityLayerDeployer extends MiddlewareRouterDeployer< await super.enrollRemoteRouters(contractsMap); this.logger(`Enroll CircleBridgeAdapters with each other`); + // Hack to allow use of super.enrollRemoteRouters await super.enrollRemoteRouters( - objFilter( - objMap(contractsMap, (_chain, contracts) => ({ - router: contracts.circleBridgeAdapter, - })), - (chain, _): _ is { router: CircleBridgeAdapter } => !!_.router, - ), + objMap(contractsMap, (_, contracts) => ({ + liquidityLayerRouter: { + contract: contracts.circleBridgeAdapter, + }, + })) as unknown as ChainMap, ); this.logger(`Enroll PortalAdapters with each other`); + // Hack to allow use of super.enrollRemoteRouters await super.enrollRemoteRouters( - objFilter( - objMap(contractsMap, (_chain, contracts) => ({ - router: contracts.portalAdapter, - })), - (chain, _): _ is { router: PortalAdapter } => !!_.router, - ), + objMap(contractsMap, (_, contracts) => ({ + liquidityLayerRouter: { + contract: contracts.portalAdapter, + }, + })) as unknown as ChainMap, ); } @@ -97,7 +101,10 @@ export class LiquidityLayerDeployer extends MiddlewareRouterDeployer< chain: ChainName, config: LiquidityLayerConfig, ): Promise { - const routerContracts = await super.deployContracts(chain, config); + const routerContracts = (await super.deployContracts( + chain, + config, + )) as LiquidityLayerContracts; const bridgeAdapters: Partial = {}; @@ -106,7 +113,7 @@ export class LiquidityLayerDeployer extends MiddlewareRouterDeployer< chain, config.circle, config.owner, - routerContracts.router, + routerContracts.liquidityLayerRouter.contract, ); } if (config.portal) { @@ -114,14 +121,14 @@ export class LiquidityLayerDeployer extends MiddlewareRouterDeployer< chain, config.portal, config.owner, - routerContracts.router, + routerContracts.liquidityLayerRouter.contract, ); } return { ...routerContracts, ...bridgeAdapters, - } as any; + } as LiquidityLayerContracts; } async deployPortalAdapter( diff --git a/typescript/sdk/src/middleware/liquidity-layer/contracts.ts b/typescript/sdk/src/middleware/liquidity-layer/contracts.ts index 69c96a338..f4a14ac9a 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/contracts.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/contracts.ts @@ -8,28 +8,24 @@ import { ProxyAdmin__factory, } from '@hyperlane-xyz/core'; -import { - ProxiedRouterContracts, - ProxiedRouterFactories, -} from '../../router/types'; +import { ProxiedContract } from '../../proxy'; +import { ProxiedContracts, ProxiedFactories } from '../../router/types'; -export type LiquidityLayerFactories = - ProxiedRouterFactories & { - circleBridgeAdapter: CircleBridgeAdapter__factory; - portalAdapter: PortalAdapter__factory; - }; +export type LiquidityLayerFactories = ProxiedFactories & { + liquidityLayerRouter: LiquidityLayerRouter__factory; + circleBridgeAdapter: CircleBridgeAdapter__factory; + portalAdapter: PortalAdapter__factory; +}; export const liquidityLayerFactories: LiquidityLayerFactories = { - router: new LiquidityLayerRouter__factory(), circleBridgeAdapter: new CircleBridgeAdapter__factory(), portalAdapter: new PortalAdapter__factory(), - // TODO: where to put these? proxyAdmin: new ProxyAdmin__factory(), liquidityLayerRouter: new LiquidityLayerRouter__factory(), }; -export type LiquidityLayerContracts = - ProxiedRouterContracts & { - circleBridgeAdapter?: CircleBridgeAdapter; - portalAdapter?: PortalAdapter; - }; +export type LiquidityLayerContracts = ProxiedContracts & { + liquidityLayerRouter: ProxiedContract; + circleBridgeAdapter?: CircleBridgeAdapter; + portalAdapter?: PortalAdapter; +}; diff --git a/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts b/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts index 41112683d..c5437b972 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts @@ -121,7 +121,8 @@ describe('LiquidityLayerRouter', async () => { liquidityLayerApp = new LiquidityLayerApp(contracts, multiProvider, config); - local = liquidityLayerApp.getContracts(localChain).router; + local = + liquidityLayerApp.getContracts(localChain).liquidityLayerRouter.contract; }); it('can transfer tokens via Circle', async () => { diff --git a/typescript/sdk/src/middleware/query/InterchainQuery.ts b/typescript/sdk/src/middleware/query/InterchainQuery.ts new file mode 100644 index 000000000..f5f3c4ae0 --- /dev/null +++ b/typescript/sdk/src/middleware/query/InterchainQuery.ts @@ -0,0 +1,58 @@ +import { InterchainQueryRouter } from '@hyperlane-xyz/core'; + +import { + HyperlaneEnvironment, + hyperlaneEnvironments, +} from '../../consts/environments'; +import { HyperlaneAddresses } from '../../contracts'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterApp } from '../../router/RouterApps'; +import { ChainMap, ChainName } from '../../types'; + +import { + InterchainQueryContracts, + interchainQueryFactories, +} from './contracts'; + +export type InterchainQueryContractsMap = ChainMap; + +export class InterchainQuery extends RouterApp { + constructor( + contractsMap: InterchainQueryContractsMap, + multiProvider: MultiProvider, + ) { + super(contractsMap, multiProvider); + } + + router(contracts: InterchainQueryContracts): InterchainQueryRouter { + return contracts.interchainQueryRouter.contract; + } + + static fromAddresses( + addresses: ChainMap, + multiProvider: MultiProvider, + ): InterchainQuery { + const { contracts, intersectionProvider } = + this.buildContracts( + addresses, + interchainQueryFactories, + multiProvider, + ); + return new InterchainQuery(contracts, intersectionProvider); + } + + static fromEnvironment( + env: Env, + multiProvider: MultiProvider, + ): InterchainQuery { + const envAddresses = hyperlaneEnvironments[env]; + if (!envAddresses) { + throw new Error(`No addresses found for ${env}`); + } + return InterchainQuery.fromAddresses(envAddresses, multiProvider); + } + + getContracts(chain: ChainName): InterchainQueryContracts { + return super.getContracts(chain); + } +} diff --git a/typescript/sdk/src/middleware/query/InterchainQueryChecker.ts b/typescript/sdk/src/middleware/query/InterchainQueryChecker.ts new file mode 100644 index 000000000..e6ef06616 --- /dev/null +++ b/typescript/sdk/src/middleware/query/InterchainQueryChecker.ts @@ -0,0 +1,11 @@ +import { MiddlewareRouterChecker } from '../MiddlewareRouterChecker'; + +import { InterchainQuery } from './InterchainQuery'; +import { InterchainQueryConfig } from './InterchainQueryDeployer'; +import { InterchainQueryContracts } from './contracts'; + +export class InterchainQueryChecker extends MiddlewareRouterChecker< + InterchainQuery, + InterchainQueryConfig, + InterchainQueryContracts +> {} diff --git a/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts b/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts new file mode 100644 index 000000000..93443232b --- /dev/null +++ b/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts @@ -0,0 +1,31 @@ +import { InterchainQueryRouter__factory } from '@hyperlane-xyz/core'; + +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterConfig } from '../../router/types'; +import { ChainMap } from '../../types'; +import { MiddlewareRouterDeployer } from '../MiddlewareRouterDeployer'; + +import { + InterchainQueryContracts, + InterchainQueryFactories, + interchainQueryFactories, +} from './contracts'; + +export type InterchainQueryConfig = RouterConfig; + +export class InterchainQueryDeployer extends MiddlewareRouterDeployer< + InterchainQueryConfig, + InterchainQueryContracts, + InterchainQueryFactories, + InterchainQueryRouter__factory +> { + readonly routerContractName = 'interchainQueryRouter'; + + constructor( + multiProvider: MultiProvider, + configMap: ChainMap, + create2salt = 'queryrouter2', + ) { + super(multiProvider, configMap, interchainQueryFactories, create2salt); + } +} diff --git a/typescript/sdk/src/middleware/query/contracts.ts b/typescript/sdk/src/middleware/query/contracts.ts new file mode 100644 index 000000000..dfcb1672a --- /dev/null +++ b/typescript/sdk/src/middleware/query/contracts.ts @@ -0,0 +1,23 @@ +import { + InterchainQueryRouter, + InterchainQueryRouter__factory, + ProxyAdmin, + ProxyAdmin__factory, +} from '@hyperlane-xyz/core'; + +import { ProxiedContract } from '../../proxy'; + +export type InterchainQueryFactories = { + interchainQueryRouter: InterchainQueryRouter__factory; + proxyAdmin: ProxyAdmin__factory; +}; + +export const interchainQueryFactories = { + interchainQueryRouter: new InterchainQueryRouter__factory(), + proxyAdmin: new ProxyAdmin__factory(), +}; + +export type InterchainQueryContracts = { + interchainQueryRouter: ProxiedContract; + proxyAdmin: ProxyAdmin; +}; diff --git a/typescript/sdk/src/middleware/queries.hardhat-test.ts b/typescript/sdk/src/middleware/query/queries.hardhat-test.ts similarity index 66% rename from typescript/sdk/src/middleware/queries.hardhat-test.ts rename to typescript/sdk/src/middleware/query/queries.hardhat-test.ts index 9daf50ccb..29788b378 100644 --- a/typescript/sdk/src/middleware/queries.hardhat-test.ts +++ b/typescript/sdk/src/middleware/query/queries.hardhat-test.ts @@ -9,16 +9,19 @@ import { } from '@hyperlane-xyz/core'; import { utils } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata'; -import { Chains } from '../consts/chains'; -import { TestCoreApp } from '../core/TestCoreApp'; -import { TestCoreDeployer } from '../core/TestCoreDeployer'; -import { MultiProvider } from '../providers/MultiProvider'; -import { RouterConfig } from '../router/types'; -import { deployTestIgpsAndGetRouterConfig } from '../test/testUtils'; -import { ChainMap } from '../types'; +import { chainMetadata } from '../../consts/chainMetadata'; +import { Chains } from '../../consts/chains'; +import { TestCoreApp } from '../../core/TestCoreApp'; +import { TestCoreDeployer } from '../../core/TestCoreDeployer'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterConfig } from '../../router/types'; +import { deployTestIgpsAndGetRouterConfig } from '../../test/testUtils'; +import { ChainMap } from '../../types'; -import { InterchainQueryDeployer } from './deploy'; +import { InterchainQuery } from './InterchainQuery'; +import { InterchainQueryChecker } from './InterchainQueryChecker'; +import { InterchainQueryDeployer } from './InterchainQueryDeployer'; +import { InterchainQueryContracts } from './contracts'; describe('InterchainQueryRouter', async () => { const localChain = Chains.test1; @@ -26,6 +29,7 @@ describe('InterchainQueryRouter', async () => { const localDomain = chainMetadata[localChain].chainId; const remoteDomain = chainMetadata[remoteChain].chainId; + let contracts: ChainMap; let signer: SignerWithAddress; let local: InterchainQueryRouter; let remote: InterchainQueryRouter; @@ -52,14 +56,21 @@ describe('InterchainQueryRouter', async () => { beforeEach(async () => { const InterchainQuery = new InterchainQueryDeployer(multiProvider, config); - const contracts = await InterchainQuery.deploy(); + contracts = await InterchainQuery.deploy(); - local = contracts[localChain].router; - remote = contracts[remoteChain].router; + local = contracts[localChain].interchainQueryRouter.contract; + remote = contracts[remoteChain].interchainQueryRouter.contract; testQuery = await new TestQuery__factory(signer).deploy(local.address); }); + it('checks', async () => { + const app = new InterchainQuery(contracts, multiProvider); + const checker = new InterchainQueryChecker(multiProvider, app, config); + await checker.check(); + expect(checker.violations.length).to.eql(0); + }); + it('completes query round trip and invokes callback', async () => { const secret = 123; const sender = testQuery.address; diff --git a/typescript/sdk/src/proxy.ts b/typescript/sdk/src/proxy.ts index 4ebd80685..33d7a12bd 100644 --- a/typescript/sdk/src/proxy.ts +++ b/typescript/sdk/src/proxy.ts @@ -59,3 +59,18 @@ export class ProxiedContract< ); } } + +export function isProxiedContract( + contract: unknown, +): contract is ProxiedContract { + // The presence of `implementation` is intentionally not checked + // to allow deploying new implementations by deleting the implementation + // from the artifacts + return ( + contract !== null && + typeof contract === 'object' && + 'addresses' in contract && + 'contract' in contract && + isProxyAddresses((contract as any).addresses) + ); +} diff --git a/typescript/sdk/src/router/GasRouterDeployer.ts b/typescript/sdk/src/router/GasRouterDeployer.ts index dfc251fd4..902d343c5 100644 --- a/typescript/sdk/src/router/GasRouterDeployer.ts +++ b/typescript/sdk/src/router/GasRouterDeployer.ts @@ -1,23 +1,22 @@ import { debug } from 'debug'; -import { GasRouter } from '@hyperlane-xyz/core'; - +import { HyperlaneFactories } from '../contracts'; import { DeployerOptions } from '../deploy/HyperlaneDeployer'; import { MultiProvider } from '../providers/MultiProvider'; import { ChainMap } from '../types'; import { HyperlaneRouterDeployer } from './HyperlaneRouterDeployer'; -import { GasRouterConfig, RouterContracts, RouterFactories } from './types'; +import { GasRouterContracts } from './RouterApps'; +import { GasRouterConfig } from './types'; export abstract class GasRouterDeployer< Config extends GasRouterConfig, - Contracts extends RouterContracts, - Factories extends RouterFactories, -> extends HyperlaneRouterDeployer { + Contracts extends GasRouterContracts, +> extends HyperlaneRouterDeployer { constructor( multiProvider: MultiProvider, configMap: ChainMap, - factories: Factories, + factories: HyperlaneFactories, options?: DeployerOptions, ) { super(multiProvider, configMap, factories, { diff --git a/typescript/sdk/src/router/HyperlaneRouterChecker.ts b/typescript/sdk/src/router/HyperlaneRouterChecker.ts index 2a618a4a6..37f4a935e 100644 --- a/typescript/sdk/src/router/HyperlaneRouterChecker.ts +++ b/typescript/sdk/src/router/HyperlaneRouterChecker.ts @@ -1,24 +1,22 @@ import { ethers } from 'ethers'; -import { Ownable } from '@hyperlane-xyz/core'; import { utils } from '@hyperlane-xyz/utils'; -import { HyperlaneApp } from '../HyperlaneApp'; +import { HyperlaneContracts } from '../contracts'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker'; -import { RouterContracts } from '../router/types'; import { ChainName } from '../types'; +import { RouterApp } from './RouterApps'; import { RouterConfig } from './types'; export class HyperlaneRouterChecker< - App extends HyperlaneApp, + App extends RouterApp, Config extends RouterConfig, - Contracts extends RouterContracts, + Contracts extends HyperlaneContracts, > extends HyperlaneAppChecker { checkOwnership(chain: ChainName): Promise { const owner = this.configMap[chain].owner; - const ownables = this.ownables(chain); - return super.checkOwnership(chain, owner, ownables); + return super.checkOwnership(chain, owner); } async checkChain(chain: ChainName): Promise { @@ -28,7 +26,7 @@ export class HyperlaneRouterChecker< } async checkHyperlaneConnectionClient(chain: ChainName): Promise { - const router = this.app.getContracts(chain).router; + const router = this.app.router(this.app.getContracts(chain)); const mailbox = await router.mailbox(); const igp = await router.interchainGasPaymaster(); const ism = await router.interchainSecurityModule(); @@ -48,19 +46,17 @@ export class HyperlaneRouterChecker< } async checkEnrolledRouters(chain: ChainName): Promise { - const router = this.app.getContracts(chain).router; + const router = this.app.router(this.app.getContracts(chain)); await Promise.all( this.app.remoteChains(chain).map(async (remoteChain) => { - const remoteRouter = this.app.getContracts(remoteChain).router; + const remoteRouter = this.app.router( + this.app.getContracts(remoteChain), + ); const remoteDomainId = this.multiProvider.getDomainId(remoteChain); const address = await router.routers(remoteDomainId); utils.assert(address === utils.addressToBytes32(remoteRouter.address)); }), ); } - - ownables(chain: ChainName): Ownable[] { - return [this.app.getContracts(chain).router]; - } } diff --git a/typescript/sdk/src/router/HyperlaneRouterDeployer.ts b/typescript/sdk/src/router/HyperlaneRouterDeployer.ts index 41830cda1..d42f6322e 100644 --- a/typescript/sdk/src/router/HyperlaneRouterDeployer.ts +++ b/typescript/sdk/src/router/HyperlaneRouterDeployer.ts @@ -1,24 +1,22 @@ import { debug } from 'debug'; +import { Router } from '@hyperlane-xyz/core'; import { utils } from '@hyperlane-xyz/utils'; +import { HyperlaneContracts, HyperlaneFactories } from '../contracts'; import { DeployerOptions, HyperlaneDeployer, } from '../deploy/HyperlaneDeployer'; import { MultiProvider } from '../providers/MultiProvider'; -import { - RouterConfig, - RouterContracts, - RouterFactories, -} from '../router/types'; +import { RouterConfig } from '../router/types'; import { ChainMap } from '../types'; import { objMap, promiseObjAll } from '../utils/objects'; export abstract class HyperlaneRouterDeployer< Config extends RouterConfig, - Contracts extends RouterContracts, - Factories extends RouterFactories, + Contracts extends HyperlaneContracts, + Factories extends HyperlaneFactories, > extends HyperlaneDeployer { constructor( multiProvider: MultiProvider, @@ -32,6 +30,8 @@ export abstract class HyperlaneRouterDeployer< }); } + abstract router(contracts: Contracts): Router; + async initConnectionClients( contractsMap: ChainMap, ): Promise { @@ -39,24 +39,20 @@ export abstract class HyperlaneRouterDeployer< objMap(contractsMap, async (local, contracts) => super.initConnectionClient( local, - contracts.router, + this.router(contracts), this.configMap[local], ), ), ); } - async enrollRemoteRouters( - contractsMap: ChainMap, - ): Promise { + async enrollRemoteRouters(contractsMap: ChainMap): Promise { this.logger( `Enrolling deployed routers with each other (if not already)...`, ); // Make all routers aware of each other. const deployedChains = Object.keys(contractsMap); - for (const [chain, contracts] of Object.entries( - contractsMap, - )) { + for (const [chain, contracts] of Object.entries(contractsMap)) { // only enroll chains which are deployed const deployedRemoteChains = this.multiProvider .getRemoteChains(chain) @@ -65,9 +61,9 @@ export abstract class HyperlaneRouterDeployer< const enrollEntries = await Promise.all( deployedRemoteChains.map(async (remote) => { const remoteDomain = this.multiProvider.getDomainId(remote); - const current = await contracts.router.routers(remoteDomain); + const current = await this.router(contracts).routers(remoteDomain); const expected = utils.addressToBytes32( - contractsMap[remote].router.address, + this.router(contractsMap[remote]).address, ); return current !== expected ? [remoteDomain, expected] : undefined; }), @@ -83,14 +79,14 @@ export abstract class HyperlaneRouterDeployer< return; } - await super.runIfOwner(chain, contracts.router, async () => { + await super.runIfOwner(chain, this.router(contracts), async () => { const chains = domains.map((id) => this.multiProvider.getChainName(id)); this.logger( `Enrolling remote routers (${chains.join(', ')}) on ${chain}`, ); await this.multiProvider.handleTx( chain, - contracts.router.enrollRemoteRouters( + this.router(contracts).enrollRemoteRouters( domains, addresses, this.multiProvider.getTransactionOverrides(chain), @@ -105,13 +101,13 @@ export abstract class HyperlaneRouterDeployer< await promiseObjAll( objMap(contractsMap, async (chain, contracts) => { const owner = this.configMap[chain].owner; - const currentOwner = await contracts.router.owner(); + const currentOwner = await this.router(contracts).owner(); if (owner != currentOwner) { this.logger(`Transfer ownership of ${chain}'s router to ${owner}`); - await super.runIfOwner(chain, contracts.router, async () => { + await super.runIfOwner(chain, this.router(contracts), async () => { await this.multiProvider.handleTx( chain, - contracts.router.transferOwnership( + this.router(contracts).transferOwnership( owner, this.multiProvider.getTransactionOverrides(chain), ), diff --git a/typescript/sdk/src/router/RouterApps.ts b/typescript/sdk/src/router/RouterApps.ts index 3801cf850..ac6ccfa4d 100644 --- a/typescript/sdk/src/router/RouterApps.ts +++ b/typescript/sdk/src/router/RouterApps.ts @@ -1,35 +1,48 @@ import type { BigNumber } from 'ethers'; -import { GasRouter } from '@hyperlane-xyz/core'; +import { GasRouter, GasRouter__factory, Router } from '@hyperlane-xyz/core'; import type { types } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../HyperlaneApp'; +import { HyperlaneContracts } from '../contracts'; import { ChainMap, ChainName } from '../types'; import { objMap, promiseObjAll } from '../utils/objects'; -import { RouterContracts } from './types'; - export { Router } from '@hyperlane-xyz/core'; -export class RouterApp< - Contracts extends RouterContracts, +export abstract class RouterApp< + Contracts extends HyperlaneContracts, > extends HyperlaneApp { + abstract router(contracts: Contracts): Router; + getSecurityModules = (): Promise> => promiseObjAll( objMap(this.contractsMap, (_, contracts) => - contracts.router.interchainSecurityModule(), + this.router(contracts).interchainSecurityModule(), ), ); getOwners = (): Promise> => promiseObjAll( - objMap(this.contractsMap, (_, contracts) => contracts.router.owner()), + objMap(this.contractsMap, (_, contracts) => + this.router(contracts).owner(), + ), ); } -export class GasRouterApp< - Contracts extends RouterContracts, -> extends RouterApp { +export type GasRouterContracts = { + router: GasRouter; +}; + +export type GasRouterFactories = { + router: GasRouter__factory; +}; + +export class GasRouterApp extends RouterApp { + router(contracts: GasRouterContracts): GasRouter { + return contracts.router; + } + async quoteGasPayment( origin: ChainName, destination: ChainName, diff --git a/typescript/sdk/src/router/app.ts b/typescript/sdk/src/router/app.ts deleted file mode 100644 index 450ecdd46..000000000 --- a/typescript/sdk/src/router/app.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { BigNumber } from 'ethers'; - -import { GasRouter } from '@hyperlane-xyz/core'; -import { types } from '@hyperlane-xyz/utils'; - -import { HyperlaneApp } from '../HyperlaneApp'; -import { ChainMap, ChainName } from '../types'; -import { objMap, promiseObjAll } from '../utils/objects'; - -import { RouterContracts } from './types'; - -export class RouterApp< - Contracts extends RouterContracts, -> extends HyperlaneApp { - getSecurityModules = (): Promise> => - promiseObjAll( - objMap(this.contractsMap, (_, contracts) => - contracts.router.interchainSecurityModule(), - ), - ); - - getOwners = (): Promise> => - promiseObjAll( - objMap(this.contractsMap, (_, contracts) => contracts.router.owner()), - ); -} - -export class GasRouterApp< - Contracts extends RouterContracts, -> extends RouterApp { - async quoteGasPayment( - origin: ChainName, - destination: ChainName, - ): Promise { - return this.getContracts(origin).router.quoteGasPayment( - this.multiProvider.getDomainId(destination), - ); - } -} diff --git a/typescript/sdk/src/router/types.ts b/typescript/sdk/src/router/types.ts index 7de279275..ed35f07d7 100644 --- a/typescript/sdk/src/router/types.ts +++ b/typescript/sdk/src/router/types.ts @@ -1,10 +1,7 @@ -import { ethers } from 'ethers'; - -import { ProxyAdmin, ProxyAdmin__factory, Router } from '@hyperlane-xyz/core'; +import { ProxyAdmin, ProxyAdmin__factory } from '@hyperlane-xyz/core'; import type { types } from '@hyperlane-xyz/utils'; import { HyperlaneContracts, HyperlaneFactories } from '../contracts'; -import { ProxiedContract, TransparentProxyAddresses } from '../proxy'; export type OwnableConfig = { owner: types.Address; @@ -18,31 +15,13 @@ type GasConfig = { export type GasRouterConfig = RouterConfig & GasConfig; -export type RouterContracts = - HyperlaneContracts & { - router: RouterContract; - }; - -export type ProxiedRouterContracts = - RouterContracts & { - proxyAdmin: ProxyAdmin; - proxiedRouter: ProxiedContract; - }; - -type RouterFactory = - ethers.ContractFactory & { - deploy: (...args: any[]) => Promise; - }; - -export type RouterFactories = - HyperlaneFactories & { - router: RouterFactory; - }; - -export type ProxiedRouterFactories = - RouterFactories & { - proxyAdmin: ProxyAdmin__factory; - }; +export type ProxiedFactories = HyperlaneFactories & { + proxyAdmin: ProxyAdmin__factory; +}; + +export type ProxiedContracts = HyperlaneContracts & { + proxyAdmin: ProxyAdmin; +}; export type ConnectionClientConfig = { mailbox: types.Address; diff --git a/typescript/sdk/src/test/envSubsetDeployer/app.ts b/typescript/sdk/src/test/envSubsetDeployer/app.ts index 9d8f07d26..824f894b7 100644 --- a/typescript/sdk/src/test/envSubsetDeployer/app.ts +++ b/typescript/sdk/src/test/envSubsetDeployer/app.ts @@ -1,6 +1,5 @@ -import { TestRouter__factory } from '@hyperlane-xyz/core'; +import { TestRouter, TestRouter__factory } from '@hyperlane-xyz/core'; -import { HyperlaneApp } from '../../HyperlaneApp'; import { chainMetadata } from '../../consts/chainMetadata'; import { Chains } from '../../consts/chains'; import { HyperlaneCore } from '../../core/HyperlaneCore'; @@ -8,43 +7,56 @@ import { HyperlaneDeployer } from '../../deploy/HyperlaneDeployer'; import { MultiProvider } from '../../providers/MultiProvider'; import { HyperlaneRouterChecker } from '../../router/HyperlaneRouterChecker'; import { HyperlaneRouterDeployer } from '../../router/HyperlaneRouterDeployer'; -import { - RouterConfig, - RouterContracts, - RouterFactories, -} from '../../router/types'; +import { RouterApp } from '../../router/RouterApps'; +import { RouterConfig } from '../../router/types'; import { ChainMap, ChainName } from '../../types'; import { objMap, pick, promiseObjAll } from '../../utils/objects'; export const alfajoresChainConfig = pick(chainMetadata, [Chains.alfajores]); -export class EnvSubsetApp extends HyperlaneApp {} +export type TestRouterContracts = { + router: TestRouter; +}; + +export type TestRouterFactories = { + router: TestRouter__factory; +}; + +export const testRouterFactories: TestRouterFactories = { + router: new TestRouter__factory(), +}; + +export class EnvSubsetApp extends RouterApp { + router(contracts: TestRouterContracts) { + return contracts.router; + } +} export class EnvSubsetChecker extends HyperlaneRouterChecker< EnvSubsetApp, RouterConfig, - RouterContracts + TestRouterContracts > {} -export const envSubsetFactories: RouterFactories = { - router: new TestRouter__factory(), -}; - export class EnvSubsetDeployer extends HyperlaneRouterDeployer< RouterConfig, - RouterContracts, - RouterFactories + TestRouterContracts, + TestRouterFactories > { constructor( multiProvider: MultiProvider, configMap: ChainMap, protected core: HyperlaneCore, ) { - super(multiProvider, configMap, envSubsetFactories, {}); + super(multiProvider, configMap, testRouterFactories, {}); + } + + router(contracts: TestRouterContracts): TestRouter { + return contracts.router; } // Consider moving this up to HyperlaneRouterDeployer - async initRouter(contractsMap: ChainMap): Promise { + async initRouter(contractsMap: ChainMap): Promise { this.logger(`Calling initialize on routers...`); await promiseObjAll( objMap(contractsMap, async (chain, contracts) => { @@ -53,17 +65,16 @@ export class EnvSubsetDeployer extends HyperlaneRouterDeployer< const overrides = this.multiProvider.getTransactionOverrides(chain); await this.multiProvider.handleTx( chain, - // @ts-ignore TestRouter does implement this, though Router type does not - contracts.router.initialize(mailbox, igp, overrides), + this.router(contracts).initialize(mailbox, igp, overrides), ); }), ); } - async deploy(): Promise> { + async deploy(): Promise> { const contractsMap = (await HyperlaneDeployer.prototype.deploy.apply( this, - )) as ChainMap; + )) as ChainMap; await this.initRouter(contractsMap); await this.enrollRemoteRouters(contractsMap); return contractsMap; diff --git a/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts b/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts index 6d18374ed..eff931a34 100644 --- a/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts +++ b/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts @@ -2,11 +2,15 @@ import { buildContracts } from '../../contracts'; import { HyperlaneCore } from '../../core/HyperlaneCore'; import { HyperlaneIgp } from '../../gas/HyperlaneIgp'; import { MultiProvider } from '../../providers/MultiProvider'; -import { RouterContracts } from '../../router/types'; import { ChainMap } from '../../types'; import { createRouterConfigMap } from '../testUtils'; -import { EnvSubsetApp, EnvSubsetChecker, envSubsetFactories } from './app'; +import { + EnvSubsetApp, + EnvSubsetChecker, + TestRouterContracts, + testRouterFactories, +} from './app'; // Copied from output of deploy-single-chain.ts script const deploymentAddresses = { @@ -23,8 +27,8 @@ async function check() { const contractsMap = buildContracts( deploymentAddresses, - envSubsetFactories, - ) as ChainMap; + testRouterFactories, + ) as ChainMap; const app = new EnvSubsetApp(contractsMap, multiProvider); const core = HyperlaneCore.fromEnvironment('testnet', multiProvider); const igp = HyperlaneIgp.fromEnvironment('testnet', multiProvider); diff --git a/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts b/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts index ac6ad94b2..f83c75fd1 100644 --- a/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts +++ b/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts @@ -5,18 +5,23 @@ import { TestChains } from '../../consts/chains'; import { TestCoreApp } from '../../core/TestCoreApp'; import { TestCoreDeployer } from '../../core/TestCoreDeployer'; import { MultiProvider } from '../../providers/MultiProvider'; -import { RouterConfig, RouterContracts } from '../../router/types'; +import { RouterConfig } from '../../router/types'; import { ChainMap, ChainName } from '../../types'; import { deployTestIgpsAndGetRouterConfig, testCoreConfig } from '../testUtils'; -import { EnvSubsetApp, EnvSubsetChecker, EnvSubsetDeployer } from './app'; +import { + EnvSubsetApp, + EnvSubsetChecker, + EnvSubsetDeployer, + TestRouterContracts, +} from './app'; // Tests deploying the basic EnvSubsetApp to a local hardhat-based test env describe('deploy app for full test env', async () => { let multiProvider: MultiProvider; let config: ChainMap; let deployer: EnvSubsetDeployer; - let contracts: ChainMap; + let contracts: ChainMap; let app: EnvSubsetApp; before(async () => { @@ -46,7 +51,7 @@ describe('deploy app to test env subset', async () => { let multiProvider: MultiProvider; let config: ChainMap; let deployer: EnvSubsetDeployer; - let contracts: ChainMap; + let contracts: ChainMap; let app: EnvSubsetApp; before(async () => { diff --git a/typescript/token b/typescript/token index df21762a2..306b107e6 160000 --- a/typescript/token +++ b/typescript/token @@ -1 +1 @@ -Subproject commit df21762a269d3d813fa28799a1a0216d5457e008 +Subproject commit 306b107e662c4c87e963693bfe29cf2985f3090f