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 |
||||
|
@ -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, |
||||
SubmissionStrategySchema, |
||||
} from '@hyperlane-xyz/sdk'; |
||||
import type { MultiProvider, SubmissionStrategy } from '@hyperlane-xyz/sdk'; |
||||
|
||||
export type SubmitterBuilderSettings = { |
||||
submissionStrategy: z.infer<typeof SubmissionStrategySchema>; |
||||
submissionStrategy: SubmissionStrategy; |
||||
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