Refactor IGP related code out of HyperlaneCore (#1907)
### Description A number of changes intended to make the SDK more easily consumed by hyperlane-deploy - Splits the IGP related code out of HyperlaneCore* into HyperlaneIgp* - Moves agent config building from infra to SDK so that it can be accessed by hyperlane-deploy - Modifies CoreConfig to specify a ChainMap<MultisigIsmConfig> (i.e. specify everything that you need to know to deploy to a chain in that chain's CoreConfig) - Removes core.extendWithConnectionClientConfig - Moves common logic into HyperlaneAppGovernor to be shared between HyperlaneCoreGovernor and HyperlaneIgpGovernor - Adds TestRecipient contract addresses to the SDK addresses for consumption by hyperlane-deploy - Allow buildContracts to build when address keys are a superset of factory keys (via filterContracts). This is useful as it allows us to just throw all the addresses at a HyperlaneApp constructor without needing to remove the ones that aren't relevant - Removes InterchainGasCalculator, the logic we want to keep now lives in HyperlaneIgp - Allows chains to be added to a MultiProvider ### Drive-by changes - Merges the infra govern script into the check script (to dedupe) - Some minor renaming where "Core" was being used liberally - Default to using whatever is configured in `owners.ts` in the router configs for infra (for ICA, IQS, HelloWorld, and LL deployment) ### Related issues - Fixes #[issue number here] ### Backward compatibility _Are these changes backward compatible?_ No, existing consumers of the SDK may need to adjust. _Are there any infrastructure implications, e.g. changes that would prohibit deploying older commits using this infra tooling?_ None ### Testing _What kind of testing have these changes undergone?_ Manual / Unit Testsyorhodes-patch-1
parent
e8d90775f7
commit
97d17570cc
@ -1 +1 @@ |
|||||||
Subproject commit 99bcdfca24e8c9a5f2d3cca8eb6da891ff3c715e |
Subproject commit 3b443c79bcb7671ef3d49ba7d6765c9adb139789 |
@ -1,177 +1,20 @@ |
|||||||
import { |
import { |
||||||
ChainMap, |
ChainMap, |
||||||
CoreConfig, |
CoreConfig, |
||||||
GasOracleContractType, |
defaultMultisigIsmConfigs, |
||||||
|
objMap, |
||||||
} from '@hyperlane-xyz/sdk'; |
} from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
import { MainnetChains, chainNames } from './chains'; |
import { chainNames } from './chains'; |
||||||
|
import { owners } from './owners'; |
||||||
|
|
||||||
function getGasOracles(local: MainnetChains) { |
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => { |
||||||
return Object.fromEntries( |
return { |
||||||
chainNames |
owner, |
||||||
.filter((name) => name !== local) |
multisigIsm: Object.fromEntries( |
||||||
.map((name) => [name, GasOracleContractType.StorageGasOracle]), |
Object.entries(defaultMultisigIsmConfigs).filter( |
||||||
); |
([chain]) => chain !== local && chainNames.includes(chain), |
||||||
} |
), |
||||||
|
), |
||||||
const KEY_FUNDER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; |
}; |
||||||
|
}); |
||||||
export const core: ChainMap<CoreConfig> = { |
|
||||||
celo: { |
|
||||||
owner: '0x1DE69322B55AC7E0999F8e7738a1428C8b130E4d', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0x1f20274b1210046769d48174c2f0e7c25ca7d5c5', // abacus works 0
|
|
||||||
'0x3bc014bafa43f93d534aed34f750997cdffcf007', // dsrv
|
|
||||||
'0xd79d506d741fa735938f7b7847a926e34a6fe6b0', // everstake
|
|
||||||
'0xe4a258bc61e65914c2a477b2a8a433ab4ebdf44b', // zplabs
|
|
||||||
'0x6aea63b0be4679c1385c26a92a3ff8aa6a8379f2', // staked
|
|
||||||
'0xc0085e1a49bcc69e534272adb82c74c0e007e1ca', // zkv
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('celo'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
ethereum: { |
|
||||||
owner: '0x12C5AB61Fe17dF9c65739DBa73dF294708f78d23', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0x4c327ccb881a7542be77500b2833dc84c839e7b7', // abacus works 0
|
|
||||||
'0x84cb373148ef9112b277e68acf676fefa9a9a9a0', // dsrv
|
|
||||||
'0x0d860c2b28bec3af4fd3a5997283e460ff6f2789', // everstake
|
|
||||||
'0xd4c1211f0eefb97a846c4e6d6589832e52fc03db', // zp labs
|
|
||||||
'0x600c90404d5c9df885404d2cc5350c9b314ea3a2', // staked
|
|
||||||
'0x892DC66F5B2f8C438E03f6323394e34A9C24F2D6', // zkv
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('ethereum'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
avalanche: { |
|
||||||
owner: '0xDF9B28B76877f1b1B4B8a11526Eb7D8D7C49f4f3', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0xa7aa52623fe3d78c343008c95894be669e218b8d', // abacus works 0
|
|
||||||
'0xb6004433fb04f643e2d48ae765c0e7f890f0bc0c', // dsrv
|
|
||||||
'0xa07e213e0985b21a6128e6c22ab5fb73948b0cc2', // everstake
|
|
||||||
'0x73853ed9a5f6f2e4c521970a94d43469e3cdaea6', // zplabs
|
|
||||||
'0xbd2e136cda02ba627ca882e49b184cbe976081c8', // staked
|
|
||||||
'0x1418126f944a44dad9edbab32294a8c890e7a9e3', // zkv
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('avalanche'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
polygon: { |
|
||||||
owner: '0x0D195469f76146F6ae3De8fc887e0f0DFBA691e7', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0x59a001c3451e7f9f3b4759ea215382c1e9aa5fc1', // abacus works 0
|
|
||||||
'0x009fb042d28944017177920c1d40da02bfebf474', // dsrv
|
|
||||||
'0xba4b13e23705a5919c1901150d9697e8ffb3ea71', // everstake
|
|
||||||
'0x2faa4071b718972f9b4beec1d8cbaa4eb6cca6c6', // zp labs
|
|
||||||
'0x5ae9b0f833dfe09ef455562a1f603f1634504dd6', // staked
|
|
||||||
'0x6a163d312f7352a95c9b81dca15078d5bf77a442', // zkv
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('polygon'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
bsc: { |
|
||||||
owner: '0xA0d3dcB9d61Fba32cc02Ad63983e101b29E2f28a', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0xcc84b1eb711e5076b2755cf4ad1d2b42c458a45e', // abacus works 0
|
|
||||||
'0xefe34eae2bca1846b895d2d0762ec21796aa196a', // dsrv
|
|
||||||
'0x662674e80e189b0861d6835c287693f50ee0c2ff', // everstake
|
|
||||||
'0x8a0f59075af466841808c529624807656309c9da', // zp labs
|
|
||||||
'0xdd2ff046ccd748a456b4757a73d47f165469669f', // staked
|
|
||||||
'0x034c4924c30ec4aa1b7f3ad58548988f0971e1bf', // zkv
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('bsc'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
arbitrum: { |
|
||||||
owner: '0xbA47E1b575980B7D1b1508cc48bE1Df4EE508111', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0xbcb815f38d481a5eba4d7ac4c9e74d9d0fc2a7e7', // abacus works 0
|
|
||||||
'0xd839424e2e5ace0a81152298dc2b1e3bb3c7fb20', // dsrv
|
|
||||||
'0xb8085c954b75b7088bcce69e61d12fcef797cd8d', // everstake
|
|
||||||
'0x9856dcb10fd6e5407fa74b5ab1d3b96cc193e9b7', // zp labs
|
|
||||||
'0x505dff4e0827aa5065f5e001db888e0569d46490', // staked
|
|
||||||
'0x25c6779d4610f940bf2488732e10bcffb9d36f81', // ZKV
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('arbitrum'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
optimism: { |
|
||||||
owner: '0xb523CFAf45AACF472859f8B793CB0BFDB16bD257', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 4, |
|
||||||
validators: [ |
|
||||||
'0x9f2296d5cfc6b5176adc7716c7596898ded13d35', // abacus works 0
|
|
||||||
'0x9c10bbe8efa03a8f49dfdb5c549258e3a8dca097', // dsrv
|
|
||||||
'0x62144d4a52a0a0335ea5bb84392ef9912461d9dd', // everstake
|
|
||||||
'0xaff4718d5d637466ad07441ee3b7c4af8e328dbd', // zp labs
|
|
||||||
'0xc64d1efeab8ae222bc889fe669f75d21b23005d9', // staked
|
|
||||||
'0xfa174eb2b4921bb652bc1ada3e8b00e7e280bf3c', // ZKV
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('optimism'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
moonbeam: { |
|
||||||
owner: '0xF0cb1f968Df01fc789762fddBfA704AE0F952197', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 3, |
|
||||||
validators: [ |
|
||||||
'0x237243d32d10e3bdbbf8dbcccc98ad44c1c172ea', // abacus works 0
|
|
||||||
'0x9509c8cf0a06955f27342262af501b74874e98fb', // dsrv
|
|
||||||
'0xb7113c999e4d587b162dd1a28c73f3f51c6bdcdc', // everstake
|
|
||||||
'0x26725501597d47352a23cd26f122709f69ad53bc', // staked
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('moonbeam'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
gnosis: { |
|
||||||
owner: '0x36b0AA0e7d04e7b825D7E409FEa3c9A3d57E4C22', |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0xd0529ec8df08d0d63c0f023786bfa81e4bb51fd6', // abacus works 0
|
|
||||||
'0x829d6ec129bc7187fb1ed161adcf7939fe0c515f', // abacus works 1
|
|
||||||
'0x00009f8935e94bfe52ab3441df3526ab7cc38db1', // abacus works 2
|
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: KEY_FUNDER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('gnosis'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
}; |
|
||||||
|
@ -0,0 +1,44 @@ |
|||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
GasOracleContractType, |
||||||
|
OverheadIgpConfig, |
||||||
|
defaultMultisigIsmConfigs, |
||||||
|
multisigIsmVerificationCost, |
||||||
|
objMap, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { utils } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { MainnetChains, chainNames } from './chains'; |
||||||
|
import { owners } from './owners'; |
||||||
|
|
||||||
|
const KEY_FUNDER_ADDRESS = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; |
||||||
|
|
||||||
|
function getGasOracles(local: MainnetChains) { |
||||||
|
return Object.fromEntries( |
||||||
|
utils |
||||||
|
.exclude(local, chainNames) |
||||||
|
.map((name) => [name, GasOracleContractType.StorageGasOracle]), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export const igp: ChainMap<OverheadIgpConfig> = objMap( |
||||||
|
owners, |
||||||
|
(chain, owner) => { |
||||||
|
return { |
||||||
|
owner, |
||||||
|
beneficiary: KEY_FUNDER_ADDRESS, |
||||||
|
gasOracleType: getGasOracles(chain), |
||||||
|
overhead: Object.fromEntries( |
||||||
|
utils |
||||||
|
.exclude(chain, chainNames) |
||||||
|
.map((remote) => [ |
||||||
|
remote, |
||||||
|
multisigIsmVerificationCost( |
||||||
|
defaultMultisigIsmConfigs[remote].threshold, |
||||||
|
defaultMultisigIsmConfigs[remote].validators.length, |
||||||
|
), |
||||||
|
]), |
||||||
|
), |
||||||
|
}; |
||||||
|
}, |
||||||
|
); |
@ -0,0 +1,13 @@ |
|||||||
|
import { ChainMap } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
export const owners: ChainMap<string> = { |
||||||
|
celo: '0x1DE69322B55AC7E0999F8e7738a1428C8b130E4d', |
||||||
|
ethereum: '0x12C5AB61Fe17dF9c65739DBa73dF294708f78d23', |
||||||
|
avalanche: '0xDF9B28B76877f1b1B4B8a11526Eb7D8D7C49f4f3', |
||||||
|
polygon: '0x0D195469f76146F6ae3De8fc887e0f0DFBA691e7', |
||||||
|
bsc: '0xA0d3dcB9d61Fba32cc02Ad63983e101b29E2f28a', |
||||||
|
arbitrum: '0xbA47E1b575980B7D1b1508cc48bE1Df4EE508111', |
||||||
|
optimism: '0xb523CFAf45AACF472859f8B793CB0BFDB16bD257', |
||||||
|
moonbeam: '0xF0cb1f968Df01fc789762fddBfA704AE0F952197', |
||||||
|
gnosis: '0x36b0AA0e7d04e7b825D7E409FEa3c9A3d57E4C22', |
||||||
|
}; |
@ -1,55 +1,13 @@ |
|||||||
import { |
import { ChainMap, CoreConfig, objMap } from '@hyperlane-xyz/sdk'; |
||||||
ChainMap, |
|
||||||
CoreConfig, |
import { multisigIsm } from './multisigIsm'; |
||||||
GasOracleContractType, |
import { owners } from './owners'; |
||||||
} from '@hyperlane-xyz/sdk'; |
|
||||||
|
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => { |
||||||
import { TestChains, chainNames } from './chains'; |
return { |
||||||
|
owner, |
||||||
function getGasOracles(local: TestChains) { |
multisigIsm: Object.fromEntries( |
||||||
return Object.fromEntries( |
Object.entries(multisigIsm).filter(([chain]) => chain !== local), |
||||||
chainNames |
), |
||||||
.filter((name) => name !== local) |
}; |
||||||
.map((name) => [name, GasOracleContractType.StorageGasOracle]), |
}); |
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'; |
|
||||||
|
|
||||||
export const core: ChainMap<CoreConfig> = { |
|
||||||
// Owner is hardhat account 0
|
|
||||||
// Validators are hardhat accounts 1-3
|
|
||||||
test1: { |
|
||||||
owner: OWNER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
validators: ['0x70997970c51812dc3a010c7d01b50e0d17dc79c8'], |
|
||||||
threshold: 1, |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: OWNER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('test1'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
test2: { |
|
||||||
owner: OWNER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
validators: ['0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc'], |
|
||||||
threshold: 1, |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: OWNER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('test2'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
test3: { |
|
||||||
owner: OWNER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
validators: ['0x90f79bf6eb2c4f870365e785982e1f101e93b906'], |
|
||||||
threshold: 1, |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: OWNER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('test3'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
}; |
|
||||||
|
@ -0,0 +1,42 @@ |
|||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
GasOracleContractType, |
||||||
|
OverheadIgpConfig, |
||||||
|
multisigIsmVerificationCost, |
||||||
|
objMap, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { utils } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { TestChains, chainNames } from './chains'; |
||||||
|
import { multisigIsm } from './multisigIsm'; |
||||||
|
import { owners } from './owners'; |
||||||
|
|
||||||
|
function getGasOracles(local: TestChains) { |
||||||
|
return Object.fromEntries( |
||||||
|
utils |
||||||
|
.exclude(local, chainNames) |
||||||
|
.map((name) => [name, GasOracleContractType.StorageGasOracle]), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export const igp: ChainMap<OverheadIgpConfig> = objMap( |
||||||
|
owners, |
||||||
|
(chain, owner) => { |
||||||
|
return { |
||||||
|
owner, |
||||||
|
beneficiary: owner, |
||||||
|
gasOracleType: getGasOracles(chain), |
||||||
|
overhead: Object.fromEntries( |
||||||
|
utils |
||||||
|
.exclude(chain, chainNames) |
||||||
|
.map((remote) => [ |
||||||
|
remote, |
||||||
|
multisigIsmVerificationCost( |
||||||
|
multisigIsm[remote].threshold, |
||||||
|
multisigIsm[remote].validators.length, |
||||||
|
), |
||||||
|
]), |
||||||
|
), |
||||||
|
}; |
||||||
|
}, |
||||||
|
); |
@ -0,0 +1,17 @@ |
|||||||
|
import { ChainMap, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
export const multisigIsm: ChainMap<MultisigIsmConfig> = { |
||||||
|
// Validators are hardhat accounts 1-3
|
||||||
|
test1: { |
||||||
|
validators: ['0x70997970c51812dc3a010c7d01b50e0d17dc79c8'], |
||||||
|
threshold: 1, |
||||||
|
}, |
||||||
|
test2: { |
||||||
|
validators: ['0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc'], |
||||||
|
threshold: 1, |
||||||
|
}, |
||||||
|
test3: { |
||||||
|
validators: ['0x90f79bf6eb2c4f870365e785982e1f101e93b906'], |
||||||
|
threshold: 1, |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,10 @@ |
|||||||
|
import { ChainMap } from '@hyperlane-xyz/sdk'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { chainNames } from './chains'; |
||||||
|
|
||||||
|
// Owner is hardhat account 0
|
||||||
|
const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'; |
||||||
|
export const owners: ChainMap<types.Address> = Object.fromEntries( |
||||||
|
chainNames.map((chain) => [chain, OWNER_ADDRESS]), |
||||||
|
); |
@ -1,155 +1,20 @@ |
|||||||
import { |
import { |
||||||
ChainMap, |
ChainMap, |
||||||
CoreConfig, |
CoreConfig, |
||||||
GasOracleContractType, |
defaultMultisigIsmConfigs, |
||||||
|
objMap, |
||||||
} from '@hyperlane-xyz/sdk'; |
} from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
import { TestnetChains, chainNames } from './chains'; |
import { chainNames } from './chains'; |
||||||
|
import { owners } from './owners'; |
||||||
|
|
||||||
function getGasOracles(local: TestnetChains) { |
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => { |
||||||
return Object.fromEntries( |
return { |
||||||
chainNames |
owner, |
||||||
.filter((name) => name !== local) |
multisigIsm: Object.fromEntries( |
||||||
.map((name) => [name, GasOracleContractType.StorageGasOracle]), |
Object.entries(defaultMultisigIsmConfigs).filter( |
||||||
); |
([chain]) => chain !== local && chainNames.includes(chain), |
||||||
} |
), |
||||||
|
), |
||||||
const DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'; |
}; |
||||||
|
}); |
||||||
export const core: ChainMap<CoreConfig> = { |
|
||||||
alfajores: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0xe6072396568e73ce6803b12b7e04164e839f1e54', |
|
||||||
'0x9f177f51289b22515f41f95872e1511391b8e105', |
|
||||||
'0x15f77400845eb1c971ad08de050861d5508cad6c', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('alfajores'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
fuji: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0x9fa19ead5ec76e437948b35e227511b106293c40', |
|
||||||
'0x227e7d6507762ece0c94678f8c103eff9d682476', |
|
||||||
'0x2379e43740e4aa4fde48cf4f00a3106df1d8420d', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('fuji'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
mumbai: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0x0a664ea799447da6b15645cf8b9e82072a68343f', |
|
||||||
'0x6ae6f12929a960aba24ba74ea310e3d37d0ac045', |
|
||||||
'0x51f70c047cd73bc7873273707501568857a619c4', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('mumbai'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
bsctestnet: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0x23338c8714976dd4a57eaeff17cbd26d7e275c08', |
|
||||||
'0x85a618d7450ebc37e0d682371f08dac94eec7a76', |
|
||||||
'0x95b76562e4ba1791a27ba4236801271c9115b141', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('bsctestnet'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
goerli: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0xf43fbd072fd38e1121d4b3b0b8a35116bbb01ea9', |
|
||||||
'0xa33020552a21f35e75bd385c6ab95c3dfa82d930', |
|
||||||
'0x0bba4043ff242f8bf3f39bafa8930a84d644d947', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('goerli'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
sepolia: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0xbc748ee311f5f2d1975d61cdf531755ce8ce3066', |
|
||||||
'0xc4233b2bfe5aec08964a94b403052abb3eafcf07', |
|
||||||
'0x6b36286c19f5c10bdc139ea9ee7f82287303f61d', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('sepolia'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
moonbasealpha: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0x890c2aeac157c3f067f3e42b8afc797939c59a32', |
|
||||||
'0x1b06d6fe69b972ed7420c83599d5a5c0fc185904', |
|
||||||
'0xe70b85206a968a99a597581f0fa09c99e7681093', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('moonbasealpha'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
optimismgoerli: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0xbb8d77eefbecc55db6e5a19b0fc3dc290776f189', |
|
||||||
'0x69792508b4ddaa3ca52241ccfcd1e0b119a1ee65', |
|
||||||
'0x11ddb46c6b653e0cdd7ad5bee32ae316e18f8453', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('optimismgoerli'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
arbitrumgoerli: { |
|
||||||
owner: DEPLOYER_ADDRESS, |
|
||||||
multisigIsm: { |
|
||||||
threshold: 2, |
|
||||||
validators: [ |
|
||||||
'0xce798fa21e323f6b24d9838a10ffecdefdfc4f30', |
|
||||||
'0xa792d39dca4426927e0f00c1618d61c9cb41779d', |
|
||||||
'0xdf181fcc11dfac5d01467e4547101a856dd5aa04', |
|
||||||
], |
|
||||||
}, |
|
||||||
igp: { |
|
||||||
beneficiary: DEPLOYER_ADDRESS, |
|
||||||
gasOracles: getGasOracles('arbitrumgoerli'), |
|
||||||
}, |
|
||||||
}, |
|
||||||
}; |
|
||||||
|
@ -0,0 +1,42 @@ |
|||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
GasOracleContractType, |
||||||
|
OverheadIgpConfig, |
||||||
|
defaultMultisigIsmConfigs, |
||||||
|
multisigIsmVerificationCost, |
||||||
|
objMap, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { utils } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { TestnetChains, chainNames } from './chains'; |
||||||
|
import { owners } from './owners'; |
||||||
|
|
||||||
|
function getGasOracles(local: TestnetChains) { |
||||||
|
return Object.fromEntries( |
||||||
|
utils |
||||||
|
.exclude(local, chainNames) |
||||||
|
.map((name) => [name, GasOracleContractType.StorageGasOracle]), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export const igp: ChainMap<OverheadIgpConfig> = objMap( |
||||||
|
owners, |
||||||
|
(chain, owner) => { |
||||||
|
return { |
||||||
|
owner, |
||||||
|
beneficiary: owner, |
||||||
|
gasOracleType: getGasOracles(chain), |
||||||
|
overhead: Object.fromEntries( |
||||||
|
utils |
||||||
|
.exclude(chain, chainNames) |
||||||
|
.map((remote) => [ |
||||||
|
remote, |
||||||
|
multisigIsmVerificationCost( |
||||||
|
defaultMultisigIsmConfigs[remote].threshold, |
||||||
|
defaultMultisigIsmConfigs[remote].validators.length, |
||||||
|
), |
||||||
|
]), |
||||||
|
), |
||||||
|
}; |
||||||
|
}, |
||||||
|
); |
@ -0,0 +1,10 @@ |
|||||||
|
import { ChainMap } from '@hyperlane-xyz/sdk'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { chainNames } from './chains'; |
||||||
|
|
||||||
|
const DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'; |
||||||
|
|
||||||
|
export const owners: ChainMap<types.Address> = Object.fromEntries( |
||||||
|
chainNames.map((chain) => [chain, DEPLOYER_ADDRESS]), |
||||||
|
); |
@ -1,96 +0,0 @@ |
|||||||
import { |
|
||||||
buildContracts, |
|
||||||
coreFactories, |
|
||||||
serializeContracts, |
|
||||||
} from '@hyperlane-xyz/sdk'; |
|
||||||
|
|
||||||
import { deployEnvToSdkEnv } from '../src/config/environment'; |
|
||||||
import { HyperlaneCoreInfraDeployer } from '../src/core/deploy'; |
|
||||||
import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; |
|
||||||
import { readJSON, writeJSON } from '../src/utils/utils'; |
|
||||||
|
|
||||||
import { |
|
||||||
assertEnvironment, |
|
||||||
getArgsWithFork, |
|
||||||
getCoreContractsSdkFilepath, |
|
||||||
getCoreEnvironmentConfig, |
|
||||||
getCoreRustDirectory, |
|
||||||
getCoreVerificationDirectory, |
|
||||||
} from './utils'; |
|
||||||
|
|
||||||
async function main() { |
|
||||||
const argv = await getArgsWithFork().argv; |
|
||||||
const environment = assertEnvironment(argv.environment); |
|
||||||
const config = getCoreEnvironmentConfig(environment); |
|
||||||
const multiProvider = await config.getMultiProvider(); |
|
||||||
|
|
||||||
if (argv.fork) { |
|
||||||
await useLocalProvider(multiProvider, argv.fork); |
|
||||||
|
|
||||||
// TODO: make this more generic
|
|
||||||
const deployerAddress = |
|
||||||
environment === 'testnet3' |
|
||||||
? '0xfaD1C94469700833717Fa8a3017278BC1cA8031C' |
|
||||||
: '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; |
|
||||||
|
|
||||||
const signer = await impersonateAccount(deployerAddress); |
|
||||||
multiProvider.setSigner(argv.fork, signer); |
|
||||||
} |
|
||||||
|
|
||||||
const deployer = new HyperlaneCoreInfraDeployer( |
|
||||||
multiProvider, |
|
||||||
config.core, |
|
||||||
environment, |
|
||||||
); |
|
||||||
|
|
||||||
if (environment !== 'test') { |
|
||||||
try { |
|
||||||
const addresses = readJSON( |
|
||||||
getCoreContractsSdkFilepath(), |
|
||||||
`${deployEnvToSdkEnv[environment]}.json`, |
|
||||||
); |
|
||||||
deployer.cacheContracts(buildContracts(addresses, coreFactories) as any); |
|
||||||
} catch (e) { |
|
||||||
console.info('Could not load partial core addresses, file may not exist'); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (argv.fork) { |
|
||||||
await deployer.deployContracts(argv.fork, config.core[argv.fork]); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
await deployer.deploy(); |
|
||||||
} catch (e) { |
|
||||||
console.error(`Encountered error during deploy`); |
|
||||||
console.error(e); |
|
||||||
} |
|
||||||
|
|
||||||
// Persist artifacts, irrespective of deploy success
|
|
||||||
writeJSON( |
|
||||||
getCoreContractsSdkFilepath(), |
|
||||||
`${deployEnvToSdkEnv[environment]}.json`, |
|
||||||
serializeContracts(deployer.deployedContracts), |
|
||||||
); |
|
||||||
const verificationDir = getCoreVerificationDirectory(environment); |
|
||||||
const verificationFile = 'verification.json'; |
|
||||||
let existingVerificationInputs = []; |
|
||||||
try { |
|
||||||
existingVerificationInputs = readJSON(verificationDir, verificationFile); |
|
||||||
} catch (err) { |
|
||||||
/* ignore error */ |
|
||||||
} |
|
||||||
|
|
||||||
writeJSON( |
|
||||||
getCoreVerificationDirectory(environment), |
|
||||||
'verification.json', |
|
||||||
deployer.mergeWithExistingVerificationInputs(existingVerificationInputs), |
|
||||||
); |
|
||||||
|
|
||||||
deployer.writeRustConfigs(getCoreRustDirectory()); |
|
||||||
} |
|
||||||
|
|
||||||
main() |
|
||||||
.then() |
|
||||||
.catch(() => process.exit(1)); |
|
@ -0,0 +1,138 @@ |
|||||||
|
import { |
||||||
|
HyperlaneDeployer, |
||||||
|
HyperlaneFactories, |
||||||
|
buildAgentConfig, |
||||||
|
buildContracts, |
||||||
|
coreFactories, |
||||||
|
serializeContracts, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { igpFactories } from '@hyperlane-xyz/sdk/dist/gas/contracts'; |
||||||
|
|
||||||
|
import { deployEnvToSdkEnv } from '../src/config/environment'; |
||||||
|
import { HyperlaneCoreInfraDeployer } from '../src/core/deploy'; |
||||||
|
import { HyperlaneIgpInfraDeployer } from '../src/gas/deploy'; |
||||||
|
import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; |
||||||
|
import { readJSON, writeJSON, writeMergedJSON } from '../src/utils/utils'; |
||||||
|
|
||||||
|
import { |
||||||
|
getAgentConfigDirectory, |
||||||
|
getArgsWithModuleAndFork, |
||||||
|
getContractAddressesSdkFilepath, |
||||||
|
getEnvironmentConfig, |
||||||
|
getVerificationDirectory, |
||||||
|
} from './utils'; |
||||||
|
|
||||||
|
async function main() { |
||||||
|
const { module, fork, environment } = await getArgsWithModuleAndFork().argv; |
||||||
|
const config = await getEnvironmentConfig(); |
||||||
|
const multiProvider = await config.getMultiProvider(); |
||||||
|
|
||||||
|
if (fork) { |
||||||
|
await useLocalProvider(multiProvider, fork); |
||||||
|
|
||||||
|
// TODO: make this more generic
|
||||||
|
const deployerAddress = |
||||||
|
environment === 'testnet3' |
||||||
|
? '0xfaD1C94469700833717Fa8a3017278BC1cA8031C' |
||||||
|
: '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; |
||||||
|
|
||||||
|
const signer = await impersonateAccount(deployerAddress); |
||||||
|
multiProvider.setSigner(fork, signer); |
||||||
|
} |
||||||
|
|
||||||
|
// Write agent config indexing from the latest block numbers.
|
||||||
|
// For non-net-new deployments, these changes will need to be
|
||||||
|
// reverted manually.
|
||||||
|
const chains = multiProvider.getKnownChainNames(); |
||||||
|
const startBlocks = Object.fromEntries( |
||||||
|
await Promise.all( |
||||||
|
chains.map(async (c) => [ |
||||||
|
c, |
||||||
|
await multiProvider.getProvider(c).getBlockNumber, |
||||||
|
]), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
let factories: HyperlaneFactories; |
||||||
|
let deployer: HyperlaneDeployer<any, any, any>; |
||||||
|
if (module === 'core') { |
||||||
|
factories = coreFactories; |
||||||
|
deployer = new HyperlaneCoreInfraDeployer( |
||||||
|
multiProvider, |
||||||
|
config.core, |
||||||
|
environment, |
||||||
|
); |
||||||
|
} else if (module === 'igp') { |
||||||
|
factories = igpFactories; |
||||||
|
deployer = new HyperlaneIgpInfraDeployer( |
||||||
|
multiProvider, |
||||||
|
config.igp, |
||||||
|
environment, |
||||||
|
); |
||||||
|
} else { |
||||||
|
throw new Error('Unknown module type'); |
||||||
|
} |
||||||
|
|
||||||
|
if (environment !== 'test') { |
||||||
|
try { |
||||||
|
const addresses = readJSON( |
||||||
|
getContractAddressesSdkFilepath(), |
||||||
|
`${deployEnvToSdkEnv[environment]}.json`, |
||||||
|
); |
||||||
|
deployer.cacheContracts(buildContracts(addresses, factories) as any); |
||||||
|
} catch (e) { |
||||||
|
console.info('Could not load partial addresses, file may not exist'); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (fork) { |
||||||
|
await deployer.deployContracts(fork, config.core[fork]); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
await deployer.deploy(); |
||||||
|
} catch (e) { |
||||||
|
console.error('Encountered error during deploy', e); |
||||||
|
} |
||||||
|
|
||||||
|
// Persist address artifacts, irrespective of deploy success
|
||||||
|
writeMergedJSON( |
||||||
|
getContractAddressesSdkFilepath(), |
||||||
|
`${deployEnvToSdkEnv[environment]}.json`, |
||||||
|
serializeContracts(deployer.deployedContracts), |
||||||
|
); |
||||||
|
const verificationDir = getVerificationDirectory(environment, module); |
||||||
|
const verificationFile = 'verification.json'; |
||||||
|
let existingVerificationInputs = []; |
||||||
|
try { |
||||||
|
existingVerificationInputs = readJSON(verificationDir, verificationFile); |
||||||
|
} catch (err) { |
||||||
|
/* ignore error */ |
||||||
|
} |
||||||
|
|
||||||
|
writeJSON( |
||||||
|
verificationDir, |
||||||
|
verificationFile, |
||||||
|
deployer.mergeWithExistingVerificationInputs(existingVerificationInputs), |
||||||
|
); |
||||||
|
|
||||||
|
const sdkEnv = deployEnvToSdkEnv[environment]; |
||||||
|
const addresses = readJSON( |
||||||
|
getContractAddressesSdkFilepath(), |
||||||
|
`${sdkEnv}.json`, |
||||||
|
); |
||||||
|
|
||||||
|
const agentConfig = await buildAgentConfig( |
||||||
|
multiProvider.getKnownChainNames(), |
||||||
|
multiProvider, |
||||||
|
addresses, |
||||||
|
startBlocks, |
||||||
|
); |
||||||
|
|
||||||
|
writeJSON(getAgentConfigDirectory(), `${sdkEnv}_config.json`, agentConfig); |
||||||
|
} |
||||||
|
|
||||||
|
main() |
||||||
|
.then() |
||||||
|
.catch(() => process.exit(1)); |
@ -1,50 +0,0 @@ |
|||||||
import { HyperlaneCore, HyperlaneCoreChecker } from '@hyperlane-xyz/sdk'; |
|
||||||
|
|
||||||
import { deployEnvToSdkEnv } from '../src/config/environment'; |
|
||||||
import { HyperlaneCoreGovernor } from '../src/core/govern'; |
|
||||||
import { impersonateAccount, useLocalProvider } from '../src/utils/fork'; |
|
||||||
|
|
||||||
import { |
|
||||||
assertEnvironment, |
|
||||||
getArgsWithFork, |
|
||||||
getCoreEnvironmentConfig, |
|
||||||
} from './utils'; |
|
||||||
|
|
||||||
async function check() { |
|
||||||
const argv = await getArgsWithFork().argv; |
|
||||||
const environment = assertEnvironment(argv.environment); |
|
||||||
const config = getCoreEnvironmentConfig(environment); |
|
||||||
const multiProvider = await config.getMultiProvider(); |
|
||||||
|
|
||||||
// must rotate to forked provider before building core contracts
|
|
||||||
if (argv.fork) { |
|
||||||
await useLocalProvider(multiProvider, argv.fork); |
|
||||||
const owner = config.core[argv.fork].owner; |
|
||||||
const signer = await impersonateAccount(owner); |
|
||||||
multiProvider.setSigner(argv.fork, signer); |
|
||||||
} |
|
||||||
|
|
||||||
const core = HyperlaneCore.fromEnvironment( |
|
||||||
deployEnvToSdkEnv[environment], |
|
||||||
multiProvider, |
|
||||||
); |
|
||||||
|
|
||||||
const coreChecker = new HyperlaneCoreChecker( |
|
||||||
multiProvider, |
|
||||||
core, |
|
||||||
config.core, |
|
||||||
); |
|
||||||
const governor = new HyperlaneCoreGovernor(coreChecker); |
|
||||||
|
|
||||||
if (argv.fork) { |
|
||||||
await coreChecker.checkChain(argv.fork); |
|
||||||
await governor.governChain(argv.fork, false); |
|
||||||
} else { |
|
||||||
await coreChecker.check(); |
|
||||||
await governor.govern(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
check() |
|
||||||
.then() |
|
||||||
.catch(() => process.exit(1)); |
|
@ -1,8 +1,10 @@ |
|||||||
import { ConnectionType, DockerConfig } from './agent'; |
import { AgentConnectionType } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
import { DockerConfig } from './agent'; |
||||||
|
|
||||||
export interface LiquidityLayerRelayerConfig { |
export interface LiquidityLayerRelayerConfig { |
||||||
docker: DockerConfig; |
docker: DockerConfig; |
||||||
namespace: string; |
namespace: string; |
||||||
connectionType: ConnectionType.Http | ConnectionType.HttpQuorum; |
connectionType: AgentConnectionType.Http | AgentConnectionType.HttpQuorum; |
||||||
prometheusPushGateway: string; |
prometheusPushGateway: string; |
||||||
} |
} |
||||||
|
@ -0,0 +1,71 @@ |
|||||||
|
import { ethers } from 'ethers'; |
||||||
|
|
||||||
|
import { |
||||||
|
InterchainGasPaymaster, |
||||||
|
OverheadIgp, |
||||||
|
ProxyAdmin, |
||||||
|
StorageGasOracle, |
||||||
|
} from '@hyperlane-xyz/core'; |
||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
ChainName, |
||||||
|
HyperlaneIgpDeployer, |
||||||
|
MultiProvider, |
||||||
|
OverheadIgpConfig, |
||||||
|
ProxiedContract, |
||||||
|
TransparentProxyAddresses, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { DeployEnvironment } from '../config'; |
||||||
|
|
||||||
|
export class HyperlaneIgpInfraDeployer extends HyperlaneIgpDeployer { |
||||||
|
environment: DeployEnvironment; |
||||||
|
|
||||||
|
constructor( |
||||||
|
multiProvider: MultiProvider, |
||||||
|
configMap: ChainMap<OverheadIgpConfig>, |
||||||
|
environment: DeployEnvironment, |
||||||
|
) { |
||||||
|
super(multiProvider, configMap); |
||||||
|
this.environment = environment; |
||||||
|
} |
||||||
|
|
||||||
|
async deployInterchainGasPaymaster( |
||||||
|
chain: ChainName, |
||||||
|
proxyAdmin: ProxyAdmin, |
||||||
|
storageGasOracle: StorageGasOracle, |
||||||
|
): Promise< |
||||||
|
ProxiedContract<InterchainGasPaymaster, TransparentProxyAddresses> |
||||||
|
> { |
||||||
|
const deployOpts = { |
||||||
|
create2Salt: ethers.utils.solidityKeccak256( |
||||||
|
['string', 'string', 'uint8'], |
||||||
|
[this.environment, 'interchainGasPaymaster', 6], |
||||||
|
), |
||||||
|
}; |
||||||
|
return super.deployInterchainGasPaymaster( |
||||||
|
chain, |
||||||
|
proxyAdmin, |
||||||
|
storageGasOracle, |
||||||
|
deployOpts, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async deployOverheadInterchainGasPaymaster( |
||||||
|
chain: ChainName, |
||||||
|
interchainGasPaymasterAddress: types.Address, |
||||||
|
): Promise<OverheadIgp> { |
||||||
|
const deployOpts = { |
||||||
|
create2Salt: ethers.utils.solidityKeccak256( |
||||||
|
['string', 'string', 'uint8'], |
||||||
|
[this.environment, 'defaultIsmInterchainGasPaymaster', 4], |
||||||
|
), |
||||||
|
}; |
||||||
|
return super.deployOverheadInterchainGasPaymaster( |
||||||
|
chain, |
||||||
|
interchainGasPaymasterAddress, |
||||||
|
deployOpts, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,126 @@ |
|||||||
|
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core'; |
||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
ChainName, |
||||||
|
HyperlaneIgp, |
||||||
|
HyperlaneIgpChecker, |
||||||
|
IgpBeneficiaryViolation, |
||||||
|
IgpGasOraclesViolation, |
||||||
|
IgpOverheadViolation, |
||||||
|
IgpViolation, |
||||||
|
IgpViolationType, |
||||||
|
OverheadIgpConfig, |
||||||
|
ProxyKind, |
||||||
|
ProxyViolation, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { HyperlaneAppGovernor } from '../govern/HyperlaneAppGovernor'; |
||||||
|
|
||||||
|
export class HyperlaneIgpGovernor extends HyperlaneAppGovernor< |
||||||
|
HyperlaneIgp, |
||||||
|
OverheadIgpConfig |
||||||
|
> { |
||||||
|
constructor(checker: HyperlaneIgpChecker, owners: ChainMap<types.Address>) { |
||||||
|
super(checker, owners); |
||||||
|
} |
||||||
|
|
||||||
|
protected async mapViolationsToCalls() { |
||||||
|
for (const violation of this.checker.violations) { |
||||||
|
switch (violation.type) { |
||||||
|
case 'InterchainGasPaymaster': { |
||||||
|
this.handleIgpViolation(violation as IgpViolation); |
||||||
|
break; |
||||||
|
} |
||||||
|
case ProxyKind.Transparent: { |
||||||
|
this.handleProxyViolation(violation as ProxyViolation); |
||||||
|
break; |
||||||
|
} |
||||||
|
default: |
||||||
|
throw new Error(`Unsupported violation type ${violation.type}`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
handleIgpViolation(violation: IgpViolation) { |
||||||
|
switch (violation.subType) { |
||||||
|
case IgpViolationType.Beneficiary: { |
||||||
|
const beneficiaryViolation = violation as IgpBeneficiaryViolation; |
||||||
|
this.pushCall(beneficiaryViolation.chain, { |
||||||
|
to: beneficiaryViolation.contract.address, |
||||||
|
data: beneficiaryViolation.contract.interface.encodeFunctionData( |
||||||
|
'setBeneficiary', |
||||||
|
[beneficiaryViolation.expected], |
||||||
|
), |
||||||
|
description: `Set IGP beneficiary to ${beneficiaryViolation.expected}`, |
||||||
|
}); |
||||||
|
break; |
||||||
|
} |
||||||
|
case IgpViolationType.GasOracles: { |
||||||
|
const gasOraclesViolation = violation as IgpGasOraclesViolation; |
||||||
|
|
||||||
|
const configs: InterchainGasPaymaster.GasOracleConfigStruct[] = []; |
||||||
|
for (const [remote, expected] of Object.entries( |
||||||
|
gasOraclesViolation.expected, |
||||||
|
)) { |
||||||
|
const remoteId = this.checker.multiProvider.getDomainId(remote); |
||||||
|
|
||||||
|
configs.push({ |
||||||
|
remoteDomain: remoteId, |
||||||
|
gasOracle: expected, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
this.pushCall(gasOraclesViolation.chain, { |
||||||
|
to: gasOraclesViolation.contract.address, |
||||||
|
data: gasOraclesViolation.contract.interface.encodeFunctionData( |
||||||
|
'setGasOracles', |
||||||
|
[configs], |
||||||
|
), |
||||||
|
description: `Setting ${Object.keys(gasOraclesViolation.expected) |
||||||
|
.map((remoteStr) => { |
||||||
|
const remote = remoteStr as ChainName; |
||||||
|
const remoteId = this.checker.multiProvider.getDomainId(remote); |
||||||
|
const expected = gasOraclesViolation.expected[remote]; |
||||||
|
return `gas oracle for ${remote} (domain ID ${remoteId}) to ${expected}`; |
||||||
|
}) |
||||||
|
.join(', ')}`,
|
||||||
|
}); |
||||||
|
break; |
||||||
|
} |
||||||
|
case IgpViolationType.Overhead: { |
||||||
|
const overheadViolation = violation as IgpOverheadViolation; |
||||||
|
const configs: OverheadIgp.DomainConfigStruct[] = Object.entries( |
||||||
|
violation.expected, |
||||||
|
).map( |
||||||
|
([remote, gasOverhead]) => |
||||||
|
({ |
||||||
|
domain: this.checker.multiProvider.getDomainId(remote), |
||||||
|
gasOverhead: gasOverhead, |
||||||
|
} as OverheadIgp.DomainConfigStruct), |
||||||
|
); |
||||||
|
|
||||||
|
this.pushCall(violation.chain, { |
||||||
|
to: overheadViolation.contract.address, |
||||||
|
data: overheadViolation.contract.interface.encodeFunctionData( |
||||||
|
'setDestinationGasOverheads', |
||||||
|
[configs], |
||||||
|
), |
||||||
|
description: `Setting ${Object.keys(violation.expected) |
||||||
|
.map((remoteStr) => { |
||||||
|
const remote = remoteStr as ChainName; |
||||||
|
const remoteId = this.checker.multiProvider.getDomainId(remote); |
||||||
|
const expected = violation.expected[remote]; |
||||||
|
return `destination gas overhead for ${remote} (domain ID ${remoteId}) to ${expected}`; |
||||||
|
}) |
||||||
|
.join(', ')}`,
|
||||||
|
}); |
||||||
|
break; |
||||||
|
} |
||||||
|
default: |
||||||
|
throw new Error( |
||||||
|
`Unsupported IgpViolation subType: ${violation.subType}`, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,219 @@ |
|||||||
|
import { prompts } from 'prompts'; |
||||||
|
|
||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
ChainName, |
||||||
|
CoreContracts, |
||||||
|
HyperlaneApp, |
||||||
|
HyperlaneAppChecker, |
||||||
|
OwnerViolation, |
||||||
|
ProxyViolation, |
||||||
|
objMap, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { canProposeSafeTransactions } from '../utils/safe'; |
||||||
|
|
||||||
|
import { |
||||||
|
ManualMultiSend, |
||||||
|
MultiSend, |
||||||
|
SafeMultiSend, |
||||||
|
SignerMultiSend, |
||||||
|
} from './multisend'; |
||||||
|
|
||||||
|
export enum SubmissionType { |
||||||
|
MANUAL = 'MANUAL', |
||||||
|
SIGNER = 'SIGNER', |
||||||
|
SAFE = 'SAFE', |
||||||
|
} |
||||||
|
|
||||||
|
export type AnnotatedCallData = types.CallData & { |
||||||
|
submissionType?: SubmissionType; |
||||||
|
description: string; |
||||||
|
}; |
||||||
|
|
||||||
|
export abstract class HyperlaneAppGovernor< |
||||||
|
App extends HyperlaneApp<any>, |
||||||
|
Config, |
||||||
|
> { |
||||||
|
readonly checker: HyperlaneAppChecker<App, Config>; |
||||||
|
private owners: ChainMap<types.Address>; |
||||||
|
private calls: ChainMap<AnnotatedCallData[]>; |
||||||
|
private canPropose: ChainMap<Map<string, boolean>>; |
||||||
|
|
||||||
|
constructor( |
||||||
|
checker: HyperlaneAppChecker<App, Config>, |
||||||
|
owners: ChainMap<types.Address>, |
||||||
|
) { |
||||||
|
this.checker = checker; |
||||||
|
this.owners = owners; |
||||||
|
this.calls = objMap(this.checker.app.contractsMap, () => []); |
||||||
|
this.canPropose = objMap(this.checker.app.contractsMap, () => new Map()); |
||||||
|
} |
||||||
|
|
||||||
|
async govern(confirm = true, chain?: ChainName) { |
||||||
|
// 1. Produce calls from checker violations.
|
||||||
|
await this.mapViolationsToCalls(); |
||||||
|
|
||||||
|
// 2. For each call, infer how it should be submitted on-chain.
|
||||||
|
await this.inferCallSubmissionTypes(); |
||||||
|
|
||||||
|
// 3. Prompt the user to confirm that the count, description,
|
||||||
|
// and submission methods look correct before submitting.
|
||||||
|
const chains = chain ? [chain] : Object.keys(this.calls); |
||||||
|
for (const chain of chains) { |
||||||
|
await this.sendCalls(chain, confirm); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected async sendCalls(chain: ChainName, confirm: boolean) { |
||||||
|
const calls = this.calls[chain]; |
||||||
|
console.log(`\nFound ${calls.length} transactions for ${chain}`); |
||||||
|
const filterCalls = (submissionType: SubmissionType) => |
||||||
|
calls.filter((call) => call.submissionType == submissionType); |
||||||
|
const summarizeCalls = async ( |
||||||
|
submissionType: SubmissionType, |
||||||
|
calls: AnnotatedCallData[], |
||||||
|
): Promise<boolean> => { |
||||||
|
if (calls.length > 0) { |
||||||
|
console.log( |
||||||
|
`> ${calls.length} calls will be submitted via ${submissionType}`, |
||||||
|
); |
||||||
|
calls.map((c) => |
||||||
|
console.log(`> > ${c.description} (to: ${c.to} data: ${c.data})`), |
||||||
|
); |
||||||
|
const response = |
||||||
|
!confirm || |
||||||
|
prompts.confirm({ |
||||||
|
type: 'confirm', |
||||||
|
name: 'value', |
||||||
|
message: 'Can you confirm?', |
||||||
|
initial: false, |
||||||
|
}); |
||||||
|
return !!response; |
||||||
|
} |
||||||
|
return false; |
||||||
|
}; |
||||||
|
|
||||||
|
const sendCallsForType = async ( |
||||||
|
submissionType: SubmissionType, |
||||||
|
multiSend: MultiSend, |
||||||
|
) => { |
||||||
|
const calls = filterCalls(submissionType); |
||||||
|
if (calls.length > 0) { |
||||||
|
const confirmed = await summarizeCalls(submissionType, calls); |
||||||
|
if (confirmed) { |
||||||
|
console.log(`Submitting calls on ${chain} via ${submissionType}`); |
||||||
|
await multiSend.sendTransactions( |
||||||
|
calls.map((call) => ({ to: call.to, data: call.data })), |
||||||
|
); |
||||||
|
} else { |
||||||
|
console.log( |
||||||
|
`Skipping submission of calls on ${chain} via ${submissionType}`, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
await sendCallsForType( |
||||||
|
SubmissionType.SIGNER, |
||||||
|
new SignerMultiSend(this.checker.multiProvider, chain), |
||||||
|
); |
||||||
|
await sendCallsForType( |
||||||
|
SubmissionType.SAFE, |
||||||
|
new SafeMultiSend(this.checker.multiProvider, chain, this.owners[chain]), |
||||||
|
); |
||||||
|
await sendCallsForType(SubmissionType.MANUAL, new ManualMultiSend(chain)); |
||||||
|
} |
||||||
|
|
||||||
|
protected pushCall(chain: ChainName, call: AnnotatedCallData) { |
||||||
|
this.calls[chain].push(call); |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract mapViolationsToCalls(): Promise<void>; |
||||||
|
|
||||||
|
handleProxyViolation(violation: ProxyViolation) { |
||||||
|
const contracts: CoreContracts = |
||||||
|
this.checker.app.contractsMap[violation.chain]; |
||||||
|
const data = contracts.proxyAdmin.interface.encodeFunctionData('upgrade', [ |
||||||
|
violation.data.proxyAddresses.proxy, |
||||||
|
violation.data.proxyAddresses.implementation, |
||||||
|
]); |
||||||
|
|
||||||
|
this.pushCall(violation.chain, { |
||||||
|
to: contracts.proxyAdmin.address, |
||||||
|
data, |
||||||
|
description: `Upgrade proxy ${violation.data.proxyAddresses.proxy} to implementation ${violation.data.proxyAddresses.implementation}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
protected async inferCallSubmissionTypes() { |
||||||
|
for (const chain of Object.keys(this.calls)) { |
||||||
|
for (const call of this.calls[chain]) { |
||||||
|
call.submissionType = await this.inferCallSubmissionType(chain, call); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected async inferCallSubmissionType( |
||||||
|
chain: ChainName, |
||||||
|
call: AnnotatedCallData, |
||||||
|
): Promise<SubmissionType> { |
||||||
|
const multiProvider = this.checker.multiProvider; |
||||||
|
const signer = multiProvider.getSigner(chain); |
||||||
|
const signerAddress = await signer.getAddress(); |
||||||
|
|
||||||
|
const canUseSubmissionType = async ( |
||||||
|
submitterAddress: types.Address, |
||||||
|
): Promise<boolean> => { |
||||||
|
try { |
||||||
|
await multiProvider.estimateGas(chain, call, submitterAddress); |
||||||
|
return true; |
||||||
|
} catch (e) {} // eslint-disable-line no-empty
|
||||||
|
return false; |
||||||
|
}; |
||||||
|
|
||||||
|
if (await canUseSubmissionType(signerAddress)) { |
||||||
|
return SubmissionType.SIGNER; |
||||||
|
} |
||||||
|
|
||||||
|
// 2. Check if the call will succeed via Gnosis Safe.
|
||||||
|
const safeAddress = this.owners[chain]; |
||||||
|
if (!safeAddress) throw new Error(`Owner address not found for ${chain}`); |
||||||
|
// 2a. Confirm that the signer is a Safe owner or delegate.
|
||||||
|
// This should implicitly check whether or not the owner is a gnosis
|
||||||
|
// safe.
|
||||||
|
if (!this.canPropose[chain].has(safeAddress)) { |
||||||
|
this.canPropose[chain].set( |
||||||
|
safeAddress, |
||||||
|
await canProposeSafeTransactions( |
||||||
|
signerAddress, |
||||||
|
chain, |
||||||
|
multiProvider, |
||||||
|
safeAddress, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// 2b. Check if calling from the owner/safeAddress will succeed.
|
||||||
|
if ( |
||||||
|
this.canPropose[chain].get(safeAddress) && |
||||||
|
(await canUseSubmissionType(safeAddress)) |
||||||
|
) { |
||||||
|
return SubmissionType.SAFE; |
||||||
|
} |
||||||
|
|
||||||
|
return SubmissionType.MANUAL; |
||||||
|
} |
||||||
|
|
||||||
|
handleOwnerViolation(violation: OwnerViolation) { |
||||||
|
this.pushCall(violation.chain, { |
||||||
|
to: violation.contract.address, |
||||||
|
data: violation.contract.interface.encodeFunctionData( |
||||||
|
'transferOwnership', |
||||||
|
[violation.expected], |
||||||
|
), |
||||||
|
description: `Transfer ownership of ${violation.contract.address} to ${violation.expected}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,97 @@ |
|||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { MultiProvider } from '../providers/MultiProvider'; |
||||||
|
import { getProxyAddress } from '../proxy'; |
||||||
|
import { ChainMap, ChainName } from '../types'; |
||||||
|
|
||||||
|
export type AgentSigner = { |
||||||
|
key: string; |
||||||
|
type: string; // TODO
|
||||||
|
}; |
||||||
|
|
||||||
|
export enum AgentConnectionType { |
||||||
|
Http = 'http', |
||||||
|
Ws = 'ws', |
||||||
|
HttpQuorum = 'httpQuorum', |
||||||
|
HttpFallback = 'httpFallback', |
||||||
|
} |
||||||
|
|
||||||
|
export type AgentConnection = |
||||||
|
| { |
||||||
|
type: AgentConnectionType.Http; |
||||||
|
url: string; |
||||||
|
} |
||||||
|
| { type: AgentConnectionType.Ws; url: string } |
||||||
|
| { type: AgentConnectionType.HttpQuorum; urls: string } |
||||||
|
| { type: AgentConnectionType.HttpFallback; urls: string }; |
||||||
|
|
||||||
|
export type HyperlaneAgentAddresses = { |
||||||
|
mailbox: types.Address; |
||||||
|
interchainGasPaymaster: types.Address; |
||||||
|
validatorAnnounce: types.Address; |
||||||
|
}; |
||||||
|
|
||||||
|
export type AgentChainSetupBase = { |
||||||
|
name: ChainName; |
||||||
|
domain: number; |
||||||
|
signer?: AgentSigner; |
||||||
|
finalityBlocks: number; |
||||||
|
addresses: HyperlaneAgentAddresses; |
||||||
|
protocol: 'ethereum' | 'fuel'; |
||||||
|
connection?: AgentConnection; |
||||||
|
index?: { from: number }; |
||||||
|
}; |
||||||
|
|
||||||
|
export interface AgentChainSetup extends AgentChainSetupBase { |
||||||
|
signer: AgentSigner; |
||||||
|
connection: AgentConnection; |
||||||
|
} |
||||||
|
|
||||||
|
export type AgentConfig = { |
||||||
|
chains: Partial<ChainMap<AgentChainSetupBase>>; |
||||||
|
tracing?: { |
||||||
|
level?: string; |
||||||
|
fmt?: 'json'; |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function buildAgentConfig( |
||||||
|
chains: ChainName[], |
||||||
|
multiProvider: MultiProvider, |
||||||
|
addresses: ChainMap<HyperlaneAgentAddresses>, |
||||||
|
startBlocks: ChainMap<number>, |
||||||
|
): AgentConfig { |
||||||
|
const agentConfig: AgentConfig = { |
||||||
|
chains: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
for (const chain of [...chains].sort()) { |
||||||
|
const metadata = multiProvider.getChainMetadata(chain); |
||||||
|
const chainConfig: AgentChainSetupBase = { |
||||||
|
name: chain, |
||||||
|
domain: metadata.chainId, |
||||||
|
addresses: { |
||||||
|
mailbox: getProxyAddress(addresses[chain].mailbox), |
||||||
|
interchainGasPaymaster: getProxyAddress( |
||||||
|
addresses[chain].interchainGasPaymaster, |
||||||
|
), |
||||||
|
validatorAnnounce: getProxyAddress(addresses[chain].validatorAnnounce), |
||||||
|
}, |
||||||
|
protocol: 'ethereum', |
||||||
|
finalityBlocks: metadata.blocks?.reorgPeriod ?? 1, |
||||||
|
connection: { |
||||||
|
// not a valid connection but we want to fill in the HTTP type for
|
||||||
|
// them as a default and leave out the URL
|
||||||
|
type: AgentConnectionType.Http, |
||||||
|
url: undefined, |
||||||
|
} as any as AgentConnection, |
||||||
|
}; |
||||||
|
|
||||||
|
chainConfig.index = { |
||||||
|
from: startBlocks[chain], |
||||||
|
}; |
||||||
|
|
||||||
|
agentConfig.chains[chain] = chainConfig; |
||||||
|
} |
||||||
|
return agentConfig; |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
export enum BytecodeHash { |
||||||
|
MAILBOX_WITHOUT_LOCAL_DOMAIN_BYTE_CODE_HASH = '0x29b7294ab3ad2e8587e5cce0e2289ce65e12a2ea2f1e7ab34a05e7737616f457', |
||||||
|
MAILBOX_WITHOUT_LOCAL_DOMAIN_NONZERO_PAUSE_BYTE_CODE_HASH = '0x4e73e34c0982b93eebb4ac4889e9e4e1611f7c24feacf016c3a13e389f146d9c', |
||||||
|
TRANSPARENT_PROXY_BYTECODE_HASH = '0x4dde3d0906b6492bf1d4947f667afe8d53c8899f1d8788cabafd082938dceb2d', |
||||||
|
MULTISIG_ISM_BYTECODE_HASH = '0x5565704ffa5b10fdf37d57abfddcf137101d5fb418ded21fa6c5f90262c57dc2', |
||||||
|
PROXY_ADMIN_BYTECODE_HASH = '0x7c378e9d49408861ca754fe684b9f7d1ea525bddf095ee0463902df701453ba0', |
||||||
|
INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xe995bcd732f4861606036357edb2a4d4c3e9b8d7e599fe548790ac1cf26888f8', |
||||||
|
OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH = '0xd2c5b00ac2d058117491d581d63c3c4fcf6aeb2667c6cc0c7caed359c9eebea1', |
||||||
|
OVERHEAD_IGP_BYTECODE_HASH = '0x3cfed1f24f1e9b28a76d5a8c61696a04f7bc474404b823a2fcc210ea52346252', |
||||||
|
} |
@ -0,0 +1,173 @@ |
|||||||
|
import { MultisigIsmConfig } from '../core/types'; |
||||||
|
import { ChainMap } from '../types'; |
||||||
|
|
||||||
|
export const defaultMultisigIsmConfigs: ChainMap<MultisigIsmConfig> = { |
||||||
|
// ----------------- Mainnets -----------------
|
||||||
|
celo: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0x1f20274b1210046769d48174c2f0e7c25ca7d5c5', // abacus
|
||||||
|
'0x3bc014bafa43f93d534aed34f750997cdffcf007', // dsrv
|
||||||
|
'0xd79d506d741fa735938f7b7847a926e34a6fe6b0', // everstake
|
||||||
|
'0xe4a258bc61e65914c2a477b2a8a433ab4ebdf44b', // zee prime
|
||||||
|
'0x6aea63b0be4679c1385c26a92a3ff8aa6a8379f2', // staked
|
||||||
|
'0xc0085e1a49bcc69e534272adb82c74c0e007e1ca', // zkv
|
||||||
|
], |
||||||
|
}, |
||||||
|
ethereum: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0x4c327ccb881a7542be77500b2833dc84c839e7b7', // abacus
|
||||||
|
'0x84cb373148ef9112b277e68acf676fefa9a9a9a0', // dsrv
|
||||||
|
'0x0d860c2b28bec3af4fd3a5997283e460ff6f2789', // everstake
|
||||||
|
'0xd4c1211f0eefb97a846c4e6d6589832e52fc03db', // zee prime
|
||||||
|
'0x600c90404d5c9df885404d2cc5350c9b314ea3a2', // staked
|
||||||
|
'0x892DC66F5B2f8C438E03f6323394e34A9C24F2D6', // zkv
|
||||||
|
], |
||||||
|
}, |
||||||
|
avalanche: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0xa7aa52623fe3d78c343008c95894be669e218b8d', // abacus
|
||||||
|
'0xb6004433fb04f643e2d48ae765c0e7f890f0bc0c', // dsrv
|
||||||
|
'0xa07e213e0985b21a6128e6c22ab5fb73948b0cc2', // everstake
|
||||||
|
'0x73853ed9a5f6f2e4c521970a94d43469e3cdaea6', // zee prime
|
||||||
|
'0xbd2e136cda02ba627ca882e49b184cbe976081c8', // staked
|
||||||
|
'0x1418126f944a44dad9edbab32294a8c890e7a9e3', // zkv
|
||||||
|
], |
||||||
|
}, |
||||||
|
polygon: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0x59a001c3451e7f9f3b4759ea215382c1e9aa5fc1', // abacus
|
||||||
|
'0x009fb042d28944017177920c1d40da02bfebf474', // dsrv
|
||||||
|
'0xba4b13e23705a5919c1901150d9697e8ffb3ea71', // everstake
|
||||||
|
'0x2faa4071b718972f9b4beec1d8cbaa4eb6cca6c6', // zee prime
|
||||||
|
'0x5ae9b0f833dfe09ef455562a1f603f1634504dd6', // staked
|
||||||
|
'0x6a163d312f7352a95c9b81dca15078d5bf77a442', // zkv
|
||||||
|
], |
||||||
|
}, |
||||||
|
bsc: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0xcc84b1eb711e5076b2755cf4ad1d2b42c458a45e', // abacus
|
||||||
|
'0xefe34eae2bca1846b895d2d0762ec21796aa196a', // dsrv
|
||||||
|
'0x662674e80e189b0861d6835c287693f50ee0c2ff', // everstake
|
||||||
|
'0x8a0f59075af466841808c529624807656309c9da', // zee prime
|
||||||
|
'0xdd2ff046ccd748a456b4757a73d47f165469669f', // staked
|
||||||
|
'0x034c4924c30ec4aa1b7f3ad58548988f0971e1bf', // zkv
|
||||||
|
], |
||||||
|
}, |
||||||
|
arbitrum: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0xbcb815f38d481a5eba4d7ac4c9e74d9d0fc2a7e7', // abacus
|
||||||
|
'0xd839424e2e5ace0a81152298dc2b1e3bb3c7fb20', // dsrv
|
||||||
|
'0xb8085c954b75b7088bcce69e61d12fcef797cd8d', // everstake
|
||||||
|
'0x9856dcb10fd6e5407fa74b5ab1d3b96cc193e9b7', // zee prime
|
||||||
|
'0x505dff4e0827aa5065f5e001db888e0569d46490', // staked
|
||||||
|
'0x25c6779d4610f940bf2488732e10bcffb9d36f81', // ZKV
|
||||||
|
], |
||||||
|
}, |
||||||
|
optimism: { |
||||||
|
threshold: 4, |
||||||
|
validators: [ |
||||||
|
'0x9f2296d5cfc6b5176adc7716c7596898ded13d35', // abacus
|
||||||
|
'0x9c10bbe8efa03a8f49dfdb5c549258e3a8dca097', // dsrv
|
||||||
|
'0x62144d4a52a0a0335ea5bb84392ef9912461d9dd', // everstake
|
||||||
|
'0xaff4718d5d637466ad07441ee3b7c4af8e328dbd', // zee prime
|
||||||
|
'0xc64d1efeab8ae222bc889fe669f75d21b23005d9', // staked
|
||||||
|
'0xfa174eb2b4921bb652bc1ada3e8b00e7e280bf3c', // ZKV
|
||||||
|
], |
||||||
|
}, |
||||||
|
moonbeam: { |
||||||
|
threshold: 3, |
||||||
|
validators: [ |
||||||
|
'0x237243d32d10e3bdbbf8dbcccc98ad44c1c172ea', // abacus
|
||||||
|
'0x9509c8cf0a06955f27342262af501b74874e98fb', // dsrv
|
||||||
|
'0xb7113c999e4d587b162dd1a28c73f3f51c6bdcdc', // everstake
|
||||||
|
'0x26725501597d47352a23cd26f122709f69ad53bc', // staked
|
||||||
|
], |
||||||
|
}, |
||||||
|
gnosis: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0xd0529ec8df08d0d63c0f023786bfa81e4bb51fd6', // abacus
|
||||||
|
'0x829d6ec129bc7187fb1ed161adcf7939fe0c515f', // abacus
|
||||||
|
'0x00009f8935e94bfe52ab3441df3526ab7cc38db1', // abacus
|
||||||
|
], |
||||||
|
}, |
||||||
|
// ----------------- Testnets -----------------
|
||||||
|
alfajores: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0xe6072396568e73ce6803b12b7e04164e839f1e54', |
||||||
|
'0x9f177f51289b22515f41f95872e1511391b8e105', |
||||||
|
'0x15f77400845eb1c971ad08de050861d5508cad6c', |
||||||
|
], |
||||||
|
}, |
||||||
|
fuji: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0x9fa19ead5ec76e437948b35e227511b106293c40', |
||||||
|
'0x227e7d6507762ece0c94678f8c103eff9d682476', |
||||||
|
'0x2379e43740e4aa4fde48cf4f00a3106df1d8420d', |
||||||
|
], |
||||||
|
}, |
||||||
|
mumbai: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0x0a664ea799447da6b15645cf8b9e82072a68343f', |
||||||
|
'0x6ae6f12929a960aba24ba74ea310e3d37d0ac045', |
||||||
|
'0x51f70c047cd73bc7873273707501568857a619c4', |
||||||
|
], |
||||||
|
}, |
||||||
|
bsctestnet: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0x23338c8714976dd4a57eaeff17cbd26d7e275c08', |
||||||
|
'0x85a618d7450ebc37e0d682371f08dac94eec7a76', |
||||||
|
'0x95b76562e4ba1791a27ba4236801271c9115b141', |
||||||
|
], |
||||||
|
}, |
||||||
|
goerli: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0xf43fbd072fd38e1121d4b3b0b8a35116bbb01ea9', |
||||||
|
'0xa33020552a21f35e75bd385c6ab95c3dfa82d930', |
||||||
|
'0x0bba4043ff242f8bf3f39bafa8930a84d644d947', |
||||||
|
], |
||||||
|
}, |
||||||
|
sepolia: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0xbc748ee311f5f2d1975d61cdf531755ce8ce3066', |
||||||
|
'0xc4233b2bfe5aec08964a94b403052abb3eafcf07', |
||||||
|
'0x6b36286c19f5c10bdc139ea9ee7f82287303f61d', |
||||||
|
], |
||||||
|
}, |
||||||
|
moonbasealpha: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0x890c2aeac157c3f067f3e42b8afc797939c59a32', |
||||||
|
'0x1b06d6fe69b972ed7420c83599d5a5c0fc185904', |
||||||
|
'0xe70b85206a968a99a597581f0fa09c99e7681093', |
||||||
|
], |
||||||
|
}, |
||||||
|
optimismgoerli: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0xbb8d77eefbecc55db6e5a19b0fc3dc290776f189', |
||||||
|
'0x69792508b4ddaa3ca52241ccfcd1e0b119a1ee65', |
||||||
|
'0x11ddb46c6b653e0cdd7ad5bee32ae316e18f8453', |
||||||
|
], |
||||||
|
}, |
||||||
|
arbitrumgoerli: { |
||||||
|
threshold: 2, |
||||||
|
validators: [ |
||||||
|
'0xce798fa21e323f6b24d9838a10ffecdefdfc4f30', |
||||||
|
'0xa792d39dca4426927e0f00c1618d61c9cb41779d', |
||||||
|
'0xdf181fcc11dfac5d01467e4547101a856dd5aa04', |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
@ -1,54 +1,40 @@ |
|||||||
import { |
import { |
||||||
Create2Factory__factory, |
Create2Factory__factory, |
||||||
InterchainAccountRouter__factory, |
InterchainAccountRouter__factory, |
||||||
InterchainGasPaymaster, |
|
||||||
InterchainGasPaymaster__factory, |
|
||||||
InterchainQueryRouter__factory, |
InterchainQueryRouter__factory, |
||||||
LegacyMultisigIsm, |
LegacyMultisigIsm, |
||||||
LegacyMultisigIsm__factory, |
LegacyMultisigIsm__factory, |
||||||
Mailbox, |
Mailbox, |
||||||
Mailbox__factory, |
Mailbox__factory, |
||||||
OverheadIgp, |
|
||||||
OverheadIgp__factory, |
|
||||||
ProxyAdmin, |
ProxyAdmin, |
||||||
ProxyAdmin__factory, |
ProxyAdmin__factory, |
||||||
StorageGasOracle, |
|
||||||
StorageGasOracle__factory, |
|
||||||
ValidatorAnnounce, |
ValidatorAnnounce, |
||||||
ValidatorAnnounce__factory, |
ValidatorAnnounce__factory, |
||||||
} from '@hyperlane-xyz/core'; |
} from '@hyperlane-xyz/core'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
import { ProxiedContract, TransparentProxyAddresses } from '../proxy'; |
import { ProxiedContract, TransparentProxyAddresses } from '../proxy'; |
||||||
|
|
||||||
export type GasOracleContracts = { |
export type CoreAddresses = { |
||||||
storageGasOracle: StorageGasOracle; |
mailbox: types.Address | TransparentProxyAddresses; |
||||||
|
multisigIsm: types.Address; |
||||||
|
proxyAdmin: types.Address; |
||||||
|
validatorAnnounce: types.Address; |
||||||
}; |
}; |
||||||
|
|
||||||
export type ConnectionClientContracts = { |
export type CoreContracts = { |
||||||
interchainGasPaymaster: ProxiedContract< |
mailbox: ProxiedContract<Mailbox, TransparentProxyAddresses>; |
||||||
InterchainGasPaymaster, |
multisigIsm: LegacyMultisigIsm; |
||||||
TransparentProxyAddresses |
proxyAdmin: ProxyAdmin; |
||||||
>; |
validatorAnnounce: ValidatorAnnounce; |
||||||
defaultIsmInterchainGasPaymaster: OverheadIgp; |
|
||||||
}; |
}; |
||||||
|
|
||||||
export type CoreContracts = GasOracleContracts & |
|
||||||
ConnectionClientContracts & { |
|
||||||
mailbox: ProxiedContract<Mailbox, TransparentProxyAddresses>; |
|
||||||
multisigIsm: LegacyMultisigIsm; |
|
||||||
proxyAdmin: ProxyAdmin; |
|
||||||
validatorAnnounce: ValidatorAnnounce; |
|
||||||
}; |
|
||||||
|
|
||||||
export const coreFactories = { |
export const coreFactories = { |
||||||
interchainAccountRouter: new InterchainAccountRouter__factory(), |
interchainAccountRouter: new InterchainAccountRouter__factory(), |
||||||
interchainQueryRouter: new InterchainQueryRouter__factory(), |
interchainQueryRouter: new InterchainQueryRouter__factory(), |
||||||
validatorAnnounce: new ValidatorAnnounce__factory(), |
validatorAnnounce: new ValidatorAnnounce__factory(), |
||||||
create2Factory: new Create2Factory__factory(), |
create2Factory: new Create2Factory__factory(), |
||||||
proxyAdmin: new ProxyAdmin__factory(), |
proxyAdmin: new ProxyAdmin__factory(), |
||||||
interchainGasPaymaster: new InterchainGasPaymaster__factory(), |
|
||||||
defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(), |
|
||||||
storageGasOracle: new StorageGasOracle__factory(), |
|
||||||
multisigIsm: new LegacyMultisigIsm__factory(), |
multisigIsm: new LegacyMultisigIsm__factory(), |
||||||
mailbox: new Mailbox__factory(), |
mailbox: new Mailbox__factory(), |
||||||
}; |
}; |
||||||
|
@ -1,15 +0,0 @@ |
|||||||
import { types } from '@hyperlane-xyz/utils'; |
|
||||||
|
|
||||||
import { ChainMap } from '../types'; |
|
||||||
import { objMap } from '../utils/objects'; |
|
||||||
|
|
||||||
export function getChainToOwnerMap( |
|
||||||
configMap: ChainMap<any>, |
|
||||||
owner: types.Address, |
|
||||||
): ChainMap<{ owner: string }> { |
|
||||||
return objMap(configMap, () => { |
|
||||||
return { |
|
||||||
owner, |
|
||||||
}; |
|
||||||
}); |
|
||||||
} |
|
@ -0,0 +1,129 @@ |
|||||||
|
import { BigNumber } from 'ethers'; |
||||||
|
|
||||||
|
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { HyperlaneApp } from '../HyperlaneApp'; |
||||||
|
import { |
||||||
|
HyperlaneEnvironment, |
||||||
|
hyperlaneEnvironments, |
||||||
|
} from '../consts/environments'; |
||||||
|
import { HyperlaneAddresses } from '../contracts'; |
||||||
|
import { MultiProvider } from '../providers/MultiProvider'; |
||||||
|
import { ChainMap, ChainName } from '../types'; |
||||||
|
|
||||||
|
import { IgpContracts, igpFactories } from './contracts'; |
||||||
|
|
||||||
|
export type IgpContractsMap = ChainMap<IgpContracts>; |
||||||
|
|
||||||
|
export class HyperlaneIgp extends HyperlaneApp<IgpContracts> { |
||||||
|
constructor(contractsMap: IgpContractsMap, multiProvider: MultiProvider) { |
||||||
|
super(contractsMap, multiProvider); |
||||||
|
} |
||||||
|
|
||||||
|
static fromAddresses( |
||||||
|
addresses: ChainMap<HyperlaneAddresses>, |
||||||
|
multiProvider: MultiProvider, |
||||||
|
): HyperlaneIgp { |
||||||
|
const { contracts, intersectionProvider } = |
||||||
|
this.buildContracts<IgpContracts>(addresses, igpFactories, multiProvider); |
||||||
|
return new HyperlaneIgp(contracts, intersectionProvider); |
||||||
|
} |
||||||
|
|
||||||
|
static fromEnvironment<Env extends HyperlaneEnvironment>( |
||||||
|
env: Env, |
||||||
|
multiProvider: MultiProvider, |
||||||
|
): HyperlaneIgp { |
||||||
|
const envAddresses = hyperlaneEnvironments[env]; |
||||||
|
if (!envAddresses) { |
||||||
|
throw new Error(`No addresses found for ${env}`); |
||||||
|
} |
||||||
|
return HyperlaneIgp.fromAddresses(envAddresses, multiProvider); |
||||||
|
} |
||||||
|
|
||||||
|
getContracts(chain: ChainName): IgpContracts { |
||||||
|
return super.getContracts(chain); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls the default ISM IGP's `quoteGasPayment` function to get the amount of native tokens |
||||||
|
* required to pay for interchain gas. |
||||||
|
* The default ISM IGP will add any gas overhead amounts related to the Mailbox |
||||||
|
* and default ISM on the destination to the provided gasAmount. |
||||||
|
* @param origin The name of the origin chain. |
||||||
|
* @param destination The name of the destination chain. |
||||||
|
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`. |
||||||
|
* The default IGP is expected to add any gas overhead related to the Mailbox |
||||||
|
* or ISM, so this gas amount is only required to cover the usage of the `handle` |
||||||
|
* function. |
||||||
|
* @returns The amount of native tokens required to pay for interchain gas. |
||||||
|
*/ |
||||||
|
quoteGasPayment( |
||||||
|
origin: ChainName, |
||||||
|
destination: ChainName, |
||||||
|
gasAmount: BigNumber, |
||||||
|
): Promise<BigNumber> { |
||||||
|
const igp = this.getContracts(origin).interchainGasPaymaster; |
||||||
|
return this.quoteGasPaymentForIgp( |
||||||
|
origin, |
||||||
|
destination, |
||||||
|
gasAmount, |
||||||
|
igp.address, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls the default ISM IGP's `quoteGasPayment` function to get the amount of native tokens |
||||||
|
* required to pay for interchain gas. |
||||||
|
* The default ISM IGP will add any gas overhead amounts related to the Mailbox |
||||||
|
* and default ISM on the destination to the provided gasAmount. |
||||||
|
* @param origin The name of the origin chain. |
||||||
|
* @param destination The name of the destination chain. |
||||||
|
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`. |
||||||
|
* The default IGP is expected to add any gas overhead related to the Mailbox |
||||||
|
* or ISM, so this gas amount is only required to cover the usage of the `handle` |
||||||
|
* function. |
||||||
|
* @returns The amount of native tokens required to pay for interchain gas. |
||||||
|
*/ |
||||||
|
quoteGasPaymentForDefaultIsmIgp( |
||||||
|
origin: ChainName, |
||||||
|
destination: ChainName, |
||||||
|
gasAmount: BigNumber, |
||||||
|
): Promise<BigNumber> { |
||||||
|
const igp = this.getContracts(origin).defaultIsmInterchainGasPaymaster; |
||||||
|
return this.quoteGasPaymentForIgp( |
||||||
|
origin, |
||||||
|
destination, |
||||||
|
gasAmount, |
||||||
|
igp.address, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Calls the origin's default IGP's `quoteGasPayment` function to get the |
||||||
|
* amount of native tokens required to pay for interchain gas. |
||||||
|
* The default IGP is expected to add any gas overhead related to the Mailbox |
||||||
|
* and ISM to the provided gasAmount. |
||||||
|
* @param origin The name of the origin chain. |
||||||
|
* @param destination The name of the destination chain. |
||||||
|
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`. |
||||||
|
* The default IGP is expected to add any gas overhead related to the Mailbox |
||||||
|
* or ISM, so this gas amount is only required to cover the usage of the `handle` |
||||||
|
* function. |
||||||
|
* @returns The amount of native tokens required to pay for interchain gas. |
||||||
|
*/ |
||||||
|
protected quoteGasPaymentForIgp( |
||||||
|
origin: ChainName, |
||||||
|
destination: ChainName, |
||||||
|
gasAmount: BigNumber, |
||||||
|
interchainGasPaymasterAddress: types.Address, |
||||||
|
): Promise<BigNumber> { |
||||||
|
const originProvider = this.multiProvider.getProvider(origin); |
||||||
|
const igp = InterchainGasPaymaster__factory.connect( |
||||||
|
interchainGasPaymasterAddress, |
||||||
|
originProvider, |
||||||
|
); |
||||||
|
const domainId = this.multiProvider.getDomainId(destination); |
||||||
|
return igp.quoteGasPayment(domainId, gasAmount); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,194 @@ |
|||||||
|
import { BigNumber, utils as ethersUtils } from 'ethers'; |
||||||
|
|
||||||
|
import { types, utils } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { BytecodeHash } from '../consts/bytecode'; |
||||||
|
import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker'; |
||||||
|
import { ChainName } from '../types'; |
||||||
|
|
||||||
|
import { HyperlaneIgp } from './HyperlaneIgp'; |
||||||
|
import { |
||||||
|
GasOracleContractType, |
||||||
|
IgpBeneficiaryViolation, |
||||||
|
IgpGasOraclesViolation, |
||||||
|
IgpOverheadViolation, |
||||||
|
IgpViolationType, |
||||||
|
OverheadIgpConfig, |
||||||
|
} from './types'; |
||||||
|
|
||||||
|
export class HyperlaneIgpChecker extends HyperlaneAppChecker< |
||||||
|
HyperlaneIgp, |
||||||
|
OverheadIgpConfig |
||||||
|
> { |
||||||
|
async checkChain(chain: ChainName): Promise<void> { |
||||||
|
await this.checkDomainOwnership(chain); |
||||||
|
await this.checkProxiedContracts(chain); |
||||||
|
await this.checkBytecodes(chain); |
||||||
|
await this.checkOverheadInterchainGasPaymaster(chain); |
||||||
|
await this.checkInterchainGasPaymaster(chain); |
||||||
|
} |
||||||
|
|
||||||
|
async checkDomainOwnership(chain: ChainName): Promise<void> { |
||||||
|
const config = this.configMap[chain]; |
||||||
|
if (config.owner) { |
||||||
|
const contracts = this.app.getContracts(chain); |
||||||
|
const ownables = [ |
||||||
|
contracts.proxyAdmin, |
||||||
|
contracts.interchainGasPaymaster.contract, |
||||||
|
contracts.defaultIsmInterchainGasPaymaster, |
||||||
|
]; |
||||||
|
return this.checkOwnership(chain, config.owner, ownables); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async checkBytecodes(chain: ChainName): Promise<void> { |
||||||
|
const contracts = this.app.getContracts(chain); |
||||||
|
await this.checkBytecode( |
||||||
|
chain, |
||||||
|
'InterchainGasPaymaster proxy', |
||||||
|
contracts.interchainGasPaymaster.address, |
||||||
|
[BytecodeHash.TRANSPARENT_PROXY_BYTECODE_HASH], |
||||||
|
); |
||||||
|
await this.checkBytecode( |
||||||
|
chain, |
||||||
|
'InterchainGasPaymaster implementation', |
||||||
|
contracts.interchainGasPaymaster.addresses.implementation, |
||||||
|
[ |
||||||
|
BytecodeHash.INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH, |
||||||
|
BytecodeHash.OWNER_INITIALIZABLE_INTERCHAIN_GAS_PAYMASTER_BYTECODE_HASH, |
||||||
|
], |
||||||
|
); |
||||||
|
|
||||||
|
await this.checkBytecode( |
||||||
|
chain, |
||||||
|
'OverheadIGP', |
||||||
|
contracts.defaultIsmInterchainGasPaymaster.address, |
||||||
|
[BytecodeHash.OVERHEAD_IGP_BYTECODE_HASH], |
||||||
|
(bytecode) => |
||||||
|
// Remove the address of the wrapped IGP from the bytecode
|
||||||
|
bytecode.replaceAll( |
||||||
|
ethersUtils.defaultAbiCoder |
||||||
|
.encode( |
||||||
|
['address'], |
||||||
|
[contracts.interchainGasPaymaster.addresses.proxy], |
||||||
|
) |
||||||
|
.slice(2), |
||||||
|
'', |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async checkProxiedContracts(chain: ChainName): Promise<void> { |
||||||
|
const contracts = this.app.getContracts(chain); |
||||||
|
await this.checkProxiedContract( |
||||||
|
chain, |
||||||
|
'InterchainGasPaymaster', |
||||||
|
contracts.interchainGasPaymaster.addresses, |
||||||
|
contracts.proxyAdmin.address, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
async checkOverheadInterchainGasPaymaster(local: ChainName): Promise<void> { |
||||||
|
const coreContracts = this.app.getContracts(local); |
||||||
|
const defaultIsmIgp = coreContracts.defaultIsmInterchainGasPaymaster; |
||||||
|
|
||||||
|
// Construct the violation, updating the actual & expected
|
||||||
|
// objects as violations are found.
|
||||||
|
// A single violation is used so that only a single `setDestinationGasOverheads`
|
||||||
|
// call is generated to set multiple gas overheads.
|
||||||
|
const overheadViolation: IgpOverheadViolation = { |
||||||
|
type: 'InterchainGasPaymaster', |
||||||
|
subType: IgpViolationType.Overhead, |
||||||
|
contract: defaultIsmIgp, |
||||||
|
chain: local, |
||||||
|
actual: {}, |
||||||
|
expected: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
const remotes = this.app.remoteChains(local); |
||||||
|
for (const remote of remotes) { |
||||||
|
const expectedOverhead = this.configMap[local].overhead[remote]; |
||||||
|
|
||||||
|
const remoteId = this.multiProvider.getDomainId(remote); |
||||||
|
const existingOverhead = await defaultIsmIgp.destinationGasOverhead( |
||||||
|
remoteId, |
||||||
|
); |
||||||
|
if (!existingOverhead.eq(expectedOverhead)) { |
||||||
|
const remoteChain = remote as ChainName; |
||||||
|
overheadViolation.actual[remoteChain] = existingOverhead; |
||||||
|
overheadViolation.expected[remoteChain] = |
||||||
|
BigNumber.from(expectedOverhead); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (Object.keys(overheadViolation.actual).length > 0) { |
||||||
|
this.addViolation(overheadViolation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async checkInterchainGasPaymaster(local: ChainName): Promise<void> { |
||||||
|
const coreContracts = this.app.getContracts(local); |
||||||
|
const igp = coreContracts.interchainGasPaymaster.contract; |
||||||
|
|
||||||
|
// Construct the violation, updating the actual & expected
|
||||||
|
// objects as violations are found.
|
||||||
|
// A single violation is used so that only a single `setGasOracles`
|
||||||
|
// call is generated to set multiple gas oracles.
|
||||||
|
const gasOraclesViolation: IgpGasOraclesViolation = { |
||||||
|
type: 'InterchainGasPaymaster', |
||||||
|
subType: IgpViolationType.GasOracles, |
||||||
|
contract: igp, |
||||||
|
chain: local, |
||||||
|
actual: {}, |
||||||
|
expected: {}, |
||||||
|
}; |
||||||
|
|
||||||
|
const remotes = this.app.remoteChains(local); |
||||||
|
for (const remote of remotes) { |
||||||
|
const remoteId = this.multiProvider.getDomainId(remote); |
||||||
|
const actualGasOracle = await igp.gasOracles(remoteId); |
||||||
|
const expectedGasOracle = this.getGasOracleAddress(local, remote); |
||||||
|
|
||||||
|
if (!utils.eqAddress(actualGasOracle, expectedGasOracle)) { |
||||||
|
const remoteChain = remote as ChainName; |
||||||
|
gasOraclesViolation.actual[remoteChain] = actualGasOracle; |
||||||
|
gasOraclesViolation.expected[remoteChain] = expectedGasOracle; |
||||||
|
} |
||||||
|
} |
||||||
|
// Add the violation only if it's been populated with gas oracle inconsistencies
|
||||||
|
if (Object.keys(gasOraclesViolation.actual).length > 0) { |
||||||
|
this.addViolation(gasOraclesViolation); |
||||||
|
} |
||||||
|
|
||||||
|
const actualBeneficiary = await igp.beneficiary(); |
||||||
|
const expectedBeneficiary = this.configMap[local].beneficiary; |
||||||
|
if (!utils.eqAddress(actualBeneficiary, expectedBeneficiary)) { |
||||||
|
const violation: IgpBeneficiaryViolation = { |
||||||
|
type: 'InterchainGasPaymaster', |
||||||
|
subType: IgpViolationType.Beneficiary, |
||||||
|
contract: igp, |
||||||
|
chain: local, |
||||||
|
actual: actualBeneficiary, |
||||||
|
expected: expectedBeneficiary, |
||||||
|
}; |
||||||
|
this.addViolation(violation); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getGasOracleAddress(local: ChainName, remote: ChainName): types.Address { |
||||||
|
const config = this.configMap[local]; |
||||||
|
const gasOracleType = config.gasOracleType[remote]; |
||||||
|
if (!gasOracleType) { |
||||||
|
throw Error( |
||||||
|
`Expected gas oracle type for local ${local} and remote ${remote}`, |
||||||
|
); |
||||||
|
} |
||||||
|
const coreContracts = this.app.getContracts(local); |
||||||
|
switch (gasOracleType) { |
||||||
|
case GasOracleContractType.StorageGasOracle: |
||||||
|
return coreContracts.storageGasOracle.address; |
||||||
|
default: |
||||||
|
throw Error(`Unsupported gas oracle type ${gasOracleType}`); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,184 @@ |
|||||||
|
import debug from 'debug'; |
||||||
|
|
||||||
|
import { |
||||||
|
InterchainGasPaymaster, |
||||||
|
OverheadIgp, |
||||||
|
Ownable, |
||||||
|
Ownable__factory, |
||||||
|
ProxyAdmin, |
||||||
|
StorageGasOracle, |
||||||
|
} from '@hyperlane-xyz/core'; |
||||||
|
import { types, utils } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { DeployOptions, HyperlaneDeployer } from '../deploy/HyperlaneDeployer'; |
||||||
|
import { MultiProvider } from '../providers/MultiProvider'; |
||||||
|
import { ProxiedContract, TransparentProxyAddresses } from '../proxy'; |
||||||
|
import { ChainMap, ChainName } from '../types'; |
||||||
|
import { objMap } from '../utils/objects'; |
||||||
|
|
||||||
|
import { IgpContracts, igpFactories } from './contracts'; |
||||||
|
import { OverheadIgpConfig } from './types'; |
||||||
|
|
||||||
|
export class HyperlaneIgpDeployer extends HyperlaneDeployer< |
||||||
|
OverheadIgpConfig, |
||||||
|
IgpContracts, |
||||||
|
typeof igpFactories |
||||||
|
> { |
||||||
|
startingBlockNumbers: ChainMap<number | undefined>; |
||||||
|
|
||||||
|
constructor( |
||||||
|
multiProvider: MultiProvider, |
||||||
|
configMap: ChainMap<OverheadIgpConfig>, |
||||||
|
factoriesOverride = igpFactories, |
||||||
|
) { |
||||||
|
super(multiProvider, configMap, factoriesOverride, { |
||||||
|
logger: debug('hyperlane:IgpDeployer'), |
||||||
|
}); |
||||||
|
this.startingBlockNumbers = objMap(configMap, () => undefined); |
||||||
|
} |
||||||
|
|
||||||
|
async deployInterchainGasPaymaster( |
||||||
|
chain: ChainName, |
||||||
|
proxyAdmin: ProxyAdmin, |
||||||
|
storageGasOracle: StorageGasOracle, |
||||||
|
deployOpts?: DeployOptions, |
||||||
|
): Promise< |
||||||
|
ProxiedContract<InterchainGasPaymaster, TransparentProxyAddresses> |
||||||
|
> { |
||||||
|
const owner = this.configMap[chain].owner; |
||||||
|
const beneficiary = this.configMap[chain].beneficiary; |
||||||
|
const igp = await this.deployProxiedContract( |
||||||
|
chain, |
||||||
|
'interchainGasPaymaster', |
||||||
|
[beneficiary], |
||||||
|
proxyAdmin, |
||||||
|
[owner, beneficiary], |
||||||
|
deployOpts, |
||||||
|
); |
||||||
|
|
||||||
|
// Set the gas oracles
|
||||||
|
const configChains = Object.keys(this.configMap); |
||||||
|
const remotes = this.multiProvider |
||||||
|
.intersect(configChains, false) |
||||||
|
.multiProvider.getRemoteChains(chain); |
||||||
|
|
||||||
|
const gasOracleConfigsToSet: InterchainGasPaymaster.GasOracleConfigStruct[] = |
||||||
|
[]; |
||||||
|
|
||||||
|
for (const remote of remotes) { |
||||||
|
const remoteId = this.multiProvider.getDomainId(remote); |
||||||
|
const currentGasOracle = await igp.contract.gasOracles(remoteId); |
||||||
|
if (!utils.eqAddress(currentGasOracle, storageGasOracle.address)) { |
||||||
|
gasOracleConfigsToSet.push({ |
||||||
|
remoteDomain: remoteId, |
||||||
|
gasOracle: storageGasOracle.address, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (gasOracleConfigsToSet.length > 0) { |
||||||
|
await this.runIfOwner(chain, igp.contract, async () => |
||||||
|
this.multiProvider.handleTx( |
||||||
|
chain, |
||||||
|
igp.contract.setGasOracles(gasOracleConfigsToSet), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
return igp; |
||||||
|
} |
||||||
|
|
||||||
|
async deployOverheadInterchainGasPaymaster( |
||||||
|
chain: ChainName, |
||||||
|
interchainGasPaymasterAddress: types.Address, |
||||||
|
deployOpts?: DeployOptions, |
||||||
|
): Promise<OverheadIgp> { |
||||||
|
const deployer = await this.multiProvider.getSignerAddress(chain); |
||||||
|
// Transfer ownership to the deployer so the destination gas overheads can be set
|
||||||
|
const initCalldata = Ownable__factory.createInterface().encodeFunctionData( |
||||||
|
'transferOwnership', |
||||||
|
[deployer], |
||||||
|
); |
||||||
|
const overheadInterchainGasPaymaster = await this.deployContract( |
||||||
|
chain, |
||||||
|
'defaultIsmInterchainGasPaymaster', |
||||||
|
[interchainGasPaymasterAddress], |
||||||
|
{ |
||||||
|
...deployOpts, |
||||||
|
initCalldata, |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
const configChains = Object.keys(this.configMap); |
||||||
|
const remotes = this.multiProvider |
||||||
|
.intersect(configChains, false) |
||||||
|
.multiProvider.getRemoteChains(chain); |
||||||
|
|
||||||
|
// Only set gas overhead configs if they differ from what's on chain
|
||||||
|
const configs: OverheadIgp.DomainConfigStruct[] = []; |
||||||
|
for (const remote of remotes) { |
||||||
|
const remoteDomain = this.multiProvider.getDomainId(remote); |
||||||
|
const gasOverhead = this.configMap[chain].overhead[remote]; |
||||||
|
const existingOverhead = |
||||||
|
await overheadInterchainGasPaymaster.destinationGasOverhead( |
||||||
|
remoteDomain, |
||||||
|
); |
||||||
|
if (!existingOverhead.eq(gasOverhead)) { |
||||||
|
configs.push({ domain: remoteDomain, gasOverhead }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (configs.length > 0) { |
||||||
|
await this.runIfOwner(chain, overheadInterchainGasPaymaster, () => |
||||||
|
this.multiProvider.handleTx( |
||||||
|
chain, |
||||||
|
overheadInterchainGasPaymaster.setDestinationGasOverheads( |
||||||
|
configs, |
||||||
|
this.multiProvider.getTransactionOverrides(chain), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return overheadInterchainGasPaymaster; |
||||||
|
} |
||||||
|
|
||||||
|
async deployStorageGasOracle( |
||||||
|
chain: ChainName, |
||||||
|
deployOpts?: DeployOptions, |
||||||
|
): Promise<StorageGasOracle> { |
||||||
|
return this.deployContract(chain, 'storageGasOracle', [], deployOpts); |
||||||
|
} |
||||||
|
|
||||||
|
async deployContracts( |
||||||
|
chain: ChainName, |
||||||
|
config: OverheadIgpConfig, |
||||||
|
): Promise<IgpContracts> { |
||||||
|
const provider = this.multiProvider.getProvider(chain); |
||||||
|
const startingBlockNumber = await provider.getBlockNumber(); |
||||||
|
this.startingBlockNumbers[chain] = startingBlockNumber; |
||||||
|
// NB: To share ProxyAdmins with HyperlaneCore, ensure the ProxyAdmin
|
||||||
|
// is loaded into the contract cache.
|
||||||
|
const proxyAdmin = await this.deployContract(chain, 'proxyAdmin', []); |
||||||
|
const storageGasOracle = await this.deployStorageGasOracle(chain); |
||||||
|
const interchainGasPaymaster = await this.deployInterchainGasPaymaster( |
||||||
|
chain, |
||||||
|
proxyAdmin, |
||||||
|
storageGasOracle, |
||||||
|
); |
||||||
|
const overheadInterchainGasPaymaster = |
||||||
|
await this.deployOverheadInterchainGasPaymaster( |
||||||
|
chain, |
||||||
|
interchainGasPaymaster.address, |
||||||
|
); |
||||||
|
// Ownership of the Mailbox and the interchainGasPaymaster is transferred upon initialization.
|
||||||
|
const ownables: Ownable[] = [overheadInterchainGasPaymaster]; |
||||||
|
await this.transferOwnershipOfContracts(chain, config.owner, ownables); |
||||||
|
|
||||||
|
return { |
||||||
|
proxyAdmin, |
||||||
|
storageGasOracle, |
||||||
|
interchainGasPaymaster, |
||||||
|
defaultIsmInterchainGasPaymaster: overheadInterchainGasPaymaster, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -1,78 +0,0 @@ |
|||||||
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
|
||||||
import { expect } from 'chai'; |
|
||||||
import { BigNumber } from 'ethers'; |
|
||||||
import { ethers } from 'hardhat'; |
|
||||||
|
|
||||||
import { types } from '@hyperlane-xyz/utils'; |
|
||||||
|
|
||||||
import { Chains } from '../consts/chains'; |
|
||||||
import { HyperlaneCore } from '../core/HyperlaneCore'; |
|
||||||
import { TestCoreDeployer } from '../core/TestCoreDeployer'; |
|
||||||
import { MultiProvider } from '../providers/MultiProvider'; |
|
||||||
|
|
||||||
import { InterchainGasCalculator } from './calculator'; |
|
||||||
|
|
||||||
describe('InterchainGasCalculator', async () => { |
|
||||||
const localChain = Chains.test1; |
|
||||||
const remoteChain = Chains.test2; |
|
||||||
|
|
||||||
const testGasAmount = BigNumber.from('100000'); |
|
||||||
|
|
||||||
let signer: SignerWithAddress; |
|
||||||
let multiProvider: MultiProvider; |
|
||||||
|
|
||||||
let calculator: InterchainGasCalculator; |
|
||||||
let igp: types.Address; |
|
||||||
|
|
||||||
before(async () => { |
|
||||||
[signer] = await ethers.getSigners(); |
|
||||||
|
|
||||||
multiProvider = MultiProvider.createTestMultiProvider({ signer }); |
|
||||||
|
|
||||||
const coreDeployer = new TestCoreDeployer(multiProvider); |
|
||||||
const coreContractsMaps = await coreDeployer.deploy(); |
|
||||||
const core = new HyperlaneCore(coreContractsMaps, multiProvider); |
|
||||||
calculator = new InterchainGasCalculator(multiProvider, core); |
|
||||||
igp = coreContractsMaps[localChain].interchainGasPaymaster.address; |
|
||||||
}); |
|
||||||
|
|
||||||
describe('quoteGasPaymentForDefaultIsmIgp', () => { |
|
||||||
it("calls the default ISM IGP's quoteGasPayment function", async () => { |
|
||||||
const quote = await calculator.quoteGasPaymentForDefaultIsmIgp( |
|
||||||
localChain, |
|
||||||
remoteChain, |
|
||||||
testGasAmount, |
|
||||||
); |
|
||||||
|
|
||||||
// (100,000 gas amount + 151,966 overhead) * 10 gas price
|
|
||||||
expect(quote).to.equal(BigNumber.from('2519660')); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('quoteGasPayment', () => { |
|
||||||
it("calls the IGP's quoteGasPayment function", async () => { |
|
||||||
const quote = await calculator.quoteGasPayment( |
|
||||||
localChain, |
|
||||||
remoteChain, |
|
||||||
testGasAmount, |
|
||||||
); |
|
||||||
|
|
||||||
// 100,000 gas amount * 10 gas price
|
|
||||||
expect(quote).to.equal(BigNumber.from('1000000')); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('quoteGasPaymentForIGP', () => { |
|
||||||
it("calls the provided IGP's quoteGasPayment", async () => { |
|
||||||
const quote = await calculator.quoteGasPaymentForIGP( |
|
||||||
localChain, |
|
||||||
remoteChain, |
|
||||||
testGasAmount, |
|
||||||
igp, |
|
||||||
); |
|
||||||
|
|
||||||
// 100,000 gas amount * 10 gas price
|
|
||||||
expect(quote).to.equal(BigNumber.from('1000000')); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,267 +0,0 @@ |
|||||||
import { expect } from 'chai'; |
|
||||||
import { BigNumber } from 'ethers'; |
|
||||||
import sinon from 'sinon'; |
|
||||||
|
|
||||||
import { Chains } from '../consts/chains'; |
|
||||||
import { HyperlaneCore } from '../core/HyperlaneCore'; |
|
||||||
import { CoreContracts } from '../core/contracts'; |
|
||||||
import { MultiProvider } from '../providers/MultiProvider'; |
|
||||||
import { MockProvider, MockTokenPriceGetter } from '../test/testUtils'; |
|
||||||
import { ChainName, TestChainNames } from '../types'; |
|
||||||
|
|
||||||
import { InterchainGasCalculator, ParsedMessage } from './calculator'; |
|
||||||
|
|
||||||
const HANDLE_GAS = 100_000; |
|
||||||
const SUGGESTED_GAS_PRICE = 10; |
|
||||||
const INBOX_PROCESS_OVERHEAD_GAS = 100_000; |
|
||||||
|
|
||||||
// Exposes protected methods so they can be stubbed.
|
|
||||||
class TestInterchainGasCalculator extends InterchainGasCalculator { |
|
||||||
estimateGasForProcess( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
): Promise<BigNumber> { |
|
||||||
return super.estimateGasForProcess(origin, destination); |
|
||||||
} |
|
||||||
estimateGasForHandle(message: ParsedMessage): Promise<BigNumber> { |
|
||||||
return super.estimateGasForHandle(message); |
|
||||||
} |
|
||||||
convertBetweenTokens( |
|
||||||
fromChain: ChainName, |
|
||||||
toChain: ChainName, |
|
||||||
fromAmount: BigNumber, |
|
||||||
): Promise<BigNumber> { |
|
||||||
return super.convertBetweenTokens(fromChain, toChain, fromAmount); |
|
||||||
} |
|
||||||
tokenDecimals(chain: ChainName): number { |
|
||||||
return super.tokenDecimals(chain); |
|
||||||
} |
|
||||||
getGasPrice(chain: ChainName): Promise<BigNumber> { |
|
||||||
return super.getGasPrice(chain); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
describe('InterchainGasCalculator', () => { |
|
||||||
const provider = new MockProvider(); |
|
||||||
|
|
||||||
const multiProvider = MultiProvider.createTestMultiProvider({ provider }); |
|
||||||
const core: HyperlaneCore = HyperlaneCore.fromEnvironment( |
|
||||||
'test', |
|
||||||
multiProvider, |
|
||||||
); |
|
||||||
const origin = Chains.test1; |
|
||||||
const destination = Chains.test2; |
|
||||||
|
|
||||||
let tokenPriceGetter: MockTokenPriceGetter; |
|
||||||
let calculator: TestInterchainGasCalculator; |
|
||||||
|
|
||||||
beforeEach(() => { |
|
||||||
tokenPriceGetter = new MockTokenPriceGetter(); |
|
||||||
tokenPriceGetter.setTokenPrice(origin, 9.0909); |
|
||||||
tokenPriceGetter.setTokenPrice(destination, 5.5); |
|
||||||
calculator = new TestInterchainGasCalculator(multiProvider, core, { |
|
||||||
tokenPriceGetter, |
|
||||||
// A multiplier of 1 makes testing easier to reason about
|
|
||||||
paymentEstimateMultiplier: '1', |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
afterEach(() => { |
|
||||||
sinon.restore(); |
|
||||||
provider.clearMethodResolveValues(); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('estimatePaymentForGas', () => { |
|
||||||
it('estimates origin token payment from a specified destination gas amount', async () => { |
|
||||||
// Set destination gas price to 10 wei
|
|
||||||
provider.setMethodResolveValue( |
|
||||||
'getGasPrice', |
|
||||||
BigNumber.from(SUGGESTED_GAS_PRICE), |
|
||||||
); |
|
||||||
|
|
||||||
const estimatedPayment = await calculator.estimatePaymentForGas( |
|
||||||
origin, |
|
||||||
destination, |
|
||||||
BigNumber.from(HANDLE_GAS), |
|
||||||
); |
|
||||||
|
|
||||||
// 100k gas * 10 gas price * ($5.5 per destination token / $9.0909 per origin token)
|
|
||||||
expect(estimatedPayment.toNumber()).to.equal(605_000); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('estimatePaymentForHandleGas', () => { |
|
||||||
it('estimates origin token payment from a specified destination handle gas amount', async () => { |
|
||||||
// Set destination gas price to 10 wei
|
|
||||||
provider.setMethodResolveValue( |
|
||||||
'getGasPrice', |
|
||||||
BigNumber.from(SUGGESTED_GAS_PRICE), |
|
||||||
); |
|
||||||
|
|
||||||
// Stub the inbox process overhead gas
|
|
||||||
sinon |
|
||||||
.stub(calculator, 'estimateGasForProcess') |
|
||||||
.returns(Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS))); |
|
||||||
|
|
||||||
const estimatedPayment = await calculator.estimatePaymentForHandleGas( |
|
||||||
origin, |
|
||||||
destination, |
|
||||||
BigNumber.from(HANDLE_GAS), |
|
||||||
); |
|
||||||
|
|
||||||
// (100_000 dest handler gas + 100_000 process overhead gas)
|
|
||||||
// * 10 gas price * ($5.5 per destination token / $9.0909 per origin token)
|
|
||||||
expect(estimatedPayment.toNumber()).to.equal(1_210_000); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
/* |
|
||||||
describe('estimatePaymentForMessage', () => { |
|
||||||
it('estimates origin token payment from a specified message', async () => { |
|
||||||
// Set destination gas price to 10 wei
|
|
||||||
provider.setMethodResolveValue( |
|
||||||
'getGasPrice', |
|
||||||
BigNumber.from(SUGGESTED_GAS_PRICE), |
|
||||||
); |
|
||||||
// Set the estimated handle gas
|
|
||||||
sinon |
|
||||||
.stub(calculator, 'estimateGasForHandle') |
|
||||||
.returns(Promise.resolve(BigNumber.from(HANDLE_GAS))); |
|
||||||
// Stub the inbox process overhead gas
|
|
||||||
sinon |
|
||||||
.stub(calculator, 'estimateGasForProcess') |
|
||||||
.returns(Promise.resolve(BigNumber.from(INBOX_PROCESS_OVERHEAD_GAS))); |
|
||||||
|
|
||||||
const zeroAddressBytes32 = utils.addressToBytes32( |
|
||||||
ethers.constants.AddressZero, |
|
||||||
); |
|
||||||
const message: ParsedMessage<TestChainNames, Chains.test2> = { |
|
||||||
origin: origin, |
|
||||||
sender: zeroAddressBytes32, |
|
||||||
destination: destination, |
|
||||||
recipient: zeroAddressBytes32, |
|
||||||
body: '0x12345678', |
|
||||||
}; |
|
||||||
|
|
||||||
const estimatedPayment = await calculator.estimatePaymentForMessage( |
|
||||||
message, |
|
||||||
); |
|
||||||
|
|
||||||
// (100_000 dest handler gas + 100_000 process overhead gas)
|
|
||||||
// * 10 gas price * ($5.5 per destination token / $9.0909 per origin token)
|
|
||||||
expect(estimatedPayment.toNumber()).to.equal(1_210_000); |
|
||||||
}); |
|
||||||
}); |
|
||||||
*/ |
|
||||||
|
|
||||||
describe('convertBetweenTokens', () => { |
|
||||||
const destinationWei = BigNumber.from('1000'); |
|
||||||
|
|
||||||
it('converts using the USD value of origin and destination native tokens', async () => { |
|
||||||
const originWei = await calculator.convertBetweenTokens( |
|
||||||
destination, |
|
||||||
origin, |
|
||||||
destinationWei, |
|
||||||
); |
|
||||||
|
|
||||||
// 1000 * (5.5 / 9.0909)
|
|
||||||
expect(originWei.toNumber()).to.equal(605); |
|
||||||
}); |
|
||||||
|
|
||||||
it('considers when the origin token decimals > the destination token decimals', async () => { |
|
||||||
calculator.tokenDecimals = (chain: TestChainNames) => { |
|
||||||
if (chain === origin) { |
|
||||||
return 20; |
|
||||||
} |
|
||||||
return 18; |
|
||||||
}; |
|
||||||
|
|
||||||
const originWei = await calculator.convertBetweenTokens( |
|
||||||
destination, |
|
||||||
origin, |
|
||||||
destinationWei, |
|
||||||
); |
|
||||||
|
|
||||||
// 1000 * (5.5 / 9.0909) * 100
|
|
||||||
expect(originWei.toNumber()).to.equal(60500); |
|
||||||
}); |
|
||||||
|
|
||||||
it('considers when the origin token decimals < the destination token decimals', async () => { |
|
||||||
sinon.stub(calculator, 'tokenDecimals').callsFake((chain: ChainName) => { |
|
||||||
if (chain === origin) { |
|
||||||
return 16; |
|
||||||
} |
|
||||||
return 18; |
|
||||||
}); |
|
||||||
|
|
||||||
const originWei = await calculator.convertBetweenTokens( |
|
||||||
destination, |
|
||||||
origin, |
|
||||||
destinationWei, |
|
||||||
); |
|
||||||
|
|
||||||
// 1000 * (5.5 / 9.0909) / 100
|
|
||||||
expect(originWei.toNumber()).to.equal(6); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('getGasPrice', () => { |
|
||||||
it('gets the gas price from the provider', async () => { |
|
||||||
provider.setMethodResolveValue( |
|
||||||
'getGasPrice', |
|
||||||
BigNumber.from(SUGGESTED_GAS_PRICE), |
|
||||||
); |
|
||||||
|
|
||||||
expect((await calculator.getGasPrice(destination)).toNumber()).to.equal( |
|
||||||
SUGGESTED_GAS_PRICE, |
|
||||||
); |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
describe('estimateGasForProcess', () => { |
|
||||||
let threshold: number; |
|
||||||
// Mock the return value of MultisigIsm.threshold
|
|
||||||
// to return `threshold`. Because the mocking involves a closure,
|
|
||||||
// changing `threshold` will change the return value of MultisigIsm.threshold.
|
|
||||||
before(() => { |
|
||||||
const getContractsStub = sinon.stub(core, 'getContracts'); |
|
||||||
let thresholdStub: sinon.SinonStub | undefined; |
|
||||||
getContractsStub.callsFake((chain) => { |
|
||||||
// Get the "real" return value of getContracts.
|
|
||||||
const contracts: CoreContracts = |
|
||||||
getContractsStub.wrappedMethod.bind(core)(chain); |
|
||||||
|
|
||||||
// Ethers contracts are frozen using Object.freeze, so we make a copy
|
|
||||||
// of the object so we can stub `threshold`.
|
|
||||||
const multisigIsm = Object.assign({}, contracts.multisigIsm); |
|
||||||
|
|
||||||
// Because we are stubbing vaidatorManager.threshold when core.getContracts gets called,
|
|
||||||
// we must ensure we don't try to stub more than once or sinon will complain.
|
|
||||||
if (!thresholdStub) { |
|
||||||
thresholdStub = sinon |
|
||||||
.stub(multisigIsm, 'threshold') |
|
||||||
.callsFake(() => Promise.resolve(threshold)); |
|
||||||
|
|
||||||
contracts.multisigIsm = multisigIsm; |
|
||||||
} |
|
||||||
return contracts; |
|
||||||
}); |
|
||||||
}); |
|
||||||
|
|
||||||
it('scales the gas cost with the quorum threshold', async () => { |
|
||||||
threshold = 2; |
|
||||||
const gasWithThresholdLow = await calculator.estimateGasForProcess( |
|
||||||
origin, |
|
||||||
destination, |
|
||||||
); |
|
||||||
|
|
||||||
threshold = 3; |
|
||||||
const gasWithThresholdHigh = await calculator.estimateGasForProcess( |
|
||||||
origin, |
|
||||||
destination, |
|
||||||
); |
|
||||||
|
|
||||||
expect(gasWithThresholdHigh.gt(gasWithThresholdLow)).to.be.true; |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
@ -1,416 +0,0 @@ |
|||||||
import CoinGecko from 'coingecko-api'; |
|
||||||
import { BigNumber, FixedNumber, ethers } from 'ethers'; |
|
||||||
|
|
||||||
import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; |
|
||||||
import { types, utils } from '@hyperlane-xyz/utils'; |
|
||||||
|
|
||||||
import { CoreEnvironment, HyperlaneCore } from '../core/HyperlaneCore'; |
|
||||||
import { MultiProvider } from '../providers/MultiProvider'; |
|
||||||
import { ChainName } from '../types'; |
|
||||||
import { convertDecimalValue, mulBigAndFixed } from '../utils/number'; |
|
||||||
|
|
||||||
import { CoinGeckoTokenPriceGetter, TokenPriceGetter } from './token-prices'; |
|
||||||
|
|
||||||
/** |
|
||||||
* A note on arithmetic: |
|
||||||
* The ethers.BigNumber implementation behaves very similar to Solidity's |
|
||||||
* number handling by not supporting decimals. To avoid adding another big |
|
||||||
* number implementation as a dependency, we use ethers.FixedNumber, a |
|
||||||
* fixed point implementation intended to model how Solidity's half-supported |
|
||||||
* fixed point numbers work, see https://docs.soliditylang.org/en/v0.8.13/types.html#fixed-point-numbers).
|
|
||||||
* |
|
||||||
* Generally, ceiling is used rather than floor here to err on the side of over- |
|
||||||
* estimating amounts. |
|
||||||
*/ |
|
||||||
|
|
||||||
// If a chain doesn't specify how many decimals their native token has, 18 is used.
|
|
||||||
const DEFAULT_TOKEN_DECIMALS = 18; |
|
||||||
|
|
||||||
// Intrinsic gas for a transaction. Does not consider calldata costs or differences in
|
|
||||||
// intrinsic gas for different chains.
|
|
||||||
const GAS_INTRINSIC = 21_000; |
|
||||||
|
|
||||||
// The gas used to process a message when the quorum size is zero.
|
|
||||||
// Includes intrinsic gas, mailbox overhead gas, all other gas that does not scale with the
|
|
||||||
// quorum size. Excludes the cost of the recipient's handle function.
|
|
||||||
const GAS_OVERHEAD_BASE = 155_000; |
|
||||||
|
|
||||||
// The amount of gas used for each signature when a signed checkpoint
|
|
||||||
// is submitted for verification.
|
|
||||||
// Really observed to be about 6500, but rounding up for safety.
|
|
||||||
const GAS_OVERHEAD_PER_SIGNATURE = 7_500; |
|
||||||
|
|
||||||
export interface InterchainGasCalculatorConfig { |
|
||||||
/** |
|
||||||
* A multiplier applied to the estimated origin token payment amount. |
|
||||||
* This should be high enough to account for movements in token exchange |
|
||||||
* rates and gas prices. |
|
||||||
* Only used for gas payment estimates that are not quoted on-chain. |
|
||||||
* @defaultValue 1.25 |
|
||||||
*/ |
|
||||||
paymentEstimateMultiplier?: string; |
|
||||||
/** |
|
||||||
* An amount of additional gas to add to the estimated gas of processing a message. |
|
||||||
* Only used when estimating a payment from a message. |
|
||||||
* Only used for gas payment estimates that are not quoted on-chain. |
|
||||||
* @defaultValue 50,000 |
|
||||||
*/ |
|
||||||
messageGasEstimateBuffer?: string; |
|
||||||
/** |
|
||||||
* Used to get the native token prices of the origin and destination chains. |
|
||||||
* Only used for gas payment estimates that are not quoted on-chain. |
|
||||||
* @defaultValue An instance of DefaultTokenPriceGetter. |
|
||||||
*/ |
|
||||||
tokenPriceGetter?: TokenPriceGetter; |
|
||||||
} |
|
||||||
|
|
||||||
export interface ParsedMessage { |
|
||||||
origin: ChainName; |
|
||||||
sender: string; |
|
||||||
destination: ChainName; |
|
||||||
recipient: string; |
|
||||||
body: string; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Calculates interchain gas payments. |
|
||||||
*/ |
|
||||||
export class InterchainGasCalculator { |
|
||||||
private core: HyperlaneCore; |
|
||||||
private multiProvider: MultiProvider; |
|
||||||
|
|
||||||
private tokenPriceGetter: TokenPriceGetter; |
|
||||||
|
|
||||||
private paymentEstimateMultiplier: ethers.FixedNumber; |
|
||||||
private messageGasEstimateBuffer: ethers.BigNumber; |
|
||||||
|
|
||||||
static fromEnvironment<Env extends CoreEnvironment>( |
|
||||||
env: Env, |
|
||||||
multiProvider: MultiProvider, |
|
||||||
config?: InterchainGasCalculatorConfig, |
|
||||||
): InterchainGasCalculator { |
|
||||||
const core = HyperlaneCore.fromEnvironment(env, multiProvider); |
|
||||||
return new InterchainGasCalculator(multiProvider, core, config); |
|
||||||
} |
|
||||||
|
|
||||||
constructor( |
|
||||||
multiProvider: MultiProvider, |
|
||||||
core: HyperlaneCore, |
|
||||||
config?: InterchainGasCalculatorConfig, |
|
||||||
) { |
|
||||||
this.multiProvider = multiProvider; |
|
||||||
this.core = core; |
|
||||||
|
|
||||||
if (config?.tokenPriceGetter) { |
|
||||||
this.tokenPriceGetter = config.tokenPriceGetter; |
|
||||||
} else { |
|
||||||
const coinGecko = new CoinGecko(); |
|
||||||
this.tokenPriceGetter = new CoinGeckoTokenPriceGetter(coinGecko); |
|
||||||
} |
|
||||||
|
|
||||||
this.paymentEstimateMultiplier = FixedNumber.from( |
|
||||||
config?.paymentEstimateMultiplier ?? '1.25', |
|
||||||
); |
|
||||||
this.messageGasEstimateBuffer = BigNumber.from( |
|
||||||
config?.messageGasEstimateBuffer ?? 50_000, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Only intended for IGPs that quote gas payments on-chain. |
|
||||||
* Calls the default ISM IGP's `quoteGasPayment` function to get the amount of native tokens |
|
||||||
* required to pay for interchain gas. |
|
||||||
* The default ISM IGP will add any gas overhead amounts related to the Mailbox |
|
||||||
* and default ISM on the destination to the provided gasAmount. |
|
||||||
* @param origin The name of the origin chain. |
|
||||||
* @param destination The name of the destination chain. |
|
||||||
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`. |
|
||||||
* The default IGP is expected to add any gas overhead related to the Mailbox |
|
||||||
* or ISM, so this gas amount is only required to cover the usage of the `handle` |
|
||||||
* function. |
|
||||||
* @returns The amount of native tokens required to pay for interchain gas. |
|
||||||
*/ |
|
||||||
async quoteGasPaymentForDefaultIsmIgp( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
gasAmount: BigNumber, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const igpAddress = |
|
||||||
this.core.getContracts(origin).defaultIsmInterchainGasPaymaster; |
|
||||||
return this.quoteGasPaymentForIGP( |
|
||||||
origin, |
|
||||||
destination, |
|
||||||
gasAmount, |
|
||||||
igpAddress.address, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Only intended for IGPs that quote gas payments on-chain. |
|
||||||
* Calls the "base" IGP's `quoteGasPayment` function to get the amount of native tokens |
|
||||||
* required to pay for interchain gas. |
|
||||||
* This IGP will not apply any overhead gas to the provided gasAmount. |
|
||||||
* @param origin The name of the origin chain. |
|
||||||
* @param destination The name of the destination chain. |
|
||||||
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`. |
|
||||||
* This is expected to be the total amount of gas that a transaction would use |
|
||||||
* on the destination chain. This should consider intrinsic transaction gas, |
|
||||||
* Mailbox overhead gas costs, ISM gas costs, and the recipient's handle function |
|
||||||
* gas cost. |
|
||||||
* @returns The amount of native tokens required to pay for interchain gas. |
|
||||||
*/ |
|
||||||
async quoteGasPayment( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
gasAmount: BigNumber, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const igpAddress = this.core.getContracts(origin).interchainGasPaymaster; |
|
||||||
return this.quoteGasPaymentForIGP( |
|
||||||
origin, |
|
||||||
destination, |
|
||||||
gasAmount, |
|
||||||
igpAddress.address, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Only intended for IGPs that quote gas payments on-chain. |
|
||||||
* Calls the origin's default IGP's `quoteGasPayment` function to get the |
|
||||||
* amount of native tokens required to pay for interchain gas. |
|
||||||
* The default IGP is expected to add any gas overhead related to the Mailbox |
|
||||||
* and ISM to the provided gasAmount. |
|
||||||
* @param origin The name of the origin chain. |
|
||||||
* @param destination The name of the destination chain. |
|
||||||
* @param gasAmount The amount of gas to use when calling `quoteGasPayment`. |
|
||||||
* The default IGP is expected to add any gas overhead related to the Mailbox |
|
||||||
* or ISM, so this gas amount is only required to cover the usage of the `handle` |
|
||||||
* function. |
|
||||||
* @returns The amount of native tokens required to pay for interchain gas. |
|
||||||
*/ |
|
||||||
async quoteGasPaymentForIGP( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
gasAmount: BigNumber, |
|
||||||
interchainGasPaymasterAddress: types.Address, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const originProvider = this.multiProvider.getProvider(origin); |
|
||||||
const igp = InterchainGasPaymaster__factory.connect( |
|
||||||
interchainGasPaymasterAddress, |
|
||||||
originProvider, |
|
||||||
); |
|
||||||
const domainId = this.multiProvider.getDomainId(destination); |
|
||||||
return igp.quoteGasPayment(domainId, gasAmount); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Only intended for IGPs that do *not* quote gas payments on-chain. |
|
||||||
* Given an amount of gas to consume on the destination chain, calculates the |
|
||||||
* estimated payment denominated in the native token of the origin chain. |
|
||||||
* Considers the exchange rate between the native tokens of the origin and |
|
||||||
* destination chains and the suggested gas price of the destination chain. |
|
||||||
* @param origin The name of the origin chain. |
|
||||||
* @param destination The name of the destination chain. |
|
||||||
* @param gas The amount of gas to pay for on the destination chain. |
|
||||||
* @returns An estimated amount of origin chain tokens to cover gas costs on the |
|
||||||
* destination chain. |
|
||||||
*/ |
|
||||||
async estimatePaymentForGas( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
gas: BigNumber, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const destinationGasPrice = await this.getGasPrice(destination); |
|
||||||
const destinationGasCost = gas.mul(destinationGasPrice); |
|
||||||
const originGasCost = await this.convertBetweenTokens( |
|
||||||
destination, |
|
||||||
origin, |
|
||||||
destinationGasCost, |
|
||||||
); |
|
||||||
// Applies a multiplier
|
|
||||||
return mulBigAndFixed( |
|
||||||
originGasCost, |
|
||||||
this.paymentEstimateMultiplier, |
|
||||||
true, // ceil
|
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Only intended for IGPs that do *not* quote gas payments on-chain. |
|
||||||
* Given an amount of gas the message's recipient `handle` function is expected |
|
||||||
* to use, calculates the estimated payment denominated in the native |
|
||||||
* token of the origin chain. Considers the exchange rate between the native |
|
||||||
* tokens of the origin and destination chains, the suggested gas price on |
|
||||||
* the destination chain, gas costs incurred by a relayer when submitting a signed |
|
||||||
* checkpoint to the destination chain, and the overhead gas cost in Inbox of processing |
|
||||||
* a message. |
|
||||||
* @param origin The name of the origin chain. |
|
||||||
* @param destination The name of the destination chain. |
|
||||||
* @param handleGas The amount of gas the recipient `handle` function |
|
||||||
* is estimated to use. |
|
||||||
* @returns An estimated amount of origin chain tokens to cover gas costs of the |
|
||||||
* message on the destination chain. |
|
||||||
*/ |
|
||||||
async estimatePaymentForHandleGas( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
handleGas: BigNumber, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const destinationGas = handleGas.add( |
|
||||||
await this.estimateGasForProcess(origin, destination), |
|
||||||
); |
|
||||||
return this.estimatePaymentForGas(origin, destination, destinationGas); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Only intended for IGPs that do *not* quote gas payments on-chain. |
|
||||||
* Calculates the estimated payment to process the message on its destination chain, |
|
||||||
* denominated in the native token of the origin chain. The gas used by the message's |
|
||||||
* recipient handler function is estimated in an eth_estimateGas call to the |
|
||||||
* destination chain, and is then used to calculate the payment using |
|
||||||
* Currently made private as it does not work properly for Arbitrum. |
|
||||||
* {@link estimatePaymentForHandleGasAmount}. |
|
||||||
* @param message The parsed message to estimate payment for. |
|
||||||
* @returns An estimated amount of origin chain tokens to cover gas costs of the |
|
||||||
* message on the destination chain. |
|
||||||
*/ |
|
||||||
protected async estimatePaymentForMessage( |
|
||||||
message: ParsedMessage, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const handleGas = await this.estimateGasForHandle(message); |
|
||||||
return this.estimatePaymentForHandleGas( |
|
||||||
message.origin, |
|
||||||
message.destination, |
|
||||||
handleGas, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Using the exchange rates provided by tokenPriceGetter, returns the amount of |
|
||||||
* `toChain` native tokens equivalent in value to the provided `fromAmount` of |
|
||||||
* `fromChain` native tokens. Accounts for differences in the decimals of the tokens. |
|
||||||
* @param fromChain The chain whose native token is being converted from. |
|
||||||
* @param toChain The chain whose native token is being converted into. |
|
||||||
* @param fromAmount The amount of `fromChain` native tokens to convert from. |
|
||||||
* @returns The amount of `toChain` native tokens whose value is equivalent to |
|
||||||
* `fromAmount` of `fromChain` native tokens. |
|
||||||
*/ |
|
||||||
protected async convertBetweenTokens( |
|
||||||
fromChain: ChainName, |
|
||||||
toChain: ChainName, |
|
||||||
value: BigNumber, |
|
||||||
): Promise<BigNumber> { |
|
||||||
// Does not factor in differing token decimals.
|
|
||||||
const exchangeRate = await this.tokenPriceGetter.getTokenExchangeRate( |
|
||||||
fromChain, |
|
||||||
toChain, |
|
||||||
); |
|
||||||
|
|
||||||
// 1/100th of a cent
|
|
||||||
const PRECISION = 1000; |
|
||||||
|
|
||||||
return convertDecimalValue( |
|
||||||
value.mul(Math.round(exchangeRate * PRECISION)).div(PRECISION), |
|
||||||
this.tokenDecimals(fromChain), |
|
||||||
this.tokenDecimals(toChain), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Gets a suggested gas price for a chain. |
|
||||||
* @param chainName The name of the chain to get the gas price for |
|
||||||
* @returns The suggested gas price in wei on the destination chain. |
|
||||||
*/ |
|
||||||
protected async getGasPrice(chain: ChainName): Promise<BigNumber> { |
|
||||||
const provider = this.multiProvider.getProvider(chain); |
|
||||||
if (provider == undefined) { |
|
||||||
throw new Error(`Missing provider for ${chain}`); |
|
||||||
} |
|
||||||
return provider.getGasPrice(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Gets the number of decimals of the provided chain's native token. |
|
||||||
* @param chain The chain. |
|
||||||
* @returns The number of decimals of `chain`'s native token. |
|
||||||
*/ |
|
||||||
protected tokenDecimals(chain: ChainName): number { |
|
||||||
return ( |
|
||||||
this.multiProvider.tryGetChainMetadata(chain)?.nativeToken?.decimals ?? |
|
||||||
DEFAULT_TOKEN_DECIMALS |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Estimates the amount of gas used by message's recipient `handle` function |
|
||||||
* on its destination chain. This does not assume the Inbox of the destination |
|
||||||
* chain has a checkpoint that the message is included in, and does not |
|
||||||
* consider intrinsic gas or any "overhead" gas incurred by Inbox.process. |
|
||||||
* The estimated gas returned is the sum of: |
|
||||||
* 1. The estimated gas consumption of a direct call to the `handle` |
|
||||||
* function of the recipient address using the correct parameters and |
|
||||||
* setting the `from` address of the transaction to the address of the inbox. |
|
||||||
* 2. A buffer to account for inaccuracies in the above estimation. |
|
||||||
* @param message The message to estimate recipient `handle` gas usage for. |
|
||||||
* @returns The estimated gas required by the message's recipient handle function |
|
||||||
* on the destination chain. |
|
||||||
*/ |
|
||||||
protected async estimateGasForHandle( |
|
||||||
message: ParsedMessage, |
|
||||||
): Promise<BigNumber> { |
|
||||||
const provider = this.multiProvider.getProvider(message.destination); |
|
||||||
|
|
||||||
const mailbox = this.core.getContracts(message.destination).mailbox |
|
||||||
.contract; |
|
||||||
|
|
||||||
const handlerInterface = new ethers.utils.Interface([ |
|
||||||
'function handle(uint32,bytes32,bytes)', |
|
||||||
]); |
|
||||||
// Estimates a direct call to the `handle` function of the recipient
|
|
||||||
// with the `from` address set to the inbox.
|
|
||||||
// This includes intrinsic gas.
|
|
||||||
const directHandleCallGas = await provider.estimateGas({ |
|
||||||
to: utils.bytes32ToAddress(message.recipient), |
|
||||||
from: mailbox.address, |
|
||||||
data: handlerInterface.encodeFunctionData('handle', [ |
|
||||||
this.multiProvider.getChainId(message.origin), |
|
||||||
utils.addressToBytes32(message.sender), |
|
||||||
message.body, |
|
||||||
]), |
|
||||||
}); |
|
||||||
|
|
||||||
// Subtract intrinsic gas, which is included in directHandleCallGas.
|
|
||||||
// Note the "real" intrinsic gas will always be higher than this.intrinsicGas
|
|
||||||
// due to calldata costs, but this is desired because subtracting the lower bound
|
|
||||||
// this.intrinsicGas will result in a more generous final estimate.
|
|
||||||
return directHandleCallGas |
|
||||||
.add(this.messageGasEstimateBuffer) |
|
||||||
.sub(this.intrinsicGas()); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @returns A generous estimation of the gas consumption of all process |
|
||||||
* operations within Inbox.sol, including intrinsic gas. Does not include any gas |
|
||||||
* consumed within a message's recipient `handle` function. |
|
||||||
*/ |
|
||||||
protected async estimateGasForProcess( |
|
||||||
origin: ChainName, |
|
||||||
destination: ChainName, |
|
||||||
): Promise<BigNumber> { |
|
||||||
// TODO: Check the recipient module
|
|
||||||
const module = this.core.getContracts(destination).multisigIsm; |
|
||||||
const threshold = await module.threshold( |
|
||||||
this.multiProvider.getDomainId(origin), |
|
||||||
); |
|
||||||
return BigNumber.from(threshold) |
|
||||||
.mul(GAS_OVERHEAD_PER_SIGNATURE) |
|
||||||
.add(GAS_OVERHEAD_BASE); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* @returns The intrinsic gas of a basic transaction. Note this does not consider calldata |
|
||||||
* costs or potentially different intrinsic gas costs for different chains. |
|
||||||
*/ |
|
||||||
protected intrinsicGas(): BigNumber { |
|
||||||
return BigNumber.from(GAS_INTRINSIC); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,37 @@ |
|||||||
|
import { |
||||||
|
InterchainGasPaymaster, |
||||||
|
InterchainGasPaymaster__factory, |
||||||
|
OverheadIgp, |
||||||
|
OverheadIgp__factory, |
||||||
|
ProxyAdmin, |
||||||
|
ProxyAdmin__factory, |
||||||
|
StorageGasOracle, |
||||||
|
StorageGasOracle__factory, |
||||||
|
} from '@hyperlane-xyz/core'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { ProxiedContract, TransparentProxyAddresses } from '../proxy'; |
||||||
|
|
||||||
|
export type IgpAddresses = { |
||||||
|
proxyAdmin: types.Address; |
||||||
|
interchainGasPaymaster: types.Address | TransparentProxyAddresses; |
||||||
|
defaultIsmInterchainGasPaymaster: types.Address; |
||||||
|
storageGasOracle: types.Address; |
||||||
|
}; |
||||||
|
|
||||||
|
export type IgpContracts = { |
||||||
|
proxyAdmin: ProxyAdmin; |
||||||
|
interchainGasPaymaster: ProxiedContract< |
||||||
|
InterchainGasPaymaster, |
||||||
|
TransparentProxyAddresses |
||||||
|
>; |
||||||
|
defaultIsmInterchainGasPaymaster: OverheadIgp; |
||||||
|
storageGasOracle: StorageGasOracle; |
||||||
|
}; |
||||||
|
|
||||||
|
export const igpFactories = { |
||||||
|
proxyAdmin: new ProxyAdmin__factory(), |
||||||
|
interchainGasPaymaster: new InterchainGasPaymaster__factory(), |
||||||
|
defaultIsmInterchainGasPaymaster: new OverheadIgp__factory(), |
||||||
|
storageGasOracle: new StorageGasOracle__factory(), |
||||||
|
}; |
@ -1,70 +1,53 @@ |
|||||||
import { LegacyMultisigIsm, Mailbox } from '@hyperlane-xyz/core'; |
import { BigNumber } from 'ethers'; |
||||||
|
|
||||||
|
import { InterchainGasPaymaster, OverheadIgp } from '@hyperlane-xyz/core'; |
||||||
import type { types } from '@hyperlane-xyz/utils'; |
import type { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
import type { CheckerViolation } from '../deploy/types'; |
import type { CheckerViolation } from '../deploy/types'; |
||||||
import { ChainName } from '../types'; |
import { ChainMap } from '../types'; |
||||||
|
|
||||||
export type MultisigIsmConfig = { |
export enum GasOracleContractType { |
||||||
validators: Array<types.Address>; |
StorageGasOracle = 'StorageGasOracle', |
||||||
threshold: number; |
} |
||||||
}; |
|
||||||
|
|
||||||
export type CoreConfig = { |
export type IgpConfig = { |
||||||
multisigIsm: MultisigIsmConfig; |
|
||||||
owner: types.Address; |
owner: types.Address; |
||||||
remove?: boolean; |
beneficiary: types.Address; |
||||||
|
gasOracleType: ChainMap<GasOracleContractType>; |
||||||
}; |
}; |
||||||
|
|
||||||
export enum CoreViolationType { |
export type OverheadIgpConfig = IgpConfig & { |
||||||
MultisigIsm = 'MultisigIsm', |
overhead: ChainMap<number>; |
||||||
Mailbox = 'Mailbox', |
}; |
||||||
ConnectionManager = 'ConnectionManager', |
|
||||||
ValidatorAnnounce = 'ValidatorAnnounce', |
|
||||||
} |
|
||||||
|
|
||||||
export enum MultisigIsmViolationType { |
|
||||||
EnrolledValidators = 'EnrolledValidators', |
|
||||||
Threshold = 'Threshold', |
|
||||||
} |
|
||||||
|
|
||||||
export enum MailboxViolationType { |
export enum IgpViolationType { |
||||||
DefaultIsm = 'DefaultIsm', |
Beneficiary = 'Beneficiary', |
||||||
|
GasOracles = 'GasOracles', |
||||||
|
Overhead = 'Overhead', |
||||||
} |
} |
||||||
|
|
||||||
export interface MailboxViolation extends CheckerViolation { |
export interface IgpViolation extends CheckerViolation { |
||||||
type: CoreViolationType.Mailbox; |
type: 'InterchainGasPaymaster'; |
||||||
contract: Mailbox; |
subType: IgpViolationType; |
||||||
mailboxType: MailboxViolationType; |
|
||||||
} |
} |
||||||
|
|
||||||
export interface MailboxMultisigIsmViolation extends MailboxViolation { |
export interface IgpBeneficiaryViolation extends IgpViolation { |
||||||
|
subType: IgpViolationType.Beneficiary; |
||||||
|
contract: InterchainGasPaymaster; |
||||||
actual: types.Address; |
actual: types.Address; |
||||||
expected: types.Address; |
expected: types.Address; |
||||||
} |
} |
||||||
|
|
||||||
export interface MultisigIsmViolation extends CheckerViolation { |
export interface IgpGasOraclesViolation extends IgpViolation { |
||||||
type: CoreViolationType.MultisigIsm; |
subType: IgpViolationType.GasOracles; |
||||||
contract: LegacyMultisigIsm; |
contract: InterchainGasPaymaster; |
||||||
subType: MultisigIsmViolationType; |
actual: ChainMap<types.Address>; |
||||||
remote: ChainName; |
expected: ChainMap<types.Address>; |
||||||
} |
|
||||||
|
|
||||||
export interface EnrolledValidatorsViolation extends MultisigIsmViolation { |
|
||||||
subType: MultisigIsmViolationType.EnrolledValidators; |
|
||||||
actual: Set<types.Address>; |
|
||||||
expected: Set<types.Address>; |
|
||||||
} |
|
||||||
|
|
||||||
export interface ThresholdViolation extends MultisigIsmViolation { |
|
||||||
subType: MultisigIsmViolationType.Threshold; |
|
||||||
actual: number; |
|
||||||
expected: number; |
|
||||||
} |
} |
||||||
|
|
||||||
export interface ValidatorAnnounceViolation extends CheckerViolation { |
export interface IgpOverheadViolation extends IgpViolation { |
||||||
type: CoreViolationType.ValidatorAnnounce; |
subType: IgpViolationType.Overhead; |
||||||
chain: ChainName; |
contract: OverheadIgp; |
||||||
validator: types.Address; |
actual: ChainMap<BigNumber>; |
||||||
actual: boolean; |
expected: ChainMap<BigNumber>; |
||||||
expected: boolean; |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,39 @@ |
|||||||
|
import { BigNumber } from 'ethers'; |
||||||
|
|
||||||
|
import { GasRouter } from '@hyperlane-xyz/core'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
|
||||||
|
import { HyperlaneApp } from '../HyperlaneApp'; |
||||||
|
import { ChainMap, ChainName } from '../types'; |
||||||
|
import { objMap, promiseObjAll } from '../utils/objects'; |
||||||
|
|
||||||
|
import { RouterContracts } from './types'; |
||||||
|
|
||||||
|
export class RouterApp< |
||||||
|
Contracts extends RouterContracts, |
||||||
|
> extends HyperlaneApp<Contracts> { |
||||||
|
getSecurityModules = (): Promise<ChainMap<types.Address>> => |
||||||
|
promiseObjAll( |
||||||
|
objMap(this.contractsMap, (_, contracts) => |
||||||
|
contracts.router.interchainSecurityModule(), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
getOwners = (): Promise<ChainMap<types.Address>> => |
||||||
|
promiseObjAll( |
||||||
|
objMap(this.contractsMap, (_, contracts) => contracts.router.owner()), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export class GasRouterApp< |
||||||
|
Contracts extends RouterContracts<GasRouter>, |
||||||
|
> extends RouterApp<Contracts> { |
||||||
|
async quoteGasPayment( |
||||||
|
origin: ChainName, |
||||||
|
destination: ChainName, |
||||||
|
): Promise<BigNumber> { |
||||||
|
return this.getContracts(origin).router.quoteGasPayment( |
||||||
|
this.multiProvider.getDomainId(destination), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue