From db85f8aaf450395640af89b349bad6ee1315397a Mon Sep 17 00:00:00 2001 From: Lee <6251863+ltyu@users.noreply.github.com> Date: Sun, 31 Mar 2024 10:40:09 -0400 Subject: [PATCH] Upgradable warp routes (#3474) ### Description Makes the following Warp Routes upgradable through CLI deployment ``` - FastHypERC20Collateral - FastHypERC20 - HypERC20 - HypERC20Collateral - HypERC20CollateralVaultDeposit - HypNative - HypNativeScaled - HypERC721Collateral - HypERC721 - HypERC721URICollateral - HypERC721URIStorage ``` - Adds `initialize()` to each contract - It mostly contains `_MailboxClient_initialize`, and any additional proxy specific constructor logic - Refactors - Update `GasRouterDeployer` to inherit from `ProxiedRouterDeployer` - Update `ProxiedRouterDeployer.routerContractName` to `abstract ProxiedRouterDeployer.routerContractName(): RouterKey` . - This allows child classes to specify their own contract name instead of being locked-in to a single name upon construction. Similar concept to the existing `ProxiedRouterDeployer.constructorArgs()` and `ProxiedRouterDeployer.initializeArgs()` - Update `router()` - Add function into `InterchainQueryDeployer` - Add function into `InterchainAccountDeployer` - Update to abstract function in `ProxiedRouterDeployer` - ### Drive-by changes ### Related issues Fixes #999 ### Backward compatibility - Should be backward compatible ### Testing - Updates contract unit tests - Manual testing through CLI and verified contracts --- solidity/contracts/token/HypERC20.sol | 6 +- .../contracts/token/HypERC20Collateral.sol | 8 + .../token/HypERC20CollateralVaultDeposit.sol | 8 + solidity/contracts/token/HypERC721.sol | 14 +- .../contracts/token/HypERC721Collateral.sol | 14 + solidity/contracts/token/HypNative.sol | 14 + solidity/test/token/HypERC20.t.sol | 51 +++- .../HypERC20CollateralVaultDeposit.t.sol | 19 +- solidity/test/token/HypERC721.t.sol | 89 +++++- solidity/test/token/HypNativeScaled.t.sol | 39 ++- typescript/cli/src/deploy/warp.ts | 2 +- .../sdk/src/core/HyperlaneCoreDeployer.ts | 1 + .../sdk/src/deploy/HyperlaneDeployer.ts | 6 +- .../sdk/src/gas/HyperlaneIgpDeployer.ts | 1 + .../account/InterchainAccountDeployer.ts | 28 +- .../LiquidityLayerRouterDeployer.ts | 27 +- .../query/InterchainQueryDeployer.ts | 30 +- .../sdk/src/router/GasRouterDeployer.ts | 14 +- .../sdk/src/router/ProxiedRouterDeployer.ts | 36 ++- typescript/sdk/src/token/config.ts | 4 + typescript/sdk/src/token/contracts.ts | 9 + typescript/sdk/src/token/deploy.ts | 270 +++++++++--------- 22 files changed, 463 insertions(+), 227 deletions(-) diff --git a/solidity/contracts/token/HypERC20.sol b/solidity/contracts/token/HypERC20.sol index b3f815d94..80e7bb6cd 100644 --- a/solidity/contracts/token/HypERC20.sol +++ b/solidity/contracts/token/HypERC20.sol @@ -26,11 +26,15 @@ contract HypERC20 is ERC20Upgradeable, TokenRouter { function initialize( uint256 _totalSupply, string memory _name, - string memory _symbol + string memory _symbol, + address _hook, + address _interchainSecurityModule, + address _owner ) external initializer { // Initialize ERC20 metadata __ERC20_init(_name, _symbol); _mint(msg.sender, _totalSupply); + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); } function decimals() public view override returns (uint8) { diff --git a/solidity/contracts/token/HypERC20Collateral.sol b/solidity/contracts/token/HypERC20Collateral.sol index 00f0acd9a..858a3140b 100644 --- a/solidity/contracts/token/HypERC20Collateral.sol +++ b/solidity/contracts/token/HypERC20Collateral.sol @@ -25,6 +25,14 @@ contract HypERC20Collateral is TokenRouter { wrappedToken = IERC20(erc20); } + function initialize( + address _hook, + address _interchainSecurityModule, + address _owner + ) public virtual initializer { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + function balanceOf( address _account ) external view override returns (uint256) { diff --git a/solidity/contracts/token/HypERC20CollateralVaultDeposit.sol b/solidity/contracts/token/HypERC20CollateralVaultDeposit.sol index b4085a9b2..11b530d6a 100644 --- a/solidity/contracts/token/HypERC20CollateralVaultDeposit.sol +++ b/solidity/contracts/token/HypERC20CollateralVaultDeposit.sol @@ -21,7 +21,15 @@ contract HypERC20CollateralVaultDeposit is HypERC20Collateral { address _mailbox ) HypERC20Collateral(_vault.asset(), _mailbox) { vault = _vault; + } + + function initialize( + address _hook, + address _interchainSecurityModule, + address _owner + ) public override initializer { wrappedToken.approve(address(vault), type(uint256).max); + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); } /** diff --git a/solidity/contracts/token/HypERC721.sol b/solidity/contracts/token/HypERC721.sol index 57c9e0e7d..ced9020b8 100644 --- a/solidity/contracts/token/HypERC721.sol +++ b/solidity/contracts/token/HypERC721.sol @@ -19,18 +19,22 @@ contract HypERC721 is ERC721EnumerableUpgradeable, TokenRouter { * @param _mintAmount The amount of NFTs to mint to `msg.sender`. * @param _name The name of the token. * @param _symbol The symbol of the token. + * @param _hook The post-dispatch hook contract. + @param _interchainSecurityModule The interchain security module contract. + @param _owner The this contract. */ function initialize( uint256 _mintAmount, string memory _name, - string memory _symbol + string memory _symbol, + address _hook, + address _interchainSecurityModule, + address _owner ) external initializer { - address owner = msg.sender; - _transferOwnership(owner); - + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); __ERC721_init(_name, _symbol); for (uint256 i = 0; i < _mintAmount; i++) { - _safeMint(owner, i); + _safeMint(msg.sender, i); } } diff --git a/solidity/contracts/token/HypERC721Collateral.sol b/solidity/contracts/token/HypERC721Collateral.sol index b12faf8b3..720eebd97 100644 --- a/solidity/contracts/token/HypERC721Collateral.sol +++ b/solidity/contracts/token/HypERC721Collateral.sol @@ -21,6 +21,20 @@ contract HypERC721Collateral is TokenRouter { wrappedToken = IERC721(erc721); } + /** + * @notice Initializes the Hyperlane router + * @param _hook The post-dispatch hook contract. + @param _interchainSecurityModule The interchain security module contract. + @param _owner The this contract. + */ + function initialize( + address _hook, + address _interchainSecurityModule, + address _owner + ) public virtual initializer { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + function ownerOf(uint256 _tokenId) external view returns (address) { return IERC721(wrappedToken).ownerOf(_tokenId); } diff --git a/solidity/contracts/token/HypNative.sol b/solidity/contracts/token/HypNative.sol index 7dd9bbbd0..b8c6d6822 100644 --- a/solidity/contracts/token/HypNative.sol +++ b/solidity/contracts/token/HypNative.sol @@ -20,6 +20,20 @@ contract HypNative is TokenRouter { constructor(address _mailbox) TokenRouter(_mailbox) {} + /** + * @notice Initializes the Hyperlane router + * @param _hook The post-dispatch hook contract. + @param _interchainSecurityModule The interchain security module contract. + @param _owner The this contract. + */ + function initialize( + address _hook, + address _interchainSecurityModule, + address _owner + ) public initializer { + _MailboxClient_initialize(_hook, _interchainSecurityModule, _owner); + } + /** * @inheritdoc TokenRouter * @dev uses (`msg.value` - `_amount`) as interchain gas payment and `msg.sender` as refund address. diff --git a/solidity/test/token/HypERC20.t.sol b/solidity/test/token/HypERC20.t.sol index 0b196f3c6..94fd83ed2 100644 --- a/solidity/test/token/HypERC20.t.sol +++ b/solidity/test/token/HypERC20.t.sol @@ -14,6 +14,7 @@ pragma solidity ^0.8.13; @@@@@@@@@ @@@@@@@@*/ import "forge-std/Test.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; @@ -40,6 +41,7 @@ abstract contract HypTokenTest is Test { string internal constant SYMBOL = "HYP"; address internal constant ALICE = address(0x1); address internal constant BOB = address(0x2); + address internal constant PROXY_ADMIN = address(0x37); ERC20Test internal primaryToken; TokenRouter internal localToken; @@ -73,8 +75,24 @@ abstract contract HypTokenTest is Test { REQUIRED_VALUE = noopHook.quoteDispatch("", ""); - remoteToken = new HypERC20(DECIMALS, address(remoteMailbox)); - remoteToken.initialize(TOTAL_SUPPLY, NAME, SYMBOL); + HypERC20 implementation = new HypERC20( + DECIMALS, + address(remoteMailbox) + ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC20.initialize.selector, + TOTAL_SUPPLY, + NAME, + SYMBOL, + address(noopHook), + address(igp), + address(this) + ) + ); + remoteToken = HypERC20(address(proxy)); remoteToken.enrollRemoteRouter( ORIGIN, address(localToken).addressToBytes32() @@ -188,10 +206,22 @@ contract HypERC20Test is HypTokenTest { function setUp() public override { super.setUp(); - localToken = new HypERC20(DECIMALS, address(localMailbox)); - erc20Token = HypERC20(address(localToken)); - - erc20Token.initialize(TOTAL_SUPPLY, NAME, SYMBOL); + HypERC20 implementation = new HypERC20(DECIMALS, address(localMailbox)); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC20.initialize.selector, + TOTAL_SUPPLY, + NAME, + SYMBOL, + address(address(noopHook)), + address(igp), + address(this) + ) + ); + localToken = HypERC20(address(proxy)); + erc20Token = HypERC20(address(proxy)); erc20Token.enrollRemoteRouter( DESTINATION, @@ -204,7 +234,14 @@ contract HypERC20Test is HypTokenTest { function testInitialize_revert_ifAlreadyInitialized() public { vm.expectRevert("Initializable: contract is already initialized"); - erc20Token.initialize(TOTAL_SUPPLY, NAME, SYMBOL); + erc20Token.initialize( + TOTAL_SUPPLY, + NAME, + SYMBOL, + address(address(noopHook)), + address(igp), + BOB + ); } function testTotalSupply() public { diff --git a/solidity/test/token/HypERC20CollateralVaultDeposit.t.sol b/solidity/test/token/HypERC20CollateralVaultDeposit.t.sol index bf0166536..3dc4e7532 100644 --- a/solidity/test/token/HypERC20CollateralVaultDeposit.t.sol +++ b/solidity/test/token/HypERC20CollateralVaultDeposit.t.sol @@ -14,6 +14,8 @@ pragma solidity ^0.8.13; @@@@@@@@@ @@@@@@@@*/ import "forge-std/Test.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + import {ERC4626Test} from "../../contracts/test/ERC4626/ERC4626Test.sol"; import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {HypTokenTest} from "./HypERC20.t.sol"; @@ -31,10 +33,21 @@ contract HypERC20CollateralVaultDepositTest is HypTokenTest { super.setUp(); vault = new ERC4626Test(address(primaryToken), "Regular Vault", "RV"); - localToken = new HypERC20CollateralVaultDeposit( - vault, - address(localMailbox) + HypERC20CollateralVaultDeposit implementation = new HypERC20CollateralVaultDeposit( + vault, + address(localMailbox) + ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC20CollateralVaultDeposit.initialize.selector, + address(address(noopHook)), + address(igp), + address(this) + ) ); + localToken = HypERC20CollateralVaultDeposit(address(proxy)); erc20CollateralVaultDeposit = HypERC20CollateralVaultDeposit( address(localToken) ); diff --git a/solidity/test/token/HypERC721.t.sol b/solidity/test/token/HypERC721.t.sol index cf55ff476..70a2a229c 100644 --- a/solidity/test/token/HypERC721.t.sol +++ b/solidity/test/token/HypERC721.t.sol @@ -14,6 +14,9 @@ pragma solidity ^0.8.13; @@@@@@@@@ @@@@@@@@*/ import "forge-std/Test.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; import {TestPostDispatchHook} from "../../contracts/test/TestPostDispatchHook.sol"; @@ -26,9 +29,6 @@ import {HypERC721Collateral} from "../../contracts/token/HypERC721Collateral.sol import {HypERC721URIStorage} from "../../contracts/token/extensions/HypERC721URIStorage.sol"; import {HypERC721URICollateral} from "../../contracts/token/extensions/HypERC721URICollateral.sol"; -import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; -import {ERC721URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; - abstract contract HypTokenTest is Test, IERC721Receiver { using TypeCasts for address; @@ -38,6 +38,7 @@ abstract contract HypTokenTest is Test, IERC721Receiver { address internal constant ALICE = address(0x1); address internal constant BOB = address(0x2); + address internal constant PROXY_ADMIN = address(0x37); uint32 internal constant ORIGIN = 11; uint32 internal constant DESTINATION = 22; uint256 internal constant TRANSFER_ID = 0; @@ -77,19 +78,42 @@ abstract contract HypTokenTest is Test, IERC721Receiver { function _deployRemoteToken(bool isCollateral) internal { if (isCollateral) { - remoteToken = new HypERC721Collateral( + HypERC721Collateral implementation = new HypERC721Collateral( address(remotePrimaryToken), address(remoteMailbox) ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC721Collateral.initialize.selector, + address(0), + address(0), + address(this) + ) + ); + remoteToken = HypERC721Collateral(address(proxy)); remotePrimaryToken.transferFrom( address(this), address(remoteToken), 0 ); // need for processing messages } else { - HypERC721 erc721 = new HypERC721(address(remoteMailbox)); - erc721.initialize(0, NAME, SYMBOL); - remoteToken = TokenRouter(address(erc721)); + HypERC721 implementation = new HypERC721(address(remoteMailbox)); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC721.initialize.selector, + 0, + NAME, + SYMBOL, + address(0), + address(0), + address(this) + ) + ); + remoteToken = TokenRouter(address(proxy)); } remoteToken.enrollRemoteRouter( ORIGIN, @@ -151,10 +175,22 @@ contract HypERC721Test is HypTokenTest { function setUp() public virtual override { super.setUp(); - localToken = new HypERC721(address(localMailbox)); - hyp721 = HypERC721(address(localToken)); - - hyp721.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + HypERC721 implementation = new HypERC721(address(localMailbox)); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC721.initialize.selector, + INITIAL_SUPPLY, + NAME, + SYMBOL, + address(0), + address(0), + address(this) + ) + ); + localToken = HypERC721(address(proxy)); + hyp721 = HypERC721(address(proxy)); hyp721.enrollRemoteRouter( DESTINATION, @@ -164,7 +200,14 @@ contract HypERC721Test is HypTokenTest { function testInitialize_revert_ifAlreadyInitialized() public { vm.expectRevert("Initializable: contract is already initialized"); - hyp721.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + hyp721.initialize( + INITIAL_SUPPLY, + NAME, + SYMBOL, + address(0), + address(0), + address(this) + ); } function testTotalSupply() public { @@ -228,7 +271,14 @@ contract HypERC721URIStorageTest is HypTokenTest { localToken = new MockHypERC721URIStorage(address(localMailbox)); hyp721Storage = MockHypERC721URIStorage(address(localToken)); - hyp721Storage.initialize(INITIAL_SUPPLY, NAME, SYMBOL); + hyp721Storage.initialize( + INITIAL_SUPPLY, + NAME, + SYMBOL, + address(0), + address(0), + address(this) + ); hyp721Storage.setTokenURI(0, URI); hyp721Storage.enrollRemoteRouter( DESTINATION, @@ -254,10 +304,21 @@ contract HypERC721CollateralTest is HypTokenTest { function setUp() public override { super.setUp(); - localToken = new HypERC721Collateral( + HypERC721Collateral implementation = new HypERC721Collateral( address(localPrimaryToken), address(localMailbox) ); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(implementation), + PROXY_ADMIN, + abi.encodeWithSelector( + HypERC721Collateral.initialize.selector, + address(0), + address(0), + address(this) + ) + ); + localToken = HypERC721Collateral(address(proxy)); hyp721Collateral = HypERC721Collateral(address(localToken)); hyp721Collateral.enrollRemoteRouter( diff --git a/solidity/test/token/HypNativeScaled.t.sol b/solidity/test/token/HypNativeScaled.t.sol index 09865606a..a9af062ae 100644 --- a/solidity/test/token/HypNativeScaled.t.sol +++ b/solidity/test/token/HypNativeScaled.t.sol @@ -2,9 +2,11 @@ pragma solidity >=0.8.0; import "forge-std/Test.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import {HypNativeScaled} from "../../contracts/token/extensions/HypNativeScaled.sol"; import {HypERC20} from "../../contracts/token/HypERC20.sol"; +import {HypNative} from "../../contracts/token/HypNative.sol"; import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {MockHyperlaneEnvironment} from "../../contracts/mock/MockHyperlaneEnvironment.sol"; @@ -37,20 +39,41 @@ contract HypNativeScaledTest is Test { function setUp() public { environment = new MockHyperlaneEnvironment(synthDomain, nativeDomain); - synth = new HypERC20( + HypERC20 implementationSynth = new HypERC20( decimals, address(environment.mailboxes(synthDomain)) ); - synth.initialize( - mintAmount * (10 ** decimals), - "Zebec BSC Token", - "ZBC" - ); - - native = new HypNativeScaled( + TransparentUpgradeableProxy proxySynth = new TransparentUpgradeableProxy( + address(implementationSynth), + address(9), + abi.encodeWithSelector( + HypERC20.initialize.selector, + mintAmount * (10 ** decimals), + "Zebec BSC Token", + "ZBC", + address(0), + address(0), + address(this) + ) + ); + synth = HypERC20(address(proxySynth)); + + HypNativeScaled implementationNative = new HypNativeScaled( scale, address(environment.mailboxes(nativeDomain)) ); + TransparentUpgradeableProxy proxyNative = new TransparentUpgradeableProxy( + address(implementationNative), + address(9), + abi.encodeWithSelector( + HypNative.initialize.selector, + address(0), + address(0), + address(this) + ) + ); + + native = HypNativeScaled(payable(address(proxyNative))); native.enrollRemoteRouter( synthDomain, diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 515443e44..677930bb6 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -296,7 +296,7 @@ async function fetchBaseTokenMetadata( (base.type === TokenType.collateral && address) ) { // If it's a collateral type, use a TokenAdapter to query for its metadata - log(`Fetching token metadata for ${address} on ${chainName}}`); + log(`Fetching token metadata for ${address} on ${chainName}`); const adapter = new EvmTokenAdapter( chainName, MultiProtocolProvider.fromMultiProvider(multiProvider), diff --git a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts index 2f1f737bb..b1a4ed153 100644 --- a/typescript/sdk/src/core/HyperlaneCoreDeployer.ts +++ b/typescript/sdk/src/core/HyperlaneCoreDeployer.ts @@ -65,6 +65,7 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer< const mailbox = await this.deployProxiedContract( chain, 'mailbox', + 'mailbox', proxyAdmin, [domain], ); diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index 06b6bb9b2..0712694e9 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -642,14 +642,16 @@ export abstract class HyperlaneDeployer< */ async deployProxiedContract( chain: ChainName, - contractName: K, + contractKey: K, + contractName: string, proxyAdmin: string, constructorArgs: Parameters, initializeArgs?: Parameters[K]['initialize']>, ): Promise[K]> { // Try to initialize the implementation even though it may not be necessary - const implementation = await this.deployContract( + const implementation = await this.deployContractWithName( chain, + contractKey, contractName, constructorArgs, initializeArgs, diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index 2966d0a3c..89a87b918 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -40,6 +40,7 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< const igp = await this.deployProxiedContract( chain, 'interchainGasPaymaster', + 'interchainGasPaymaster', proxyAdmin.address, [], [await this.multiProvider.getSignerAddress(chain), beneficiary], diff --git a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts index 329539150..ed45510e8 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts @@ -1,5 +1,7 @@ import { ethers } from 'ethers'; +import { Router } from '@hyperlane-xyz/core'; + import { HyperlaneContracts } from '../../contracts/types'; import { ContractVerifier } from '../../deploy/verify/ContractVerifier'; import { MultiProvider } from '../../providers/MultiProvider'; @@ -16,11 +18,8 @@ export type InterchainAccountConfig = ProxiedRouterConfig; export class InterchainAccountDeployer extends ProxiedRouterDeployer< InterchainAccountConfig, - InterchainAccountFactories, - 'interchainAccountRouter' + InterchainAccountFactories > { - readonly routerContractName = 'interchainAccountRouter'; - constructor( multiProvider: MultiProvider, contractVerifier?: ContractVerifier, @@ -29,15 +28,26 @@ export class InterchainAccountDeployer extends ProxiedRouterDeployer< contractVerifier, }); } + routerContractName(): string { + return 'InterchainAccountRouter'; + } - async constructorArgs(_: string, config: RouterConfig): Promise<[string]> { - return [config.mailbox]; + routerContractKey(): K { + return 'interchainAccountRouter' as K; } - async initializeArgs( - chain: string, + router(contracts: HyperlaneContracts): Router { + return contracts.interchainAccountRouter; + } + + async constructorArgs( + _: string, config: RouterConfig, - ): Promise<[string, string, string]> { + ): Promise> { + return [config.mailbox] as any; + } + + async initializeArgs(chain: string, config: RouterConfig): Promise { const owner = await this.multiProvider.getSignerAddress(chain); return [ config.hook ?? ethers.constants.AddressZero, diff --git a/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts b/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts index c3d9060b7..015920f21 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/LiquidityLayerRouterDeployer.ts @@ -4,6 +4,7 @@ import { CircleBridgeAdapter, LiquidityLayerRouter, PortalAdapter, + Router, } from '@hyperlane-xyz/core'; import { Address, eqAddress, objFilter, objMap } from '@hyperlane-xyz/utils'; @@ -53,11 +54,8 @@ export type LiquidityLayerConfig = RouterConfig & BridgeAdapterConfig; export class LiquidityLayerDeployer extends ProxiedRouterDeployer< LiquidityLayerConfig, - LiquidityLayerFactories, - 'liquidityLayerRouter' + LiquidityLayerFactories > { - readonly routerContractName = 'liquidityLayerRouter'; - constructor( multiProvider: MultiProvider, contractVerifier?: ContractVerifier, @@ -66,18 +64,31 @@ export class LiquidityLayerDeployer extends ProxiedRouterDeployer< contractVerifier, }); } + routerContractName(): string { + return 'LiquidityLayerRouter'; + } + + routerContractKey( + _: RouterConfig, + ): K { + return 'liquidityLayerRouter' as K; + } + + router(contracts: HyperlaneContracts): Router { + return contracts.liquidityLayerRouter; + } - async constructorArgs( + async constructorArgs( _: string, config: LiquidityLayerConfig, - ): Promise<[string]> { - return [config.mailbox]; + ): Promise> { + return [config.mailbox] as any; } async initializeArgs( chain: string, config: LiquidityLayerConfig, - ): Promise<[string, string, string]> { + ): Promise { const owner = await this.multiProvider.getSignerAddress(chain); if (typeof config.interchainSecurityModule === 'object') { throw new Error('ISM as object unimplemented'); diff --git a/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts b/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts index 51af12bb9..93e5dab3d 100644 --- a/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts +++ b/typescript/sdk/src/middleware/query/InterchainQueryDeployer.ts @@ -1,5 +1,8 @@ import { ethers } from 'ethers'; +import { Router } from '@hyperlane-xyz/core'; + +import { HyperlaneContracts } from '../../contracts/types'; import { ContractVerifier } from '../../deploy/verify/ContractVerifier'; import { MultiProvider } from '../../providers/MultiProvider'; import { ProxiedRouterDeployer } from '../../router/ProxiedRouterDeployer'; @@ -14,11 +17,8 @@ export type InterchainQueryConfig = RouterConfig; export class InterchainQueryDeployer extends ProxiedRouterDeployer< InterchainQueryConfig, - InterchainQueryFactories, - 'interchainQueryRouter' + InterchainQueryFactories > { - readonly routerContractName = 'interchainQueryRouter'; - constructor( multiProvider: MultiProvider, contractVerifier?: ContractVerifier, @@ -28,14 +28,26 @@ export class InterchainQueryDeployer extends ProxiedRouterDeployer< }); } - async constructorArgs(_: string, config: RouterConfig): Promise<[string]> { - return [config.mailbox]; + routerContractName(): string { + return 'InterchainQueryRouter'; + } + + routerContractKey(): K { + return 'interchainQueryRouter' as K; } - async initializeArgs( - chain: string, + router(contracts: HyperlaneContracts): Router { + return contracts.interchainQueryRouter; + } + + async constructorArgs( + _: string, config: RouterConfig, - ): Promise<[string, string, string]> { + ): Promise> { + return [config.mailbox] as any; + } + + async initializeArgs(chain: string, config: RouterConfig): Promise { const owner = await this.multiProvider.getSignerAddress(chain); if (typeof config.interchainSecurityModule === 'object') { throw new Error('ISM as object unimplemented'); diff --git a/typescript/sdk/src/router/GasRouterDeployer.ts b/typescript/sdk/src/router/GasRouterDeployer.ts index 3e4437c00..28b147112 100644 --- a/typescript/sdk/src/router/GasRouterDeployer.ts +++ b/typescript/sdk/src/router/GasRouterDeployer.ts @@ -1,20 +1,16 @@ import { GasRouter } from '@hyperlane-xyz/core'; import { Address } from '@hyperlane-xyz/utils'; -import { - HyperlaneContracts, - HyperlaneContractsMap, - HyperlaneFactories, -} from '../contracts/types'; +import { HyperlaneContracts, HyperlaneContractsMap } from '../contracts/types'; import { ChainMap } from '../types'; -import { HyperlaneRouterDeployer } from './HyperlaneRouterDeployer'; -import { GasRouterConfig } from './types'; +import { ProxiedRouterDeployer } from './ProxiedRouterDeployer'; +import { GasRouterConfig, ProxiedFactories } from './types'; export abstract class GasRouterDeployer< Config extends GasRouterConfig, - Factories extends HyperlaneFactories, -> extends HyperlaneRouterDeployer { + Factories extends ProxiedFactories, +> extends ProxiedRouterDeployer { abstract router(contracts: HyperlaneContracts): GasRouter; async enrollRemoteRouters( diff --git a/typescript/sdk/src/router/ProxiedRouterDeployer.ts b/typescript/sdk/src/router/ProxiedRouterDeployer.ts index e5a58b3ec..e68ba262a 100644 --- a/typescript/sdk/src/router/ProxiedRouterDeployer.ts +++ b/typescript/sdk/src/router/ProxiedRouterDeployer.ts @@ -16,20 +16,37 @@ import { ProxiedFactories, ProxiedRouterConfig } from './types'; export abstract class ProxiedRouterDeployer< Config extends ProxiedRouterConfig, Factories extends ProxiedFactories, - RouterKey extends keyof Factories, > extends HyperlaneRouterDeployer { - abstract routerContractName: RouterKey; + abstract router(contracts: HyperlaneContracts): Router; - router(contracts: HyperlaneContracts): Router { - return contracts[this.routerContractName] as Router; - } + /** + * Returns the contract name + * @param config Router config + */ + abstract routerContractName(config: Config): string; + + /** + * Returns the contract key + * @param config Router config + */ + abstract routerContractKey(config: Config): keyof Factories; - abstract constructorArgs( + /** + * Returns the constructor arguments for the proxy + * @param chain Name of chain + * @param config Router config + */ + abstract constructorArgs( chain: ChainName, config: Config, ): Promise>; - abstract initializeArgs( + /** + * Returns the initialize arguments for the proxy + * @param chain Name of chain + * @param config Router config + */ + abstract initializeArgs( chain: ChainName, config: Config, ): Promise< @@ -79,14 +96,15 @@ export abstract class ProxiedRouterDeployer< const proxiedRouter = await this.deployProxiedContract( chain, - this.routerContractName, + this.routerContractKey(config), + this.routerContractName(config), proxyAdmin.address, await this.constructorArgs(chain, config), await this.initializeArgs(chain, config), ); return { - [this.routerContractName]: proxiedRouter, + [this.routerContractKey(config)]: proxiedRouter, proxyAdmin, timelockController, } as HyperlaneContracts; diff --git a/typescript/sdk/src/token/config.ts b/typescript/sdk/src/token/config.ts index 7e2738a74..d8c0cc561 100644 --- a/typescript/sdk/src/token/config.ts +++ b/typescript/sdk/src/token/config.ts @@ -60,6 +60,10 @@ export const isCollateralConfig = ( config.type === TokenType.fastCollateral || config.type == TokenType.collateralVault; +export const isCollateralVaultConfig = ( + config: TokenConfig, +): config is CollateralConfig => config.type === TokenType.collateralVault; + export const isSyntheticConfig = ( config: TokenConfig, ): config is SyntheticConfig => diff --git a/typescript/sdk/src/token/contracts.ts b/typescript/sdk/src/token/contracts.ts index a361ae5f0..478729f84 100644 --- a/typescript/sdk/src/token/contracts.ts +++ b/typescript/sdk/src/token/contracts.ts @@ -12,6 +12,8 @@ import { HypNative__factory, } from '@hyperlane-xyz/core'; +import { proxiedFactories } from '../router/types'; + import { TokenType } from './config'; export const hypERC20contracts = { @@ -23,6 +25,8 @@ export const hypERC20contracts = { [TokenType.native]: 'HypNative', [TokenType.nativeScaled]: 'HypNativeScaled', }; +export type HypERC20contracts = typeof hypERC20contracts; + export const hypERC20factories = { [TokenType.fastCollateral]: new FastHypERC20Collateral__factory(), [TokenType.fastSynthetic]: new FastHypERC20__factory(), @@ -31,6 +35,7 @@ export const hypERC20factories = { [TokenType.collateralVault]: new HypERC20CollateralVaultDeposit__factory(), [TokenType.native]: new HypNative__factory(), [TokenType.nativeScaled]: new HypNativeScaled__factory(), + ...proxiedFactories, }; export type HypERC20Factories = typeof hypERC20factories; @@ -40,11 +45,15 @@ export const hypERC721contracts = { [TokenType.syntheticUri]: 'HypERC721URIStorage', [TokenType.synthetic]: 'HypERC721', }; + +export type HypERC721contracts = typeof hypERC721contracts; + export const hypERC721factories = { [TokenType.collateralUri]: new HypERC721URICollateral__factory(), [TokenType.collateral]: new HypERC721Collateral__factory(), [TokenType.syntheticUri]: new HypERC721URIStorage__factory(), [TokenType.synthetic]: new HypERC721__factory(), + ...proxiedFactories, }; export type HypERC721Factories = typeof hypERC721factories; diff --git a/typescript/sdk/src/token/deploy.ts b/typescript/sdk/src/token/deploy.ts index 6a12b38db..f72ab12e4 100644 --- a/typescript/sdk/src/token/deploy.ts +++ b/typescript/sdk/src/token/deploy.ts @@ -1,14 +1,11 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { providers } from 'ethers'; +import { constants, providers } from 'ethers'; import { ERC20__factory, ERC721EnumerableUpgradeable__factory, - HypERC20, - HypERC20Collateral, - HypERC721, - HypERC721Collateral, - HypNative, + GasRouter, + MailboxClient, } from '@hyperlane-xyz/core'; import { objKeys, objMap, rootLogger } from '@hyperlane-xyz/utils'; @@ -25,15 +22,13 @@ import { ERC20Metadata, ERC20RouterConfig, ERC721RouterConfig, - HypERC20CollateralConfig, HypERC20Config, - HypERC721CollateralConfig, HypERC721Config, - HypNativeConfig, TokenConfig, TokenMetadata, TokenType, isCollateralConfig, + isCollateralVaultConfig, isErc20Metadata, isFastConfig, isNativeConfig, @@ -44,6 +39,7 @@ import { import { HypERC20Factories, HypERC721Factories, + HypERC721contracts, hypERC20contracts, hypERC20factories, hypERC721contracts, @@ -66,6 +62,73 @@ export class HypERC20Deployer extends GasRouterDeployer< }); // factories not used in deploy } + routerContractName(config: ERC20RouterConfig): string { + return hypERC20contracts[this.routerContractKey(config)]; + } + + routerContractKey(config: ERC20RouterConfig) { + if (isCollateralConfig(config)) { + if (isFastConfig(config)) { + return TokenType.fastCollateral; + } else if (isCollateralVaultConfig(config)) { + return TokenType.collateralVault; + } else { + return TokenType.collateral; + } + } else if (isNativeConfig(config)) { + return config.scale ? TokenType.nativeScaled : TokenType.native; + } else if (isSyntheticConfig(config)) { + return isFastConfig(config) + ? TokenType.fastSynthetic + : TokenType.synthetic; + } else { + throw new Error('Unknown collateral type when constructing router name'); + } + } + + async constructorArgs( + _: ChainName, + config: ERC20RouterConfig, + ): Promise> { + if (isCollateralConfig(config)) { + return [config.token, config.mailbox] as any; + } else if (isNativeConfig(config)) { + return config.scale + ? [config.scale, config.mailbox] + : ([config.mailbox] as any); + } else if (isSyntheticConfig(config)) { + return [config.decimals, config.mailbox] as any; + } else { + throw new Error('Unknown collateral type when constructing arguments'); + } + } + + async initializeArgs(_: ChainName, config: HypERC20Config): Promise { + // ISM config can be an object, but is not supported right now + if (typeof config.interchainSecurityModule === 'object') { + throw new Error('Token deployer does not support ISM objects currently'); + } + const defaultArgs = [ + config.hook ?? constants.AddressZero, + config.interchainSecurityModule ?? constants.AddressZero, + config.owner, + ]; + if (isCollateralConfig(config)) { + return defaultArgs as any; + } else if (isNativeConfig(config)) { + return defaultArgs as any; + } else if (isSyntheticConfig(config)) { + return [ + config.totalSupply, + config.name, + config.symbol, + ...defaultArgs, + ] as any; + } else { + throw new Error('Unknown collateral type when initializing arguments'); + } + } + static async fetchMetadata( provider: providers.Provider, config: CollateralConfig, @@ -132,104 +195,20 @@ export class HypERC20Deployer extends GasRouterDeployer< } as ERC20Metadata; } - protected async deployCollateral( - chain: ChainName, - config: HypERC20CollateralConfig, - ): Promise { - let tokenType: - | TokenType.fastCollateral - | TokenType.collateral - | TokenType.collateralVault; - switch (config.type) { - case TokenType.fastSynthetic || TokenType.fastCollateral: - tokenType = TokenType.fastCollateral; - break; - case TokenType.collateral: - tokenType = TokenType.collateral; - break; - case TokenType.collateralVault: - tokenType = TokenType.collateralVault; - break; - default: - throw new Error(`Unknown collateral type ${config.type}`); - } - return this.deployContractWithName( - chain, - tokenType, - hypERC20contracts[tokenType], - [config.token, config.mailbox], - ); - } - - protected async deployNative( - chain: ChainName, - config: HypNativeConfig, - ): Promise { - if (config.scale) { - return this.deployContractWithName( - chain, - TokenType.nativeScaled, - hypERC20contracts[TokenType.nativeScaled], - [config.scale, config.mailbox], - ); - } else { - return this.deployContractWithName( - chain, - TokenType.native, - hypERC20contracts[TokenType.native], - [config.mailbox], - ); - } - } - - protected async deploySynthetic( - chain: ChainName, - config: HypERC20Config, - ): Promise { - const tokenType = isFastConfig(config) - ? TokenType.fastSynthetic - : TokenType.synthetic; - const router: HypERC20 = await this.deployContractWithName( - chain, - tokenType, - hypERC20contracts[tokenType], - [config.decimals, config.mailbox], - ); - try { - await this.multiProvider.handleTx( - chain, - router.initialize(config.totalSupply, config.name, config.symbol), - ); - } catch (e: any) { - if (!e.message.includes('already initialized')) { - throw e; - } - this.logger.debug(`${config.type} already initialized`); - } - return router; - } - router(contracts: HyperlaneContracts) { for (const key of objKeys(hypERC20factories)) { if (contracts[key]) { - return contracts[key]; + return contracts[key] as GasRouter; } } throw new Error('No matching contract found'); } 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'); - } - await this.configureClient(chain, router, config); + const { [this.routerContractKey(config)]: router } = + await super.deployContracts(chain, config); + + await this.configureClient(chain, router as MailboxClient, config); return { [config.type]: router } as any; } @@ -306,6 +285,52 @@ export class HypERC721Deployer extends GasRouterDeployer< contractVerifier, }); } + routerContractName(config: ERC721RouterConfig): string { + return hypERC721contracts[this.routerContractKey(config)]; + } + + routerContractKey( + config: ERC721RouterConfig, + ): K { + if (isCollateralConfig(config)) { + return ( + isUriConfig(config) ? TokenType.collateralUri : TokenType.collateral + ) as K; + } else { + // if isSyntheticConfig + return ( + isUriConfig(config) ? TokenType.syntheticUri : TokenType.synthetic + ) as K; + } + } + + async constructorArgs( + _: ChainName, + config: ERC721RouterConfig, + ): Promise { + if (isCollateralConfig(config)) { + return [config.token, config.mailbox]; + } else if (isSyntheticConfig(config)) { + return [config.mailbox]; + } else { + throw new Error('Unknown collateral type when constructing arguments'); + } + } + + async initializeArgs(_: ChainName, config: ERC721RouterConfig): Promise { + const defaultArgs = [ + config.hook ?? constants.AddressZero, + config.interchainSecurityModule ?? constants.AddressZero, + config.owner, + ]; + if (isCollateralConfig(config)) { + return defaultArgs; + } else if (isSyntheticConfig(config)) { + return [config.totalSupply, config.name, config.symbol, ...defaultArgs]; + } else { + throw new Error('Unknown collateral type when initializing arguments'); + } + } static async fetchMetadata( provider: providers.Provider, @@ -337,59 +362,20 @@ export class HypERC721Deployer extends GasRouterDeployer< } } - protected async deployCollateral( - chain: ChainName, - config: HypERC721CollateralConfig, - ): Promise { - const tokenType = isUriConfig(config) - ? TokenType.collateralUri - : TokenType.collateral; - return this.deployContractWithName( - chain, - tokenType, - hypERC721contracts[tokenType], - [config.token, config.mailbox], - ); - } - - protected async deploySynthetic( - chain: ChainName, - config: HypERC721Config, - ): Promise { - const tokenType = isUriConfig(config) - ? TokenType.syntheticUri - : TokenType.synthetic; - const router = await this.deployContractWithName( - chain, - tokenType, - hypERC721contracts[tokenType], - [config.mailbox], - ); - await this.multiProvider.handleTx( - chain, - router.initialize(config.totalSupply, config.name, config.symbol), - ); - return router; - } - router(contracts: HyperlaneContracts) { for (const key of objKeys(hypERC721factories)) { if (contracts[key]) { - return contracts[key]; + return contracts[key] as GasRouter; } } throw new Error('No matching contract found'); } 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'); - } + const { [this.routerContractKey(config)]: router } = + await super.deployContracts(chain, config); + + await this.configureClient(chain, router as MailboxClient, config); return { [config.type]: router } as any; }