feat(cli): add submit functionality support to warp apply (#4225)
### Description - adds submit functionality support to warp apply - enables dynamic submission of transactions to vanilla json rpc, gnosis safe, and impersonated accounts while dry-running - allows easy support of ICA tx submissions in the future ### Drive-by changes - just updated `ApplyParams` to `WarpApplyParams` - e2e tests written in ts ### Related issues - https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/4242 ### Backward compatibility - yes ### Testing - [x] Single warp route transfer ownership from EOA to Safe - [x] Single warp route transfer from Safe to EOA - [x] 2 warp route transfer ownership from EOA to safe - [x] 2 warp route transfer from Safe to EOA (sepolia and basesepolia) Multichain enrollments through their respective Safes: - [x] Deploy to sepolia with address to Signer - [x] Transfer Owner to safe - [x] Warp apply to extend a synthetic Route to base sepolia and set owner to safe - [x] Approve safe txs to enroll each other - [x] Send a test message E2e Testing --------- Co-authored-by: Le Yu <6251863+ltyu@users.noreply.github.com>pull/4412/head
parent
ef813b9810
commit
3c07ded5b7
@ -1,5 +1,5 @@ |
|||||||
--- |
--- |
||||||
"@hyperlane-xyz/sdk": patch |
'@hyperlane-xyz/sdk': patch |
||||||
--- |
--- |
||||||
|
|
||||||
Support DefaultFallbackRoutingIsm in metadata builder |
Support DefaultFallbackRoutingIsm in metadata builder |
||||||
|
@ -0,0 +1,6 @@ |
|||||||
|
--- |
||||||
|
'@hyperlane-xyz/cli': minor |
||||||
|
'@hyperlane-xyz/sdk': minor |
||||||
|
--- |
||||||
|
|
||||||
|
Add Safe submit functionality to warp apply |
@ -0,0 +1,10 @@ |
|||||||
|
sepolia: |
||||||
|
submitter: |
||||||
|
type: gnosisSafe |
||||||
|
chain: sepolia |
||||||
|
safeAddress: '0x7fd32493Ca3A38cDf78A4cb74F32f6292f822aBe' |
||||||
|
basesepolia: |
||||||
|
submitter: |
||||||
|
type: gnosisSafe |
||||||
|
chain: basesepolia |
||||||
|
safeAddress: '0x7fd32493Ca3A38cDf78A4cb74F32f6292f822aBe' |
@ -0,0 +1,3 @@ |
|||||||
|
anvil1: |
||||||
|
submitter: |
||||||
|
type: jsonRpc |
@ -0,0 +1,10 @@ |
|||||||
|
# Sends some eth |
||||||
|
[ |
||||||
|
{ |
||||||
|
'data': '0x00', |
||||||
|
'value': 1, |
||||||
|
'to': '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', |
||||||
|
'from': '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', |
||||||
|
'chainId': 31337, |
||||||
|
}, |
||||||
|
] |
@ -0,0 +1,25 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
function cleanup() { |
||||||
|
set +e |
||||||
|
pkill -f anvil |
||||||
|
rm -rf /tmp/anvil2 |
||||||
|
rm -rf /tmp/anvil3 |
||||||
|
rm -f ./test-configs/anvil/chains/anvil2/addresses.yaml |
||||||
|
rm -f ./test-configs/anvil/chains/anvil3/addresses.yaml |
||||||
|
set -e |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
# cleanup |
||||||
|
|
||||||
|
echo "Starting anvil2 and anvil3 chain" |
||||||
|
anvil --chain-id 31338 -p 8555 --state /tmp/anvil2/state --gas-price 1 > /dev/null & |
||||||
|
anvil --chain-id 31347 -p 8600 --state /tmp/anvil3/state --gas-price 1 > /dev/null & |
||||||
|
|
||||||
|
echo "Running all tests" |
||||||
|
yarn mocha --config .mocharc.json |
||||||
|
|
||||||
|
# cleanup |
||||||
|
|
||||||
|
echo "Done all tests" |
@ -1,11 +1,6 @@ |
|||||||
import { z } from 'zod'; |
import type { MultiProvider, SubmissionStrategy } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
import type { |
|
||||||
MultiProvider, |
|
||||||
SubmissionStrategySchema, |
|
||||||
} from '@hyperlane-xyz/sdk'; |
|
||||||
|
|
||||||
export type SubmitterBuilderSettings = { |
export type SubmitterBuilderSettings = { |
||||||
submissionStrategy: z.infer<typeof SubmissionStrategySchema>; |
submissionStrategy: SubmissionStrategy; |
||||||
multiProvider: MultiProvider; |
multiProvider: MultiProvider; |
||||||
}; |
}; |
||||||
|
@ -0,0 +1,20 @@ |
|||||||
|
import { $ } from 'zx'; |
||||||
|
|
||||||
|
import { ANVIL_KEY, REGISTRY_PATH } from './helpers.js'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Deploys the Hyperlane core contracts to the specified chain using the provided config. |
||||||
|
*/ |
||||||
|
export async function hyperlaneCoreDeploy( |
||||||
|
chain: string, |
||||||
|
coreInputPath: string, |
||||||
|
) { |
||||||
|
return $`yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \ |
||||||
|
--registry ${REGISTRY_PATH} \ |
||||||
|
--overrides " " \ |
||||||
|
--config ${coreInputPath} \ |
||||||
|
--chain ${chain} \ |
||||||
|
--key ${ANVIL_KEY} \ |
||||||
|
--verbosity debug \ |
||||||
|
--yes`;
|
||||||
|
} |
@ -0,0 +1,116 @@ |
|||||||
|
import { ChainAddresses } from '@hyperlane-xyz/registry'; |
||||||
|
import { |
||||||
|
TokenRouterConfig, |
||||||
|
WarpCoreConfig, |
||||||
|
WarpCoreConfigSchema, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { Address } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { getContext } from '../../context/context.js'; |
||||||
|
import { readYamlOrJson, writeYamlOrJson } from '../../utils/files.js'; |
||||||
|
|
||||||
|
import { hyperlaneCoreDeploy } from './core.js'; |
||||||
|
import { hyperlaneWarpApply, readWarpConfig } from './warp.js'; |
||||||
|
|
||||||
|
export const TEST_CONFIGS_PATH = './test-configs'; |
||||||
|
export const REGISTRY_PATH = `${TEST_CONFIGS_PATH}/anvil`; |
||||||
|
|
||||||
|
export const ANVIL_KEY = |
||||||
|
'0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves the deployed Warp address from the Warp core config. |
||||||
|
*/ |
||||||
|
export function getDeployedWarpAddress(chain: string, warpCorePath: string) { |
||||||
|
const warpCoreConfig: WarpCoreConfig = readYamlOrJson(warpCorePath); |
||||||
|
WarpCoreConfigSchema.parse(warpCoreConfig); |
||||||
|
return warpCoreConfig.tokens.find((t) => t.chainName === chain)! |
||||||
|
.addressOrDenom; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the owner of the Warp route deployment config, and then output to a file |
||||||
|
*/ |
||||||
|
export async function updateWarpOwnerConfig( |
||||||
|
chain: string, |
||||||
|
owner: Address, |
||||||
|
warpCorePath: string, |
||||||
|
warpDeployPath: string, |
||||||
|
): Promise<string> { |
||||||
|
const warpDeployConfig = await readWarpConfig( |
||||||
|
chain, |
||||||
|
warpCorePath, |
||||||
|
warpDeployPath, |
||||||
|
); |
||||||
|
warpDeployConfig[chain].owner = owner; |
||||||
|
writeYamlOrJson(warpDeployPath, warpDeployConfig); |
||||||
|
|
||||||
|
return warpDeployPath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the Warp route deployment configuration with a new owner, and then applies the changes. |
||||||
|
*/ |
||||||
|
export async function updateOwner( |
||||||
|
owner: Address, |
||||||
|
chain: string, |
||||||
|
warpConfigPath: string, |
||||||
|
warpCoreConfigPath: string, |
||||||
|
) { |
||||||
|
await updateWarpOwnerConfig(chain, owner, warpCoreConfigPath, warpConfigPath); |
||||||
|
return hyperlaneWarpApply(warpConfigPath, warpCoreConfigPath); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Extends the Warp route deployment with a new warp config |
||||||
|
*/ |
||||||
|
export async function extendWarpConfig( |
||||||
|
chain: string, |
||||||
|
chainToExtend: string, |
||||||
|
extendedConfig: TokenRouterConfig, |
||||||
|
warpCorePath: string, |
||||||
|
warpDeployPath: string, |
||||||
|
): Promise<string> { |
||||||
|
const warpDeployConfig = await readWarpConfig( |
||||||
|
chain, |
||||||
|
warpCorePath, |
||||||
|
warpDeployPath, |
||||||
|
); |
||||||
|
warpDeployConfig[chainToExtend] = extendedConfig; |
||||||
|
writeYamlOrJson(warpDeployPath, warpDeployConfig); |
||||||
|
await hyperlaneWarpApply(warpDeployPath, warpCorePath); |
||||||
|
|
||||||
|
return warpDeployPath; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Deploys new core contracts on the specified chain if it doesn't already exist, and returns the chain addresses. |
||||||
|
*/ |
||||||
|
export async function deployOrUseExistingCore( |
||||||
|
chain: string, |
||||||
|
coreInputPath: string, |
||||||
|
key: string, |
||||||
|
) { |
||||||
|
const { registry } = await getContext({ |
||||||
|
registryUri: REGISTRY_PATH, |
||||||
|
registryOverrideUri: '', |
||||||
|
key, |
||||||
|
}); |
||||||
|
const addresses = (await registry.getChainAddresses(chain)) as ChainAddresses; |
||||||
|
|
||||||
|
if (!addresses) { |
||||||
|
await hyperlaneCoreDeploy(chain, coreInputPath); |
||||||
|
return deployOrUseExistingCore(chain, coreInputPath, key); |
||||||
|
} |
||||||
|
|
||||||
|
return addresses; |
||||||
|
} |
||||||
|
export async function getChainId(chainName: string, key: string) { |
||||||
|
const { registry } = await getContext({ |
||||||
|
registryUri: REGISTRY_PATH, |
||||||
|
registryOverrideUri: '', |
||||||
|
key, |
||||||
|
}); |
||||||
|
const chainMetadata = await registry.getChainMetadata(chainName); |
||||||
|
return String(chainMetadata?.chainId); |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
import { $ } from 'zx'; |
||||||
|
|
||||||
|
import { WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
import { readYamlOrJson } from '../../utils/files.js'; |
||||||
|
|
||||||
|
import { ANVIL_KEY, REGISTRY_PATH, getDeployedWarpAddress } from './helpers.js'; |
||||||
|
|
||||||
|
$.verbose = true; |
||||||
|
|
||||||
|
/** |
||||||
|
* Deploys the Warp route to the specified chain using the provided config. |
||||||
|
*/ |
||||||
|
export async function hyperlaneWarpDeploy(warpCorePath: string) { |
||||||
|
return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp deploy \ |
||||||
|
--registry ${REGISTRY_PATH} \ |
||||||
|
--overrides " " \ |
||||||
|
--config ${warpCorePath} \ |
||||||
|
--key ${ANVIL_KEY} \ |
||||||
|
--verbosity debug \ |
||||||
|
--yes`;
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Applies updates to the Warp route config. |
||||||
|
*/ |
||||||
|
export async function hyperlaneWarpApply( |
||||||
|
warpDeployPath: string, |
||||||
|
warpCorePath: string, |
||||||
|
) { |
||||||
|
return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp apply \ |
||||||
|
--registry ${REGISTRY_PATH} \ |
||||||
|
--overrides " " \ |
||||||
|
--config ${warpDeployPath} \ |
||||||
|
--warp ${warpCorePath} \ |
||||||
|
--key ${ANVIL_KEY} \ |
||||||
|
--verbosity debug \ |
||||||
|
--yes`;
|
||||||
|
} |
||||||
|
|
||||||
|
export async function hyperlaneWarpRead( |
||||||
|
chain: string, |
||||||
|
warpAddress: string, |
||||||
|
warpDeployPath: string, |
||||||
|
) { |
||||||
|
return $`yarn workspace @hyperlane-xyz/cli run hyperlane warp read \ |
||||||
|
--registry ${REGISTRY_PATH} \ |
||||||
|
--overrides " " \ |
||||||
|
--address ${warpAddress} \ |
||||||
|
--chain ${chain} \ |
||||||
|
--config ${warpDeployPath}`;
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Reads the Warp route deployment config to specified output path. |
||||||
|
* @param warpCorePath path to warp core |
||||||
|
* @param warpDeployPath path to output the resulting read |
||||||
|
* @returns The Warp route deployment config. |
||||||
|
*/ |
||||||
|
export async function readWarpConfig( |
||||||
|
chain: string, |
||||||
|
warpCorePath: string, |
||||||
|
warpDeployPath: string, |
||||||
|
): Promise<WarpRouteDeployConfig> { |
||||||
|
const warpAddress = getDeployedWarpAddress(chain, warpCorePath); |
||||||
|
await hyperlaneWarpRead(chain, warpAddress!, warpDeployPath); |
||||||
|
return readYamlOrJson(warpDeployPath); |
||||||
|
} |
@ -0,0 +1,151 @@ |
|||||||
|
import { expect } from 'chai'; |
||||||
|
import { Wallet } from 'ethers'; |
||||||
|
|
||||||
|
import { ChainAddresses } from '@hyperlane-xyz/registry'; |
||||||
|
import { |
||||||
|
TokenRouterConfig, |
||||||
|
TokenType, |
||||||
|
WarpRouteDeployConfig, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; |
||||||
|
|
||||||
|
import { |
||||||
|
ANVIL_KEY, |
||||||
|
REGISTRY_PATH, |
||||||
|
deployOrUseExistingCore, |
||||||
|
extendWarpConfig, |
||||||
|
getChainId, |
||||||
|
updateOwner, |
||||||
|
} from './commands/helpers.js'; |
||||||
|
import { hyperlaneWarpDeploy, readWarpConfig } from './commands/warp.js'; |
||||||
|
|
||||||
|
/// To run: 1) start 2 anvils, 2) yarn run tsx tests/warp.zs-test.ts inside of cli/
|
||||||
|
const CHAIN_NAME_2 = 'anvil2'; |
||||||
|
const CHAIN_NAME_3 = 'anvil3'; |
||||||
|
|
||||||
|
const BURN_ADDRESS = '0x0000000000000000000000000000000000000001'; |
||||||
|
const EXAMPLES_PATH = './examples'; |
||||||
|
const CORE_CONFIG_PATH = `${EXAMPLES_PATH}/core-config.yaml`; |
||||||
|
const WARP_CONFIG_PATH_EXAMPLE = `${EXAMPLES_PATH}/warp-route-deployment.yaml`; |
||||||
|
|
||||||
|
const TEMP_PATH = '/tmp'; // /temp gets removed at the end of all-test.sh
|
||||||
|
const WARP_CONFIG_PATH_2 = `${TEMP_PATH}/anvil2/warp-route-deployment-anvil2.yaml`; |
||||||
|
const WARP_CORE_CONFIG_PATH_2 = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-config.yaml`; |
||||||
|
|
||||||
|
describe('WarpApply e2e tests', async function () { |
||||||
|
let chain2Addresses: ChainAddresses = {}; |
||||||
|
this.timeout(0); // No limit timeout since these tests can take a while
|
||||||
|
before(async function () { |
||||||
|
await deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY); |
||||||
|
chain2Addresses = await deployOrUseExistingCore( |
||||||
|
CHAIN_NAME_3, |
||||||
|
CORE_CONFIG_PATH, |
||||||
|
ANVIL_KEY, |
||||||
|
); |
||||||
|
|
||||||
|
// Create a new warp config using the example
|
||||||
|
const warpConfig: WarpRouteDeployConfig = readYamlOrJson( |
||||||
|
WARP_CONFIG_PATH_EXAMPLE, |
||||||
|
); |
||||||
|
const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; |
||||||
|
writeYamlOrJson(WARP_CONFIG_PATH_2, anvil2Config); |
||||||
|
}); |
||||||
|
|
||||||
|
after(async function () { |
||||||
|
this.timeout(2500); |
||||||
|
}); |
||||||
|
|
||||||
|
beforeEach(async function () { |
||||||
|
await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should burn owner address', async function () { |
||||||
|
const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; |
||||||
|
await updateOwner( |
||||||
|
BURN_ADDRESS, |
||||||
|
CHAIN_NAME_2, |
||||||
|
warpConfigPath, |
||||||
|
WARP_CORE_CONFIG_PATH_2, |
||||||
|
); |
||||||
|
const updatedWarpDeployConfig = await readWarpConfig( |
||||||
|
CHAIN_NAME_2, |
||||||
|
WARP_CORE_CONFIG_PATH_2, |
||||||
|
warpConfigPath, |
||||||
|
); |
||||||
|
expect(updatedWarpDeployConfig.anvil2.owner).to.equal(BURN_ADDRESS); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should not update the same owner', async () => { |
||||||
|
const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; |
||||||
|
await updateOwner( |
||||||
|
BURN_ADDRESS, |
||||||
|
CHAIN_NAME_2, |
||||||
|
warpConfigPath, |
||||||
|
WARP_CORE_CONFIG_PATH_2, |
||||||
|
); |
||||||
|
const { stdout } = await updateOwner( |
||||||
|
BURN_ADDRESS, |
||||||
|
CHAIN_NAME_2, |
||||||
|
warpConfigPath, |
||||||
|
WARP_CORE_CONFIG_PATH_2, |
||||||
|
); |
||||||
|
|
||||||
|
expect(stdout).to.include( |
||||||
|
'Warp config on anvil2 is the same as target. No updates needed.', |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should extend an existing warp route', async () => { |
||||||
|
// Read existing config into a file
|
||||||
|
const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; |
||||||
|
await readWarpConfig(CHAIN_NAME_2, WARP_CORE_CONFIG_PATH_2, warpConfigPath); |
||||||
|
|
||||||
|
// Extend with new config
|
||||||
|
const config: TokenRouterConfig = { |
||||||
|
decimals: 18, |
||||||
|
mailbox: chain2Addresses!.mailbox, |
||||||
|
name: 'Ether', |
||||||
|
owner: new Wallet(ANVIL_KEY).address, |
||||||
|
symbol: 'ETH', |
||||||
|
totalSupply: 0, |
||||||
|
type: TokenType.native, |
||||||
|
}; |
||||||
|
|
||||||
|
await extendWarpConfig( |
||||||
|
CHAIN_NAME_2, |
||||||
|
CHAIN_NAME_3, |
||||||
|
config, |
||||||
|
WARP_CORE_CONFIG_PATH_2, |
||||||
|
warpConfigPath, |
||||||
|
); |
||||||
|
|
||||||
|
const COMBINED_WARP_CORE_CONFIG_PATH = `${REGISTRY_PATH}/deployments/warp_routes/ETH/anvil2-anvil3-config.yaml`; |
||||||
|
|
||||||
|
// Check that chain2 is enrolled in chain1
|
||||||
|
const updatedWarpDeployConfig1 = await readWarpConfig( |
||||||
|
CHAIN_NAME_2, |
||||||
|
COMBINED_WARP_CORE_CONFIG_PATH, |
||||||
|
warpConfigPath, |
||||||
|
); |
||||||
|
|
||||||
|
const chain2Id = await getChainId(CHAIN_NAME_3, ANVIL_KEY); |
||||||
|
const remoteRouterKeys1 = Object.keys( |
||||||
|
updatedWarpDeployConfig1[CHAIN_NAME_2].remoteRouters!, |
||||||
|
); |
||||||
|
expect(remoteRouterKeys1).to.include(chain2Id); |
||||||
|
|
||||||
|
// Check that chain1 is enrolled in chain2
|
||||||
|
const updatedWarpDeployConfig2 = await readWarpConfig( |
||||||
|
CHAIN_NAME_3, |
||||||
|
COMBINED_WARP_CORE_CONFIG_PATH, |
||||||
|
warpConfigPath, |
||||||
|
); |
||||||
|
|
||||||
|
const chain1Id = await getChainId(CHAIN_NAME_2, ANVIL_KEY); |
||||||
|
const remoteRouterKeys2 = Object.keys( |
||||||
|
updatedWarpDeployConfig2[CHAIN_NAME_3].remoteRouters!, |
||||||
|
); |
||||||
|
expect(remoteRouterKeys2).to.include(chain1Id); |
||||||
|
}); |
||||||
|
}); |
@ -0,0 +1,22 @@ |
|||||||
|
# Configs for describing chain metadata for use in Hyperlane deployments or apps |
||||||
|
# Consists of a map of chain names to metadata |
||||||
|
# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts |
||||||
|
--- |
||||||
|
chainId: 31347 |
||||||
|
domainId: 31347 |
||||||
|
name: anvil3 |
||||||
|
protocol: ethereum |
||||||
|
rpcUrls: |
||||||
|
- http: http://127.0.0.1:8600 |
||||||
|
blockExplorers: # Array: List of BlockExplorer configs |
||||||
|
# Required fields: |
||||||
|
- name: My Chain Explorer # String: Human-readable name for the explorer |
||||||
|
url: https://mychain.com/explorer # String: Base URL for the explorer |
||||||
|
apiUrl: https://mychain.com/api # String: Base URL for the explorer API |
||||||
|
# Optional fields: |
||||||
|
apiKey: myapikey # String: API key for the explorer (optional) |
||||||
|
family: etherscan # ExplorerFamily: See ExplorerFamily for valid values |
||||||
|
nativeToken: |
||||||
|
name: Ether |
||||||
|
symbol: ETH |
||||||
|
decimals: 18 |
Loading…
Reference in new issue