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`pull/859/head
parent
d24eaa4e32
commit
44df310300
@ -1,11 +0,0 @@ |
||||
{ |
||||
"test1": { |
||||
"router": "0x5FbDB2315678afecb367f032d93F642f64180aa3" |
||||
}, |
||||
"test2": { |
||||
"router": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" |
||||
}, |
||||
"test3": { |
||||
"router": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" |
||||
} |
||||
} |
@ -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<Chain extends ChainName>( |
||||
environmentConfig: EnvironmentConfig<Chain>, |
||||
signer: ethers.Signer, |
||||
export function getTestMultiProvider<Chain extends TestChainNames>( |
||||
signerOrProvider: Signer | providers.Provider, |
||||
configs: EnvironmentConfig<Chain> = testChainConnectionConfigs, |
||||
): MultiProvider<Chain> { |
||||
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<Chain extends ChainName>( |
||||
configMap: ChainMap<Chain, any>, |
||||
owner: types.Address, |
||||
): ChainMap<Chain, { owner: string }> { |
||||
return objMap(configMap, () => { |
||||
return { |
||||
owner, |
||||
}; |
||||
}); |
||||
} |
||||
|
@ -1,5 +1,6 @@ |
||||
{ |
||||
"rules": { |
||||
"no-console": ["off"], |
||||
"@typescript-eslint/explicit-module-boundary-types": ["off"] |
||||
} |
||||
} |
||||
|
@ -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` |
@ -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<RouterContracts, Chain> {} |
||||
|
||||
export class EnvSubsetChecker< |
||||
Chain extends ChainName, |
||||
> extends AbacusRouterChecker< |
||||
Chain, |
||||
EnvSubsetApp<Chain>, |
||||
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<Chain>, |
||||
configMap: ChainMap<Chain, RouterConfig>, |
||||
protected core: AbacusCore<Chain>, |
||||
) { |
||||
super(multiProvider, configMap, envSubsetFactories, {}); |
||||
} |
||||
|
||||
// Consider moving this up to AbacusRouterDeployer
|
||||
async initRouter( |
||||
contractsMap: ChainMap<Chain, RouterContracts>, |
||||
): Promise<void> { |
||||
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<ChainMap<Chain, RouterContracts>> { |
||||
const contractsMap = (await AbacusDeployer.prototype.deploy.apply( |
||||
this, |
||||
)) as Record<Chain, RouterContracts>; |
||||
await this.initRouter(contractsMap); |
||||
await this.enrollRemoteRouters(contractsMap); |
||||
return contractsMap; |
||||
} |
||||
|
||||
async deployContracts(chain: Chain) { |
||||
const router = await this.deployContract(chain, 'router', []); |
||||
return { |
||||
router, |
||||
}; |
||||
} |
||||
} |
@ -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<ChainName, RouterContracts>; |
||||
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); |
@ -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); |
@ -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<TestChainNames>; |
||||
let config: ChainMap<TestChainNames, RouterConfig>; |
||||
let deployer: EnvSubsetDeployer<TestChainNames>; |
||||
let contracts: Record<TestChainNames, RouterContracts>; |
||||
let app: EnvSubsetApp<TestChainNames>; |
||||
|
||||
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<SubsetChains>; |
||||
let config: ChainMap<SubsetChains, RouterConfig>; |
||||
let deployer: EnvSubsetDeployer<SubsetChains>; |
||||
let contracts: Record<SubsetChains, RouterContracts>; |
||||
let app: EnvSubsetApp<SubsetChains>; |
||||
|
||||
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<Chain extends TestChainNames>( |
||||
environmentConfig: EnvironmentConfig<Chain>, |
||||
) { |
||||
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 }; |
||||
} |
@ -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); |
||||
} |
Loading…
Reference in new issue