From 44df310300159257907630cf1ce03de9711d128f Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 28 Jul 2022 10:59:27 -0400 Subject: [PATCH] Support env subset deployments (#767) - Support env subset deployements in AbacusCore and TestCore - Harden MultiProvider against missing chain values - Add `tryGet*` methods to MultiProvider and other convenience methods - Add `getMultiProviderFromConfigAndProvider` and `getChainToOwnerMap` utils to SDK - Make `enrollRemoteRouters` static in `AbacusRouterDeployer` - Create minimal envSubsetDeployer app in SDK to test subset chain deployments - Reduce boilerplate needed for HelloWorld app - Restructure HelloWorld check and deploy scripts to be more realistic - Add `pick` util function for objects - BREAKING CHANGE: Make order of generic types consistent across core classes - BREAKING CHANGE: Replace `getMultiProviderFromSignerAndConfig` with `getTestMultiProvider` --- .../helloworld/src/app/environments/test.json | 11 -- typescript/helloworld/src/deploy/check.ts | 4 +- typescript/helloworld/src/deploy/config.ts | 32 +---- typescript/helloworld/src/deploy/deploy.ts | 4 +- typescript/helloworld/src/scripts/check.ts | 35 +++--- typescript/helloworld/src/scripts/deploy.ts | 28 +++-- typescript/helloworld/src/test/deploy.test.ts | 10 +- .../helloworld/src/test/helloworld.test.ts | 10 +- .../infra/config/environments/test/index.ts | 4 +- typescript/infra/hardhat.config.ts | 6 +- typescript/infra/test/core.test.ts | 7 +- typescript/sdk/package.json | 1 + .../sdk/src/consts/chainConnectionConfigs.ts | 6 + typescript/sdk/src/core/AbacusCore.ts | 34 ++++-- typescript/sdk/src/core/TestCoreApp.ts | 13 +- typescript/sdk/src/core/TestCoreDeployer.ts | 31 +++-- .../src/core/testAbacusDeploy.hardhat-test.ts | 9 +- typescript/sdk/src/deploy/AbacusDeployer.ts | 2 +- .../sdk/src/deploy/core/AbacusCoreDeployer.ts | 4 +- .../src/deploy/router/AbacusRouterChecker.ts | 2 +- .../src/deploy/router/AbacusRouterDeployer.ts | 6 +- typescript/sdk/src/deploy/utils.ts | 39 ++++-- typescript/sdk/src/index.ts | 7 +- typescript/sdk/src/providers/MultiProvider.ts | 115 +++++++++++++++++- typescript/sdk/src/test/.eslintrc | 1 + .../sdk/src/test/envSubsetDeployer/README.md | 8 ++ .../sdk/src/test/envSubsetDeployer/app.ts | 97 +++++++++++++++ .../envSubsetDeployer/check-single-chain.ts | 55 +++++++++ .../envSubsetDeployer/deploy-single-chain.ts | 36 ++++++ .../envSubsetDeployer/deploy.hardhat-test.ts | 96 +++++++++++++++ .../sdk/src/test/envSubsetDeployer/utils.ts | 23 ++++ typescript/sdk/src/types.ts | 6 + typescript/sdk/src/utils/MultiGeneric.ts | 65 ++++++++-- typescript/sdk/src/utils/objects.ts | 17 ++- yarn.lock | 1 + 35 files changed, 668 insertions(+), 157 deletions(-) delete mode 100644 typescript/helloworld/src/app/environments/test.json create mode 100644 typescript/sdk/src/test/envSubsetDeployer/README.md create mode 100644 typescript/sdk/src/test/envSubsetDeployer/app.ts create mode 100644 typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts create mode 100644 typescript/sdk/src/test/envSubsetDeployer/deploy-single-chain.ts create mode 100644 typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts create mode 100644 typescript/sdk/src/test/envSubsetDeployer/utils.ts diff --git a/typescript/helloworld/src/app/environments/test.json b/typescript/helloworld/src/app/environments/test.json deleted file mode 100644 index ce558dac3..000000000 --- a/typescript/helloworld/src/app/environments/test.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "test1": { - "router": "0x5FbDB2315678afecb367f032d93F642f64180aa3" - }, - "test2": { - "router": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" - }, - "test3": { - "router": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" - } -} diff --git a/typescript/helloworld/src/deploy/check.ts b/typescript/helloworld/src/deploy/check.ts index 7e9c10817..fb87a9818 100644 --- a/typescript/helloworld/src/deploy/check.ts +++ b/typescript/helloworld/src/deploy/check.ts @@ -9,7 +9,7 @@ export class HelloWorldChecker< Chain extends ChainName, > extends AbacusRouterChecker< Chain, - HelloWorldContracts, HelloWorldApp, - HelloWorldConfig + HelloWorldConfig, + HelloWorldContracts > {} diff --git a/typescript/helloworld/src/deploy/config.ts b/typescript/helloworld/src/deploy/config.ts index ff4979ead..c2c84432f 100644 --- a/typescript/helloworld/src/deploy/config.ts +++ b/typescript/helloworld/src/deploy/config.ts @@ -1,32 +1,8 @@ -import { - ChainMap, - RouterConfig, - TestChainNames, - chainConnectionConfigs, -} from '@abacus-network/sdk'; +import { RouterConfig, chainConnectionConfigs } from '@abacus-network/sdk'; export type HelloWorldConfig = RouterConfig; -// TODO reduce this config boilerplate - -export const testConfigs = { - test1: chainConnectionConfigs.test1, - test2: chainConnectionConfigs.test2, - test3: chainConnectionConfigs.test3, +// SET DESIRED NETWORKS HERE +export const prodConfigs = { + alfajores: chainConnectionConfigs.alfajores, }; - -export function getConfigMap( - signerAddress: string, -): ChainMap { - return { - test1: { - owner: signerAddress, - }, - test2: { - owner: signerAddress, - }, - test3: { - owner: signerAddress, - }, - }; -} diff --git a/typescript/helloworld/src/deploy/deploy.ts b/typescript/helloworld/src/deploy/deploy.ts index 0fce59d19..6245aeb6c 100644 --- a/typescript/helloworld/src/deploy/deploy.ts +++ b/typescript/helloworld/src/deploy/deploy.ts @@ -18,9 +18,9 @@ export class HelloWorldDeployer< Chain extends ChainName, > extends AbacusRouterDeployer< Chain, + HelloWorldConfig, HelloWorldContracts, - HelloWorldFactories, - HelloWorldConfig + HelloWorldFactories > { constructor( multiProvider: MultiProvider, diff --git a/typescript/helloworld/src/scripts/check.ts b/typescript/helloworld/src/scripts/check.ts index 9f2645fd7..2aea704b1 100644 --- a/typescript/helloworld/src/scripts/check.ts +++ b/typescript/helloworld/src/scripts/check.ts @@ -1,38 +1,45 @@ -import { ethers } from 'hardhat'; - import { AbacusCore, ChainMap, ChainName, + MultiProvider, buildContracts, - getMultiProviderFromConfigAndSigner, + getChainToOwnerMap, + objMap, } from '@abacus-network/sdk'; import { HelloWorldApp } from '../app/app'; import { HelloWorldContracts, helloWorldFactories } from '../app/contracts'; -import testEnvironmentAddresses from '../app/environments/test.json'; import { HelloWorldChecker } from '../deploy/check'; -import { getConfigMap, testConfigs } from '../deploy/config'; +import { prodConfigs } from '../deploy/config'; + +// COPY FROM OUTPUT OF DEPLOYMENT SCRIPT OR IMPORT FROM ELSEWHERE +const deploymentAddresses = {}; + +// SET CONTRACT OWNER ADDRESS HERE +const ownerAddress = '0x123...'; async function check() { - const [signer] = await ethers.getSigners(); - const multiProvider = getMultiProviderFromConfigAndSigner( - testConfigs, - signer, - ); + console.info('Preparing utilities'); + const chainProviders = objMap(prodConfigs, (_, config) => ({ + provider: config.provider, + confirmations: config.confirmations, + overrides: config.overrides, + })); + const multiProvider = new MultiProvider(chainProviders); const contractsMap = buildContracts( - testEnvironmentAddresses, + deploymentAddresses, helloWorldFactories, ) as ChainMap; - const app = new HelloWorldApp(contractsMap, multiProvider); - const core = AbacusCore.fromEnvironment('test', multiProvider); + const core = AbacusCore.fromEnvironment('testnet2', multiProvider); const config = core.extendWithConnectionClientConfig( - getConfigMap(signer.address), + getChainToOwnerMap(prodConfigs, ownerAddress), ); + console.info('Starting check'); const helloWorldChecker = new HelloWorldChecker(multiProvider, app, config); await helloWorldChecker.check(); helloWorldChecker.expectEmpty(); diff --git a/typescript/helloworld/src/scripts/deploy.ts b/typescript/helloworld/src/scripts/deploy.ts index 8d5b613a1..5348a9942 100644 --- a/typescript/helloworld/src/scripts/deploy.ts +++ b/typescript/helloworld/src/scripts/deploy.ts @@ -1,25 +1,31 @@ -import '@nomiclabs/hardhat-ethers'; -import { ethers } from 'hardhat'; +import { Wallet } from 'ethers'; import { AbacusCore, - getMultiProviderFromConfigAndSigner, + MultiProvider, + getChainToOwnerMap, + objMap, serializeContracts, } from '@abacus-network/sdk'; -import { getConfigMap, testConfigs } from '../deploy/config'; +import { prodConfigs } from '../deploy/config'; import { HelloWorldDeployer } from '../deploy/deploy'; async function main() { - const [signer] = await ethers.getSigners(); - const multiProvider = getMultiProviderFromConfigAndSigner( - testConfigs, - signer, - ); + console.info('Getting signer'); + const signer = new Wallet('SET KEY HERE OR CREATE YOUR OWN SIGNER'); + + console.info('Preparing utilities'); + const chainProviders = objMap(prodConfigs, (_, config) => ({ + provider: config.provider, + confirmations: config.confirmations, + overrides: config.overrides, + })); + const multiProvider = new MultiProvider(chainProviders); - const core = AbacusCore.fromEnvironment('test', multiProvider); + const core = AbacusCore.fromEnvironment('testnet2', multiProvider); const config = core.extendWithConnectionClientConfig( - getConfigMap(signer.address), + getChainToOwnerMap(prodConfigs, signer.address), ); const deployer = new HelloWorldDeployer(multiProvider, config, core); diff --git a/typescript/helloworld/src/test/deploy.test.ts b/typescript/helloworld/src/test/deploy.test.ts index 25eba98d9..5587efc60 100644 --- a/typescript/helloworld/src/test/deploy.test.ts +++ b/typescript/helloworld/src/test/deploy.test.ts @@ -7,13 +7,15 @@ import { TestChainNames, TestCoreApp, TestCoreDeployer, - getMultiProviderFromConfigAndSigner, + getChainToOwnerMap, + getTestMultiProvider, + testChainConnectionConfigs, } from '@abacus-network/sdk'; import { HelloWorldApp } from '../app/app'; import { HelloWorldContracts } from '../app/contracts'; import { HelloWorldChecker } from '../deploy/check'; -import { HelloWorldConfig, getConfigMap, testConfigs } from '../deploy/config'; +import { HelloWorldConfig } from '../deploy/config'; import { HelloWorldDeployer } from '../deploy/deploy'; describe('deploy', async () => { @@ -26,13 +28,13 @@ describe('deploy', async () => { before(async () => { const [signer] = await ethers.getSigners(); - multiProvider = getMultiProviderFromConfigAndSigner(testConfigs, signer); + multiProvider = getTestMultiProvider(signer); const coreDeployer = new TestCoreDeployer(multiProvider); const coreContractsMaps = await coreDeployer.deploy(); core = new TestCoreApp(coreContractsMaps, multiProvider); config = core.extendWithConnectionClientConfig( - getConfigMap(signer.address), + getChainToOwnerMap(testChainConnectionConfigs, signer.address), ); deployer = new HelloWorldDeployer(multiProvider, config, core); }); diff --git a/typescript/helloworld/src/test/helloworld.test.ts b/typescript/helloworld/src/test/helloworld.test.ts index ddeaa546c..c3b9246f5 100644 --- a/typescript/helloworld/src/test/helloworld.test.ts +++ b/typescript/helloworld/src/test/helloworld.test.ts @@ -9,10 +9,12 @@ import { TestChainNames, TestCoreApp, TestCoreDeployer, - getMultiProviderFromConfigAndSigner, + getChainToOwnerMap, + getTestMultiProvider, + testChainConnectionConfigs, } from '@abacus-network/sdk'; -import { HelloWorldConfig, getConfigMap, testConfigs } from '../deploy/config'; +import { HelloWorldConfig } from '../deploy/config'; import { HelloWorldDeployer } from '../deploy/deploy'; import { HelloWorld } from '../types'; @@ -32,13 +34,13 @@ describe('HelloWorld', async () => { before(async () => { [signer] = await ethers.getSigners(); - multiProvider = getMultiProviderFromConfigAndSigner(testConfigs, signer); + multiProvider = getTestMultiProvider(signer); const coreDeployer = new TestCoreDeployer(multiProvider); const coreContractsMaps = await coreDeployer.deploy(); coreApp = new TestCoreApp(coreContractsMaps, multiProvider); config = coreApp.extendWithConnectionClientConfig( - getConfigMap(signer.address), + getChainToOwnerMap(testChainConnectionConfigs, signer.address), ); }); diff --git a/typescript/infra/config/environments/test/index.ts b/typescript/infra/config/environments/test/index.ts index ba3206f04..2d2cf5eea 100644 --- a/typescript/infra/config/environments/test/index.ts +++ b/typescript/infra/config/environments/test/index.ts @@ -1,6 +1,6 @@ import { JsonRpcProvider } from '@ethersproject/providers'; -import { getMultiProviderFromConfigAndSigner } from '@abacus-network/sdk'; +import { getTestMultiProvider } from '@abacus-network/sdk'; import { CoreEnvironmentConfig } from '../../../src/config'; @@ -19,6 +19,6 @@ export const environment: CoreEnvironmentConfig = { getMultiProvider: async () => { const provider = testConfigs.test1.provider! as JsonRpcProvider; const signer = provider.getSigner(0); - return getMultiProviderFromConfigAndSigner(testConfigs, signer); + return getTestMultiProvider(signer, testConfigs); }, }; diff --git a/typescript/infra/hardhat.config.ts b/typescript/infra/hardhat.config.ts index 97a3b3f41..cc1eca547 100644 --- a/typescript/infra/hardhat.config.ts +++ b/typescript/infra/hardhat.config.ts @@ -8,7 +8,7 @@ import { AbacusCore, ChainName, ChainNameToDomainId, - getMultiProviderFromConfigAndSigner, + getTestMultiProvider, } from '@abacus-network/sdk'; import { getCoreEnvironmentConfig } from './scripts/utils'; @@ -63,9 +63,9 @@ task('kathy', 'Dispatches random abacus messages') const interchainGasPayment = hre.ethers.utils.parseUnits('100', 'gwei'); const config = getCoreEnvironmentConfig(environment); const [signer] = await hre.ethers.getSigners(); - const multiProvider = getMultiProviderFromConfigAndSigner( - config.transactionConfigs, + const multiProvider = getTestMultiProvider( signer, + config.transactionConfigs, ); const core = AbacusCore.fromEnvironment(environment, multiProvider); diff --git a/typescript/infra/test/core.test.ts b/typescript/infra/test/core.test.ts index 9754583ea..1b0963e1e 100644 --- a/typescript/infra/test/core.test.ts +++ b/typescript/infra/test/core.test.ts @@ -12,7 +12,7 @@ import { CoreConfig, CoreContractsMap, MultiProvider, - getMultiProviderFromConfigAndSigner, + getTestMultiProvider, objMap, serializeContracts, } from '@abacus-network/sdk'; @@ -35,10 +35,7 @@ describe('core', async () => { beforeEach(async () => { const [signer, owner] = await ethers.getSigners(); // This is kind of awkward and really these tests shouldn't live here - multiProvider = getMultiProviderFromConfigAndSigner( - testConfig.transactionConfigs, - signer, - ); + multiProvider = getTestMultiProvider(signer, testConfig.transactionConfigs); coreConfig = testConfig.core; deployer = new AbacusCoreInfraDeployer(multiProvider, coreConfig); owners = objMap(testConfig.transactionConfigs, () => owner.address); diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 8eb3bb4e6..1a701651f 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -26,6 +26,7 @@ "mocha": "^9.2.2", "prettier": "^2.4.1", "sinon": "^13.0.2", + "ts-node": "^10.8.0", "typescript": "^4.7.2" }, "files": [ diff --git a/typescript/sdk/src/consts/chainConnectionConfigs.ts b/typescript/sdk/src/consts/chainConnectionConfigs.ts index 0c5eee936..09f875ae7 100644 --- a/typescript/sdk/src/consts/chainConnectionConfigs.ts +++ b/typescript/sdk/src/consts/chainConnectionConfigs.ts @@ -189,3 +189,9 @@ export const chainConnectionConfigs: ChainMap = { test2, test3, }; + +export const testChainConnectionConfigs = { + test1, + test2, + test3, +}; diff --git a/typescript/sdk/src/core/AbacusCore.ts b/typescript/sdk/src/core/AbacusCore.ts index 213b1960f..1b1106f74 100644 --- a/typescript/sdk/src/core/AbacusCore.ts +++ b/typescript/sdk/src/core/AbacusCore.ts @@ -15,7 +15,7 @@ import { ChainConnection } from '../providers/ChainConnection'; import { MultiProvider } from '../providers/MultiProvider'; import { ConnectionClientConfig } from '../router'; import { ChainMap, ChainName, Remotes } from '../types'; -import { objMap } from '../utils'; +import { objMap, pick } from '../utils'; import { CoreContracts, coreFactories } from './contracts'; @@ -46,15 +46,33 @@ export class AbacusCore extends AbacusApp< super(contractsMap, multiProvider); } - static fromEnvironment( - env: Env, - multiProvider: MultiProvider>, - ): AbacusCore> { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + static fromEnvironment< + Env extends CoreEnvironment, + Chain extends ChainName = ChainName, + >(env: Env, multiProvider: MultiProvider) { + const envConfig = environments[env]; + if (!envConfig) { + throw new Error(`No default env config found for ${env}`); + } + + type EnvChain = keyof typeof envConfig; + type IntersectionChain = EnvChain & Chain; + const envChains = Object.keys(envConfig) as IntersectionChain[]; + + const { intersection, multiProvider: intersectionProvider } = + multiProvider.intersect(envChains); + + const intersectionConfig = pick( + envConfig as ChainMap, + intersection, + ); const contractsMap = buildContracts( - environments[env], + intersectionConfig, coreFactories, - ) as CoreContractsMap>; - return new AbacusCore(contractsMap, multiProvider); + ) as CoreContractsMap; + + return new AbacusCore(contractsMap, intersectionProvider); } // override type to be derived from chain key diff --git a/typescript/sdk/src/core/TestCoreApp.ts b/typescript/sdk/src/core/TestCoreApp.ts index 3ae3ca71c..839277998 100644 --- a/typescript/sdk/src/core/TestCoreApp.ts +++ b/typescript/sdk/src/core/TestCoreApp.ts @@ -32,18 +32,17 @@ export type TestCoreContracts = CoreContracts< inboxes: ChainMap, TestInboxContracts>; }; -export class TestCoreApp extends AbacusCore { - getContracts( +export class TestCoreApp< + TestChain extends TestChainNames = TestChainNames, +> extends AbacusCore { + getContracts( chain: Local, ): TestCoreContracts { return super.getContracts(chain) as TestCoreContracts; } async processMessages(): Promise< - Map< - TestChainNames, - Map - > + Map> > { const responses = new Map(); for (const origin of this.chains()) { @@ -57,7 +56,7 @@ export class TestCoreApp extends AbacusCore { return responses; } - async processOutboundMessages( + async processOutboundMessages( origin: Local, ): Promise> { const responses = new Map(); diff --git a/typescript/sdk/src/core/TestCoreDeployer.ts b/typescript/sdk/src/core/TestCoreDeployer.ts index 73d33a9d9..7ba7a8792 100644 --- a/typescript/sdk/src/core/TestCoreDeployer.ts +++ b/typescript/sdk/src/core/TestCoreDeployer.ts @@ -7,7 +7,7 @@ import { AbacusCoreDeployer } from '../deploy/core/AbacusCoreDeployer'; import { CoreConfig, ValidatorManagerConfig } from '../deploy/core/types'; import { MultiProvider } from '../providers/MultiProvider'; import { ProxiedContract } from '../proxy'; -import { Remotes, TestChainNames } from '../types'; +import { ChainMap, Remotes, TestChainNames } from '../types'; import { TestCoreApp, @@ -38,21 +38,26 @@ function mockProxy(contract: ethers.Contract) { }); } -export class TestCoreDeployer extends AbacusCoreDeployer { - constructor(public readonly multiProvider: MultiProvider) { - super( - multiProvider, - { +export class TestCoreDeployer< + TestChain extends TestChainNames = TestChainNames, +> extends AbacusCoreDeployer { + constructor( + public readonly multiProvider: MultiProvider, + configMap?: ChainMap, + ) { + const configs = + configMap ?? + ({ test1: testValidatorManagerConfig, test2: testValidatorManagerConfig, test3: testValidatorManagerConfig, - }, - testCoreFactories, - ); + } as ChainMap); // cast so param can be optional + + super(multiProvider, configs, testCoreFactories); } // skip proxying - async deployOutbox( + async deployOutbox( chain: LocalChain, config: ValidatorManagerConfig, ): Promise { @@ -74,9 +79,9 @@ export class TestCoreDeployer extends AbacusCoreDeployer { } // skip proxying - async deployInbox( + async deployInbox( local: LocalChain, - remote: Remotes, + remote: Remotes, config: ValidatorManagerConfig, ): Promise { const localDomain = chainMetadata[local].id; @@ -96,7 +101,7 @@ export class TestCoreDeployer extends AbacusCoreDeployer { } as TestInboxContracts; } - async deployApp(): Promise { + async deployApp(): Promise> { return new TestCoreApp(await this.deploy(), this.multiProvider); } } diff --git a/typescript/sdk/src/core/testAbacusDeploy.hardhat-test.ts b/typescript/sdk/src/core/testAbacusDeploy.hardhat-test.ts index 28f14f408..85e196981 100644 --- a/typescript/sdk/src/core/testAbacusDeploy.hardhat-test.ts +++ b/typescript/sdk/src/core/testAbacusDeploy.hardhat-test.ts @@ -8,7 +8,7 @@ import { TestOutbox, TestRecipient__factory } from '@abacus-network/core'; import { utils } from '@abacus-network/utils'; import { chainMetadata } from '../consts/chainMetadata'; -import { getMultiProviderFromConfigAndSigner } from '../deploy/utils'; +import { getTestMultiProvider } from '../deploy/utils'; import { TestCoreApp } from './TestCoreApp'; import { TestCoreDeployer } from './TestCoreDeployer'; @@ -28,12 +28,7 @@ describe('TestCoreDeployer', async () => { beforeEach(async () => { const [signer] = await ethers.getSigners(); - const config = { - test1: { provider: ethers.provider }, - test2: { provider: ethers.provider }, - test3: { provider: ethers.provider }, - }; - const multiProvider = getMultiProviderFromConfigAndSigner(config, signer); + const multiProvider = getTestMultiProvider(signer); const deployer = new TestCoreDeployer(multiProvider); abacus = await deployer.deployApp(); diff --git a/typescript/sdk/src/deploy/AbacusDeployer.ts b/typescript/sdk/src/deploy/AbacusDeployer.ts index fa495ecbf..d1b8da249 100644 --- a/typescript/sdk/src/deploy/AbacusDeployer.ts +++ b/typescript/sdk/src/deploy/AbacusDeployer.ts @@ -28,8 +28,8 @@ export interface DeployerOptions { export abstract class AbacusDeployer< Chain extends ChainName, Config, - Factories extends AbacusFactories, Contracts extends AbacusContracts, + Factories extends AbacusFactories, > { public deployedContracts: Partial> = {}; diff --git a/typescript/sdk/src/deploy/core/AbacusCoreDeployer.ts b/typescript/sdk/src/deploy/core/AbacusCoreDeployer.ts index 3ce0ea5d3..2a93711b9 100644 --- a/typescript/sdk/src/deploy/core/AbacusCoreDeployer.ts +++ b/typescript/sdk/src/deploy/core/AbacusCoreDeployer.ts @@ -24,8 +24,8 @@ import { CoreConfig, ValidatorManagerConfig } from './types'; export class AbacusCoreDeployer extends AbacusDeployer< Chain, CoreConfig, - typeof coreFactories, - CoreContracts + CoreContracts, + typeof coreFactories > { startingBlockNumbers: ChainMap; diff --git a/typescript/sdk/src/deploy/router/AbacusRouterChecker.ts b/typescript/sdk/src/deploy/router/AbacusRouterChecker.ts index 0a0603aa9..2dc2e0be6 100644 --- a/typescript/sdk/src/deploy/router/AbacusRouterChecker.ts +++ b/typescript/sdk/src/deploy/router/AbacusRouterChecker.ts @@ -11,9 +11,9 @@ import { RouterConfig } from './types'; export class AbacusRouterChecker< Chain extends ChainName, - Contracts extends RouterContracts, App extends AbacusApp, Config extends RouterConfig, + Contracts extends RouterContracts, > extends AbacusAppChecker { checkOwnership(chain: Chain): Promise { const owner = this.configMap[chain].owner; diff --git a/typescript/sdk/src/deploy/router/AbacusRouterDeployer.ts b/typescript/sdk/src/deploy/router/AbacusRouterDeployer.ts index 532a17fd0..5f70af7cb 100644 --- a/typescript/sdk/src/deploy/router/AbacusRouterDeployer.ts +++ b/typescript/sdk/src/deploy/router/AbacusRouterDeployer.ts @@ -14,10 +14,10 @@ import { RouterConfig } from './types'; export abstract class AbacusRouterDeployer< Chain extends ChainName, + Config extends RouterConfig, Contracts extends RouterContracts, Factories extends RouterFactories, - Config extends RouterConfig, -> extends AbacusDeployer { +> extends AbacusDeployer { constructor( multiProvider: MultiProvider, configMap: ChainMap, @@ -70,7 +70,7 @@ export abstract class AbacusRouterDeployer< contractsMap: ChainMap, ): Promise { this.logger(`Enrolling deployed routers with each other...`); - // Make all routers aware of eachother. + // Make all routers aware of each other. await promiseObjAll( objMap(contractsMap, async (local, contracts) => { const chainConnection = this.multiProvider.getChainConnection(local); diff --git a/typescript/sdk/src/deploy/utils.ts b/typescript/sdk/src/deploy/utils.ts index e590273fb..7ece4b31c 100644 --- a/typescript/sdk/src/deploy/utils.ts +++ b/typescript/sdk/src/deploy/utils.ts @@ -1,20 +1,45 @@ -import { ethers } from 'ethers'; +import { Signer, providers } from 'ethers'; +import { types } from '@abacus-network/utils'; + +import { testChainConnectionConfigs } from '../consts/chainConnectionConfigs'; import { MultiProvider } from '../providers/MultiProvider'; -import { ChainName } from '../types'; +import { ChainMap, ChainName, TestChainNames } from '../types'; import { objMap } from '../utils'; import { EnvironmentConfig } from './types'; -export function getMultiProviderFromConfigAndSigner( - environmentConfig: EnvironmentConfig, - signer: ethers.Signer, +export function getTestMultiProvider( + signerOrProvider: Signer | providers.Provider, + configs: EnvironmentConfig = testChainConnectionConfigs, ): MultiProvider { - const chainProviders = objMap(environmentConfig, (_, config) => ({ - provider: signer.provider!, + let signer: Signer | undefined; + let provider: providers.Provider; + if (Signer.isSigner(signerOrProvider) && signerOrProvider.provider) { + signer = signerOrProvider; + provider = signerOrProvider.provider; + } else if (providers.Provider.isProvider(signerOrProvider)) { + provider = signerOrProvider; + } else { + throw new Error('signerOrProvider is invalid'); + } + + const chainProviders = objMap(configs, (_, config) => ({ signer, + provider, confirmations: config.confirmations, overrides: config.overrides, })); return new MultiProvider(chainProviders); } + +export function getChainToOwnerMap( + configMap: ChainMap, + owner: types.Address, +): ChainMap { + return objMap(configMap, () => { + return { + owner, + }; + }); +} diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 2209f4240..cb9fc21d4 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -1,6 +1,9 @@ export { AllChains, Chains } from './consts/chains'; export { chainMetadata } from './consts/chainMetadata'; -export { chainConnectionConfigs } from './consts/chainConnectionConfigs'; +export { + chainConnectionConfigs, + testChainConnectionConfigs, +} from './consts/chainConnectionConfigs'; export { environments as coreEnvironments } from './consts/environments'; export { @@ -99,7 +102,7 @@ export { UpgradeBeaconViolation } from './deploy/proxy'; export { AbacusRouterDeployer } from './deploy/router/AbacusRouterDeployer'; export { AbacusRouterChecker } from './deploy/router/AbacusRouterChecker'; export { RouterConfig } from './deploy/router/types'; -export { getMultiProviderFromConfigAndSigner } from './deploy/utils'; +export { getTestMultiProvider, getChainToOwnerMap } from './deploy/utils'; export { ContractVerifier } from './deploy/verify/ContractVerifier'; export { ContractVerificationInput, diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index fcc5580f1..6f44c2ce0 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -1,5 +1,7 @@ -import { ChainMap, ChainName, IChainConnection } from '../types'; -import { MultiGeneric, objMap } from '../utils'; +import { ethers } from 'ethers'; + +import { ChainMap, ChainName, IChainConnection, Remotes } from '../types'; +import { MultiGeneric, objMap, pick } from '../utils'; import { ChainConnection } from './ChainConnection'; @@ -14,9 +16,116 @@ export class MultiProvider< ), ); } - getChainConnection(chain: Chain): ChainMap[Chain] { + + /** + * Get chainConnection for a chain + * @throws if chain is invalid or has not been set + */ + getChainConnection(chain: Chain): ChainConnection { return this.get(chain); } + + /** + * Get chainConnection for a chain + * @returns value or null if chain value has not been set + */ + tryGetChainConnection(chain: Chain): ChainConnection | null { + return this.tryGet(chain); + } + + /** + * Set value for a chain + * @throws if chain is invalid or has not been set + */ + setChainConnection( + chain: Chain, + chainConnectionConfig: IChainConnection, + ): ChainConnection { + const connection = new ChainConnection(chainConnectionConfig); + return this.set(chain, connection); + } + + /** + * Get provider for a chain + * @throws if chain is invalid or has not been set + */ + getChainProvider(chain: Chain): ethers.providers.Provider { + const chainConnection = this.get(chain); + if (!chainConnection.provider) { + throw new Error(`No provider set for chain ${chain}`); + } + return chainConnection.provider; + } + + /** + * Get provider for a chain + * @returns value or null if chain value has not been set + */ + tryGetChainProvider(chain: Chain): ethers.providers.Provider | null { + return this.tryGet(chain)?.provider ?? null; + } + + /** + * Get signer for a chain + * @throws if chain is invalid or has not been set + */ + getChainSigner(chain: Chain): ethers.Signer { + const chainConnection = this.get(chain); + if (!chainConnection.signer) { + throw new Error(`No signer set for chain ${chain}`); + } + return chainConnection.signer; + } + + /** + * Get signer for a chain + * @returns value or null if chain value has not been set + */ + tryGetChainSigner(chain: Chain): ethers.Signer | null { + return this.tryGet(chain)?.signer ?? null; + } + + /** + * Create a new MultiProvider which includes the provided chain connection config + */ + extendWithChain>( + chain: New, + chainConnectionConfig: IChainConnection, + ): MultiProvider { + const connection = new ChainConnection(chainConnectionConfig); + return new MultiProvider({ + ...this.chainMap, + [chain]: connection, + }); + } + + /** + * Create a new MultiProvider from the intersection + * of current's chains and the provided chain list + */ + intersect( + chains: ChainName[], + ): { + intersection: IntersectionChain[]; + multiProvider: MultiProvider; + } { + const ownChains = this.chains(); + const intersection = ownChains.filter((c) => + chains.includes(c), + ) as IntersectionChain[]; + + if (!intersection.length) { + throw new Error(`No chains shared between MultiProvider and list`); + } + + const intersectionChainMap = pick(this.chainMap, intersection); + + const multiProvider = new MultiProvider({ + ...intersectionChainMap, + }); + return { intersection, multiProvider }; + } + // This doesn't work on hardhat providers so we skip for now // ready() { // return Promise.all(this.values().map((dc) => dc.provider!.ready)); diff --git a/typescript/sdk/src/test/.eslintrc b/typescript/sdk/src/test/.eslintrc index ba8754a12..a0e3b7a52 100644 --- a/typescript/sdk/src/test/.eslintrc +++ b/typescript/sdk/src/test/.eslintrc @@ -1,5 +1,6 @@ { "rules": { + "no-console": ["off"], "@typescript-eslint/explicit-module-boundary-types": ["off"] } } diff --git a/typescript/sdk/src/test/envSubsetDeployer/README.md b/typescript/sdk/src/test/envSubsetDeployer/README.md new file mode 100644 index 000000000..293cf03e8 --- /dev/null +++ b/typescript/sdk/src/test/envSubsetDeployer/README.md @@ -0,0 +1,8 @@ +# Environment Subset Deployer App + +A trivial app intended to test deployments to a subset of an environment's chains. +For example, test deploying to just alfajores from env `testnet2`. + +To test deployment to a local hardhat network, run `yarn test:hardhat` +To test actual deployments to Alfajores run `MNEMONIC="your_mnemonic" yarn ts-node src/test/envSubsetDeployer/deploy-single-chain.ts` +To check run `yarn ts-node src/test/envSubsetDeployer/check-single-chain.ts` diff --git a/typescript/sdk/src/test/envSubsetDeployer/app.ts b/typescript/sdk/src/test/envSubsetDeployer/app.ts new file mode 100644 index 000000000..d24da44df --- /dev/null +++ b/typescript/sdk/src/test/envSubsetDeployer/app.ts @@ -0,0 +1,97 @@ +import { TestRouter__factory } from '@abacus-network/app'; + +import { AbacusApp } from '../../AbacusApp'; +import { chainConnectionConfigs } from '../../consts/chainConnectionConfigs'; +import { AbacusCore } from '../../core/AbacusCore'; +import { AbacusDeployer } from '../../deploy/AbacusDeployer'; +import { AbacusRouterChecker } from '../../deploy/router/AbacusRouterChecker'; +import { AbacusRouterDeployer } from '../../deploy/router/AbacusRouterDeployer'; +import { RouterConfig } from '../../deploy/router/types'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterContracts, RouterFactories } from '../../router'; +import { ChainMap, ChainName } from '../../types'; +import { objMap, promiseObjAll } from '../../utils'; + +export const fullEnvTestConfigs = { + test1: chainConnectionConfigs.test1, + test2: chainConnectionConfigs.test2, + test3: chainConnectionConfigs.test3, +}; + +export const subsetTestConfigs = { + test1: chainConnectionConfigs.test1, + test2: chainConnectionConfigs.test2, +}; + +export type SubsetChains = keyof typeof subsetTestConfigs; + +export const alfajoresChainConfig = { + alfajores: chainConnectionConfigs.alfajores, +}; + +export class EnvSubsetApp< + Chain extends ChainName = ChainName, +> extends AbacusApp {} + +export class EnvSubsetChecker< + Chain extends ChainName, +> extends AbacusRouterChecker< + Chain, + EnvSubsetApp, + RouterConfig, + RouterContracts +> {} + +export const envSubsetFactories: RouterFactories = { + router: new TestRouter__factory(), +}; + +export class EnvSubsetDeployer< + Chain extends ChainName, +> extends AbacusRouterDeployer< + Chain, + RouterConfig, + RouterContracts, + RouterFactories +> { + constructor( + multiProvider: MultiProvider, + configMap: ChainMap, + protected core: AbacusCore, + ) { + super(multiProvider, configMap, envSubsetFactories, {}); + } + + // Consider moving this up to AbacusRouterDeployer + async initRouter( + contractsMap: ChainMap, + ): Promise { + this.logger(`Calling initialize on routers...`); + await promiseObjAll( + objMap(contractsMap, async (chain, contracts) => { + const chainConnection = this.multiProvider.getChainConnection(chain); + const acm = this.configMap[chain].abacusConnectionManager; + await chainConnection.handleTx( + // @ts-ignore + contracts.router.initialize(acm, chainConnection.overrides), + ); + }), + ); + } + + async deploy(): Promise> { + const contractsMap = (await AbacusDeployer.prototype.deploy.apply( + this, + )) as Record; + await this.initRouter(contractsMap); + await this.enrollRemoteRouters(contractsMap); + return contractsMap; + } + + async deployContracts(chain: Chain) { + const router = await this.deployContract(chain, 'router', []); + return { + router, + }; + } +} diff --git a/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts b/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts new file mode 100644 index 000000000..5eebcceea --- /dev/null +++ b/typescript/sdk/src/test/envSubsetDeployer/check-single-chain.ts @@ -0,0 +1,55 @@ +import { buildContracts } from '../../contracts'; +import { AbacusCore } from '../../core/AbacusCore'; +import { getChainToOwnerMap } from '../../deploy/utils'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterContracts } from '../../router'; +import { ChainMap, ChainName } from '../../types'; + +import { + EnvSubsetApp, + EnvSubsetChecker, + alfajoresChainConfig, + envSubsetFactories, +} from './app'; +import { getAlfajoresProvider } from './utils'; + +// Copied from output of deploy-single-chain.ts script +const deploymentAddresses = { + alfajores: { + router: '0x0666AD4F636210B6a418f97790b7BAABAC54b9A4', + }, +}; + +const ownerAddress = '0x35b74Ed5038bf0488Ff33bD9819b9D12D10A7560'; + +async function check() { + const provider = getAlfajoresProvider(); + + console.info('Preparing utilities'); + const multiProvider = new MultiProvider({ + alfajores: { + provider, + confirmations: alfajoresChainConfig.alfajores.confirmations, + overrides: alfajoresChainConfig.alfajores.overrides, + }, + }); + + const contractsMap = buildContracts( + deploymentAddresses, + envSubsetFactories, + ) as ChainMap; + const app = new EnvSubsetApp(contractsMap, multiProvider); + const core = AbacusCore.fromEnvironment('testnet2', multiProvider); + const config = core.extendWithConnectionClientConfig( + getChainToOwnerMap(alfajoresChainConfig, ownerAddress), + ); + const envSubsetChecker = new EnvSubsetChecker(multiProvider, app, config); + + console.info('Starting check'); + await envSubsetChecker.check(); + envSubsetChecker.expectEmpty(); +} + +check() + .then(() => console.info('Check complete')) + .catch(console.error); diff --git a/typescript/sdk/src/test/envSubsetDeployer/deploy-single-chain.ts b/typescript/sdk/src/test/envSubsetDeployer/deploy-single-chain.ts new file mode 100644 index 000000000..cb45f0d4c --- /dev/null +++ b/typescript/sdk/src/test/envSubsetDeployer/deploy-single-chain.ts @@ -0,0 +1,36 @@ +import { serializeContracts } from '../../contracts'; +import { AbacusCore } from '../../core/AbacusCore'; +import { getChainToOwnerMap } from '../../deploy/utils'; +import { MultiProvider } from '../../providers/MultiProvider'; + +import { EnvSubsetDeployer, alfajoresChainConfig } from './app'; +import { getAlfajoresSigner } from './utils'; + +async function main() { + const signer = getAlfajoresSigner(); + + console.info('Preparing utilities'); + const multiProvider = new MultiProvider({ + alfajores: { + provider: signer.provider, + confirmations: alfajoresChainConfig.alfajores.confirmations, + overrides: alfajoresChainConfig.alfajores.overrides, + }, + }); + + const core = AbacusCore.fromEnvironment('testnet2', multiProvider); + const config = core.extendWithConnectionClientConfig( + getChainToOwnerMap(alfajoresChainConfig, signer.address), + ); + + console.info('Starting deployment'); + const deployer = new EnvSubsetDeployer(multiProvider, config, core); + const chainToContracts = await deployer.deploy(); + const addresses = serializeContracts(chainToContracts); + console.info('===Contract Addresses==='); + console.info(JSON.stringify(addresses)); +} + +main() + .then(() => console.info('Deploy complete')) + .catch(console.error); diff --git a/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts b/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts new file mode 100644 index 000000000..d14ac904b --- /dev/null +++ b/typescript/sdk/src/test/envSubsetDeployer/deploy.hardhat-test.ts @@ -0,0 +1,96 @@ +import '@nomiclabs/hardhat-waffle'; +import { ethers } from 'hardhat'; + +import { TestCoreApp } from '../../core/TestCoreApp'; +import { TestCoreDeployer } from '../../core/TestCoreDeployer'; +import { RouterConfig } from '../../deploy/router/types'; +import { EnvironmentConfig } from '../../deploy/types'; +import { getChainToOwnerMap, getTestMultiProvider } from '../../deploy/utils'; +import { MultiProvider } from '../../providers/MultiProvider'; +import { RouterContracts } from '../../router'; +import { ChainMap, TestChainNames } from '../../types'; + +import { + EnvSubsetApp, + EnvSubsetChecker, + EnvSubsetDeployer, + SubsetChains, + fullEnvTestConfigs, + subsetTestConfigs, +} 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: Record; + let app: EnvSubsetApp; + + before(async () => { + const testEnv = await initTestEnv(fullEnvTestConfigs); + multiProvider = testEnv.multiProvider; + config = testEnv.config; + deployer = testEnv.deployer; + }); + + it('deploys', async () => { + contracts = await deployer.deploy(); + }); + + it('builds app', async () => { + app = new EnvSubsetApp(contracts, multiProvider); + }); + + it('checks', async () => { + const checker = new EnvSubsetChecker(multiProvider, app, config); + await checker.check(); + checker.expectEmpty(); + }); +}); + +// Tests same as above but only a subset of the full test env +describe('deploy app to test env subset', async () => { + let multiProvider: MultiProvider; + let config: ChainMap; + let deployer: EnvSubsetDeployer; + let contracts: Record; + let app: EnvSubsetApp; + + before(async () => { + const testEnv = await initTestEnv(subsetTestConfigs); + multiProvider = testEnv.multiProvider; + config = testEnv.config; + deployer = testEnv.deployer; + }); + + it('deploys', async () => { + contracts = await deployer.deploy(); + }); + + it('builds app', async () => { + app = new EnvSubsetApp(contracts, multiProvider); + }); + + it('checks', async () => { + const checker = new EnvSubsetChecker(multiProvider, app, config); + await checker.check(); + checker.expectEmpty(); + }); +}); + +async function initTestEnv( + environmentConfig: EnvironmentConfig, +) { + const [signer] = await ethers.getSigners(); + const multiProvider = getTestMultiProvider(signer, environmentConfig); + + const coreDeployer = new TestCoreDeployer(multiProvider); + const coreContractsMaps = await coreDeployer.deploy(); + const core = new TestCoreApp(coreContractsMaps, multiProvider); + const config = core.extendWithConnectionClientConfig( + getChainToOwnerMap(fullEnvTestConfigs, signer.address), + ); + const deployer = new EnvSubsetDeployer(multiProvider, config, core); + return { multiProvider, config, deployer }; +} diff --git a/typescript/sdk/src/test/envSubsetDeployer/utils.ts b/typescript/sdk/src/test/envSubsetDeployer/utils.ts new file mode 100644 index 000000000..65bfb9c84 --- /dev/null +++ b/typescript/sdk/src/test/envSubsetDeployer/utils.ts @@ -0,0 +1,23 @@ +import { Wallet } from 'ethers'; + +import { StaticCeloJsonRpcProvider } from '@abacus-network/celo-ethers-provider'; + +export const ALFAJORES_FORNO = 'https://alfajores-forno.celo-testnet.org'; +export const CELO_DERIVATION_PATH = "m/44'/52752'/0'/0/0"; + +export function getAlfajoresSigner() { + console.info('Getting signer'); + const provider = getAlfajoresProvider(); + const mnemonic = process.env.MNEMONIC; + if (!mnemonic) throw new Error('No MNEMONIC provided in env'); + const wallet = Wallet.fromMnemonic(mnemonic, CELO_DERIVATION_PATH).connect( + provider, + ); + console.info('Signer and provider ready'); + return wallet; +} + +export function getAlfajoresProvider() { + console.info('Getting provider'); + return new StaticCeloJsonRpcProvider(ALFAJORES_FORNO); +} diff --git a/typescript/sdk/src/types.ts b/typescript/sdk/src/types.ts index 0c4722184..29335786f 100644 --- a/typescript/sdk/src/types.ts +++ b/typescript/sdk/src/types.ts @@ -2,9 +2,15 @@ import type { ethers } from 'ethers'; import type { Chains } from './consts/chains'; +// A union type of the keys in the Chains enum export type ChainName = keyof typeof Chains; +// A full object map of all chains to a value type export type CompleteChainMap = Record; +// A partial object map of some chains to a value type +export type PartialChainMap = Partial>; +// A map of some specific subset of chains to a value type export type ChainMap = Record; +// The names of test chains, should be kept up to date if new are added export type TestChainNames = 'test1' | 'test2' | 'test3'; export type NameOrDomain = ChainName | number; diff --git a/typescript/sdk/src/utils/MultiGeneric.ts b/typescript/sdk/src/utils/MultiGeneric.ts index 5f90bab53..ea009b365 100644 --- a/typescript/sdk/src/utils/MultiGeneric.ts +++ b/typescript/sdk/src/utils/MultiGeneric.ts @@ -1,25 +1,55 @@ +import { AllChains } from '../consts/chains'; import { ChainMap, ChainName, Remotes } from '../types'; export class MultiGeneric { - constructor(protected readonly chainMap: ChainMap) {} + constructor(public readonly chainMap: ChainMap) {} - protected get(chain: Chain) { - return this.chainMap[chain]; + /** + * Get value for a chain + * @throws if chain is invalid or has not been set + */ + protected get(chain: Chain): Value { + if (!chain || !AllChains.includes(chain)) { + throw new Error(`Invalid chain ${chain}`); + } + const value = this.chainMap[chain] ?? null; + if (!value) { + throw new Error(`No chain value found for ${chain}`); + } + return value; + } + + /** + * Get value for a chain + * @returns value or null if chain value has not been set + */ + protected tryGet(chain: Chain): Value | null { + if (!chain || !AllChains.includes(chain)) { + return null; + } + return this.chainMap[chain] ?? null; } - protected set(chain: Chain, value: Value) { + /** + * Set value for a chain + * @throws if chain is invalid or has not been set + */ + protected set(chain: Chain, value: Value): Value { this.chainMap[chain] = value; + return value; } - chains = () => Object.keys(this.chainMap) as Chain[]; + chains(): Chain[] { + return Object.keys(this.chainMap) as Chain[]; + } - apply(fn: (n: Chain, dc: Value) => void) { + forEach(fn: (n: Chain, dc: Value) => void): void { for (const chain of this.chains()) { fn(chain, this.chainMap[chain]); } } - map(fn: (n: Chain, dc: Value) => Output) { + map(fn: (n: Chain, dc: Value) => Output): Record { const entries: [Chain, Output][] = []; const chains = this.chains(); for (const chain of chains) { @@ -28,17 +58,26 @@ export class MultiGeneric { return Object.fromEntries(entries) as Record; } - remoteChains = (name: LocalChain) => - this.chains().filter((key) => key !== name) as Remotes[]; + remoteChains( + name: LocalChain, + ): Remotes[] { + return this.chains().filter((key) => key !== name) as Remotes< + Chain, + LocalChain + >[]; + } - extendWithChain = >( + extendWithChain>( chain: New, value: Value, - ) => - new MultiGeneric({ + ): MultiGeneric { + return new MultiGeneric({ ...this.chainMap, [chain]: value, }); + } - knownChain = (chain: ChainName) => chain in this.chainMap; + knownChain(chain: ChainName): boolean { + return chain in this.chainMap; + } } diff --git a/typescript/sdk/src/utils/objects.ts b/typescript/sdk/src/utils/objects.ts index c3c321505..5bc6f908c 100644 --- a/typescript/sdk/src/utils/objects.ts +++ b/typescript/sdk/src/utils/objects.ts @@ -19,11 +19,20 @@ export function objMap( } // promiseObjectAll :: {k: Promise a} -> Promise {k: a} -export const promiseObjAll = (object: { +export function promiseObjAll(obj: { [key in K]: Promise; -}): Promise> => { - const promiseList = Object.entries(object).map(([name, promise]) => +}): Promise> { + const promiseList = Object.entries(obj).map(([name, promise]) => (promise as Promise).then((result) => [name, result]), ); return Promise.all(promiseList).then(Object.fromEntries); -}; +} + +// Get the subset of the object from key list +export function pick(obj: Record, keys: K[]) { + const ret: Partial> = {}; + for (const key of keys) { + ret[key] = obj[key]; + } + return ret as Record; +} diff --git a/yarn.lock b/yarn.lock index 70019632a..25ec3d3df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -175,6 +175,7 @@ __metadata: mocha: ^9.2.2 prettier: ^2.4.1 sinon: ^13.0.2 + ts-node: ^10.8.0 typescript: ^4.7.2 languageName: unknown linkType: soft