From 7de92cd68a025dfdab94f216a80aaa0485575f7f Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Mar 2023 13:54:43 -0400 Subject: [PATCH 01/19] Add decimals to synthetic ERC20 --- contracts/HypERC20.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contracts/HypERC20.sol b/contracts/HypERC20.sol index 6eb5ed5..b970165 100644 --- a/contracts/HypERC20.sol +++ b/contracts/HypERC20.sol @@ -11,6 +11,12 @@ import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ * @dev Supply on each chain is not constant but the aggregate supply across all chains is. */ contract HypERC20 is ERC20Upgradeable, TokenRouter { + uint8 private immutable decimalsConfig; + + constructor(uint8 _decimals) { + decimalsConfig = _decimals; + } + /** * @notice Initializes the Hyperlane router, ERC20 metadata, and mints initial supply to deployer. * @param _mailbox The address of the mailbox contract. @@ -37,6 +43,10 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter { _mint(msg.sender, _totalSupply); } + function decimals() public view override returns (uint8) { + return decimalsConfig; + } + /** * @dev Burns `_amount` of token from `msg.sender` balance. * @inheritdoc TokenRouter From 7eb849d7b3b31751440dc30fc13a59873fdd530a Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Mar 2023 13:55:03 -0400 Subject: [PATCH 02/19] Update config and tests for default token metadata --- src/config.ts | 16 ++- src/deploy.ts | 296 ++++++++++++++++++++++++++++++++------------ test/erc20.test.ts | 9 +- test/erc721.test.ts | 6 +- 4 files changed, 239 insertions(+), 88 deletions(-) diff --git a/src/config.ts b/src/config.ts index dd17b41..71172cf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,11 +10,23 @@ export enum TokenType { native = 'native', } -export type SyntheticConfig = { - type: TokenType.synthetic | TokenType.syntheticUri; +export type TokenMetadata = { name: string; symbol: string; totalSupply: ethers.BigNumberish; +} + +export type ERC20Metadata = TokenMetadata & { + decimals: number; +} + +export const isTokenMetadata = (metadata: any): metadata is TokenMetadata => + metadata.name !== undefined && metadata.symbol !== undefined && metadata.totalSupply !== undefined; + +export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => metadata.decimals !== undefined && isTokenMetadata(metadata); + +export type SyntheticConfig = Partial & { + type: TokenType.synthetic | TokenType.syntheticUri; }; export type CollateralConfig = { type: TokenType.collateral | TokenType.collateralUri; diff --git a/src/deploy.ts b/src/deploy.ts index 1def108..2319048 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -9,22 +9,36 @@ import { import { DeployerOptions } from '@hyperlane-xyz/sdk/dist/deploy/HyperlaneDeployer'; import { + CollateralConfig, HypERC20Config, HypERC721Config, + SyntheticConfig, TokenConfig, isCollateralConfig, isNativeConfig, isSyntheticConfig, isUriConfig, + NativeConfig, + isTokenMetadata, + ERC20Metadata, + isErc20Metadata, + TokenMetadata, } from './config'; import { HypERC20Contracts, HypERC721Contracts } from './contracts'; import { + ERC20__factory, + ERC721__factory, + HypERC20, + HypERC20Collateral, HypERC20Collateral__factory, HypERC20__factory, + HypERC721, + HypERC721Collateral, HypERC721Collateral__factory, HypERC721URICollateral__factory, HypERC721URIStorage__factory, HypERC721__factory, + HypNative, HypNative__factory, } from './types'; @@ -55,7 +69,7 @@ const gasDefaults = (config: TokenConfig, tokenType: TokenType) => { return 44_000; case 'collateral': default: - return 68_000; + return 69_000; } } }; @@ -65,6 +79,8 @@ export class HypERC20Deployer extends GasRouterDeployer< HypERC20Contracts, any // RouterFactories doesn't work well when router has multiple types > { + tokenMetadata?: ERC20Metadata; + constructor( multiProvider: MultiProvider, configMap: ChainMap, @@ -86,51 +102,121 @@ export class HypERC20Deployer extends GasRouterDeployer< ); } + protected async deployCollateral( + chain: ChainName, + config: CollateralConfig & HypERC20Config, + ): Promise { + this.logger(`Deploying collateral router on ${chain}...`); + const erc20 = new ERC20__factory() + .attach(config.token) + .connect(this.multiProvider.getProvider(chain)); + + const decimals = await erc20.decimals(); + const name = await erc20.name(); + const symbol = await erc20.symbol(); + const totalSupply = 0; // synthetic tokens are minted on demand + + this.tokenMetadata = { decimals, name, symbol, totalSupply }; + + const router = await this.deployContractFromFactory( + chain, + new HypERC20Collateral__factory(), + 'HypERC20Collateral', + [config.token], + ); + await this.multiProvider.handleTx( + chain, + router.initialize(config.mailbox, config.interchainGasPaymaster), + ); + return router; + } + + protected async deployNative( + chain: ChainName, + config: NativeConfig & HypERC20Config, + ): Promise { + this.logger(`Deploying native router on ${chain}...`); + const nativeToken = this.multiProvider.getChainMetadata(chain).nativeToken; + + if (nativeToken) { + this.tokenMetadata = { ...nativeToken, totalSupply: 0 }; + } + + const router = await this.deployContractFromFactory( + chain, + new HypNative__factory(), + 'HypNative', + [], + ); + await this.multiProvider.handleTx( + chain, + router.initialize(config.mailbox, config.interchainGasPaymaster), + ); + return router; + } + + protected async deploySynthetic( + chain: ChainName, + config: SyntheticConfig & HypERC20Config, + tokenMetadata: ERC20Metadata + ): Promise { + this.logger(`Deploying synthetic router on ${chain}...`); + const router = await this.deployContractFromFactory( + chain, + new HypERC20__factory(), + 'HypERC20', + [tokenMetadata.decimals], + ); + await this.multiProvider.handleTx( + chain, + router.initialize( + config.mailbox, + config.interchainGasPaymaster, + tokenMetadata.totalSupply, + tokenMetadata.name, + tokenMetadata.symbol, + ), + ); + return router; + } + + async deploy( + partialDeployment?: ChainMap, + ): Promise> { + // deploy collateral or native first to populate token metadata for synthetics + for (const [chain, config] of Object.entries(this.configMap)) { + if (isCollateralConfig(config)) { + this.deployedContracts[chain] = { + router: await this.deployCollateral(chain as ChainName, config) + }; + } else if (isNativeConfig(config)) { + this.deployedContracts[chain] = { + router: await this.deployNative(chain as ChainName, config) + }; + } + } + + // deploy synthetics + return super.deploy({ ...partialDeployment, ...this.deployedContracts }); + } + async deployContracts(chain: ChainName, config: HypERC20Config) { - if (isCollateralConfig(config)) { - const router = await this.deployContractFromFactory( - chain, - new HypERC20Collateral__factory(), - 'HypERC20Collateral', - [config.token], - ); - await this.multiProvider.handleTx( - chain, - router.initialize(config.mailbox, config.interchainGasPaymaster), - ); - return { router }; - } else if (isSyntheticConfig(config)) { - const router = await this.deployContractFromFactory( - chain, - new HypERC20__factory(), - 'HypERC20', - [], - ); - await this.multiProvider.handleTx( - chain, - router.initialize( - config.mailbox, - config.interchainGasPaymaster, - config.totalSupply, - config.name, - config.symbol, - ), - ); - return { router }; - } else if (isNativeConfig(config)) { - const router = await this.deployContractFromFactory( - chain, - new HypNative__factory(), - 'HypNative', - [], - ); - await this.multiProvider.handleTx( - chain, - router.initialize(config.mailbox, config.interchainGasPaymaster), - ); - return { router }; + // skip if already deployed + if (this.deployedContracts[chain]) { + return this.deployedContracts[chain]; } - throw new Error('Invalid config'); + + if (!isSyntheticConfig(config)) { + throw new Error('Expect only synthetic configs'); + } + + const erc20Metadata = {...this.tokenMetadata, ...config }; + if (!isErc20Metadata(erc20Metadata)) { + throw new Error(`ERC20 metadata not populated for ${chain}`); + } + + const router = await this.deploySynthetic(chain, config, erc20Metadata); + return { router }; } } @@ -140,6 +226,8 @@ export class HypERC721Deployer extends GasRouterDeployer< HypERC721Contracts, any > { + tokenMetadata?: TokenMetadata; + constructor( multiProvider: MultiProvider, configMap: ChainMap, @@ -161,42 +249,92 @@ export class HypERC721Deployer extends GasRouterDeployer< ); } + protected async deployCollateral( + chain: ChainName, + config: CollateralConfig & HypERC721Config, + ): Promise { + this.logger(`Deploying collateral router on ${chain}...`); + + const erc721 = ERC721__factory.connect(config.token, this.multiProvider.getProvider(chain)); + const name = await erc721.name(); + const symbol = await erc721.symbol(); + const totalSupply = 0; // synthetic tokens are minted on demand + this.tokenMetadata = { name, symbol, totalSupply }; + + const router = await this.deployContractFromFactory( + chain, + isUriConfig(config) + ? new HypERC721URICollateral__factory() + : new HypERC721Collateral__factory(), + `HypERC721${isUriConfig(config) ? 'URI' : ''}Collateral`, + [config.token], + ); + await this.multiProvider.handleTx( + chain, + router.initialize(config.mailbox, config.interchainGasPaymaster), + ); + return router; + } + + protected async deploySynthetic( + chain: ChainName, + config: HypERC721Config & SyntheticConfig, + tokenMetadata: TokenMetadata + ): Promise { + this.logger(`Deploying synthetic router on ${chain}...`); + const router = await this.deployContractFromFactory( + chain, + isUriConfig(config) + ? new HypERC721URIStorage__factory() + : new HypERC721__factory(), + `HypERC721${isUriConfig(config) ? 'URIStorage' : ''}`, + [], + ); + await this.multiProvider.handleTx( + chain, + router.initialize( + config.mailbox, + config.interchainGasPaymaster, + tokenMetadata.totalSupply, + tokenMetadata.name, + tokenMetadata.symbol, + ), + ); + return router; + } + + async deploy( + partialDeployment?: ChainMap, + ): Promise> { + // deploy collateral first to populate token metadata for synthetics + for (const [chain, config] of Object.entries(this.configMap)) { + if (isCollateralConfig(config)) { + this.deployedContracts[chain] = { + router: await this.deployCollateral(chain as ChainName, config) + }; + } + } + + // deploy synthetics + return super.deploy({ ...partialDeployment, ...this.deployedContracts }); + } + async deployContracts(chain: ChainName, config: HypERC721Config) { - if (isCollateralConfig(config)) { - const router = await this.deployContractFromFactory( - chain, - isUriConfig(config) - ? new HypERC721URICollateral__factory() - : new HypERC721Collateral__factory(), - `HypERC721${isUriConfig(config) ? 'URI' : ''}Collateral`, - [config.token], - ); - await this.multiProvider.handleTx( - chain, - router.initialize(config.mailbox, config.interchainGasPaymaster), - ); - return { router }; - } else if (isSyntheticConfig(config)) { - const router = await this.deployContractFromFactory( - chain, - isUriConfig(config) - ? new HypERC721URIStorage__factory() - : new HypERC721__factory(), - `HypERC721${isUriConfig(config) ? 'URIStorage' : ''}`, - [], - ); - await this.multiProvider.handleTx( - chain, - router.initialize( - config.mailbox, - config.interchainGasPaymaster, - config.totalSupply, - config.name, - config.symbol, - ), - ); - return { router }; + // skip if already deployed + if (this.deployedContracts[chain]) { + return this.deployedContracts[chain]; + } + + if (!isSyntheticConfig(config)) { + throw new Error('Expect only synthetic configs'); + } + + const erc721Metadata = {...this.tokenMetadata, ...config }; + if (!isTokenMetadata(erc721Metadata)) { + throw new Error(`Token metadata not populated for ${chain}`); } - throw new Error('Invalid config'); + + const router = await this.deploySynthetic(chain, config, erc721Metadata); + return { router }; } } diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 01f9ae1..9c829b2 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -42,7 +42,8 @@ const tokenConfig: SyntheticConfig = { type: TokenType.synthetic, name: 'HypERC20', symbol: 'HYP', - totalSupply, + decimals: 18, + totalSupply }; for (const variant of [ @@ -77,9 +78,9 @@ for (const variant of [ let erc20: ERC20 | undefined; if (variant === TokenType.collateral) { erc20 = await new ERC20Test__factory(owner).deploy( - tokenConfig.name, - tokenConfig.symbol, - tokenConfig.totalSupply, + tokenConfig.name!, + tokenConfig.symbol!, + tokenConfig.totalSupply!, ); localTokenConfig = { type: variant, diff --git a/test/erc721.test.ts b/test/erc721.test.ts index daf5c5f..794a287 100644 --- a/test/erc721.test.ts +++ b/test/erc721.test.ts @@ -100,9 +100,9 @@ for (const withCollateral of [true, false]) { let erc721: ERC721 | undefined; if (withCollateral) { erc721 = await new ERC721Test__factory(owner).deploy( - tokenConfig.name, - tokenConfig.symbol, - tokenConfig.totalSupply, + tokenConfig.name!, + tokenConfig.symbol!, + tokenConfig.totalSupply!, ); configWithTokenInfo.test1 = { ...configWithTokenInfo.test1, From 6fda2e06d67eee23350d63906ebcb65447875a61 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Mar 2023 13:55:21 -0400 Subject: [PATCH 03/19] Update example token config to fetch metadata --- configs/warp-route-token-config.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/configs/warp-route-token-config.json b/configs/warp-route-token-config.json index 11f55c1..a6569dc 100644 --- a/configs/warp-route-token-config.json +++ b/configs/warp-route-token-config.json @@ -8,27 +8,18 @@ }, "alfajores": { "type": "synthetic", - "name": "Weth", - "symbol": "WETH", - "totalSupply": 0, "owner": "0x5bA371aeA18734Cb7195650aFdfCa4f9251aa513", "mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", "interchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1" }, "fuji": { "type": "synthetic", - "name": "Weth", - "symbol": "WETH", - "totalSupply": 0, "owner": "0x5bA371aeA18734Cb7195650aFdfCa4f9251aa513", "mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", "interchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1" }, "moonbasealpha": { "type": "synthetic", - "name": "Weth", - "symbol": "WETH", - "totalSupply": 0, "owner": "0x5bA371aeA18734Cb7195650aFdfCa4f9251aa513", "mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", "interchainGasPaymaster": "0xF90cB82a76492614D07B82a7658917f3aC811Ac1" From 3c032b1413d397307d5e2bea911dac6b2d4ac2c3 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 23 Mar 2023 14:01:33 -0400 Subject: [PATCH 04/19] Fix prettier --- test/erc20.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 9c829b2..3804e81 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -43,7 +43,7 @@ const tokenConfig: SyntheticConfig = { name: 'HypERC20', symbol: 'HYP', decimals: 18, - totalSupply + totalSupply, }; for (const variant of [ From 306b107e662c4c87e963693bfe29cf2985f3090f Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Wed, 29 Mar 2023 11:21:24 -0400 Subject: [PATCH 05/19] Update with router --- src/app.ts | 16 ++++++++++++---- src/contracts.ts | 14 ++++++++------ src/deploy.ts | 14 ++++++++++---- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/app.ts b/src/app.ts index 6ad3151..f92b714 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,14 +1,22 @@ import { BigNumberish } from 'ethers'; -import { ChainName, GasRouterApp, RouterContracts } from '@hyperlane-xyz/sdk'; +import { ChainName, RouterApp } from '@hyperlane-xyz/sdk'; import { types } from '@hyperlane-xyz/utils'; -import { HypERC20Contracts, HypERC721Contracts } from './contracts'; +import { + HypERC20Contracts, + HypERC721Contracts, + TokenContracts, +} from './contracts'; import { TokenRouter } from './types'; class HyperlaneTokenApp< - Contracts extends RouterContracts, -> extends GasRouterApp { + Contracts extends TokenContracts, +> extends RouterApp { + router(contracts: TokenContracts): TokenRouter { + return contracts.router; + } + async transfer( origin: ChainName, destination: ChainName, diff --git a/src/contracts.ts b/src/contracts.ts index 7151c1e..3a41693 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -1,5 +1,3 @@ -import { RouterContracts } from '@hyperlane-xyz/sdk'; - import { HypERC20, HypERC20Collateral, @@ -9,7 +7,11 @@ import { HypNative, } from './types'; -export type HypERC20Contracts = RouterContracts; -export type HypERC721Contracts = RouterContracts< - HypERC721 | HypERC721Collateral | HypERC721URICollateral ->; +export type HypERC20Contracts = { + router: HypERC20 | HypERC20Collateral | HypNative; +}; +export type HypERC721Contracts = { + router: HypERC721 | HypERC721Collateral | HypERC721URICollateral; +}; + +export type TokenContracts = HypERC20Contracts | HypERC721Contracts; diff --git a/src/deploy.ts b/src/deploy.ts index 1def108..e678ab4 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -62,8 +62,7 @@ const gasDefaults = (config: TokenConfig, tokenType: TokenType) => { export class HypERC20Deployer extends GasRouterDeployer< HypERC20Config & GasRouterConfig, - HypERC20Contracts, - any // RouterFactories doesn't work well when router has multiple types + HypERC20Contracts > { constructor( multiProvider: MultiProvider, @@ -86,6 +85,10 @@ export class HypERC20Deployer extends GasRouterDeployer< ); } + router(contracts: HypERC20Contracts) { + return contracts.router; + } + async deployContracts(chain: ChainName, config: HypERC20Config) { if (isCollateralConfig(config)) { const router = await this.deployContractFromFactory( @@ -137,8 +140,7 @@ export class HypERC20Deployer extends GasRouterDeployer< // TODO: dedupe? export class HypERC721Deployer extends GasRouterDeployer< HypERC721Config & GasRouterConfig, - HypERC721Contracts, - any + HypERC721Contracts > { constructor( multiProvider: MultiProvider, @@ -161,6 +163,10 @@ export class HypERC721Deployer extends GasRouterDeployer< ); } + router(contracts: HypERC721Contracts) { + return contracts.router; + } + async deployContracts(chain: ChainName, config: HypERC721Config) { if (isCollateralConfig(config)) { const router = await this.deployContractFromFactory( From ab9c023d35af65d54ad2f01dad199fd7df005b1e Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Fri, 31 Mar 2023 15:09:34 -0400 Subject: [PATCH 06/19] Fix build --- src/app.ts | 18 +++++++++--------- src/contracts.ts | 25 ++++++++++++++----------- src/deploy.ts | 11 ++++++----- src/index.ts | 8 ++++---- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/app.ts b/src/app.ts index f92b714..366624b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,19 +1,19 @@ import { BigNumberish } from 'ethers'; -import { ChainName, RouterApp } from '@hyperlane-xyz/sdk'; +import { ChainName, HyperlaneContracts, RouterApp } from '@hyperlane-xyz/sdk'; import { types } from '@hyperlane-xyz/utils'; import { - HypERC20Contracts, - HypERC721Contracts, - TokenContracts, + HypERC20Factories, + HypERC721Factories, + TokenFactories, } from './contracts'; import { TokenRouter } from './types'; class HyperlaneTokenApp< - Contracts extends TokenContracts, -> extends RouterApp { - router(contracts: TokenContracts): TokenRouter { + Factories extends TokenFactories, +> extends RouterApp { + router(contracts: HyperlaneContracts): TokenRouter { return contracts.router; } @@ -43,7 +43,7 @@ class HyperlaneTokenApp< } } -export class HypERC20App extends HyperlaneTokenApp { +export class HypERC20App extends HyperlaneTokenApp { async transfer( origin: ChainName, destination: ChainName, @@ -61,7 +61,7 @@ export class HypERC20App extends HyperlaneTokenApp { } } -export class HypERC721App extends HyperlaneTokenApp { +export class HypERC721App extends HyperlaneTokenApp { async transfer( origin: ChainName, destination: ChainName, diff --git a/src/contracts.ts b/src/contracts.ts index 3a41693..e22cc49 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -1,17 +1,20 @@ import { - HypERC20, - HypERC20Collateral, - HypERC721, - HypERC721Collateral, - HypERC721URICollateral, - HypNative, + HypERC20Collateral__factory, + HypERC20__factory, + HypERC721Collateral__factory, + HypERC721URICollateral__factory, + HypERC721__factory, + HypNative__factory, } from './types'; -export type HypERC20Contracts = { - router: HypERC20 | HypERC20Collateral | HypNative; +export type HypERC20Factories = { + router: HypERC20__factory | HypERC20Collateral__factory | HypNative__factory; }; -export type HypERC721Contracts = { - router: HypERC721 | HypERC721Collateral | HypERC721URICollateral; +export type HypERC721Factories = { + router: + | HypERC721__factory + | HypERC721Collateral__factory + | HypERC721URICollateral__factory; }; -export type TokenContracts = HypERC20Contracts | HypERC721Contracts; +export type TokenFactories = HypERC20Factories | HypERC721Factories; diff --git a/src/deploy.ts b/src/deploy.ts index e678ab4..a43e2e6 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -3,6 +3,7 @@ import { ChainName, GasRouterConfig, GasRouterDeployer, + HyperlaneContracts, MultiProvider, objMap, } from '@hyperlane-xyz/sdk'; @@ -17,7 +18,7 @@ import { isSyntheticConfig, isUriConfig, } from './config'; -import { HypERC20Contracts, HypERC721Contracts } from './contracts'; +import { HypERC20Factories, HypERC721Factories } from './contracts'; import { HypERC20Collateral__factory, HypERC20__factory, @@ -62,7 +63,7 @@ const gasDefaults = (config: TokenConfig, tokenType: TokenType) => { export class HypERC20Deployer extends GasRouterDeployer< HypERC20Config & GasRouterConfig, - HypERC20Contracts + HypERC20Factories > { constructor( multiProvider: MultiProvider, @@ -85,7 +86,7 @@ export class HypERC20Deployer extends GasRouterDeployer< ); } - router(contracts: HypERC20Contracts) { + router(contracts: HyperlaneContracts) { return contracts.router; } @@ -140,7 +141,7 @@ export class HypERC20Deployer extends GasRouterDeployer< // TODO: dedupe? export class HypERC721Deployer extends GasRouterDeployer< HypERC721Config & GasRouterConfig, - HypERC721Contracts + HypERC721Factories > { constructor( multiProvider: MultiProvider, @@ -163,7 +164,7 @@ export class HypERC721Deployer extends GasRouterDeployer< ); } - router(contracts: HypERC721Contracts) { + router(contracts: HyperlaneContracts) { return contracts.router; } diff --git a/src/index.ts b/src/index.ts index 970f2c0..e446383 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,5 @@ export { HypERC20App, HypERC721App } from './app'; export { - TokenType, - TokenConfig, - SyntheticConfig, CollateralConfig, HypERC20CollateralConfig, HypERC20Config, @@ -10,7 +7,10 @@ export { HypERC721Config, isCollateralConfig, isUriConfig, + SyntheticConfig, + TokenConfig, + TokenType, } from './config'; -export { HypERC20Contracts, HypERC721Contracts } from './contracts'; +export { HypERC20Factories, HypERC721Factories } from './contracts'; export { HypERC20Deployer, HypERC721Deployer } from './deploy'; export * from './types'; From fc668c424323289e7d3a26fffcd8e07c1f1d3400 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Mon, 3 Apr 2023 12:41:45 -0400 Subject: [PATCH 07/19] bump version --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9a1462a..f740afe 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.2.3", + "version": "1.3.0", "dependencies": { - "@hyperlane-xyz/core": "1.2.3", - "@hyperlane-xyz/sdk": "1.2.3", - "@hyperlane-xyz/utils": "1.2.3", + "@hyperlane-xyz/core": "1.3.0", + "@hyperlane-xyz/sdk": "1.3.0", + "@hyperlane-xyz/utils": "1.3.0", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, From 4dfad6e0c1a352c0dda6c097d4f88863d7716354 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 4 Apr 2023 14:36:54 -0400 Subject: [PATCH 08/19] Stash pr progress --- src/config.ts | 28 ++++------ src/deploy.ts | 143 +++++++++++++++++--------------------------------- 2 files changed, 58 insertions(+), 113 deletions(-) diff --git a/src/config.ts b/src/config.ts index 71172cf..970ec59 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; -import { GasRouterConfig, RouterConfig } from '@hyperlane-xyz/sdk'; +import { GasRouterConfig } from '@hyperlane-xyz/sdk'; export enum TokenType { synthetic = 'synthetic', @@ -21,11 +21,11 @@ export type ERC20Metadata = TokenMetadata & { } export const isTokenMetadata = (metadata: any): metadata is TokenMetadata => - metadata.name !== undefined && metadata.symbol !== undefined && metadata.totalSupply !== undefined; + metadata.name && metadata.symbol && metadata.totalSupply; -export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => metadata.decimals !== undefined && isTokenMetadata(metadata); +export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => metadata.decimals && isTokenMetadata(metadata); -export type SyntheticConfig = Partial & { +export type SyntheticConfig = TokenMetadata & { type: TokenType.synthetic | TokenType.syntheticUri; }; export type CollateralConfig = { @@ -56,19 +56,9 @@ export const isUriConfig = (config: TokenConfig) => config.type === TokenType.syntheticUri || config.type === TokenType.collateralUri; -export type HypERC20Config = Partial & - RouterConfig & - TokenConfig; -export type HypERC20CollateralConfig = Partial & - RouterConfig & - CollateralConfig; -export type HypNativeConfig = Partial & - RouterConfig & - NativeConfig; +export type HypERC20Config = GasRouterConfig & SyntheticConfig & ERC20Metadata; +export type HypERC20CollateralConfig = GasRouterConfig & CollateralConfig; +export type HypNativeConfig = GasRouterConfig & NativeConfig; -export type HypERC721Config = Partial & - RouterConfig & - TokenConfig; -export type HypERC721CollateralConfig = Partial & - RouterConfig & - CollateralConfig; +export type HypERC721Config = GasRouterConfig & SyntheticConfig; +export type HypERC721CollateralConfig = GasRouterConfig & CollateralConfig; diff --git a/src/deploy.ts b/src/deploy.ts index 2319048..ee082db 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -7,22 +7,25 @@ import { objMap, } from '@hyperlane-xyz/sdk'; import { DeployerOptions } from '@hyperlane-xyz/sdk/dist/deploy/HyperlaneDeployer'; +import { objFilter } from '@hyperlane-xyz/sdk/dist/utils/objects'; import { CollateralConfig, + ERC20Metadata, + HypERC20CollateralConfig, HypERC20Config, HypERC721Config, + HypNativeConfig, + NativeConfig, SyntheticConfig, TokenConfig, + TokenMetadata, isCollateralConfig, + isErc20Metadata, isNativeConfig, isSyntheticConfig, - isUriConfig, - NativeConfig, isTokenMetadata, - ERC20Metadata, - isErc20Metadata, - TokenMetadata, + isUriConfig, } from './config'; import { HypERC20Contracts, HypERC721Contracts } from './contracts'; import { @@ -40,7 +43,9 @@ import { HypERC721__factory, HypNative, HypNative__factory, + TokenRouter, } from './types'; +import { providers } from 'ethers'; enum TokenType { erc20 = 'erc20', @@ -75,49 +80,30 @@ const gasDefaults = (config: TokenConfig, tokenType: TokenType) => { }; export class HypERC20Deployer extends GasRouterDeployer< - HypERC20Config & GasRouterConfig, + HypERC20Config, HypERC20Contracts, - any // RouterFactories doesn't work well when router has multiple types + any > { - tokenMetadata?: ERC20Metadata; - - constructor( - multiProvider: MultiProvider, - configMap: ChainMap, - factories: any, - options?: DeployerOptions, - ) { - super( - multiProvider, - objMap( - configMap, - (_, config): HypERC20Config & GasRouterConfig => - ({ - ...config, - gas: config.gas ?? gasDefaults(config, TokenType.erc20), - } as HypERC20Config & GasRouterConfig), - ), - factories, - options, - ); - } - - protected async deployCollateral( - chain: ChainName, - config: CollateralConfig & HypERC20Config, - ): Promise { - this.logger(`Deploying collateral router on ${chain}...`); + static async fetchTokenMetadata( + token: string, + provider: providers.Provider + ): Promise { const erc20 = new ERC20__factory() - .attach(config.token) - .connect(this.multiProvider.getProvider(chain)); + .attach(token) + .connect(provider); const decimals = await erc20.decimals(); const name = await erc20.name(); const symbol = await erc20.symbol(); - const totalSupply = 0; // synthetic tokens are minted on demand + const totalSupply = await erc20.totalSupply(); - this.tokenMetadata = { decimals, name, symbol, totalSupply }; + return { decimals, name, symbol, totalSupply }; + } + protected async deployCollateral( + chain: ChainName, + config: HypERC20CollateralConfig, + ): Promise { const router = await this.deployContractFromFactory( chain, new HypERC20Collateral__factory(), @@ -133,15 +119,8 @@ export class HypERC20Deployer extends GasRouterDeployer< protected async deployNative( chain: ChainName, - config: NativeConfig & HypERC20Config, + config: HypNativeConfig, ): Promise { - this.logger(`Deploying native router on ${chain}...`); - const nativeToken = this.multiProvider.getChainMetadata(chain).nativeToken; - - if (nativeToken) { - this.tokenMetadata = { ...nativeToken, totalSupply: 0 }; - } - const router = await this.deployContractFromFactory( chain, new HypNative__factory(), @@ -157,65 +136,38 @@ export class HypERC20Deployer extends GasRouterDeployer< protected async deploySynthetic( chain: ChainName, - config: SyntheticConfig & HypERC20Config, - tokenMetadata: ERC20Metadata + config: HypERC20Config, ): Promise { - this.logger(`Deploying synthetic router on ${chain}...`); const router = await this.deployContractFromFactory( chain, new HypERC20__factory(), 'HypERC20', - [tokenMetadata.decimals], + [config.decimals], ); await this.multiProvider.handleTx( chain, router.initialize( config.mailbox, config.interchainGasPaymaster, - tokenMetadata.totalSupply, - tokenMetadata.name, - tokenMetadata.symbol, + config.totalSupply, + config.name, + config.symbol, ), ); return router; } - async deploy( - partialDeployment?: ChainMap, - ): Promise> { - // deploy collateral or native first to populate token metadata for synthetics - for (const [chain, config] of Object.entries(this.configMap)) { - if (isCollateralConfig(config)) { - this.deployedContracts[chain] = { - router: await this.deployCollateral(chain as ChainName, config) - }; - } else if (isNativeConfig(config)) { - this.deployedContracts[chain] = { - router: await this.deployNative(chain as ChainName, config) - }; - } - } - - // deploy synthetics - return super.deploy({ ...partialDeployment, ...this.deployedContracts }); - } - async deployContracts(chain: ChainName, config: HypERC20Config) { - // skip if already deployed - if (this.deployedContracts[chain]) { - return this.deployedContracts[chain]; - } - - if (!isSyntheticConfig(config)) { - throw new Error('Expect only synthetic configs'); - } - - const erc20Metadata = {...this.tokenMetadata, ...config }; - if (!isErc20Metadata(erc20Metadata)) { - throw new Error(`ERC20 metadata not populated for ${chain}`); + let router: HypERC20 | HypERC20Collateral | HypNative; + if (isCollateralConfig(config)) { + router = await this.deployCollateral(chain, config); + } else if (isNativeConfig(config)) { + router = await this.deployNative(chain, config); + } else if (isSyntheticConfig(config)) { + router = await this.deploySynthetic(chain, config); + } else { + throw new Error('Invalid ERC20 token router config'); } - - const router = await this.deploySynthetic(chain, config, erc20Metadata); return { router }; } } @@ -255,7 +207,10 @@ export class HypERC721Deployer extends GasRouterDeployer< ): Promise { this.logger(`Deploying collateral router on ${chain}...`); - const erc721 = ERC721__factory.connect(config.token, this.multiProvider.getProvider(chain)); + const erc721 = ERC721__factory.connect( + config.token, + this.multiProvider.getProvider(chain), + ); const name = await erc721.name(); const symbol = await erc721.symbol(); const totalSupply = 0; // synthetic tokens are minted on demand @@ -279,7 +234,7 @@ export class HypERC721Deployer extends GasRouterDeployer< protected async deploySynthetic( chain: ChainName, config: HypERC721Config & SyntheticConfig, - tokenMetadata: TokenMetadata + tokenMetadata: TokenMetadata, ): Promise { this.logger(`Deploying synthetic router on ${chain}...`); const router = await this.deployContractFromFactory( @@ -310,7 +265,7 @@ export class HypERC721Deployer extends GasRouterDeployer< for (const [chain, config] of Object.entries(this.configMap)) { if (isCollateralConfig(config)) { this.deployedContracts[chain] = { - router: await this.deployCollateral(chain as ChainName, config) + router: await this.deployCollateral(chain as ChainName, config), }; } } @@ -328,12 +283,12 @@ export class HypERC721Deployer extends GasRouterDeployer< if (!isSyntheticConfig(config)) { throw new Error('Expect only synthetic configs'); } - - const erc721Metadata = {...this.tokenMetadata, ...config }; + + const erc721Metadata = { ...this.tokenMetadata, ...config }; if (!isTokenMetadata(erc721Metadata)) { throw new Error(`Token metadata not populated for ${chain}`); } - + const router = await this.deploySynthetic(chain, config, erc721Metadata); return { router }; } From f2bc0585fcb2d3041682697c4573a59d401dd068 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Tue, 4 Apr 2023 15:59:22 -0400 Subject: [PATCH 09/19] update lockfile --- yarn.lock | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/yarn.lock b/yarn.lock index fa9d4b3..dfebe7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1220,14 +1220,14 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@npm:1.2.3": - version: 1.2.3 - resolution: "@hyperlane-xyz/core@npm:1.2.3" +"@hyperlane-xyz/core@npm:1.3.0": + version: 1.3.0 + resolution: "@hyperlane-xyz/core@npm:1.3.0" dependencies: - "@hyperlane-xyz/utils": 1.2.3 + "@hyperlane-xyz/utils": 1.3.0 "@openzeppelin/contracts": ^4.8.0 "@openzeppelin/contracts-upgradeable": ^4.8.0 - checksum: c7e8f5c37d2e6874b6ca5f16024a4ea8b413393a2a4bb770a467ca7cc031b51a89fbe6989598c171de38acafcd2e990525be0274f793742faafc832a0db359fa + checksum: 8e4c8e72dee2ff2705697f802de06d29fcbdb8789883f051977bbb82748d842647d6274691d918edafa03fa18e9e2fb0359c6af4d8d7f7f99cbe2167a2aef1f4 languageName: node linkType: hard @@ -1235,9 +1235,9 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:." dependencies: - "@hyperlane-xyz/core": 1.2.3 - "@hyperlane-xyz/sdk": 1.2.3 - "@hyperlane-xyz/utils": 1.2.3 + "@hyperlane-xyz/core": 1.3.0 + "@hyperlane-xyz/sdk": 1.3.0 + "@hyperlane-xyz/utils": 1.3.0 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -1265,28 +1265,28 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:1.2.3": - version: 1.2.3 - resolution: "@hyperlane-xyz/sdk@npm:1.2.3" +"@hyperlane-xyz/sdk@npm:1.3.0": + version: 1.3.0 + resolution: "@hyperlane-xyz/sdk@npm:1.3.0" dependencies: - "@hyperlane-xyz/core": 1.2.3 - "@hyperlane-xyz/utils": 1.2.3 + "@hyperlane-xyz/core": 1.3.0 + "@hyperlane-xyz/utils": 1.3.0 "@wagmi/chains": ^0.2.6 coingecko-api: ^1.0.10 cross-fetch: ^3.1.5 debug: ^4.3.4 ethers: ^5.7.2 zod: ^3.21.2 - checksum: a1afc8bdfe64916fdc6623c1df06bbe1393359fe6b29cefe3f25bb5ded16108e1bac037da9a29df7f0d61cc84940e571d148e41c7fd8539964a87e9a95c03c35 + checksum: ef8f964b58cf16bd514ed09b8f22c484dcd08385510aa67bbcb896b7a95c103b7f029d66ef676807b07a9be1aaf816d937fce7d5848f9f38529373d2f276be3d languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:1.2.3": - version: 1.2.3 - resolution: "@hyperlane-xyz/utils@npm:1.2.3" +"@hyperlane-xyz/utils@npm:1.3.0": + version: 1.3.0 + resolution: "@hyperlane-xyz/utils@npm:1.3.0" dependencies: ethers: ^5.7.2 - checksum: 14222632d7ef2419f6b698afa0c4c466302dd0eb6f97d3c9031b7d42c033418547bf4092d480cc4346331f0233d4076caae75b86ffa2b7beed79741a146d3325 + checksum: 22008ac00bc37f23387b5988fa22c9066796dfa5bcc115a1cd51087b160115279cb3fa57061545fa3053de660750a27bbdb94ef8daa47abbfdf0cef8013dc787 languageName: node linkType: hard From 21d4075cae2ddd8611d5ae7269c9a15c0eafd894 Mon Sep 17 00:00:00 2001 From: Asa Oines Date: Tue, 4 Apr 2023 16:23:05 -0400 Subject: [PATCH 10/19] fix tests --- test/erc20.test.ts | 22 ++++++++++++++-------- test/erc721.test.ts | 21 ++++++++++++++------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 01f9ae1..afa1857 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -4,12 +4,15 @@ import { expect } from 'chai'; import { BigNumber, BigNumberish } from 'ethers'; import { ethers } from 'hardhat'; +import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; import { ChainMap, Chains, + HyperlaneContractsMap, MultiProvider, TestCoreApp, TestCoreDeployer, + deployTestIgpsAndGetRouterConfig, objMap, } from '@hyperlane-xyz/sdk'; import { utils } from '@hyperlane-xyz/utils'; @@ -20,7 +23,7 @@ import { TokenConfig, TokenType, } from '../src/config'; -import { HypERC20Contracts } from '../src/contracts'; +import { HypERC20Factories } from '../src/contracts'; import { HypERC20Deployer } from '../src/deploy'; import { ERC20, @@ -55,7 +58,7 @@ for (const variant of [ let recipient: SignerWithAddress; let core: TestCoreApp; let deployer: HypERC20Deployer; - let contracts: ChainMap; + let contracts: HyperlaneContractsMap; let localTokenConfig: TokenConfig = tokenConfig; let local: HypERC20 | HypERC20Collateral | HypNative; let remote: HypERC20 | HypERC20Collateral; @@ -72,7 +75,11 @@ for (const variant of [ const coreDeployer = new TestCoreDeployer(multiProvider); const coreContractsMaps = await coreDeployer.deploy(); core = new TestCoreApp(coreContractsMaps, multiProvider); - const coreConfig = core.getConnectionClientConfigMap(); + const coreConfig = await deployTestIgpsAndGetRouterConfig( + multiProvider, + owner.address, + core.contractsMap, + ); let erc20: ERC20 | undefined; if (variant === TokenType.collateral) { @@ -152,8 +159,7 @@ for (const variant of [ it('benchmark handle gas overhead', async () => { const localRaw = local.connect(ethers.provider); - const mailboxAddress = - core.contractsMap[localChain].mailbox.contract.address; + const mailboxAddress = core.contractsMap[localChain].mailbox.address; if (variant === TokenType.collateral) { const tokenAddress = await (local as HypERC20Collateral).wrappedToken(); const token = ERC20__factory.connect(tokenAddress, owner); @@ -219,9 +225,9 @@ for (const variant of [ }); it('allows interchain gas payment for remote transfers', async () => { - const interchainGasPaymaster = - core.contractsMap[localChain].interchainGasPaymaster.contract; - + const interchainGasPaymaster = new InterchainGasPaymaster__factory() + .attach(await local.interchainGasPaymaster()) + .connect(owner); await expect( local.transferRemote( remoteDomain, diff --git a/test/erc721.test.ts b/test/erc721.test.ts index daf5c5f..33c2c53 100644 --- a/test/erc721.test.ts +++ b/test/erc721.test.ts @@ -4,12 +4,15 @@ import { expect } from 'chai'; import { BigNumber, BigNumberish } from 'ethers'; import { ethers } from 'hardhat'; +import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; import { ChainMap, Chains, + HyperlaneContractsMap, MultiProvider, TestCoreApp, TestCoreDeployer, + deployTestIgpsAndGetRouterConfig, objMap, } from '@hyperlane-xyz/sdk'; import { utils } from '@hyperlane-xyz/utils'; @@ -20,7 +23,7 @@ import { SyntheticConfig, TokenType, } from '../src/config'; -import { HypERC721Contracts } from '../src/contracts'; +import { HypERC721Factories } from '../src/contracts'; import { HypERC721Deployer } from '../src/deploy'; import { ERC721, @@ -72,7 +75,7 @@ for (const withCollateral of [true, false]) { let recipient: SignerWithAddress; let core: TestCoreApp; let deployer: HypERC721Deployer; - let contracts: ChainMap; + let contracts: HyperlaneContractsMap; let local: HypERC721 | HypERC721Collateral | HypERC721URICollateral; let remote: HypERC721 | HypERC721Collateral | HypERC721URIStorage; let interchainGasPayment: BigNumberish; @@ -88,7 +91,11 @@ for (const withCollateral of [true, false]) { const coreDeployer = new TestCoreDeployer(multiProvider); const coreContractsMaps = await coreDeployer.deploy(); core = new TestCoreApp(coreContractsMaps, multiProvider); - const coreConfig = core.getConnectionClientConfigMap(); + const coreConfig = await deployTestIgpsAndGetRouterConfig( + multiProvider, + owner.address, + core.contractsMap, + ); const configWithTokenInfo: ChainMap< HypERC721Config | HypERC721CollateralConfig > = objMap(coreConfig, (key) => ({ @@ -252,8 +259,7 @@ for (const withCollateral of [true, false]) { it('benchmark handle gas overhead', async () => { const localRaw = local.connect(ethers.provider); - const mailboxAddress = - core.contractsMap[localChain].mailbox.contract.address; + const mailboxAddress = core.contractsMap[localChain].mailbox.address; let tokenIdToUse: number; if (withCollateral) { const tokenAddress = await ( @@ -285,8 +291,9 @@ for (const withCollateral of [true, false]) { }); it('allows interchain gas payment for remote transfers', async () => { - const interchainGasPaymaster = - core.contractsMap[localChain].interchainGasPaymaster.contract; + const interchainGasPaymaster = new InterchainGasPaymaster__factory() + .attach(await local.interchainGasPaymaster()) + .connect(owner); await expect( local.transferRemote( remoteDomain, From 62408e3f1901818daf270068dc087dd3b71131eb Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 4 Apr 2023 18:48:09 -0400 Subject: [PATCH 11/19] Stash more pr progress --- src/deploy.ts | 156 +++++++++++++++++--------------------------------- 1 file changed, 53 insertions(+), 103 deletions(-) diff --git a/src/deploy.ts b/src/deploy.ts index ee082db..8d699a1 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -14,6 +14,7 @@ import { ERC20Metadata, HypERC20CollateralConfig, HypERC20Config, + HypERC721CollateralConfig, HypERC721Config, HypNativeConfig, NativeConfig, @@ -30,6 +31,7 @@ import { import { HypERC20Contracts, HypERC721Contracts } from './contracts'; import { ERC20__factory, + ERC721EnumerableUpgradeable__factory, ERC721__factory, HypERC20, HypERC20Collateral, @@ -84,22 +86,6 @@ export class HypERC20Deployer extends GasRouterDeployer< HypERC20Contracts, any > { - static async fetchTokenMetadata( - token: string, - provider: providers.Provider - ): Promise { - const erc20 = new ERC20__factory() - .attach(token) - .connect(provider); - - const decimals = await erc20.decimals(); - const name = await erc20.name(); - const symbol = await erc20.symbol(); - const totalSupply = await erc20.totalSupply(); - - return { decimals, name, symbol, totalSupply }; - } - protected async deployCollateral( chain: ChainName, config: HypERC20CollateralConfig, @@ -172,58 +158,39 @@ export class HypERC20Deployer extends GasRouterDeployer< } } -// TODO: dedupe? export class HypERC721Deployer extends GasRouterDeployer< - HypERC721Config & GasRouterConfig, + HypERC721Config, HypERC721Contracts, any > { - tokenMetadata?: TokenMetadata; - - constructor( - multiProvider: MultiProvider, - configMap: ChainMap, - factories: any, - options?: DeployerOptions, - ) { - super( - multiProvider, - objMap( - configMap, - (_, config): HypERC721Config & GasRouterConfig => - ({ - ...config, - gas: config.gas ?? gasDefaults(config, TokenType.erc721), - } as HypERC721Config & GasRouterConfig), - ), - factories, - options, - ); + static async fetchMetadata(provider: providers.Provider, config: CollateralConfig): Promise { + const erc721 = ERC721EnumerableUpgradeable__factory.connect(config.token, provider); + const name = await erc721.name(); + const symbol = await erc721.symbol(); + const totalSupply = await erc721.totalSupply(); + return { name, symbol, totalSupply }; } protected async deployCollateral( chain: ChainName, - config: CollateralConfig & HypERC721Config, + config: HypERC721CollateralConfig, ): Promise { - this.logger(`Deploying collateral router on ${chain}...`); - - const erc721 = ERC721__factory.connect( - config.token, - this.multiProvider.getProvider(chain), - ); - const name = await erc721.name(); - const symbol = await erc721.symbol(); - const totalSupply = 0; // synthetic tokens are minted on demand - this.tokenMetadata = { name, symbol, totalSupply }; - - const router = await this.deployContractFromFactory( - chain, - isUriConfig(config) - ? new HypERC721URICollateral__factory() - : new HypERC721Collateral__factory(), - `HypERC721${isUriConfig(config) ? 'URI' : ''}Collateral`, - [config.token], - ); + let router: HypERC721Collateral; + if (isUriConfig(config)) { + router = await this.deployContractFromFactory( + chain, + new HypERC721URICollateral__factory(), + 'HypERC721URICollateral', + [config.token], + ); + } else { + router = await this.deployContractFromFactory( + chain, + new HypERC721Collateral__factory(), + 'HypERC721Collateral', + [config.token], + ); + } await this.multiProvider.handleTx( chain, router.initialize(config.mailbox, config.interchainGasPaymaster), @@ -233,63 +200,46 @@ export class HypERC721Deployer extends GasRouterDeployer< protected async deploySynthetic( chain: ChainName, - config: HypERC721Config & SyntheticConfig, - tokenMetadata: TokenMetadata, + config: HypERC721Config ): Promise { - this.logger(`Deploying synthetic router on ${chain}...`); - const router = await this.deployContractFromFactory( - chain, - isUriConfig(config) - ? new HypERC721URIStorage__factory() - : new HypERC721__factory(), - `HypERC721${isUriConfig(config) ? 'URIStorage' : ''}`, - [], - ); + let router: HypERC721; + if (isUriConfig(config)) { + router = await this.deployContractFromFactory( + chain, + new HypERC721URIStorage__factory(), + 'HypERC721URIStorage', + [], + ); + } else { + router = await this.deployContractFromFactory( + chain, + new HypERC721__factory(), + 'HypERC721', + [], + ); + } await this.multiProvider.handleTx( chain, router.initialize( config.mailbox, config.interchainGasPaymaster, - tokenMetadata.totalSupply, - tokenMetadata.name, - tokenMetadata.symbol, + config.totalSupply, + config.name, + config.symbol, ), ); return router; } - async deploy( - partialDeployment?: ChainMap, - ): Promise> { - // deploy collateral first to populate token metadata for synthetics - for (const [chain, config] of Object.entries(this.configMap)) { - if (isCollateralConfig(config)) { - this.deployedContracts[chain] = { - router: await this.deployCollateral(chain as ChainName, config), - }; - } - } - - // deploy synthetics - return super.deploy({ ...partialDeployment, ...this.deployedContracts }); - } - async deployContracts(chain: ChainName, config: HypERC721Config) { - // skip if already deployed - if (this.deployedContracts[chain]) { - return this.deployedContracts[chain]; - } - - if (!isSyntheticConfig(config)) { - throw new Error('Expect only synthetic configs'); - } - - const erc721Metadata = { ...this.tokenMetadata, ...config }; - if (!isTokenMetadata(erc721Metadata)) { - throw new Error(`Token metadata not populated for ${chain}`); + let router: HypERC721 | HypERC721Collateral; + if (isCollateralConfig(config)) { + router = await this.deployCollateral(chain, config); + } else if (isSyntheticConfig(config)) { + router = await this.deploySynthetic(chain, config); + } else { + throw new Error('Invalid ERC721 token router config'); } - - const router = await this.deploySynthetic(chain, config, erc721Metadata); return { router }; } } From 74a02593053b8857f5304e49bf69dbe6c959b4cb Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 5 Apr 2023 15:47:19 -0400 Subject: [PATCH 12/19] Fix tests --- contracts/test/ERC721Test.sol | 4 +- src/config.ts | 26 ++++-- src/deploy.ts | 158 +++++++++++++++++++++++----------- test/erc20.test.ts | 36 ++++---- test/erc721.test.ts | 43 ++++----- 5 files changed, 167 insertions(+), 100 deletions(-) diff --git a/contracts/test/ERC721Test.sol b/contracts/test/ERC721Test.sol index 1de5544..a8ceb1f 100644 --- a/contracts/test/ERC721Test.sol +++ b/contracts/test/ERC721Test.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; -contract ERC721Test is ERC721 { +contract ERC721Test is ERC721Enumerable { constructor( string memory name, string memory symbol, diff --git a/src/config.ts b/src/config.ts index 3b70bf7..cbb62bd 100644 --- a/src/config.ts +++ b/src/config.ts @@ -14,18 +14,19 @@ export type TokenMetadata = { name: string; symbol: string; totalSupply: ethers.BigNumberish; -} +}; export type ERC20Metadata = TokenMetadata & { decimals: number; -} +}; -export const isTokenMetadata = (metadata: any): metadata is TokenMetadata => - metadata.name && metadata.symbol && metadata.totalSupply; +export const isTokenMetadata = (metadata: any): metadata is TokenMetadata => + metadata.name && metadata.symbol && metadata.totalSupply !== undefined; // totalSupply can be 0 -export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => metadata.decimals && isTokenMetadata(metadata); +export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => + metadata.decimals && isTokenMetadata(metadata); -export type SyntheticConfig = Partial & { +export type SyntheticConfig = TokenMetadata & { type: TokenType.synthetic | TokenType.syntheticUri; }; export type CollateralConfig = { @@ -36,7 +37,11 @@ export type NativeConfig = { type: TokenType.native; }; -export type TokenConfig = SyntheticConfig | CollateralConfig | NativeConfig; +export type TokenConfig = { type: TokenType } & ( + | SyntheticConfig + | CollateralConfig + | NativeConfig +); export const isCollateralConfig = ( config: TokenConfig, @@ -56,9 +61,14 @@ export const isUriConfig = (config: TokenConfig) => config.type === TokenType.syntheticUri || config.type === TokenType.collateralUri; -export type HypERC20Config = GasRouterConfig & SyntheticConfig & Partial; +export type HypERC20Config = GasRouterConfig & SyntheticConfig & ERC20Metadata; export type HypERC20CollateralConfig = GasRouterConfig & CollateralConfig; export type HypNativeConfig = GasRouterConfig & NativeConfig; +export type ERC20RouterConfig = + | HypERC20Config + | HypERC20CollateralConfig + | HypNativeConfig; export type HypERC721Config = GasRouterConfig & SyntheticConfig; export type HypERC721CollateralConfig = GasRouterConfig & CollateralConfig; +export type ERC721RouterConfig = HypERC721Config | HypERC721CollateralConfig; diff --git a/src/deploy.ts b/src/deploy.ts index ddd9c99..067d36e 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -4,8 +4,11 @@ import { ChainMap, ChainName, GasRouterDeployer, - HyperlaneContracts + HyperlaneContracts, + MultiProvider, + objMap, } from '@hyperlane-xyz/sdk'; +import { GasConfig, RouterConfig } from '@hyperlane-xyz/sdk/dist/router/types'; import { CollateralConfig, @@ -15,6 +18,7 @@ import { HypERC721CollateralConfig, HypERC721Config, HypNativeConfig, + TokenConfig, TokenMetadata, isCollateralConfig, isErc20Metadata, @@ -22,6 +26,7 @@ import { isSyntheticConfig, isUriConfig, } from './config'; +import { isTokenMetadata } from './config'; import { HypERC20Factories, HypERC721Factories } from './contracts'; import { ERC20__factory, @@ -39,13 +44,16 @@ import { HypNative, HypNative__factory, } from './types'; -import { isTokenMetadata } from './config'; +import { ERC721RouterConfig } from './config'; +import { ERC20RouterConfig } from './config'; export class HypERC20Deployer extends GasRouterDeployer< - HypERC20Config, + ERC20RouterConfig, HypERC20Factories > { - tokenMetadata: ERC20Metadata | undefined; + constructor(multiProvider: MultiProvider) { + super(multiProvider, {} as HypERC20Factories); // factories not used in deploy + } static async fetchMetadata( provider: providers.Provider, @@ -60,6 +68,18 @@ export class HypERC20Deployer extends GasRouterDeployer< return { name, symbol, totalSupply, decimals }; } + static gasOverheadDefault(config: TokenConfig): number { + switch (config.type) { + case 'synthetic': + return 64_000; + case 'native': + return 44_000; + case 'collateral': + default: + return 68_000; + } + } + protected async deployCollateral( chain: ChainName, config: HypERC20CollateralConfig, @@ -97,22 +117,21 @@ export class HypERC20Deployer extends GasRouterDeployer< protected async deploySynthetic( chain: ChainName, config: HypERC20Config, - metadata: ERC20Metadata ): Promise { const router = await this.deployContractFromFactory( chain, new HypERC20__factory(), 'HypERC20', - [metadata.decimals], + [config.decimals], ); await this.multiProvider.handleTx( chain, router.initialize( config.mailbox, config.interchainGasPaymaster, - metadata.totalSupply, - metadata.name, - metadata.symbol, + config.totalSupply, + config.name, + config.symbol, ), ); return router; @@ -129,17 +148,16 @@ export class HypERC20Deployer extends GasRouterDeployer< } else if (isNativeConfig(config)) { router = await this.deployNative(chain, config); } else if (isSyntheticConfig(config)) { - if (!isErc20Metadata(this.tokenMetadata)) { - throw new Error('Invalid synthetic token metadata'); - } - router = await this.deploySynthetic(chain, config, this.tokenMetadata); + router = await this.deploySynthetic(chain, config); } else { throw new Error('Invalid ERC20 token router config'); } return { router }; } - async buildTokenMetadata(configMap: ChainMap): Promise { + async buildTokenMetadata( + configMap: ChainMap, + ): Promise> { let tokenMetadata: ERC20Metadata | undefined; for (const [chain, config] of Object.entries(configMap)) { @@ -151,41 +169,58 @@ export class HypERC20Deployer extends GasRouterDeployer< tokenMetadata = { ...collateralMetadata, totalSupply: 0, - } + }; } else if (isNativeConfig(config)) { const chainMetadata = this.multiProvider.getChainMetadata(chain); if (chainMetadata.nativeToken) { tokenMetadata = { - totalSupply: 0, ...chainMetadata.nativeToken, + totalSupply: 0, }; } } else if (isErc20Metadata(config)) { tokenMetadata = config; } } + + if (!isErc20Metadata(tokenMetadata)) { + throw new Error('Invalid ERC20 token metadata'); + } - return tokenMetadata; + return objMap(configMap, () => (tokenMetadata!)); } - async deploy(configMap: ChainMap) { - this.tokenMetadata = await this.buildTokenMetadata(configMap); + buildGasOverhead(configMap: ChainMap): ChainMap { + return objMap(configMap, (_, config) => ({ + gas: HypERC20Deployer.gasOverheadDefault(config), + })); + } - if (!this.tokenMetadata) { - throw new Error('No ERC20 token metadata found'); - } else { - this.logger('Found synthetic token metadata:', this.tokenMetadata); - } + async deploy(configMap: ChainMap) { + const tokenMetadata = await this.buildTokenMetadata(configMap); + const gasOverhead = this.buildGasOverhead(configMap); + const mergedConfig = objMap( + configMap, + (chain, config) => { + return { + ...tokenMetadata[chain], + ...gasOverhead[chain], + ...config, + }; + }, + ) as ChainMap; - return super.deploy(configMap); + return super.deploy(mergedConfig); } } export class HypERC721Deployer extends GasRouterDeployer< - HypERC721Config, + ERC721RouterConfig, HypERC721Factories > { - tokenMetadata: TokenMetadata | undefined; + constructor(multiProvider: MultiProvider) { + super(multiProvider, {} as HypERC721Factories); // factories not used in deploy + } static async fetchMetadata( provider: providers.Provider, @@ -198,9 +233,23 @@ export class HypERC721Deployer extends GasRouterDeployer< const name = await erc721.name(); const symbol = await erc721.symbol(); const totalSupply = await erc721.totalSupply(); + return { name, symbol, totalSupply }; } + static gasOverheadDefault(config: TokenConfig): number { + switch (config.type) { + case 'synthetic': + return 160_000; + case 'syntheticUri': + return 163_000; + case 'collateral': + case 'collateralUri': + default: + return 80_000; + } + } + protected async deployCollateral( chain: ChainName, config: HypERC721CollateralConfig, @@ -231,7 +280,6 @@ export class HypERC721Deployer extends GasRouterDeployer< protected async deploySynthetic( chain: ChainName, config: HypERC721Config, - metadata: TokenMetadata, ): Promise { let router: HypERC721; if (isUriConfig(config)) { @@ -254,9 +302,9 @@ export class HypERC721Deployer extends GasRouterDeployer< router.initialize( config.mailbox, config.interchainGasPaymaster, - metadata.totalSupply, - metadata.name, - metadata.symbol, + config.totalSupply, + config.name, + config.symbol, ), ); return router; @@ -271,46 +319,60 @@ export class HypERC721Deployer extends GasRouterDeployer< if (isCollateralConfig(config)) { router = await this.deployCollateral(chain, config); } else if (isSyntheticConfig(config)) { - if (!isTokenMetadata(this.tokenMetadata)) { - throw new Error('Invalid synthetic token metadata'); - } - router = await this.deploySynthetic(chain, config, this.tokenMetadata); + router = await this.deploySynthetic(chain, config); } else { throw new Error('Invalid ERC721 token router config'); } return { router }; } - async buildTokenMetadata(configMap: ChainMap): Promise { + async buildTokenMetadata( + configMap: ChainMap, + ): Promise> { let tokenMetadata: TokenMetadata | undefined; for (const [chain, config] of Object.entries(configMap)) { if (isCollateralConfig(config)) { - const collateralMetadata = await HypERC20Deployer.fetchMetadata( + const collateralMetadata = await HypERC721Deployer.fetchMetadata( this.multiProvider.getProvider(chain), config, ); tokenMetadata = { ...collateralMetadata, totalSupply: 0, - } + }; } else if (isTokenMetadata(config)) { tokenMetadata = config; } } - return tokenMetadata; + if (!isTokenMetadata(tokenMetadata)) { + throw new Error('Invalid ERC721 token metadata'); + } + + return objMap(configMap, () => (tokenMetadata!)); } - async deploy(configMap: ChainMap) { - this.tokenMetadata = await this.buildTokenMetadata(configMap); - - if (!this.tokenMetadata) { - throw new Error('No ERC20 token metadata found'); - } else { - this.logger('Found synthetic token metadata:', this.tokenMetadata); - } + buildGasOverhead(configMap: ChainMap): ChainMap { + return objMap(configMap, (_, config) => ({ + gas: HypERC721Deployer.gasOverheadDefault(config), + })); + } + + async deploy(configMap: ChainMap) { + const tokenMetadata = await this.buildTokenMetadata(configMap); + const gasOverhead = this.buildGasOverhead(configMap); + const mergedConfig = objMap( + configMap, + (chain, config) => { + return { + ...tokenMetadata[chain], + ...gasOverhead[chain], + ...config, + }; + }, + ) as ChainMap; - return super.deploy(configMap); + return super.deploy(mergedConfig); } } diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 3a13c86..018fda0 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -10,6 +10,7 @@ import { Chains, HyperlaneContractsMap, MultiProvider, + RouterConfig, TestCoreApp, TestCoreDeployer, deployTestIgpsAndGetRouterConfig, @@ -18,8 +19,6 @@ import { import { utils } from '@hyperlane-xyz/utils'; import { - HypERC20Config, - SyntheticConfig, TokenConfig, TokenType, } from '../src/config'; @@ -41,8 +40,7 @@ let remoteDomain: number; const totalSupply = 3000; const amount = 10; -const tokenConfig: SyntheticConfig = { - type: TokenType.synthetic, +const tokenMetadata = { name: 'HypERC20', symbol: 'HYP', decimals: 18, @@ -60,9 +58,9 @@ for (const variant of [ let core: TestCoreApp; let deployer: HypERC20Deployer; let contracts: HyperlaneContractsMap; - let localTokenConfig: TokenConfig = tokenConfig; + let localTokenConfig: TokenConfig; let local: HypERC20 | HypERC20Collateral | HypNative; - let remote: HypERC20 | HypERC20Collateral; + let remote: HypERC20; let interchainGasPayment: BigNumber; beforeEach(async () => { @@ -76,7 +74,7 @@ for (const variant of [ const coreDeployer = new TestCoreDeployer(multiProvider); const coreContractsMaps = await coreDeployer.deploy(); core = new TestCoreApp(coreContractsMaps, multiProvider); - const coreConfig = await deployTestIgpsAndGetRouterConfig( + const routerConfig = await deployTestIgpsAndGetRouterConfig( multiProvider, owner.address, core.contractsMap, @@ -85,9 +83,9 @@ for (const variant of [ let erc20: ERC20 | undefined; if (variant === TokenType.collateral) { erc20 = await new ERC20Test__factory(owner).deploy( - tokenConfig.name!, - tokenConfig.symbol!, - tokenConfig.totalSupply!, + tokenMetadata.name, + tokenMetadata.symbol, + tokenMetadata.totalSupply, ); localTokenConfig = { type: variant, @@ -97,17 +95,21 @@ for (const variant of [ localTokenConfig = { type: variant, }; + } else if (variant === TokenType.synthetic) { + localTokenConfig = { type: variant, ...tokenMetadata }; } - const config: ChainMap = objMap(coreConfig, (key) => ({ - ...coreConfig[key], - ...(key === localChain ? localTokenConfig : tokenConfig), + const config = objMap(routerConfig, (key) => ({ + ...routerConfig[key], + ...(key === localChain) + ? localTokenConfig + : { type: TokenType.synthetic }, owner: owner.address, - })); + })) as ChainMap; - deployer = new HypERC20Deployer(multiProvider, config, core); - contracts = await deployer.deploy(); - local = contracts[localChain].router as HypERC20; + deployer = new HypERC20Deployer(multiProvider); + contracts = await deployer.deploy(config); + local = contracts[localChain].router; interchainGasPayment = await local.quoteGasPayment(remoteDomain); diff --git a/test/erc721.test.ts b/test/erc721.test.ts index 91e3bdd..ba7dee1 100644 --- a/test/erc721.test.ts +++ b/test/erc721.test.ts @@ -6,7 +6,6 @@ import { ethers } from 'hardhat'; import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; import { - ChainMap, Chains, HyperlaneContractsMap, MultiProvider, @@ -18,9 +17,7 @@ import { import { utils } from '@hyperlane-xyz/utils'; import { - HypERC721CollateralConfig, - HypERC721Config, - SyntheticConfig, + TokenConfig, TokenType, } from '../src/config'; import { HypERC721Factories } from '../src/contracts'; @@ -45,20 +42,21 @@ const tokenId2 = 20; const tokenId3 = 30; const tokenId4 = 40; +const tokenMetadata = { + name: 'HypERC721', + symbol: 'HYP', + totalSupply, +}; + for (const withCollateral of [true, false]) { for (const withUri of [true, false]) { - const tokenConfig: SyntheticConfig = { + const tokenConfig: TokenConfig = { type: withUri ? TokenType.syntheticUri : TokenType.synthetic, - name: 'HypERC721', - symbol: 'HYP', - totalSupply, + ...tokenMetadata, }; const configMap = { - test1: { - ...tokenConfig, - totalSupply, - }, + test1: tokenConfig, test2: { ...tokenConfig, totalSupply: 0, @@ -96,9 +94,7 @@ for (const withCollateral of [true, false]) { owner.address, core.contractsMap, ); - const configWithTokenInfo: ChainMap< - HypERC721Config | HypERC721CollateralConfig - > = objMap(coreConfig, (key) => ({ + const configWithTokenInfo = objMap(coreConfig, (key) => ({ ...coreConfig[key], ...configMap[key], owner: owner.address, @@ -107,23 +103,20 @@ for (const withCollateral of [true, false]) { let erc721: ERC721 | undefined; if (withCollateral) { erc721 = await new ERC721Test__factory(owner).deploy( - tokenConfig.name!, - tokenConfig.symbol!, - tokenConfig.totalSupply!, + tokenConfig.name, + tokenConfig.symbol, + tokenConfig.totalSupply, ); configWithTokenInfo.test1 = { - ...configWithTokenInfo.test1, type: withUri ? TokenType.collateralUri : TokenType.collateral, token: erc721.address, + ...coreConfig.test1, }; } - deployer = new HypERC721Deployer( - multiProvider, - configWithTokenInfo, - core, - ); - contracts = await deployer.deploy(); + deployer = new HypERC721Deployer(multiProvider); + contracts = await deployer.deploy(configWithTokenInfo); + local = contracts[localChain].router; if (withCollateral) { // approve wrapper to transfer tokens From 5af73e5caae5c9c35acb242240f245649f9e386a Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Wed, 5 Apr 2023 15:49:31 -0400 Subject: [PATCH 13/19] Prettier tests --- test/erc20.test.ts | 9 +++------ test/erc721.test.ts | 5 +---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 018fda0..c347f82 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -18,10 +18,7 @@ import { } from '@hyperlane-xyz/sdk'; import { utils } from '@hyperlane-xyz/utils'; -import { - TokenConfig, - TokenType, -} from '../src/config'; +import { TokenConfig, TokenType } from '../src/config'; import { HypERC20Factories } from '../src/contracts'; import { HypERC20Deployer } from '../src/deploy'; import { @@ -101,9 +98,9 @@ for (const variant of [ const config = objMap(routerConfig, (key) => ({ ...routerConfig[key], - ...(key === localChain) + ...(key === localChain ? localTokenConfig - : { type: TokenType.synthetic }, + : { type: TokenType.synthetic }), owner: owner.address, })) as ChainMap; diff --git a/test/erc721.test.ts b/test/erc721.test.ts index ba7dee1..acd004e 100644 --- a/test/erc721.test.ts +++ b/test/erc721.test.ts @@ -16,10 +16,7 @@ import { } from '@hyperlane-xyz/sdk'; import { utils } from '@hyperlane-xyz/utils'; -import { - TokenConfig, - TokenType, -} from '../src/config'; +import { TokenConfig, TokenType } from '../src/config'; import { HypERC721Factories } from '../src/contracts'; import { HypERC721Deployer } from '../src/deploy'; import { From 52acb956a89c9f2b88210b46b2d5bccd2e0dafe3 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 6 Apr 2023 09:19:14 -0400 Subject: [PATCH 14/19] Pull in 1.3.1 --- package.json | 8 +++---- src/deploy.ts | 4 ---- yarn.lock | 61 ++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index f740afe..3a355e3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/hyperlane-token", "description": "A template for interchain ERC20 and ERC721 tokens using Hyperlane", - "version": "1.3.0", + "version": "1.3.1", "dependencies": { - "@hyperlane-xyz/core": "1.3.0", - "@hyperlane-xyz/sdk": "1.3.0", - "@hyperlane-xyz/utils": "1.3.0", + "@hyperlane-xyz/core": "1.3.1", + "@hyperlane-xyz/sdk": "1.3.1", + "@hyperlane-xyz/utils": "1.3.1", "@openzeppelin/contracts-upgradeable": "^4.8.0", "ethers": "^5.7.2" }, diff --git a/src/deploy.ts b/src/deploy.ts index 3dd0d7e..067d36e 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -141,10 +141,6 @@ export class HypERC20Deployer extends GasRouterDeployer< return contracts.router; } - router(contracts: HyperlaneContracts) { - return contracts.router; - } - async deployContracts(chain: ChainName, config: HypERC20Config) { let router: HypERC20 | HypERC20Collateral | HypNative; if (isCollateralConfig(config)) { diff --git a/yarn.lock b/yarn.lock index dfebe7d..644de22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1220,14 +1220,14 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/core@npm:1.3.0": - version: 1.3.0 - resolution: "@hyperlane-xyz/core@npm:1.3.0" +"@hyperlane-xyz/core@npm:1.3.1": + version: 1.3.1 + resolution: "@hyperlane-xyz/core@npm:1.3.1" dependencies: - "@hyperlane-xyz/utils": 1.3.0 + "@hyperlane-xyz/utils": 1.3.1 "@openzeppelin/contracts": ^4.8.0 "@openzeppelin/contracts-upgradeable": ^4.8.0 - checksum: 8e4c8e72dee2ff2705697f802de06d29fcbdb8789883f051977bbb82748d842647d6274691d918edafa03fa18e9e2fb0359c6af4d8d7f7f99cbe2167a2aef1f4 + checksum: bb341b6ab2f7bbde057082ec277d48df5918b5a4c676887af9b49a54f9dc2d349c34cf74de7b1df20bddbadbdc7467628d3689182c01702cb21e0ac0e4e71981 languageName: node linkType: hard @@ -1235,9 +1235,9 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/hyperlane-token@workspace:." dependencies: - "@hyperlane-xyz/core": 1.3.0 - "@hyperlane-xyz/sdk": 1.3.0 - "@hyperlane-xyz/utils": 1.3.0 + "@hyperlane-xyz/core": 1.3.1 + "@hyperlane-xyz/sdk": 1.3.1 + "@hyperlane-xyz/utils": 1.3.1 "@nomiclabs/hardhat-ethers": ^2.2.1 "@nomiclabs/hardhat-waffle": ^2.0.3 "@openzeppelin/contracts-upgradeable": ^4.8.0 @@ -1265,28 +1265,30 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/sdk@npm:1.3.0": - version: 1.3.0 - resolution: "@hyperlane-xyz/sdk@npm:1.3.0" +"@hyperlane-xyz/sdk@npm:1.3.1": + version: 1.3.1 + resolution: "@hyperlane-xyz/sdk@npm:1.3.1" dependencies: - "@hyperlane-xyz/core": 1.3.0 - "@hyperlane-xyz/utils": 1.3.0 + "@hyperlane-xyz/core": 1.3.1 + "@hyperlane-xyz/utils": 1.3.1 + "@types/coingecko-api": ^1.0.10 + "@types/debug": ^4.1.7 "@wagmi/chains": ^0.2.6 coingecko-api: ^1.0.10 cross-fetch: ^3.1.5 debug: ^4.3.4 ethers: ^5.7.2 zod: ^3.21.2 - checksum: ef8f964b58cf16bd514ed09b8f22c484dcd08385510aa67bbcb896b7a95c103b7f029d66ef676807b07a9be1aaf816d937fce7d5848f9f38529373d2f276be3d + checksum: 3b1339dafb9ea8ba7c91ac3a51fd86638808bc60a06abd36e94fdc86316a7d591b395e5c94f9646d63331bde258f5013ee434e014ddf4d1db3d20a9b6b81f5dc languageName: node linkType: hard -"@hyperlane-xyz/utils@npm:1.3.0": - version: 1.3.0 - resolution: "@hyperlane-xyz/utils@npm:1.3.0" +"@hyperlane-xyz/utils@npm:1.3.1": + version: 1.3.1 + resolution: "@hyperlane-xyz/utils@npm:1.3.1" dependencies: ethers: ^5.7.2 - checksum: 22008ac00bc37f23387b5988fa22c9066796dfa5bcc115a1cd51087b160115279cb3fa57061545fa3053de660750a27bbdb94ef8daa47abbfdf0cef8013dc787 + checksum: 960c972211618ea9b637bf98cdaf052f6175aa535ba6402617b84110731fe2657f8cdb387cc2be24c8250e87d46a99320f7d609067ee8e42b5d98644d0db4f95 languageName: node linkType: hard @@ -1803,6 +1805,13 @@ __metadata: languageName: node linkType: hard +"@types/coingecko-api@npm:^1.0.10": + version: 1.0.10 + resolution: "@types/coingecko-api@npm:1.0.10" + checksum: e9683f9ea9ce2f855f6565089981dd3fceb6c4674365438f3fc3877d089a2fb82cdea011b59d59c7baa1635dc610860cd29a10a4b7a650ff96521ead46f22a50 + languageName: node + linkType: hard + "@types/concat-stream@npm:^1.6.0": version: 1.6.1 resolution: "@types/concat-stream@npm:1.6.1" @@ -1812,6 +1821,15 @@ __metadata: languageName: node linkType: hard +"@types/debug@npm:^4.1.7": + version: 4.1.7 + resolution: "@types/debug@npm:4.1.7" + dependencies: + "@types/ms": "*" + checksum: 0a7b89d8ed72526858f0b61c6fd81f477853e8c4415bb97f48b1b5545248d2ae389931680b94b393b993a7cfe893537a200647d93defe6d87159b96812305adc + languageName: node + linkType: hard + "@types/form-data@npm:0.0.33": version: 0.0.33 resolution: "@types/form-data@npm:0.0.33" @@ -1895,6 +1913,13 @@ __metadata: languageName: node linkType: hard +"@types/ms@npm:*": + version: 0.7.31 + resolution: "@types/ms@npm:0.7.31" + checksum: daadd354aedde024cce6f5aa873fefe7b71b22cd0e28632a69e8b677aeb48ae8caa1c60e5919bb781df040d116b01cb4316335167a3fc0ef6a63fa3614c0f6da + languageName: node + linkType: hard + "@types/node-fetch@npm:^2.5.5": version: 2.6.2 resolution: "@types/node-fetch@npm:2.6.2" From 47e655aeceb2f328b7fea1deff3b67d5922deed1 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 6 Apr 2023 12:22:52 -0400 Subject: [PATCH 15/19] Address pr comments --- README.md | 2 ++ contracts/HypERC20.sol | 8 +++--- src/config.ts | 6 +--- src/deploy.ts | 63 +++++++++++++++++++++--------------------- 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index f9d21b0..e50a9f1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This repo contains the base Hyperlane ERC20 and ERC721 tokens (HypERC20 and HypERC721). These tokens extend the base standards with an additional `transferRemote` function. Warp Routes make any token or native asset interchain without custom contracts. Read more about Warp Routes and how to deploy your own at [Warp API docs](https://docs.hyperlane.xyz/docs/developers/warp-api). +**NOTE:** ERC721 collateral variants are assumed to [enumerable](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721Enumerable) and [metadata](https://docs.openzeppelin.com/contracts/4.x/api/token/erc721#IERC721Metadata) compliant. + ## Versions | Git Ref | Release Date | Notes | diff --git a/contracts/HypERC20.sol b/contracts/HypERC20.sol index b970165..aaceb03 100644 --- a/contracts/HypERC20.sol +++ b/contracts/HypERC20.sol @@ -11,10 +11,10 @@ import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ * @dev Supply on each chain is not constant but the aggregate supply across all chains is. */ contract HypERC20 is ERC20Upgradeable, TokenRouter { - uint8 private immutable decimalsConfig; + uint8 private immutable _decimals; - constructor(uint8 _decimals) { - decimalsConfig = _decimals; + constructor(uint8 decimals) { + _decimals = decimals; } /** @@ -44,7 +44,7 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter { } function decimals() public view override returns (uint8) { - return decimalsConfig; + return _decimals; } /** diff --git a/src/config.ts b/src/config.ts index cbb62bd..2491f35 100644 --- a/src/config.ts +++ b/src/config.ts @@ -37,11 +37,7 @@ export type NativeConfig = { type: TokenType.native; }; -export type TokenConfig = { type: TokenType } & ( - | SyntheticConfig - | CollateralConfig - | NativeConfig -); +export type TokenConfig = SyntheticConfig | CollateralConfig | NativeConfig; export const isCollateralConfig = ( config: TokenConfig, diff --git a/src/deploy.ts b/src/deploy.ts index 067d36e..6121c22 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -27,6 +27,8 @@ import { isUriConfig, } from './config'; import { isTokenMetadata } from './config'; +import { ERC721RouterConfig } from './config'; +import { ERC20RouterConfig } from './config'; import { HypERC20Factories, HypERC721Factories } from './contracts'; import { ERC20__factory, @@ -44,8 +46,6 @@ import { HypNative, HypNative__factory, } from './types'; -import { ERC721RouterConfig } from './config'; -import { ERC20RouterConfig } from './config'; export class HypERC20Deployer extends GasRouterDeployer< ERC20RouterConfig, @@ -60,10 +60,13 @@ export class HypERC20Deployer extends GasRouterDeployer< config: CollateralConfig, ): Promise { const erc20 = ERC20__factory.connect(config.token, provider); - const name = await erc20.name(); - const symbol = await erc20.symbol(); - const totalSupply = await erc20.totalSupply(); - const decimals = await erc20.decimals(); + + const [name, symbol, totalSupply, decimals] = await Promise.all([ + erc20.name(), + erc20.symbol(), + erc20.totalSupply(), + erc20.decimals(), + ]); return { name, symbol, totalSupply, decimals }; } @@ -182,12 +185,12 @@ export class HypERC20Deployer extends GasRouterDeployer< tokenMetadata = config; } } - + if (!isErc20Metadata(tokenMetadata)) { throw new Error('Invalid ERC20 token metadata'); } - return objMap(configMap, () => (tokenMetadata!)); + return objMap(configMap, () => tokenMetadata!); } buildGasOverhead(configMap: ChainMap): ChainMap { @@ -199,16 +202,13 @@ export class HypERC20Deployer extends GasRouterDeployer< async deploy(configMap: ChainMap) { const tokenMetadata = await this.buildTokenMetadata(configMap); const gasOverhead = this.buildGasOverhead(configMap); - const mergedConfig = objMap( - configMap, - (chain, config) => { - return { - ...tokenMetadata[chain], - ...gasOverhead[chain], - ...config, - }; - }, - ) as ChainMap; + const mergedConfig = objMap(configMap, (chain, config) => { + return { + ...tokenMetadata[chain], + ...gasOverhead[chain], + ...config, + }; + }) as ChainMap; return super.deploy(mergedConfig); } @@ -230,9 +230,11 @@ export class HypERC721Deployer extends GasRouterDeployer< config.token, provider, ); - const name = await erc721.name(); - const symbol = await erc721.symbol(); - const totalSupply = await erc721.totalSupply(); + const [name, symbol, totalSupply] = await Promise.all([ + erc721.name(), + erc721.symbol(), + erc721.totalSupply(), + ]); return { name, symbol, totalSupply }; } @@ -350,7 +352,7 @@ export class HypERC721Deployer extends GasRouterDeployer< throw new Error('Invalid ERC721 token metadata'); } - return objMap(configMap, () => (tokenMetadata!)); + return objMap(configMap, () => tokenMetadata!); } buildGasOverhead(configMap: ChainMap): ChainMap { @@ -362,16 +364,13 @@ export class HypERC721Deployer extends GasRouterDeployer< async deploy(configMap: ChainMap) { const tokenMetadata = await this.buildTokenMetadata(configMap); const gasOverhead = this.buildGasOverhead(configMap); - const mergedConfig = objMap( - configMap, - (chain, config) => { - return { - ...tokenMetadata[chain], - ...gasOverhead[chain], - ...config, - }; - }, - ) as ChainMap; + const mergedConfig = objMap(configMap, (chain, config) => { + return { + ...tokenMetadata[chain], + ...gasOverhead[chain], + ...config, + }; + }) as ChainMap; return super.deploy(mergedConfig); } From 9c6819a6afa057be96eeac66eb695866baf163e5 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 6 Apr 2023 12:52:00 -0400 Subject: [PATCH 16/19] Refactor and renames --- ...{HypNative.sol => HypNativeCollateral.sol} | 4 +- src/app.ts | 20 +- src/config.ts | 27 +- src/contracts.ts | 31 +- src/deploy.ts | 375 +++++++----------- src/index.ts | 5 +- test/erc20.test.ts | 105 ++--- test/erc721.test.ts | 4 +- 8 files changed, 253 insertions(+), 318 deletions(-) rename contracts/{HypNative.sol => HypNativeCollateral.sol} (93%) diff --git a/contracts/HypNative.sol b/contracts/HypNativeCollateral.sol similarity index 93% rename from contracts/HypNative.sol rename to contracts/HypNativeCollateral.sol index 4accae4..d473304 100644 --- a/contracts/HypNative.sol +++ b/contracts/HypNativeCollateral.sol @@ -6,11 +6,11 @@ import {Message} from "./libs/Message.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; /** - * @title Hyperlane Native Token Router that extends ERC20 with remote transfer functionality. + * @title Hyperlane Native Collateral Token Router that extends native asset with remote transfer functionality. * @author Abacus Works * @dev Supply on each chain is not constant but the aggregate supply across all chains is. */ -contract HypNative is TokenRouter { +contract HypNativeCollateral is TokenRouter { /** * @notice Initializes the Hyperlane router, ERC20 metadata, and mints initial supply to deployer. * @param _mailbox The address of the mailbox contract. diff --git a/src/app.ts b/src/app.ts index 366624b..28eec88 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,13 +8,13 @@ import { HypERC721Factories, TokenFactories, } from './contracts'; -import { TokenRouter } from './types'; +import { HypERC20, HypERC20Collateral, HypERC721, HypERC721Collateral, HypNativeCollateral, TokenRouter } from './types'; class HyperlaneTokenApp< Factories extends TokenFactories, > extends RouterApp { - router(contracts: HyperlaneContracts): TokenRouter { - return contracts.router; + router(contracts: HyperlaneContracts): TokenRouter { + return contracts['synthetic'] || contracts['collateral']; } async transfer( @@ -23,7 +23,7 @@ class HyperlaneTokenApp< recipient: types.Address, amountOrId: BigNumberish, ) { - const originRouter = this.getContracts(origin).router; + const originRouter = this.router(this.getContracts(origin)); const destProvider = this.multiProvider.getProvider(destination); const destinationNetwork = await destProvider.getNetwork(); const gasPayment = await originRouter.quoteGasPayment( @@ -44,13 +44,17 @@ class HyperlaneTokenApp< } export class HypERC20App extends HyperlaneTokenApp { + router(contracts: HyperlaneContracts): HypERC20 | HypERC20Collateral | HypNativeCollateral { + return contracts['synthetic'] || contracts['collateral']; + } + async transfer( origin: ChainName, destination: ChainName, recipient: types.Address, amount: BigNumberish, ) { - const originRouter = this.getContracts(origin).router; + const originRouter = this.router(this.getContracts(origin)); const signerAddress = await this.multiProvider.getSignerAddress(origin); const balance = await originRouter.balanceOf(signerAddress); if (balance.lt(amount)) @@ -62,13 +66,17 @@ export class HypERC20App extends HyperlaneTokenApp { } export class HypERC721App extends HyperlaneTokenApp { + router(contracts: HyperlaneContracts): HypERC721 | HypERC721Collateral { + return contracts['synthetic'] || contracts['collateral']; + } + async transfer( origin: ChainName, destination: ChainName, recipient: types.Address, tokenId: BigNumberish, ) { - const originRouter = this.getContracts(origin).router; + const originRouter = this.router(this.getContracts(origin)); const signerAddress = await this.multiProvider.getSignerAddress(origin); const owner = await originRouter.ownerOf(tokenId); if (signerAddress != owner) diff --git a/src/config.ts b/src/config.ts index 2491f35..a98c308 100644 --- a/src/config.ts +++ b/src/config.ts @@ -30,41 +30,26 @@ export type SyntheticConfig = TokenMetadata & { type: TokenType.synthetic | TokenType.syntheticUri; }; export type CollateralConfig = { - type: TokenType.collateral | TokenType.collateralUri; - token: string; -}; -export type NativeConfig = { - type: TokenType.native; + type: TokenType.collateral | TokenType.collateralUri | TokenType.native; + token?: string; // no token implies native collateral }; -export type TokenConfig = SyntheticConfig | CollateralConfig | NativeConfig; +export type TokenConfig = SyntheticConfig | CollateralConfig; export const isCollateralConfig = ( config: TokenConfig, ): config is CollateralConfig => config.type === TokenType.collateral || - config.type === TokenType.collateralUri; + config.type === TokenType.collateralUri || + config.type === TokenType.native; export const isSyntheticConfig = ( config: TokenConfig, ): config is SyntheticConfig => config.type === TokenType.synthetic || config.type === TokenType.syntheticUri; -export const isNativeConfig = (config: TokenConfig): config is NativeConfig => - config.type === TokenType.native; - export const isUriConfig = (config: TokenConfig) => config.type === TokenType.syntheticUri || config.type === TokenType.collateralUri; -export type HypERC20Config = GasRouterConfig & SyntheticConfig & ERC20Metadata; -export type HypERC20CollateralConfig = GasRouterConfig & CollateralConfig; -export type HypNativeConfig = GasRouterConfig & NativeConfig; -export type ERC20RouterConfig = - | HypERC20Config - | HypERC20CollateralConfig - | HypNativeConfig; - -export type HypERC721Config = GasRouterConfig & SyntheticConfig; -export type HypERC721CollateralConfig = GasRouterConfig & CollateralConfig; -export type ERC721RouterConfig = HypERC721Config | HypERC721CollateralConfig; +export type TokenRouterConfig = TokenConfig & GasRouterConfig; diff --git a/src/contracts.ts b/src/contracts.ts index e22cc49..44ff053 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -1,20 +1,29 @@ +import { ethers } from 'ethers'; import { HypERC20Collateral__factory, HypERC20__factory, HypERC721Collateral__factory, HypERC721URICollateral__factory, + HypERC721URIStorage__factory, HypERC721__factory, - HypNative__factory, + HypNativeCollateral__factory, + TokenRouter__factory, } from './types'; -export type HypERC20Factories = { - router: HypERC20__factory | HypERC20Collateral__factory | HypNative__factory; -}; -export type HypERC721Factories = { - router: - | HypERC721__factory - | HypERC721Collateral__factory - | HypERC721URICollateral__factory; -}; +export type TokenFactories = { synthetic: TokenRouter__factory & ethers.ContractFactory } | { collateral: TokenRouter__factory & ethers.ContractFactory }; -export type TokenFactories = HypERC20Factories | HypERC721Factories; +export type HypERC20Factories = + | { + synthetic: HypERC20__factory; + } + | { collateral: HypERC20Collateral__factory | HypNativeCollateral__factory }; + +export type HypERC721Factories = + | { + synthetic: HypERC721__factory | HypERC721URIStorage__factory; + } + | { + collateral: + | HypERC721Collateral__factory + | HypERC721URICollateral__factory; + }; diff --git a/src/deploy.ts b/src/deploy.ts index 6121c22..8bb40b9 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -1,5 +1,3 @@ -import { providers } from 'ethers'; - import { ChainMap, ChainName, @@ -8,28 +6,23 @@ import { MultiProvider, objMap, } from '@hyperlane-xyz/sdk'; -import { GasConfig, RouterConfig } from '@hyperlane-xyz/sdk/dist/router/types'; +import { RouterConfig } from '@hyperlane-xyz/sdk/dist/router/types'; import { CollateralConfig, ERC20Metadata, - HypERC20CollateralConfig, - HypERC20Config, - HypERC721CollateralConfig, - HypERC721Config, - HypNativeConfig, + TokenRouterConfig, + SyntheticConfig, TokenConfig, TokenMetadata, + TokenType, isCollateralConfig, isErc20Metadata, - isNativeConfig, isSyntheticConfig, isUriConfig, } from './config'; import { isTokenMetadata } from './config'; -import { ERC721RouterConfig } from './config'; -import { ERC20RouterConfig } from './config'; -import { HypERC20Factories, HypERC721Factories } from './contracts'; +import { HypERC20Factories, HypERC721Factories, TokenFactories } from './contracts'; import { ERC20__factory, ERC721EnumerableUpgradeable__factory, @@ -43,83 +36,136 @@ import { HypERC721URICollateral__factory, HypERC721URIStorage__factory, HypERC721__factory, - HypNative, - HypNative__factory, + HypNativeCollateral, + HypNativeCollateral__factory, + TokenRouter, } from './types'; -export class HypERC20Deployer extends GasRouterDeployer< - ERC20RouterConfig, - HypERC20Factories +abstract class TokenDeployer extends GasRouterDeployer< + TokenRouterConfig, + any > { + abstract fetchMetadata(config: ChainMap): Promise; + + abstract gasOverheadDefault(config: TokenConfig): number; + + abstract deployCollateral( + chain: ChainName, + config: CollateralConfig, + ): Promise; + + abstract deploySynthetic( + chain: ChainName, + config: SyntheticConfig & TokenMetadata, + ): Promise; + + async deployContracts(chain: ChainName, config: TokenConfig): Promise> { + if (isCollateralConfig(config)) { + const collateral = await this.deployCollateral(chain, config); + return { collateral }; + } else if (isSyntheticConfig(config)) { + const synthetic = await this.deploySynthetic(chain, config); + return { synthetic }; + } else { + throw new Error('Invalid token router config'); + } + } + + async deploy(configMap: ChainMap) { + const tokenMetadata = await this.fetchMetadata(configMap); + const mergedConfig = objMap( + configMap, + (_, config) => { + return { + ...tokenMetadata, + gas: this.gasOverheadDefault(config), + ...config, // override with chain-specific config + }; + }, + ); + + return super.deploy(mergedConfig); + } +} + + +export class HypERC20Deployer extends TokenDeployer { constructor(multiProvider: MultiProvider) { super(multiProvider, {} as HypERC20Factories); // factories not used in deploy } - static async fetchMetadata( - provider: providers.Provider, - config: CollateralConfig, - ): Promise { - const erc20 = ERC20__factory.connect(config.token, provider); - - const [name, symbol, totalSupply, decimals] = await Promise.all([ - erc20.name(), - erc20.symbol(), - erc20.totalSupply(), - erc20.decimals(), - ]); + async fetchMetadata(configMap: ChainMap): Promise { + for (const [chain, config] of Object.entries(configMap)) { + if (isErc20Metadata(config)) { + return config; + } else if (isCollateralConfig(config)) { + if (config.token) { + const erc20 = ERC20__factory.connect(config.token, this.multiProvider.getProvider(chain)); + + const [name, symbol, totalSupply, decimals] = await Promise.all([ + erc20.name(), + erc20.symbol(), + erc20.totalSupply(), + erc20.decimals(), + ]); + return { name, symbol, totalSupply, decimals }; + } else { // native collateral + const chainMetadata = this.multiProvider.getChainMetadata(chain); + + if (chainMetadata.nativeToken) { + return { + totalSupply: 0, + ...chainMetadata.nativeToken + } + } + } + } + } - return { name, symbol, totalSupply, decimals }; + throw new Error('No ERC20 metadata found'); } - static gasOverheadDefault(config: TokenConfig): number { + gasOverheadDefault(config: TokenConfig): number { switch (config.type) { - case 'synthetic': + case TokenType.synthetic: return 64_000; - case 'native': - return 44_000; - case 'collateral': + case TokenType.collateral: + return config.token ? 68_000 : 44_000; default: - return 68_000; + throw new Error('Invalid token type'); } } - protected async deployCollateral( + async deployCollateral( chain: ChainName, - config: HypERC20CollateralConfig, - ): Promise { - const router = await this.deployContractFromFactory( - chain, - new HypERC20Collateral__factory(), - 'HypERC20Collateral', - [config.token], - ); - await this.multiProvider.handleTx( - chain, - router.initialize(config.mailbox, config.interchainGasPaymaster), - ); - return router; - } - - protected async deployNative( - chain: ChainName, - config: HypNativeConfig, - ): Promise { - const router = await this.deployContractFromFactory( - chain, - new HypNative__factory(), - 'HypNative', - [], - ); + config: CollateralConfig & RouterConfig, + ): Promise { + let collateral: HypERC20Collateral | HypNativeCollateral; + if (config.token) { + collateral = await this.deployContractFromFactory( + chain, + new HypERC20Collateral__factory(), + 'HypERC20Collateral', + [config.token], + ); + } else { + collateral = await this.deployContractFromFactory( + chain, + new HypNativeCollateral__factory(), + 'HypNativeCollateral', + [], + ); + } await this.multiProvider.handleTx( chain, - router.initialize(config.mailbox, config.interchainGasPaymaster), + collateral.initialize(config.mailbox, config.interchainGasPaymaster), ); - return router; + return collateral; } - protected async deploySynthetic( + async deploySynthetic( chain: ChainName, - config: HypERC20Config, + config: SyntheticConfig & ERC20Metadata & RouterConfig, ): Promise { const router = await this.deployContractFromFactory( chain, @@ -141,121 +187,61 @@ export class HypERC20Deployer extends GasRouterDeployer< } router(contracts: HyperlaneContracts) { - return contracts.router; - } - - async deployContracts(chain: ChainName, config: HypERC20Config) { - let router: HypERC20 | HypERC20Collateral | HypNative; - if (isCollateralConfig(config)) { - router = await this.deployCollateral(chain, config); - } else if (isNativeConfig(config)) { - router = await this.deployNative(chain, config); - } else if (isSyntheticConfig(config)) { - router = await this.deploySynthetic(chain, config); - } else { - throw new Error('Invalid ERC20 token router config'); - } - return { router }; - } - - async buildTokenMetadata( - configMap: ChainMap, - ): Promise> { - let tokenMetadata: ERC20Metadata | undefined; - - for (const [chain, config] of Object.entries(configMap)) { - if (isCollateralConfig(config)) { - const collateralMetadata = await HypERC20Deployer.fetchMetadata( - this.multiProvider.getProvider(chain), - config, - ); - tokenMetadata = { - ...collateralMetadata, - totalSupply: 0, - }; - } else if (isNativeConfig(config)) { - const chainMetadata = this.multiProvider.getChainMetadata(chain); - if (chainMetadata.nativeToken) { - tokenMetadata = { - ...chainMetadata.nativeToken, - totalSupply: 0, - }; - } - } else if (isErc20Metadata(config)) { - tokenMetadata = config; - } - } - - if (!isErc20Metadata(tokenMetadata)) { - throw new Error('Invalid ERC20 token metadata'); - } - - return objMap(configMap, () => tokenMetadata!); - } - - buildGasOverhead(configMap: ChainMap): ChainMap { - return objMap(configMap, (_, config) => ({ - gas: HypERC20Deployer.gasOverheadDefault(config), - })); - } - - async deploy(configMap: ChainMap) { - const tokenMetadata = await this.buildTokenMetadata(configMap); - const gasOverhead = this.buildGasOverhead(configMap); - const mergedConfig = objMap(configMap, (chain, config) => { - return { - ...tokenMetadata[chain], - ...gasOverhead[chain], - ...config, - }; - }) as ChainMap; - - return super.deploy(mergedConfig); + return Object.values(contracts)[0]; } } -export class HypERC721Deployer extends GasRouterDeployer< - ERC721RouterConfig, - HypERC721Factories -> { +export class HypERC721Deployer extends TokenDeployer { constructor(multiProvider: MultiProvider) { super(multiProvider, {} as HypERC721Factories); // factories not used in deploy } - static async fetchMetadata( - provider: providers.Provider, - config: CollateralConfig, - ): Promise { - const erc721 = ERC721EnumerableUpgradeable__factory.connect( - config.token, - provider, - ); - const [name, symbol, totalSupply] = await Promise.all([ - erc721.name(), - erc721.symbol(), - erc721.totalSupply(), - ]); + async fetchMetadata(configMap: ChainMap): Promise { + for (const [chain, config] of Object.entries(configMap)) { + if (isTokenMetadata(config)) { + return config; + } else if (isCollateralConfig(config)) { + if (config.token) { + const erc721 = ERC721EnumerableUpgradeable__factory.connect( + config.token, + this.multiProvider.getProvider(chain), + ); + const [name, symbol, totalSupply] = await Promise.all([ + erc721.name(), + erc721.symbol(), + erc721.totalSupply(), + ]); + + return { name, symbol, totalSupply }; + } + } + } - return { name, symbol, totalSupply }; + throw new Error('No ERC721 metadata found'); } - static gasOverheadDefault(config: TokenConfig): number { + gasOverheadDefault(config: TokenConfig): number { switch (config.type) { - case 'synthetic': + case TokenType.synthetic: return 160_000; - case 'syntheticUri': + case TokenType.syntheticUri: return 163_000; - case 'collateral': - case 'collateralUri': - default: + case TokenType.collateral: + case TokenType.collateralUri: return 80_000; + default: + throw new Error('Invalid ERC721 token type'); } } - protected async deployCollateral( + async deployCollateral( chain: ChainName, - config: HypERC721CollateralConfig, + config: CollateralConfig & RouterConfig, ): Promise { + if (!config.token) { + throw new Error('Collateral config invalid'); + } + let router: HypERC721Collateral; if (isUriConfig(config)) { router = await this.deployContractFromFactory( @@ -279,9 +265,9 @@ export class HypERC721Deployer extends GasRouterDeployer< return router; } - protected async deploySynthetic( + async deploySynthetic( chain: ChainName, - config: HypERC721Config, + config: SyntheticConfig & RouterConfig, ): Promise { let router: HypERC721; if (isUriConfig(config)) { @@ -313,65 +299,6 @@ export class HypERC721Deployer extends GasRouterDeployer< } router(contracts: HyperlaneContracts) { - return contracts.router; - } - - async deployContracts(chain: ChainName, config: HypERC721Config) { - let router: HypERC721 | HypERC721Collateral; - if (isCollateralConfig(config)) { - router = await this.deployCollateral(chain, config); - } else if (isSyntheticConfig(config)) { - router = await this.deploySynthetic(chain, config); - } else { - throw new Error('Invalid ERC721 token router config'); - } - return { router }; - } - - async buildTokenMetadata( - configMap: ChainMap, - ): Promise> { - let tokenMetadata: TokenMetadata | undefined; - - for (const [chain, config] of Object.entries(configMap)) { - if (isCollateralConfig(config)) { - const collateralMetadata = await HypERC721Deployer.fetchMetadata( - this.multiProvider.getProvider(chain), - config, - ); - tokenMetadata = { - ...collateralMetadata, - totalSupply: 0, - }; - } else if (isTokenMetadata(config)) { - tokenMetadata = config; - } - } - - if (!isTokenMetadata(tokenMetadata)) { - throw new Error('Invalid ERC721 token metadata'); - } - - return objMap(configMap, () => tokenMetadata!); - } - - buildGasOverhead(configMap: ChainMap): ChainMap { - return objMap(configMap, (_, config) => ({ - gas: HypERC721Deployer.gasOverheadDefault(config), - })); - } - - async deploy(configMap: ChainMap) { - const tokenMetadata = await this.buildTokenMetadata(configMap); - const gasOverhead = this.buildGasOverhead(configMap); - const mergedConfig = objMap(configMap, (chain, config) => { - return { - ...tokenMetadata[chain], - ...gasOverhead[chain], - ...config, - }; - }) as ChainMap; - - return super.deploy(mergedConfig); + return Object.values(contracts)[0]; } } diff --git a/src/index.ts b/src/index.ts index e446383..5be1530 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,7 @@ export { HypERC20App, HypERC721App } from './app'; export { CollateralConfig, - HypERC20CollateralConfig, - HypERC20Config, - HypERC721CollateralConfig, - HypERC721Config, + TokenRouterConfig, isCollateralConfig, isUriConfig, SyntheticConfig, diff --git a/test/erc20.test.ts b/test/erc20.test.ts index c347f82..0e1fb3b 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -27,7 +27,7 @@ import { ERC20__factory, HypERC20, HypERC20Collateral, - HypNative, + HypNativeCollateral, } from '../src/types'; const localChain = Chains.test1; @@ -44,20 +44,24 @@ const tokenMetadata = { totalSupply, }; +let index = 0; for (const variant of [ TokenType.synthetic, TokenType.collateral, - TokenType.native, + TokenType.collateral ]) { - describe(`HypERC20${variant}`, async () => { + const isNative = index === 2; + index += 1; + + describe(`Hyp${isNative ? 'Native' : 'ERC20'}${variant}`, async () => { let owner: SignerWithAddress; let recipient: SignerWithAddress; let core: TestCoreApp; let deployer: HypERC20Deployer; let contracts: HyperlaneContractsMap; let localTokenConfig: TokenConfig; - let local: HypERC20 | HypERC20Collateral | HypNative; - let remote: HypERC20; + let local: HypERC20 | HypERC20Collateral | HypNativeCollateral; + let remote: HypERC20 | HypERC20Collateral | HypNativeCollateral; let interchainGasPayment: BigNumber; beforeEach(async () => { @@ -79,19 +83,21 @@ for (const variant of [ let erc20: ERC20 | undefined; if (variant === TokenType.collateral) { - erc20 = await new ERC20Test__factory(owner).deploy( - tokenMetadata.name, - tokenMetadata.symbol, - tokenMetadata.totalSupply, - ); - localTokenConfig = { - type: variant, - token: erc20.address, - }; - } else if (variant === TokenType.native) { - localTokenConfig = { - type: variant, - }; + if (isNative) { + localTokenConfig = { + type: variant, + }; + } else { + erc20 = await new ERC20Test__factory(owner).deploy( + tokenMetadata.name, + tokenMetadata.symbol, + tokenMetadata.totalSupply, + ); + localTokenConfig = { + type: variant, + token: erc20.address, + }; + } } else if (variant === TokenType.synthetic) { localTokenConfig = { type: variant, ...tokenMetadata }; } @@ -106,25 +112,25 @@ for (const variant of [ deployer = new HypERC20Deployer(multiProvider); contracts = await deployer.deploy(config); - local = contracts[localChain].router; + local = deployer.router(contracts[localChain]); interchainGasPayment = await local.quoteGasPayment(remoteDomain); - - if (variant === TokenType.native) { - interchainGasPayment = interchainGasPayment.add(amount); - } - + if (variant === TokenType.collateral) { - await erc20!.approve(local.address, amount); + if (isNative) { + interchainGasPayment = interchainGasPayment.add(amount); + } else { + await erc20!.approve(local.address, amount); + } } - remote = contracts[remoteChain].router as HypERC20; + remote = deployer.router(contracts[remoteChain]); }); it('should not be initializable again', async () => { const initializeTx = - variant === TokenType.collateral || variant === TokenType.native - ? (local as HypERC20Collateral).initialize( + variant === TokenType.collateral + ? (local as HypERC20Collateral | HypNativeCollateral).initialize( ethers.constants.AddressZero, ethers.constants.AddressZero, ) @@ -161,18 +167,20 @@ for (const variant of [ const localRaw = local.connect(ethers.provider); const mailboxAddress = core.contractsMap[localChain].mailbox.address; if (variant === TokenType.collateral) { - const tokenAddress = await (local as HypERC20Collateral).wrappedToken(); - const token = ERC20__factory.connect(tokenAddress, owner); - await token.transfer(local.address, totalSupply); - } else if (variant === TokenType.native) { - const remoteDomain = core.multiProvider.getDomainId(remoteChain); - // deposit amount - await local.transferRemote( - remoteDomain, - utils.addressToBytes32(remote.address), - amount, - { value: interchainGasPayment }, - ); + if (isNative) { + const remoteDomain = core.multiProvider.getDomainId(remoteChain); + // deposit amount + await local.transferRemote( + remoteDomain, + utils.addressToBytes32(remote.address), + amount, + { value: interchainGasPayment }, + ); + } else { + const tokenAddress = await (local as HypERC20Collateral).wrappedToken(); + const token = ERC20__factory.connect(tokenAddress, owner); + await token.transfer(local.address, totalSupply); + } } const message = `${utils.addressToBytes32( recipient.address, @@ -204,7 +212,7 @@ for (const variant of [ let expectedLocal = localOwner.sub(amount); await expectBalance(local, recipient, localRecipient); - if (variant === TokenType.native) { + if (isNative) { // account for tx fees, rewards, etc. expectedLocal = await local.balanceOf(owner.address); } @@ -215,7 +223,7 @@ for (const variant of [ await core.processMessages(); await expectBalance(local, recipient, localRecipient); - if (variant === TokenType.native) { + if (isNative) { // account for tx fees, rewards, etc. expectedLocal = await local.balanceOf(owner.address); } @@ -244,14 +252,15 @@ for (const variant of [ case TokenType.synthetic: return 'ERC20: burn amount exceeds balance'; case TokenType.collateral: - return 'ERC20: insufficient allowance'; - case TokenType.native: - return 'Native: amount exceeds msg.value'; + if (isNative) { + return 'Native: amount exceeds msg.value'; + } else { + return 'ERC20: insufficient allowance'; + } } return ''; }; - const value = - variant === TokenType.native ? amount - 1 : interchainGasPayment; + const value = isNative ? amount - 1 : interchainGasPayment; await expect( local .connect(recipient) @@ -283,7 +292,7 @@ for (const variant of [ } const expectBalance = async ( - token: HypERC20 | HypERC20Collateral | ERC20 | HypNative, + token: HypERC20 | HypERC20Collateral | ERC20 | HypNativeCollateral, signer: SignerWithAddress, balance: BigNumberish, ) => { diff --git a/test/erc721.test.ts b/test/erc721.test.ts index acd004e..d79132c 100644 --- a/test/erc721.test.ts +++ b/test/erc721.test.ts @@ -114,7 +114,7 @@ for (const withCollateral of [true, false]) { deployer = new HypERC721Deployer(multiProvider); contracts = await deployer.deploy(configWithTokenInfo); - local = contracts[localChain].router; + local = deployer.router(contracts[localChain]); if (withCollateral) { // approve wrapper to transfer tokens await erc721!.approve(local.address, tokenId); @@ -124,7 +124,7 @@ for (const withCollateral of [true, false]) { } interchainGasPayment = await local.quoteGasPayment(remoteDomain); - remote = contracts[remoteChain].router; + remote = deployer.router(contracts[remoteChain]); }); it('should not be initializable again', async () => { From 76f4865d8d92bd91c8ab485bbbdcf92afa5291d0 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 6 Apr 2023 15:52:28 -0400 Subject: [PATCH 17/19] Migrate to unambiguous serialization format --- src/app.ts | 32 +++-- src/contracts.ts | 38 +++--- src/deploy.ts | 282 ++++++++++++++++----------------------------- test/erc20.test.ts | 93 +++++++-------- 4 files changed, 188 insertions(+), 257 deletions(-) diff --git a/src/app.ts b/src/app.ts index 28eec88..02f8f76 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,19 +3,25 @@ import { BigNumberish } from 'ethers'; import { ChainName, HyperlaneContracts, RouterApp } from '@hyperlane-xyz/sdk'; import { types } from '@hyperlane-xyz/utils'; +import { TokenType } from './config'; import { HypERC20Factories, HypERC721Factories, TokenFactories, } from './contracts'; -import { HypERC20, HypERC20Collateral, HypERC721, HypERC721Collateral, HypNativeCollateral, TokenRouter } from './types'; +import { + HypERC20, + HypERC20Collateral, + HypERC721, + HypERC721Collateral, + HypNativeCollateral, + TokenRouter, +} from './types'; -class HyperlaneTokenApp< +abstract class HyperlaneTokenApp< Factories extends TokenFactories, > extends RouterApp { - router(contracts: HyperlaneContracts): TokenRouter { - return contracts['synthetic'] || contracts['collateral']; - } + abstract router(contracts: HyperlaneContracts): TokenRouter; async transfer( origin: ChainName, @@ -44,8 +50,14 @@ class HyperlaneTokenApp< } export class HypERC20App extends HyperlaneTokenApp { - router(contracts: HyperlaneContracts): HypERC20 | HypERC20Collateral | HypNativeCollateral { - return contracts['synthetic'] || contracts['collateral']; + router( + contracts: HyperlaneContracts, + ): HypERC20 | HypERC20Collateral | HypNativeCollateral { + return ( + contracts[TokenType.synthetic] || + contracts[TokenType.collateral] || + contracts[TokenType.native] + ); } async transfer( @@ -66,8 +78,10 @@ export class HypERC20App extends HyperlaneTokenApp { } export class HypERC721App extends HyperlaneTokenApp { - router(contracts: HyperlaneContracts): HypERC721 | HypERC721Collateral { - return contracts['synthetic'] || contracts['collateral']; + router( + contracts: HyperlaneContracts, + ): HypERC721 | HypERC721Collateral { + return contracts[TokenType.synthetic] || contracts[TokenType.collateral]; } async transfer( diff --git a/src/contracts.ts b/src/contracts.ts index 44ff053..8129b0d 100644 --- a/src/contracts.ts +++ b/src/contracts.ts @@ -1,4 +1,5 @@ -import { ethers } from 'ethers'; +import { HyperlaneFactories } from '@hyperlane-xyz/sdk'; +import { TokenType } from './config'; import { HypERC20Collateral__factory, HypERC20__factory, @@ -7,23 +8,26 @@ import { HypERC721URIStorage__factory, HypERC721__factory, HypNativeCollateral__factory, - TokenRouter__factory, + TokenRouter, } from './types'; -export type TokenFactories = { synthetic: TokenRouter__factory & ethers.ContractFactory } | { collateral: TokenRouter__factory & ethers.ContractFactory }; +type TokenRouterFactory = { deploy(...args: any[]): Promise; }; -export type HypERC20Factories = - | { - synthetic: HypERC20__factory; - } - | { collateral: HypERC20Collateral__factory | HypNativeCollateral__factory }; +export type TokenFactories = Partial> & HyperlaneFactories; -export type HypERC721Factories = - | { - synthetic: HypERC721__factory | HypERC721URIStorage__factory; - } - | { - collateral: - | HypERC721Collateral__factory - | HypERC721URICollateral__factory; - }; +export const hypErc20Factories = { + [TokenType.synthetic]: new HypERC20__factory(), + [TokenType.collateral]: new HypERC20Collateral__factory(), + [TokenType.native]: new HypNativeCollateral__factory(), +}; + +export type HypERC20Factories = typeof hypErc20Factories; + +export const hypErc721Factories = { + [TokenType.synthetic]: new HypERC721__factory(), + [TokenType.syntheticUri]: new HypERC721URIStorage__factory(), + [TokenType.collateral]: new HypERC721Collateral__factory(), + [TokenType.collateralUri]: new HypERC721URICollateral__factory(), +}; + +export type HypERC721Factories = typeof hypErc721Factories; diff --git a/src/deploy.ts b/src/deploy.ts index 8bb40b9..8f8cea2 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -1,6 +1,5 @@ import { ChainMap, - ChainName, GasRouterDeployer, HyperlaneContracts, MultiProvider, @@ -9,114 +8,88 @@ import { import { RouterConfig } from '@hyperlane-xyz/sdk/dist/router/types'; import { - CollateralConfig, ERC20Metadata, - TokenRouterConfig, - SyntheticConfig, TokenConfig, TokenMetadata, + TokenRouterConfig, TokenType, isCollateralConfig, isErc20Metadata, isSyntheticConfig, - isUriConfig, } from './config'; import { isTokenMetadata } from './config'; -import { HypERC20Factories, HypERC721Factories, TokenFactories } from './contracts'; +import { + HypERC20Factories, + HypERC721Factories, + TokenFactories, + hypErc20Factories, + hypErc721Factories, +} from './contracts'; import { ERC20__factory, ERC721EnumerableUpgradeable__factory, HypERC20, HypERC20Collateral, - HypERC20Collateral__factory, - HypERC20__factory, HypERC721, HypERC721Collateral, - HypERC721Collateral__factory, - HypERC721URICollateral__factory, - HypERC721URIStorage__factory, - HypERC721__factory, HypNativeCollateral, - HypNativeCollateral__factory, - TokenRouter, } from './types'; -abstract class TokenDeployer extends GasRouterDeployer< - TokenRouterConfig, - any -> { +abstract class TokenDeployer< + Factories extends TokenFactories, +> extends GasRouterDeployer { abstract fetchMetadata(config: ChainMap): Promise; abstract gasOverheadDefault(config: TokenConfig): number; - abstract deployCollateral( - chain: ChainName, - config: CollateralConfig, - ): Promise; - - abstract deploySynthetic( - chain: ChainName, - config: SyntheticConfig & TokenMetadata, - ): Promise; - - async deployContracts(chain: ChainName, config: TokenConfig): Promise> { - if (isCollateralConfig(config)) { - const collateral = await this.deployCollateral(chain, config); - return { collateral }; - } else if (isSyntheticConfig(config)) { - const synthetic = await this.deploySynthetic(chain, config); - return { synthetic }; - } else { - throw new Error('Invalid token router config'); - } - } - async deploy(configMap: ChainMap) { const tokenMetadata = await this.fetchMetadata(configMap); - const mergedConfig = objMap( - configMap, - (_, config) => { - return { - ...tokenMetadata, - gas: this.gasOverheadDefault(config), - ...config, // override with chain-specific config - }; - }, - ); + const mergedConfig = objMap(configMap, (_, config) => { + return { + ...tokenMetadata, + gas: this.gasOverheadDefault(config), + ...config, // override with chain-specific config + }; + }); return super.deploy(mergedConfig); } } - -export class HypERC20Deployer extends TokenDeployer { +export class HypERC20Deployer extends TokenDeployer { constructor(multiProvider: MultiProvider) { - super(multiProvider, {} as HypERC20Factories); // factories not used in deploy + super(multiProvider, hypErc20Factories); } - async fetchMetadata(configMap: ChainMap): Promise { - for (const [chain, config] of Object.entries(configMap)) { - if (isErc20Metadata(config)) { - return config; - } else if (isCollateralConfig(config)) { - if (config.token) { - const erc20 = ERC20__factory.connect(config.token, this.multiProvider.getProvider(chain)); - - const [name, symbol, totalSupply, decimals] = await Promise.all([ - erc20.name(), - erc20.symbol(), - erc20.totalSupply(), - erc20.decimals(), - ]); + async fetchMetadata( + configMap: ChainMap, + ): Promise { + for (const [chain, config] of Object.entries(configMap)) { + if (isErc20Metadata(config)) { + return config; + } else if (isCollateralConfig(config)) { + if (config.token) { + const erc20 = ERC20__factory.connect( + config.token, + this.multiProvider.getProvider(chain), + ); + + const [name, symbol, totalSupply, decimals] = await Promise.all([ + erc20.name(), + erc20.symbol(), + erc20.totalSupply(), + erc20.decimals(), + ]); return { name, symbol, totalSupply, decimals }; - } else { // native collateral + } else { + // native collateral const chainMetadata = this.multiProvider.getChainMetadata(chain); if (chainMetadata.nativeToken) { return { totalSupply: 0, - ...chainMetadata.nativeToken - } + ...chainMetadata.nativeToken, + }; } } } @@ -130,73 +103,60 @@ export class HypERC20Deployer extends TokenDeployer { case TokenType.synthetic: return 64_000; case TokenType.collateral: - return config.token ? 68_000 : 44_000; + return 68_000; + case TokenType.native: + return 44_000; default: throw new Error('Invalid token type'); } } - async deployCollateral( - chain: ChainName, - config: CollateralConfig & RouterConfig, - ): Promise { - let collateral: HypERC20Collateral | HypNativeCollateral; - if (config.token) { - collateral = await this.deployContractFromFactory( - chain, - new HypERC20Collateral__factory(), - 'HypERC20Collateral', - [config.token], - ); - } else { - collateral = await this.deployContractFromFactory( - chain, - new HypNativeCollateral__factory(), - 'HypNativeCollateral', - [], - ); - } - await this.multiProvider.handleTx( - chain, - collateral.initialize(config.mailbox, config.interchainGasPaymaster), - ); - return collateral; + router(contracts: HyperlaneContracts) { + return Object.values( + contracts, + )[0]; } - async deploySynthetic( - chain: ChainName, - config: SyntheticConfig & ERC20Metadata & RouterConfig, - ): Promise { - const router = await this.deployContractFromFactory( - chain, - new HypERC20__factory(), - 'HypERC20', - [config.decimals], - ); - await this.multiProvider.handleTx( + async deployContracts( + chain: string, + config: TokenRouterConfig & ERC20Metadata, + ): Promise> { + if ( + config.type === TokenType.syntheticUri || + config.type === TokenType.collateralUri + ) { + throw new Error('Invalid token type'); + } + + const tokenRouter = await this.deployContract( chain, - router.initialize( - config.mailbox, - config.interchainGasPaymaster, - config.totalSupply, - config.name, - config.symbol, - ), + config.type, + isSyntheticConfig(config) + ? [config.decimals] + : config.token ? [config.token] : [], + isSyntheticConfig(config) + ? [ + config.mailbox, + config.interchainGasPaymaster, + config.totalSupply, + config.name, + config.symbol, + ] + : [config.mailbox, config.interchainGasPaymaster], ); - return router; - } - router(contracts: HyperlaneContracts) { - return Object.values(contracts)[0]; + return { [config.type]: tokenRouter } as any; } } -export class HypERC721Deployer extends TokenDeployer { +export class HypERC721Deployer extends TokenDeployer { constructor(multiProvider: MultiProvider) { - super(multiProvider, {} as HypERC721Factories); // factories not used in deploy + super(multiProvider, hypErc721Factories); // factories not used in deploy } - async fetchMetadata(configMap: ChainMap): Promise { + async fetchMetadata( + configMap: ChainMap, + ): Promise { for (const [chain, config] of Object.entries(configMap)) { if (isTokenMetadata(config)) { return config; @@ -211,9 +171,9 @@ export class HypERC721Deployer extends TokenDeployer { erc721.symbol(), erc721.totalSupply(), ]); - + return { name, symbol, totalSupply }; - } + } } } @@ -234,68 +194,30 @@ export class HypERC721Deployer extends TokenDeployer { } } - async deployCollateral( - chain: ChainName, - config: CollateralConfig & RouterConfig, - ): Promise { - if (!config.token) { - throw new Error('Collateral config invalid'); + async deployContracts( + chain: string, + config: TokenRouterConfig & TokenMetadata, + ): Promise> { + if (config.type === TokenType.native) { + throw new Error('Invalid token type'); } - let router: HypERC721Collateral; - if (isUriConfig(config)) { - router = await this.deployContractFromFactory( - chain, - new HypERC721URICollateral__factory(), - 'HypERC721URICollateral', - [config.token], - ); - } else { - router = await this.deployContractFromFactory( - chain, - new HypERC721Collateral__factory(), - 'HypERC721Collateral', - [config.token], - ); - } - await this.multiProvider.handleTx( + const tokenRouter = await this.deployContract( chain, - router.initialize(config.mailbox, config.interchainGasPaymaster), + config.type, + isCollateralConfig(config) ? [config.token!] : [], + isCollateralConfig(config) + ? [config.mailbox, config.interchainGasPaymaster] + : [ + config.mailbox, + config.interchainGasPaymaster, + config.totalSupply, + config.name, + config.symbol, + ], ); - return router; - } - async deploySynthetic( - chain: ChainName, - config: SyntheticConfig & RouterConfig, - ): Promise { - let router: HypERC721; - if (isUriConfig(config)) { - router = await this.deployContractFromFactory( - chain, - new HypERC721URIStorage__factory(), - 'HypERC721URIStorage', - [], - ); - } else { - router = await this.deployContractFromFactory( - chain, - new HypERC721__factory(), - 'HypERC721', - [], - ); - } - await this.multiProvider.handleTx( - chain, - router.initialize( - config.mailbox, - config.interchainGasPaymaster, - config.totalSupply, - config.name, - config.symbol, - ), - ); - return router; + return { [config.type]: tokenRouter } as any; } router(contracts: HyperlaneContracts) { diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 0e1fb3b..3e2475e 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -44,16 +44,12 @@ const tokenMetadata = { totalSupply, }; -let index = 0; for (const variant of [ TokenType.synthetic, TokenType.collateral, - TokenType.collateral + TokenType.native, ]) { - const isNative = index === 2; - index += 1; - - describe(`Hyp${isNative ? 'Native' : 'ERC20'}${variant}`, async () => { + describe(`HypERC20${variant}`, async () => { let owner: SignerWithAddress; let recipient: SignerWithAddress; let core: TestCoreApp; @@ -83,21 +79,19 @@ for (const variant of [ let erc20: ERC20 | undefined; if (variant === TokenType.collateral) { - if (isNative) { - localTokenConfig = { - type: variant, - }; - } else { - erc20 = await new ERC20Test__factory(owner).deploy( - tokenMetadata.name, - tokenMetadata.symbol, - tokenMetadata.totalSupply, - ); - localTokenConfig = { - type: variant, - token: erc20.address, - }; - } + erc20 = await new ERC20Test__factory(owner).deploy( + tokenMetadata.name, + tokenMetadata.symbol, + tokenMetadata.totalSupply, + ); + localTokenConfig = { + type: variant, + token: erc20.address, + }; + } else if (variant === TokenType.native) { + localTokenConfig = { + type: variant, + }; } else if (variant === TokenType.synthetic) { localTokenConfig = { type: variant, ...tokenMetadata }; } @@ -115,13 +109,13 @@ for (const variant of [ local = deployer.router(contracts[localChain]); interchainGasPayment = await local.quoteGasPayment(remoteDomain); - + + if (variant === TokenType.native) { + interchainGasPayment = interchainGasPayment.add(amount); + } + if (variant === TokenType.collateral) { - if (isNative) { - interchainGasPayment = interchainGasPayment.add(amount); - } else { - await erc20!.approve(local.address, amount); - } + await erc20!.approve(local.address, amount); } remote = deployer.router(contracts[remoteChain]); @@ -129,8 +123,8 @@ for (const variant of [ it('should not be initializable again', async () => { const initializeTx = - variant === TokenType.collateral - ? (local as HypERC20Collateral | HypNativeCollateral).initialize( + variant === TokenType.collateral || variant === TokenType.native + ? (local as HypERC20Collateral).initialize( ethers.constants.AddressZero, ethers.constants.AddressZero, ) @@ -167,20 +161,18 @@ for (const variant of [ const localRaw = local.connect(ethers.provider); const mailboxAddress = core.contractsMap[localChain].mailbox.address; if (variant === TokenType.collateral) { - if (isNative) { - const remoteDomain = core.multiProvider.getDomainId(remoteChain); - // deposit amount - await local.transferRemote( - remoteDomain, - utils.addressToBytes32(remote.address), - amount, - { value: interchainGasPayment }, - ); - } else { - const tokenAddress = await (local as HypERC20Collateral).wrappedToken(); - const token = ERC20__factory.connect(tokenAddress, owner); - await token.transfer(local.address, totalSupply); - } + const tokenAddress = await (local as HypERC20Collateral).wrappedToken(); + const token = ERC20__factory.connect(tokenAddress, owner); + await token.transfer(local.address, totalSupply); + } else if (variant === TokenType.native) { + const remoteDomain = core.multiProvider.getDomainId(remoteChain); + // deposit amount + await local.transferRemote( + remoteDomain, + utils.addressToBytes32(remote.address), + amount, + { value: interchainGasPayment }, + ); } const message = `${utils.addressToBytes32( recipient.address, @@ -212,7 +204,7 @@ for (const variant of [ let expectedLocal = localOwner.sub(amount); await expectBalance(local, recipient, localRecipient); - if (isNative) { + if (variant === TokenType.native) { // account for tx fees, rewards, etc. expectedLocal = await local.balanceOf(owner.address); } @@ -223,7 +215,7 @@ for (const variant of [ await core.processMessages(); await expectBalance(local, recipient, localRecipient); - if (isNative) { + if (variant === TokenType.native) { // account for tx fees, rewards, etc. expectedLocal = await local.balanceOf(owner.address); } @@ -252,15 +244,14 @@ for (const variant of [ case TokenType.synthetic: return 'ERC20: burn amount exceeds balance'; case TokenType.collateral: - if (isNative) { - return 'Native: amount exceeds msg.value'; - } else { - return 'ERC20: insufficient allowance'; - } + return 'ERC20: insufficient allowance'; + case TokenType.native: + return 'Native: amount exceeds msg.value'; } return ''; }; - const value = isNative ? amount - 1 : interchainGasPayment; + const value = + variant === TokenType.native ? amount - 1 : interchainGasPayment; await expect( local .connect(recipient) From d907d408060ff1d51ff9b8193ea63f75b29da9ef Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 6 Apr 2023 16:31:38 -0400 Subject: [PATCH 18/19] Fix casting --- src/config.ts | 2 +- test/erc20.test.ts | 4 +--- test/erc721.test.ts | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index a98c308..be60b08 100644 --- a/src/config.ts +++ b/src/config.ts @@ -26,7 +26,7 @@ export const isTokenMetadata = (metadata: any): metadata is TokenMetadata => export const isErc20Metadata = (metadata: any): metadata is ERC20Metadata => metadata.decimals && isTokenMetadata(metadata); -export type SyntheticConfig = TokenMetadata & { +export type SyntheticConfig = Partial & { type: TokenType.synthetic | TokenType.syntheticUri; }; export type CollateralConfig = { diff --git a/test/erc20.test.ts b/test/erc20.test.ts index 3e2475e..7049edb 100644 --- a/test/erc20.test.ts +++ b/test/erc20.test.ts @@ -6,11 +6,9 @@ import { ethers } from 'hardhat'; import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; import { - ChainMap, Chains, HyperlaneContractsMap, MultiProvider, - RouterConfig, TestCoreApp, TestCoreDeployer, deployTestIgpsAndGetRouterConfig, @@ -102,7 +100,7 @@ for (const variant of [ ? localTokenConfig : { type: TokenType.synthetic }), owner: owner.address, - })) as ChainMap; + })); deployer = new HypERC20Deployer(multiProvider); contracts = await deployer.deploy(config); diff --git a/test/erc721.test.ts b/test/erc721.test.ts index d79132c..b7c6e9c 100644 --- a/test/erc721.test.ts +++ b/test/erc721.test.ts @@ -100,9 +100,9 @@ for (const withCollateral of [true, false]) { let erc721: ERC721 | undefined; if (withCollateral) { erc721 = await new ERC721Test__factory(owner).deploy( - tokenConfig.name, - tokenConfig.symbol, - tokenConfig.totalSupply, + tokenConfig.name!, + tokenConfig.symbol!, + tokenConfig.totalSupply!, ); configWithTokenInfo.test1 = { type: withUri ? TokenType.collateralUri : TokenType.collateral, From d07bcb553192f680fbb767349a179503abac2f88 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Thu, 6 Apr 2023 16:36:27 -0400 Subject: [PATCH 19/19] Make totalSupply always zero for collateral --- src/deploy.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/deploy.ts b/src/deploy.ts index 8f8cea2..f086877 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -77,7 +77,7 @@ export class HypERC20Deployer extends TokenDeployer { const [name, symbol, totalSupply, decimals] = await Promise.all([ erc20.name(), erc20.symbol(), - erc20.totalSupply(), + 0, // erc20.totalSupply(): always 0 for collateral erc20.decimals(), ]); return { name, symbol, totalSupply, decimals }; @@ -133,7 +133,9 @@ export class HypERC20Deployer extends TokenDeployer { config.type, isSyntheticConfig(config) ? [config.decimals] - : config.token ? [config.token] : [], + : config.token + ? [config.token] + : [], isSyntheticConfig(config) ? [ config.mailbox, @@ -169,7 +171,7 @@ export class HypERC721Deployer extends TokenDeployer { const [name, symbol, totalSupply] = await Promise.all([ erc721.name(), erc721.symbol(), - erc721.totalSupply(), + 0, // erc721.totalSupply() always 0 for collateral ]); return { name, symbol, totalSupply };