Replace foundry with HyperlaneCoreDeployer (#28)
This PR replaces foundry with the SDK deployment tooling. To deploy hyperlane to a new chain, users add their `ChainMetadata` to `config/chains.ts`, and their `MultisigIsmConfig(s)` to `config/multisig_ism.ts`, and run: ``` yarn ts-node scripts/deploy.ts --local foochain --remotes goerli mumbai --key 0x1234... ``` This script deploys core, IGP, ISM, and TestRecipient contracts to the local chain, and IGP, ISM, and TestRecipient contracts to the remote chains, writing contract addresses to `artifacts/addresses.json`, and agent config to `artifacts/agent_config.json` Users can test their deployment by running: ``` yarn ts-node scripts/test-messages.ts --chains foochain goerli mumbai --key 0x1234... ``` This script dispatches messages between each pair of provided chains, regularly polling the mailboxes to check to see if they were delivered.asaj/ci-2
parent
b839529296
commit
1fdf00846f
@ -1,6 +1,4 @@ |
|||||||
node_modules/ |
node_modules/ |
||||||
broadcast/ |
dist/ |
||||||
out/ |
artifacts/ |
||||||
forge-cache/ |
.yarn* |
||||||
cache/ |
|
||||||
config/*_agent_config.json |
|
||||||
|
@ -1,4 +0,0 @@ |
|||||||
[submodule "lib/forge-std"] |
|
||||||
path = lib/forge-std |
|
||||||
url = https://github.com/foundry-rs/forge-std |
|
||||||
tag = v1.1.1 |
|
@ -0,0 +1,22 @@ |
|||||||
|
{ |
||||||
|
"anvil1": { |
||||||
|
"validatorAnnounce": "0x38a024C0b412B9d1db8BC398140D00F5Af3093D4", |
||||||
|
"proxyAdmin": "0xD42912755319665397FF090fBB63B1a31aE87Cee", |
||||||
|
"mailbox": "0x525C7063E7C20997BaaE9bDa922159152D0e8417", |
||||||
|
"multisigIsm": "0xCA8c8688914e0F7096c920146cd0Ad85cD7Ae8b9", |
||||||
|
"testRecipient": "0x976fcd02f7C4773dd89C309fBF55D5923B4c98a1", |
||||||
|
"storageGasOracle": "0xfcDB4564c18A9134002b9771816092C9693622e3", |
||||||
|
"interchainGasPaymaster": "0x32EEce76C2C2e8758584A83Ee2F522D4788feA0f", |
||||||
|
"defaultIsmInterchainGasPaymaster": "0x02b0B4EFd909240FCB2Eb5FAe060dC60D112E3a4" |
||||||
|
}, |
||||||
|
"anvil2": { |
||||||
|
"multisigIsm": "0xF32D39ff9f6Aa7a7A64d7a4F00a54826Ef791a55", |
||||||
|
"testRecipient": "0x976fcd02f7C4773dd89C309fBF55D5923B4c98a1", |
||||||
|
"proxyAdmin": "0xD42912755319665397FF090fBB63B1a31aE87Cee", |
||||||
|
"storageGasOracle": "0xfcDB4564c18A9134002b9771816092C9693622e3", |
||||||
|
"interchainGasPaymaster": "0x32EEce76C2C2e8758584A83Ee2F522D4788feA0f", |
||||||
|
"defaultIsmInterchainGasPaymaster": "0x02b0B4EFd909240FCB2Eb5FAe060dC60D112E3a4", |
||||||
|
"validatorAnnounce": "0x5FeaeBfB4439F3516c74939A9D04e95AFE82C4ae", |
||||||
|
"mailbox": "0xB0f05d25e41FbC2b52013099ED9616f1206Ae21B" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
for CHAIN in anvil1 anvil2 |
||||||
|
do |
||||||
|
mkdir ./artifacts/$CHAIN ./artifacts/$CHAIN/state \ |
||||||
|
./artifacts/$CHAIN/validator ./artifacts/$CHAIN/relayer |
||||||
|
chmod 777 ./artifacts/$CHAIN -R |
||||||
|
done |
||||||
|
|
||||||
|
anvil --chain-id 31337 -p 8545 --state ./artifacts/anvil1/state > /dev/null & |
||||||
|
ANVIL_1_PID=$! |
||||||
|
|
||||||
|
anvil --chain-id 31338 -p 8555 --state ./artifacts/anvil2/state > /dev/null & |
||||||
|
ANVIL_2_PID=$! |
||||||
|
|
||||||
|
sleep 1 |
||||||
|
|
||||||
|
set -e |
||||||
|
|
||||||
|
for i in "anvil1 anvil2 --no-write-agent-config" "anvil2 anvil1 --write-agent-config" |
||||||
|
do |
||||||
|
set -- $i |
||||||
|
echo "Deploying contracts to $1" |
||||||
|
DEBUG=hyperlane* yarn ts-node scripts/deploy.ts --local $1 --remotes $2 \ |
||||||
|
--key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 $3 |
||||||
|
done |
||||||
|
|
||||||
|
|
||||||
|
for i in "anvil1 8545 ANVIL1" "anvil2 8555 ANVIL2" |
||||||
|
do |
||||||
|
set -- $i |
||||||
|
echo "Running validator on $1" |
||||||
|
# Won't work on anything but linux due to -net=host |
||||||
|
docker run --mount type=bind,source="$(pwd)/artifacts",target=/config --net=host \ |
||||||
|
-e CONFIG_FILES=/config/agent_config.json -e HYP_VALIDATOR_ORIGINCHAINNAME=$1 \ |
||||||
|
-e HYP_VALIDATOR_REORGPERIOD=1 -e HYP_VALIDATOR_INTERVAL=1 \ |
||||||
|
-e HYP_BASE_CHAINS_${3}_CONNECTION_URL=http://127.0.0.1:${2} \ |
||||||
|
-e HYP_VALIDATOR_VALIDATOR_TYPE=hexKey \ |
||||||
|
-e HYP_VALIDATOR_VALIDATOR_KEY=0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 \ |
||||||
|
-e HYP_VALIDATOR_CHECKPOINTSYNCER_TYPE=localStorage \ |
||||||
|
-e HYP_VALIDATOR_CHECKPOINTSYNCER_PATH=/config/${1}/validator \ |
||||||
|
-e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ |
||||||
|
gcr.io/abacus-labs-dev/hyperlane-agent:5bf8aed-20230323-140136 ./validator & |
||||||
|
done |
||||||
|
|
||||||
|
sleep 10 |
||||||
|
|
||||||
|
for i in "anvil1 8545" "anvil2 8555" |
||||||
|
do |
||||||
|
set -- $i |
||||||
|
echo "Announcing validator on $1" |
||||||
|
VALIDATOR_ANNOUNCE_ADDRESS=$(cat ./artifacts/addresses.json | jq -r ".$1.validatorAnnounce") |
||||||
|
VALIDATOR=$(cat ./artifacts/$1/validator/announcement.json | jq -r '.value.validator') |
||||||
|
STORAGE_LOCATION=$(cat ./artifacts/$1/validator/announcement.json | jq -r '.value.storage_location') |
||||||
|
SIGNATURE=$(cat ./artifacts/$1/validator/announcement.json | jq -r '.serialized_signature') |
||||||
|
cast send $VALIDATOR_ANNOUNCE_ADDRESS \ |
||||||
|
"announce(address, string calldata, bytes calldata)(bool)" \ |
||||||
|
$VALIDATOR $STORAGE_LOCATION $SIGNATURE --rpc-url http://127.0.0.1:$2 \ |
||||||
|
--private-key 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba |
||||||
|
done |
||||||
|
|
||||||
|
|
||||||
|
for i in "anvil1 anvil2 ANVIL2" "anvil2 anvil1 ANVIL1" |
||||||
|
do |
||||||
|
set -- $i |
||||||
|
echo "Running relayer on $1" |
||||||
|
docker run --mount type=bind,source="$(pwd)/artifacts",target=/config --net=host \ |
||||||
|
-e CONFIG_FILES=/config/agent_config.json \ |
||||||
|
-e HYP_BASE_CHAINS_ANVIL1_CONNECTION_URL=http://127.0.0.1:8545 \ |
||||||
|
-e HYP_BASE_CHAINS_ANVIL2_CONNECTION_URL=http://127.0.0.1:8555 \ |
||||||
|
-e HYP_BASE_TRACING_LEVEL=info -e HYP_BASE_TRACING_FMT=pretty \ |
||||||
|
-e HYP_RELAYER_ORIGINCHAINNAME=$1 -e HYP_RELAYER_DESTINATIONCHAINNAMES=$2 \ |
||||||
|
-e HYP_RELAYER_ALLOWLOCALCHECKPOINTSYNCERS=true -e HYP_RELAYER_DB=/config/$1/relayer \ |
||||||
|
-e HYP_RELAYER_GASPAYMENTENFORCEMENT='[{"type":"none"}]' \ |
||||||
|
-e HYP_BASE_CHAINS_${3}_SIGNER_TYPE=hexKey \ |
||||||
|
-e HYP_BASE_CHAINS_${3}_SIGNER_KEY=0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 \ |
||||||
|
gcr.io/abacus-labs-dev/hyperlane-agent:5bf8aed-20230323-140136 ./relayer & |
||||||
|
done |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
DEBUG=hyperlane* yarn ts-node scripts/test-messages.ts --chains anvil1 anvil2 --key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --timeout 60 |
||||||
|
|
||||||
|
docker ps -aq | xargs docker stop | xargs docker rm |
||||||
|
kill $ANVIL_1_PID |
||||||
|
kill $ANVIL_2_PID |
@ -0,0 +1,24 @@ |
|||||||
|
import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
export const chains: ChainMap<ChainMetadata> = { |
||||||
|
// ----------- Your chains here -----------------
|
||||||
|
anvil: { |
||||||
|
name: 'anvil1', |
||||||
|
// anvil default chain id
|
||||||
|
chainId: 31337, |
||||||
|
publicRpcUrls: [ |
||||||
|
{ |
||||||
|
http: 'http://127.0.0.1:8545', |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
anvil2: { |
||||||
|
name: 'anvil2', |
||||||
|
chainId: 31338, |
||||||
|
publicRpcUrls: [ |
||||||
|
{ |
||||||
|
http: 'http://127.0.0.1:8555', |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
@ -1,96 +0,0 @@ |
|||||||
{ |
|
||||||
"foochain": { |
|
||||||
"threshold": 1, |
|
||||||
"validators": ["0xb17861defd784d13c257f2430c4724ffa7ffc554"] |
|
||||||
}, |
|
||||||
"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" |
|
||||||
] |
|
||||||
}, |
|
||||||
"moonbasealpha": { |
|
||||||
"threshold": 2, |
|
||||||
"validators": [ |
|
||||||
"0x890c2aeac157c3f067f3e42b8afc797939c59a32", |
|
||||||
"0x1b06d6fe69b972ed7420c83599d5a5c0fc185904", |
|
||||||
"0xe70b85206a968a99a597581f0fa09c99e7681093" |
|
||||||
] |
|
||||||
}, |
|
||||||
"optimismgoerli": { |
|
||||||
"threshold": 2, |
|
||||||
"validators": [ |
|
||||||
"0xbb8d77eefbecc55db6e5a19b0fc3dc290776f189", |
|
||||||
"0x69792508b4ddaa3ca52241ccfcd1e0b119a1ee65", |
|
||||||
"0x11ddb46c6b653e0cdd7ad5bee32ae316e18f8453" |
|
||||||
] |
|
||||||
}, |
|
||||||
"arbitrumgoerli": { |
|
||||||
"threshold": 2, |
|
||||||
"validators": [ |
|
||||||
"0xce798fa21e323f6b24d9838a10ffecdefdfc4f30", |
|
||||||
"0xa792d39dca4426927e0f00c1618d61c9cb41779d", |
|
||||||
"0xdf181fcc11dfac5d01467e4547101a856dd5aa04" |
|
||||||
] |
|
||||||
}, |
|
||||||
"test1": { |
|
||||||
"threshold": 2, |
|
||||||
"validators": [ |
|
||||||
"0xf2561178e8ad8cadd161a855adc6eb02282b426d", |
|
||||||
"0x28d638618aca345450604a4bb17e50ce6fe6ee0e", |
|
||||||
"0x8dc5bd07ffd0a0053bbf4499747ba6da8442ab36" |
|
||||||
] |
|
||||||
}, |
|
||||||
"test2": { |
|
||||||
"threshold": 2, |
|
||||||
"validators": [ |
|
||||||
"0xf2561178e8ad8cadd161a855adc6eb02282b426d", |
|
||||||
"0x28d638618aca345450604a4bb17e50ce6fe6ee0e", |
|
||||||
"0x8dc5bd07ffd0a0053bbf4499747ba6da8442ab36" |
|
||||||
] |
|
||||||
}, |
|
||||||
"test3": { |
|
||||||
"threshold": 2, |
|
||||||
"validators": [ |
|
||||||
"0xf2561178e8ad8cadd161a855adc6eb02282b426d", |
|
||||||
"0x28d638618aca345450604a4bb17e50ce6fe6ee0e", |
|
||||||
"0x8dc5bd07ffd0a0053bbf4499747ba6da8442ab36" |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,19 @@ |
|||||||
|
import { ChainMap, MultisigIsmConfig } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
export const multisigIsmConfig: ChainMap<MultisigIsmConfig> = { |
||||||
|
// ----------- Your chains here -----------------
|
||||||
|
anvil1: { |
||||||
|
threshold: 1, |
||||||
|
validators: [ |
||||||
|
// Last anvil address
|
||||||
|
'0xa0ee7a142d267c1f36714e4a8f75612f20a79720', |
||||||
|
], |
||||||
|
}, |
||||||
|
anvil2: { |
||||||
|
threshold: 1, |
||||||
|
validators: [ |
||||||
|
// Last anvil address
|
||||||
|
'0xa0ee7a142d267c1f36714e4a8f75612f20a79720', |
||||||
|
], |
||||||
|
}, |
||||||
|
}; |
@ -1,119 +0,0 @@ |
|||||||
{ |
|
||||||
"foochain": { |
|
||||||
"id": 1174786466, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"testRecipient": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", |
|
||||||
"create2Factory": "0x0000000000000000000000000000000000000000", |
|
||||||
"mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", |
|
||||||
"validatorAnnounce": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", |
|
||||||
"interchainGasPaymaster": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", |
|
||||||
"proxyAdmin": "0x5FbDB2315678afecb367f032d93F642f64180aa3" |
|
||||||
} |
|
||||||
}, |
|
||||||
"alfajores": { |
|
||||||
"id": 44787, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"fuji": { |
|
||||||
"id": 43113, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"mumbai": { |
|
||||||
"id": 80001, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"bsctestnet": { |
|
||||||
"id": 97, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"goerli": { |
|
||||||
"id": 5, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"moonbasealpha": { |
|
||||||
"id": 1287, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"optimismgoerli": { |
|
||||||
"id": 420, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"arbitrumgoerli": { |
|
||||||
"id": 421613, |
|
||||||
"owner": "0xfaD1C94469700833717Fa8a3017278BC1cA8031C", |
|
||||||
"contracts": { |
|
||||||
"proxyAdmin": "0x4e4D563e2cBFC35c4BC16003685443Fae2FA702f", |
|
||||||
"mailbox": "0xCC737a94FecaeC165AbCf12dED095BB13F037685", |
|
||||||
"interchainGasPaymaster": "0x8f9C3888bFC8a5B25AED115A82eCbb788b196d2a", |
|
||||||
"create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", |
|
||||||
"validatorAnnounce": "0x3Fc742696D5dc9846e04f7A1823D92cb51695f9a", |
|
||||||
"testRecipient": "0xBC3cFeca7Df5A45d61BC60E7898E63670e1654aE" |
|
||||||
} |
|
||||||
}, |
|
||||||
"test1": { |
|
||||||
"id": 13371 |
|
||||||
}, |
|
||||||
"test2": { |
|
||||||
"id": 13372 |
|
||||||
}, |
|
||||||
"test3": { |
|
||||||
"id": 13373 |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,24 @@ |
|||||||
|
import { ChainMap } from '@hyperlane-xyz/sdk'; |
||||||
|
|
||||||
|
export const startBlocks: ChainMap<number> = { |
||||||
|
// --------------- Mainnets ---------------------
|
||||||
|
celo: 16884144, |
||||||
|
ethereum: 16271503, |
||||||
|
avalanche: 24145479, |
||||||
|
polygon: 37313389, |
||||||
|
bsc: 25063295, |
||||||
|
arbitrum: 49073182, |
||||||
|
optimism: 55698988, |
||||||
|
moonbeam: 2595747, |
||||||
|
gnosis: 25900000, |
||||||
|
// --------------- Testnets ---------------------
|
||||||
|
alfajores: 14863532, |
||||||
|
fuji: 16330615, |
||||||
|
mumbai: 29390033, |
||||||
|
bsctestnet: 25001629, |
||||||
|
goerli: 8039005, |
||||||
|
sepolia: 3082913, |
||||||
|
moonbasealpha: 3310405, |
||||||
|
optimismgoerli: 3055263, |
||||||
|
arbitrumgoerli: 1941997, |
||||||
|
}; |
@ -1,14 +0,0 @@ |
|||||||
[profile.default] |
|
||||||
src = 'scripts' |
|
||||||
out = 'out' |
|
||||||
libs = ['node_modules', 'lib'] |
|
||||||
test = 'test' |
|
||||||
cache_path = 'forge-cache' |
|
||||||
allow_paths = ["../node_modules"] |
|
||||||
solc = '0.8.17' |
|
||||||
optimizer = true |
|
||||||
optimizer_runs = 999_999 |
|
||||||
fs_permissions = [{ access = "read-write", path = "./"}] |
|
||||||
|
|
||||||
[profile.ci] |
|
||||||
verbosity = 4 |
|
@ -1,80 +0,0 @@ |
|||||||
// from https://gist.github.com/rmeissner/76d6345796909ee41fb9f36fdaa4d15f |
|
||||||
pragma solidity >=0.8.0 <0.9.0; |
|
||||||
|
|
||||||
library BytesLib { |
|
||||||
function slice( |
|
||||||
bytes memory _bytes, |
|
||||||
uint256 _start, |
|
||||||
uint256 _length |
|
||||||
) internal pure returns (bytes memory) { |
|
||||||
require(_length + 31 >= _length, "slice_overflow"); |
|
||||||
require(_bytes.length >= _start + _length, "slice_outOfBounds"); |
|
||||||
|
|
||||||
bytes memory tempBytes; |
|
||||||
|
|
||||||
// Check length is 0. `iszero` return 1 for `true` and 0 for `false`. |
|
||||||
assembly { |
|
||||||
switch iszero(_length) |
|
||||||
case 0 { |
|
||||||
// Get a location of some free memory and store it in tempBytes as |
|
||||||
// Solidity does for memory variables. |
|
||||||
tempBytes := mload(0x40) |
|
||||||
|
|
||||||
// Calculate length mod 32 to handle slices that are not a multiple of 32 in size. |
|
||||||
let lengthmod := and(_length, 31) |
|
||||||
|
|
||||||
// tempBytes will have the following format in memory: <length><data> |
|
||||||
// When copying data we will offset the start forward to avoid allocating additional memory |
|
||||||
// Therefore part of the length area will be written, but this will be overwritten later anyways. |
|
||||||
// In case no offset is require, the start is set to the data region (0x20 from the tempBytes) |
|
||||||
// mc will be used to keep track where to copy the data to. |
|
||||||
let mc := add( |
|
||||||
add(tempBytes, lengthmod), |
|
||||||
mul(0x20, iszero(lengthmod)) |
|
||||||
) |
|
||||||
let end := add(mc, _length) |
|
||||||
|
|
||||||
for { |
|
||||||
// Same logic as for mc is applied and additionally the start offset specified for the method is added |
|
||||||
let cc := add( |
|
||||||
add( |
|
||||||
add(_bytes, lengthmod), |
|
||||||
mul(0x20, iszero(lengthmod)) |
|
||||||
), |
|
||||||
_start |
|
||||||
) |
|
||||||
} lt(mc, end) { |
|
||||||
// increase `mc` and `cc` to read the next word from memory |
|
||||||
mc := add(mc, 0x20) |
|
||||||
cc := add(cc, 0x20) |
|
||||||
} { |
|
||||||
// Copy the data from source (cc location) to the slice data (mc location) |
|
||||||
mstore(mc, mload(cc)) |
|
||||||
} |
|
||||||
|
|
||||||
// Store the length of the slice. This will overwrite any partial data that |
|
||||||
// was copied when having slices that are not a multiple of 32. |
|
||||||
mstore(tempBytes, _length) |
|
||||||
|
|
||||||
// update free-memory pointer |
|
||||||
// allocating the array padded to 32 bytes like the compiler does now |
|
||||||
// To set the used memory as a multiple of 32, add 31 to the actual memory usage (mc) |
|
||||||
// and remove the modulo 32 (the `and` with `not(31)`) |
|
||||||
mstore(0x40, and(add(mc, 31), not(31))) |
|
||||||
} |
|
||||||
// if we want a zero-length slice let's just return a zero-length array |
|
||||||
default { |
|
||||||
tempBytes := mload(0x40) |
|
||||||
// zero out the 32 bytes slice we are about to return |
|
||||||
// we need to do it because Solidity does not garbage collect |
|
||||||
mstore(tempBytes, 0) |
|
||||||
|
|
||||||
// update free-memory pointer |
|
||||||
// tempBytes uses 32 bytes in memory (even when empty) for the length. |
|
||||||
mstore(0x40, add(tempBytes, 0x20)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return tempBytes; |
|
||||||
} |
|
||||||
} |
|
@ -1,105 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
import "../lib/forge-std/src/console.sol"; |
|
||||||
|
|
||||||
import {ConfigLib} from "../lib/ConfigLib.sol"; |
|
||||||
import {MultisigIsm} from "@hyperlane-xyz/core/contracts/isms/MultisigIsm.sol"; |
|
||||||
import {TransparentUpgradeableProxy} from "@hyperlane-xyz/core/contracts/upgrade/TransparentUpgradeableProxy.sol"; |
|
||||||
|
|
||||||
library CheckLib { |
|
||||||
function check( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config, |
|
||||||
ConfigLib.MultisigIsmConfig memory ismConfig |
|
||||||
) internal view { |
|
||||||
checkOwners(config); |
|
||||||
checkAdmins(config); |
|
||||||
checkMailboxIsm(config, ismConfig); |
|
||||||
console.log( |
|
||||||
"Succesfully checked Hyperlane deployment for %s", |
|
||||||
config.chainName |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
function checkOwners( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config |
|
||||||
) private view { |
|
||||||
require( |
|
||||||
config.admin.owner() == config.owner, |
|
||||||
"ProxyAdmin owner misconfigured" |
|
||||||
); |
|
||||||
require( |
|
||||||
config.igp.owner() == config.owner, |
|
||||||
"InterchainGasPaymaster owner misconfigured" |
|
||||||
); |
|
||||||
require( |
|
||||||
config.mailbox.owner() == config.owner, |
|
||||||
"Mailbox owner misconfigured" |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
function checkAdmins( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config |
|
||||||
) private view { |
|
||||||
require( |
|
||||||
config.admin.getProxyAdmin( |
|
||||||
TransparentUpgradeableProxy(payable(address(config.igp))) |
|
||||||
) == address(config.admin), |
|
||||||
"InterchainGasPaymaster proxy admin misconfigured" |
|
||||||
); |
|
||||||
require( |
|
||||||
config.admin.getProxyAdmin( |
|
||||||
TransparentUpgradeableProxy(payable(address(config.mailbox))) |
|
||||||
) == address(config.admin), |
|
||||||
"Mailbox proxy admin misconfigured" |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
function checkMailboxIsm( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config, |
|
||||||
ConfigLib.MultisigIsmConfig memory ismConfig |
|
||||||
) private view { |
|
||||||
MultisigIsm ism = MultisigIsm(address(config.mailbox.defaultIsm())); |
|
||||||
check(ismConfig, ism, config.owner); |
|
||||||
} |
|
||||||
|
|
||||||
function contains( |
|
||||||
address[] memory set, |
|
||||||
address element |
|
||||||
) internal pure returns (bool) { |
|
||||||
for (uint256 i = 0; i < set.length; i++) { |
|
||||||
if (set[i] == element) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
} |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
function check( |
|
||||||
ConfigLib.MultisigIsmConfig memory config, |
|
||||||
MultisigIsm ism, |
|
||||||
address owner |
|
||||||
) internal view { |
|
||||||
require(ism.owner() == owner, "MultisigIsm owner misconfigured"); |
|
||||||
for (uint256 i = 0; i < config.domains.length; i++) { |
|
||||||
ConfigLib.MultisigIsmDomainConfig memory domain = config.domains[i]; |
|
||||||
require( |
|
||||||
domain.threshold == ism.threshold(domain.domainId), |
|
||||||
string.concat( |
|
||||||
"Default MultisigIsm threshold misconfigured for", |
|
||||||
domain.chainName |
|
||||||
) |
|
||||||
); |
|
||||||
address[] memory validators = ism.validators(domain.domainId); |
|
||||||
require(domain.validators.length == validators.length); |
|
||||||
for (uint256 j = 0; j < validators.length; j++) { |
|
||||||
require( |
|
||||||
contains(domain.validators, validators[j]), |
|
||||||
string.concat( |
|
||||||
"Default MultisigIsm validator set misconfigured for ", |
|
||||||
domain.chainName |
|
||||||
) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,247 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
import "../lib/forge-std/src/Script.sol"; |
|
||||||
import {Vm} from "../lib/forge-std/src/Vm.sol"; |
|
||||||
|
|
||||||
import {BytesLib} from "../lib/BytesLib.sol"; |
|
||||||
import {MultisigIsm} from "@hyperlane-xyz/core/contracts/isms/MultisigIsm.sol"; |
|
||||||
import {Mailbox} from "@hyperlane-xyz/core/contracts/Mailbox.sol"; |
|
||||||
import {InterchainGasPaymaster} from "@hyperlane-xyz/core/contracts/igps/InterchainGasPaymaster.sol"; |
|
||||||
import {ValidatorAnnounce} from "@hyperlane-xyz/core/contracts/ValidatorAnnounce.sol"; |
|
||||||
import {ProxyAdmin} from "@hyperlane-xyz/core/contracts/upgrade/ProxyAdmin.sol"; |
|
||||||
import {TransparentUpgradeableProxy} from "@hyperlane-xyz/core/contracts/upgrade/TransparentUpgradeableProxy.sol"; |
|
||||||
import {Create2Factory} from "@hyperlane-xyz/core/contracts/Create2Factory.sol"; |
|
||||||
import {TestRecipient} from "@hyperlane-xyz/core/contracts/test/TestRecipient.sol"; |
|
||||||
|
|
||||||
library ConfigLib { |
|
||||||
using stdJson for string; |
|
||||||
using BytesLib for bytes; |
|
||||||
|
|
||||||
struct HyperlaneDomainConfig { |
|
||||||
string chainName; |
|
||||||
uint32 domainId; |
|
||||||
address owner; |
|
||||||
Mailbox mailbox; |
|
||||||
InterchainGasPaymaster igp; |
|
||||||
ProxyAdmin admin; |
|
||||||
Create2Factory create2; |
|
||||||
ValidatorAnnounce validatorAnnounce; |
|
||||||
TestRecipient testRecipient; |
|
||||||
} |
|
||||||
|
|
||||||
struct MultisigIsmDomainConfig { |
|
||||||
string chainName; |
|
||||||
uint32 domainId; |
|
||||||
uint8 threshold; |
|
||||||
address[] validators; |
|
||||||
} |
|
||||||
|
|
||||||
struct MultisigIsmConfig { |
|
||||||
MultisigIsmDomainConfig[] domains; |
|
||||||
} |
|
||||||
|
|
||||||
function readContractAddress( |
|
||||||
Vm vm, |
|
||||||
string memory chainName, |
|
||||||
string memory contractName |
|
||||||
) private view returns (address) { |
|
||||||
string memory json = vm.readFile("config/networks.json"); |
|
||||||
string memory prefix = ".contracts."; |
|
||||||
try |
|
||||||
vm.parseJson( |
|
||||||
json, |
|
||||||
string.concat( |
|
||||||
".", |
|
||||||
chainName, |
|
||||||
string.concat(prefix, contractName) |
|
||||||
) |
|
||||||
) |
|
||||||
returns (bytes memory result) { |
|
||||||
address parsedAddr = abi.decode(result, (address)); |
|
||||||
return parsedAddr == address(0x20) ? address(0) : parsedAddr; |
|
||||||
} catch { |
|
||||||
return address(0); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function readHyperlaneDomainConfig( |
|
||||||
Vm vm, |
|
||||||
string memory chainName |
|
||||||
) internal view returns (HyperlaneDomainConfig memory) { |
|
||||||
string memory json = vm.readFile("config/networks.json"); |
|
||||||
// console.log(json); |
|
||||||
uint32 domainId = abi.decode( |
|
||||||
vm.parseJson(json, string.concat(".", chainName, ".id")), |
|
||||||
(uint32) |
|
||||||
); |
|
||||||
address owner = abi.decode( |
|
||||||
vm.parseJson(json, string.concat(".", chainName, ".owner")), |
|
||||||
(address) |
|
||||||
); |
|
||||||
Mailbox mailbox = Mailbox( |
|
||||||
readContractAddress(vm, chainName, "mailbox") |
|
||||||
); |
|
||||||
InterchainGasPaymaster igp = InterchainGasPaymaster( |
|
||||||
readContractAddress(vm, chainName, "interchainGasPaymaster") |
|
||||||
); |
|
||||||
ProxyAdmin admin = ProxyAdmin( |
|
||||||
readContractAddress(vm, chainName, "proxyAdmin") |
|
||||||
); |
|
||||||
Create2Factory create2 = Create2Factory( |
|
||||||
readContractAddress(vm, chainName, "create2Factory") |
|
||||||
); |
|
||||||
TestRecipient recipient = TestRecipient( |
|
||||||
readContractAddress(vm, chainName, "testRecipient") |
|
||||||
); |
|
||||||
ValidatorAnnounce validatorAnnounce = ValidatorAnnounce( |
|
||||||
readContractAddress(vm, chainName, "validatorAnnounce") |
|
||||||
); |
|
||||||
return |
|
||||||
HyperlaneDomainConfig( |
|
||||||
chainName, |
|
||||||
domainId, |
|
||||||
owner, |
|
||||||
mailbox, |
|
||||||
igp, |
|
||||||
admin, |
|
||||||
create2, |
|
||||||
validatorAnnounce, |
|
||||||
recipient |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
function readMultisigIsmDomainConfig( |
|
||||||
Vm vm, |
|
||||||
string memory chainName |
|
||||||
) private view returns (MultisigIsmDomainConfig memory) { |
|
||||||
console.log(chainName); |
|
||||||
string memory json = vm.readFile("config/multisig_ism.json"); |
|
||||||
uint8 threshold = abi.decode( |
|
||||||
vm.parseJson(json, string.concat(".", chainName, ".threshold")), |
|
||||||
(uint8) |
|
||||||
); |
|
||||||
address[] memory validators = abi.decode( |
|
||||||
vm.parseJson(json, string.concat(".", chainName, ".validators")), |
|
||||||
(address[]) |
|
||||||
); |
|
||||||
|
|
||||||
json = vm.readFile("config/networks.json"); |
|
||||||
uint32 domainId = abi.decode( |
|
||||||
vm.parseJson(json, string.concat(".", chainName, ".id")), |
|
||||||
(uint32) |
|
||||||
); |
|
||||||
return |
|
||||||
MultisigIsmDomainConfig(chainName, domainId, threshold, validators); |
|
||||||
} |
|
||||||
|
|
||||||
function readMultisigIsmConfig( |
|
||||||
Vm vm, |
|
||||||
string[] memory chainNames |
|
||||||
) internal view returns (MultisigIsmConfig memory) { |
|
||||||
MultisigIsmDomainConfig[] |
|
||||||
memory domains = new MultisigIsmDomainConfig[](chainNames.length); |
|
||||||
for (uint256 i = 0; i < chainNames.length; i++) { |
|
||||||
string memory chainName = chainNames[i]; |
|
||||||
domains[i] = readMultisigIsmDomainConfig(vm, chainName); |
|
||||||
} |
|
||||||
return MultisigIsmConfig(domains); |
|
||||||
} |
|
||||||
|
|
||||||
function writeAgentConfig( |
|
||||||
HyperlaneDomainConfig memory config, |
|
||||||
Vm vm, |
|
||||||
uint256 startBlock |
|
||||||
) internal { |
|
||||||
string memory baseConfig = "config"; |
|
||||||
vm.serializeString( |
|
||||||
baseConfig, |
|
||||||
"domain", |
|
||||||
vm.toString(uint256(config.domainId)) |
|
||||||
); |
|
||||||
vm.serializeString(baseConfig, "rpcStyle", "ethereum"); |
|
||||||
vm.serializeString(baseConfig, "finalityBlocks", "POPULATE_ME"); |
|
||||||
|
|
||||||
string memory addresses = "addresses"; |
|
||||||
vm.serializeAddress(addresses, "mailbox", address(config.mailbox)); |
|
||||||
vm.serializeAddress( |
|
||||||
addresses, |
|
||||||
"validatorAnnounce", |
|
||||||
address(config.validatorAnnounce) |
|
||||||
); |
|
||||||
vm.serializeString( |
|
||||||
baseConfig, |
|
||||||
"addresses", |
|
||||||
vm.serializeAddress( |
|
||||||
addresses, |
|
||||||
"interchainGasPaymaster", |
|
||||||
address(config.igp) |
|
||||||
) |
|
||||||
); |
|
||||||
|
|
||||||
string memory connection = "connection"; |
|
||||||
vm.serializeString(connection, "type", "http"); |
|
||||||
vm.serializeString( |
|
||||||
baseConfig, |
|
||||||
"connection", |
|
||||||
vm.serializeString(connection, "url", "") |
|
||||||
); |
|
||||||
|
|
||||||
string memory index = "index"; |
|
||||||
vm.serializeString( |
|
||||||
baseConfig, |
|
||||||
"index", |
|
||||||
vm.serializeString(index, "from", vm.toString(startBlock)) |
|
||||||
); |
|
||||||
|
|
||||||
vm.serializeString(baseConfig, "name", config.chainName); |
|
||||||
|
|
||||||
vm |
|
||||||
.serializeString( |
|
||||||
"topLevel", |
|
||||||
"chains", |
|
||||||
vm.serializeString( |
|
||||||
"chainLevel", |
|
||||||
config.chainName, |
|
||||||
vm.serializeString(baseConfig, "protocol", "ethereum") |
|
||||||
) |
|
||||||
) |
|
||||||
.write( |
|
||||||
string.concat( |
|
||||||
"./config/", |
|
||||||
config.chainName, |
|
||||||
"_agent_config.json" |
|
||||||
) |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
function write(HyperlaneDomainConfig memory config, Vm vm) internal { |
|
||||||
string memory contracts = "contracts"; |
|
||||||
vm.serializeAddress(contracts, "mailbox", address(config.mailbox)); |
|
||||||
vm.serializeAddress( |
|
||||||
contracts, |
|
||||||
"interchainGasPaymaster", |
|
||||||
address(config.igp) |
|
||||||
); |
|
||||||
vm.serializeAddress(contracts, "proxyAdmin", address(config.admin)); |
|
||||||
vm.serializeAddress( |
|
||||||
contracts, |
|
||||||
"validatorAnnounce", |
|
||||||
address(config.validatorAnnounce) |
|
||||||
); |
|
||||||
vm.serializeAddress( |
|
||||||
contracts, |
|
||||||
"testRecipient", |
|
||||||
address(config.testRecipient) |
|
||||||
); |
|
||||||
vm |
|
||||||
.serializeAddress( |
|
||||||
contracts, |
|
||||||
"create2Factory", |
|
||||||
address(config.create2) |
|
||||||
) |
|
||||||
.write( |
|
||||||
"./config/networks.json", |
|
||||||
string.concat(".", config.chainName, ".contracts") |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,165 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
import "../lib/forge-std/src/console.sol"; |
|
||||||
|
|
||||||
import {ConfigLib} from "../lib/ConfigLib.sol"; |
|
||||||
import {MultisigIsm} from "@hyperlane-xyz/core/contracts/isms/MultisigIsm.sol"; |
|
||||||
import {Mailbox} from "@hyperlane-xyz/core/contracts/Mailbox.sol"; |
|
||||||
import {InterchainGasPaymaster} from "@hyperlane-xyz/core/contracts/igps/InterchainGasPaymaster.sol"; |
|
||||||
import {ValidatorAnnounce} from "@hyperlane-xyz/core/contracts/ValidatorAnnounce.sol"; |
|
||||||
import {ProxyAdmin} from "@hyperlane-xyz/core/contracts/upgrade/ProxyAdmin.sol"; |
|
||||||
import {TransparentUpgradeableProxy} from "@hyperlane-xyz/core/contracts/upgrade/TransparentUpgradeableProxy.sol"; |
|
||||||
import {Create2Factory} from "@hyperlane-xyz/core/contracts/Create2Factory.sol"; |
|
||||||
import {TestRecipient} from "@hyperlane-xyz/core/contracts/test/TestRecipient.sol"; |
|
||||||
|
|
||||||
library DeployLib { |
|
||||||
function deploy( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config, |
|
||||||
ConfigLib.MultisigIsmConfig memory ismConfig |
|
||||||
) internal { |
|
||||||
deployProxyAdmin(config); |
|
||||||
deployIgp(config); |
|
||||||
deployMailbox(config, ismConfig); |
|
||||||
deployTestRecipient(config); |
|
||||||
deployValidatorAnnounce(config); |
|
||||||
} |
|
||||||
|
|
||||||
function deployValidatorAnnounce( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config |
|
||||||
) private { |
|
||||||
if (address(config.validatorAnnounce) == address(0)) { |
|
||||||
config.validatorAnnounce = new ValidatorAnnounce( |
|
||||||
address(config.mailbox) |
|
||||||
); |
|
||||||
console.log( |
|
||||||
"ValidatorAnnounce deployed at address %s", |
|
||||||
address(config.validatorAnnounce) |
|
||||||
); |
|
||||||
} else { |
|
||||||
console.log( |
|
||||||
"Found ValidatorAnnounce at address %s, skipping deployment", |
|
||||||
address(config.validatorAnnounce) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function deployProxyAdmin( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config |
|
||||||
) private { |
|
||||||
if (address(config.admin) == address(0)) { |
|
||||||
config.admin = new ProxyAdmin(); |
|
||||||
console.log( |
|
||||||
"ProxyAdmin deployed at address %s", |
|
||||||
address(config.admin) |
|
||||||
); |
|
||||||
config.admin.transferOwnership(config.owner); |
|
||||||
} else { |
|
||||||
console.log( |
|
||||||
"Found ProxyAdmin at address %s, skipping deployment", |
|
||||||
address(config.admin) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function deployIgp(ConfigLib.HyperlaneDomainConfig memory config) private { |
|
||||||
require( |
|
||||||
address(config.admin) != address(0), |
|
||||||
"Must deploy ProxyAdmin before InterchainGasPaymaster" |
|
||||||
); |
|
||||||
if (address(config.igp) == address(0)) { |
|
||||||
InterchainGasPaymaster impl = new InterchainGasPaymaster(); |
|
||||||
bytes memory initData = abi.encodeCall( |
|
||||||
InterchainGasPaymaster.initialize, |
|
||||||
() |
|
||||||
); |
|
||||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( |
|
||||||
address(impl), |
|
||||||
address(config.admin), |
|
||||||
initData |
|
||||||
); |
|
||||||
console.log( |
|
||||||
"InterchainGasPaymaster deployed at address %s", |
|
||||||
address(proxy) |
|
||||||
); |
|
||||||
config.igp = InterchainGasPaymaster(address(proxy)); |
|
||||||
config.igp.transferOwnership(config.owner); |
|
||||||
} else { |
|
||||||
console.log( |
|
||||||
"Found InterchainGasPaymaster at address %s, skipping deployment", |
|
||||||
address(config.igp) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function deployMailbox( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config, |
|
||||||
ConfigLib.MultisigIsmConfig memory ismConfig |
|
||||||
) private { |
|
||||||
require( |
|
||||||
address(config.admin) != address(0), |
|
||||||
"Must deploy ProxyAdmin before Mailbox" |
|
||||||
); |
|
||||||
if (address(config.mailbox) == address(0)) { |
|
||||||
MultisigIsm ism = deploy(ismConfig, config.owner); |
|
||||||
|
|
||||||
Mailbox mailbox = new Mailbox(config.domainId); |
|
||||||
bytes memory initData = abi.encodeCall( |
|
||||||
Mailbox.initialize, |
|
||||||
(config.owner, address(ism)) |
|
||||||
); |
|
||||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( |
|
||||||
address(mailbox), |
|
||||||
address(config.admin), |
|
||||||
initData |
|
||||||
); |
|
||||||
console.log("Mailbox deployed at address %s", address(proxy)); |
|
||||||
config.mailbox = Mailbox(address(proxy)); |
|
||||||
} else { |
|
||||||
console.log( |
|
||||||
"Found Mailbox at address %s, skipping deployment", |
|
||||||
address(config.igp) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function deployTestRecipient( |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config |
|
||||||
) private { |
|
||||||
if (address(config.testRecipient) == address(0)) { |
|
||||||
config.testRecipient = new TestRecipient(); |
|
||||||
console.log( |
|
||||||
"TestRecipient deployed at address %s", |
|
||||||
address(config.testRecipient) |
|
||||||
); |
|
||||||
} else { |
|
||||||
console.log( |
|
||||||
"Found TestRecipient at address %s, skipping deployment", |
|
||||||
address(config.igp) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function deploy( |
|
||||||
ConfigLib.MultisigIsmConfig memory config, |
|
||||||
address owner |
|
||||||
) internal returns (MultisigIsm) { |
|
||||||
// Deploy a default MultisigIsm and enroll validators for remote |
|
||||||
// networks. |
|
||||||
MultisigIsm ism = new MultisigIsm(); |
|
||||||
console.log("MultisigIsm deployed at address %s", address(ism)); |
|
||||||
uint32[] memory remoteDomainIds = new uint32[](config.domains.length); |
|
||||||
uint8[] memory remoteThresholds = new uint8[](config.domains.length); |
|
||||||
address[][] memory remoteValidators = new address[][]( |
|
||||||
config.domains.length |
|
||||||
); |
|
||||||
for (uint256 i = 0; i < config.domains.length; i++) { |
|
||||||
remoteDomainIds[i] = config.domains[i].domainId; |
|
||||||
remoteThresholds[i] = config.domains[i].threshold; |
|
||||||
remoteValidators[i] = config.domains[i].validators; |
|
||||||
} |
|
||||||
ism.enrollValidators(remoteDomainIds, remoteValidators); |
|
||||||
ism.setThresholds(remoteDomainIds, remoteThresholds); |
|
||||||
ism.transferOwnership(owner); |
|
||||||
return ism; |
|
||||||
} |
|
||||||
} |
|
@ -1 +0,0 @@ |
|||||||
Subproject commit 181c0c686a8421cfb530e06bda9d00632c9d8637 |
|
@ -1,42 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
|
|
||||||
import "../lib/forge-std/src/Script.sol"; |
|
||||||
import "../lib/forge-std/src/console.sol"; |
|
||||||
|
|
||||||
import {BytesLib} from "../lib/BytesLib.sol"; |
|
||||||
import {ConfigLib} from "../lib/ConfigLib.sol"; |
|
||||||
import {Mailbox} from "@hyperlane-xyz/core/contracts/Mailbox.sol"; |
|
||||||
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; |
|
||||||
|
|
||||||
// TODO: Maybe take recipient as an arg.. |
|
||||||
contract CheckMessage is Script { |
|
||||||
using TypeCasts for address; |
|
||||||
using BytesLib for bytes; |
|
||||||
|
|
||||||
function run() public view { |
|
||||||
string memory destination = vm.envString("DESTINATION"); |
|
||||||
bytes32 messageId = vm.envBytes32("MESSAGE_ID"); |
|
||||||
Mailbox mailbox = ConfigLib |
|
||||||
.readHyperlaneDomainConfig(vm, destination) |
|
||||||
.mailbox; |
|
||||||
bool delivered = mailbox.delivered(messageId); |
|
||||||
if (delivered) { |
|
||||||
console.log( |
|
||||||
"Message ID '%s' HAS been delivered to %s", |
|
||||||
vm.toString(messageId), |
|
||||||
destination |
|
||||||
); |
|
||||||
} else { |
|
||||||
console.log( |
|
||||||
"Message ID '%s' HAS NOT been delivered to %s", |
|
||||||
vm.toString(messageId), |
|
||||||
destination |
|
||||||
); |
|
||||||
} |
|
||||||
console.log( |
|
||||||
"https://explorer.hyperlane.xyz/message/%s", |
|
||||||
string(abi.encodePacked(vm.toString(messageId)).slice(2, 64)) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,35 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
|
|
||||||
import "../lib/forge-std/src/Script.sol"; |
|
||||||
|
|
||||||
import {ConfigLib} from "../lib/ConfigLib.sol"; |
|
||||||
import {CheckLib} from "../lib/CheckLib.sol"; |
|
||||||
import {DeployLib} from "../lib/DeployLib.sol"; |
|
||||||
|
|
||||||
contract DeployCore is Script { |
|
||||||
using ConfigLib for ConfigLib.HyperlaneDomainConfig; |
|
||||||
using CheckLib for ConfigLib.HyperlaneDomainConfig; |
|
||||||
using DeployLib for ConfigLib.HyperlaneDomainConfig; |
|
||||||
|
|
||||||
function run() public { |
|
||||||
string memory local = vm.envString("LOCAL"); |
|
||||||
string[] memory remotes = vm.envString("REMOTES", ","); |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config = ConfigLib |
|
||||||
.readHyperlaneDomainConfig(vm, local); |
|
||||||
ConfigLib.MultisigIsmConfig memory ismConfig = ConfigLib |
|
||||||
.readMultisigIsmConfig(vm, remotes); |
|
||||||
|
|
||||||
vm.startBroadcast(); |
|
||||||
uint256 startBlock = block.number; |
|
||||||
|
|
||||||
config.deploy(ismConfig); |
|
||||||
config.check(ismConfig); |
|
||||||
|
|
||||||
// Write the output to disk |
|
||||||
config.write(vm); |
|
||||||
config.writeAgentConfig(vm, startBlock); |
|
||||||
|
|
||||||
vm.stopBroadcast(); |
|
||||||
} |
|
||||||
} |
|
@ -1,39 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
|
|
||||||
import "../lib/forge-std/src/Script.sol"; |
|
||||||
import "../lib/forge-std/src/console.sol"; |
|
||||||
|
|
||||||
import {ConfigLib} from "../lib/ConfigLib.sol"; |
|
||||||
import {CheckLib} from "../lib/CheckLib.sol"; |
|
||||||
import {DeployLib} from "../lib/DeployLib.sol"; |
|
||||||
import {MultisigIsm} from "@hyperlane-xyz/core/contracts/isms/MultisigIsm.sol"; |
|
||||||
import {TestRecipient} from "@hyperlane-xyz/core/contracts/test/TestRecipient.sol"; |
|
||||||
import {InterchainGasPaymaster} from "@hyperlane-xyz/core/contracts/igps/InterchainGasPaymaster.sol"; |
|
||||||
|
|
||||||
// TODO: Deploy test recipient, maybe write to networks. |
|
||||||
contract DeployMultisigIsm is Script { |
|
||||||
using CheckLib for ConfigLib.MultisigIsmConfig; |
|
||||||
using DeployLib for ConfigLib.MultisigIsmConfig; |
|
||||||
|
|
||||||
function run() public { |
|
||||||
address owner = vm.envAddress("OWNER"); |
|
||||||
string[] memory remotes = vm.envString("REMOTES", ","); |
|
||||||
ConfigLib.MultisigIsmConfig memory config = ConfigLib |
|
||||||
.readMultisigIsmConfig(vm, remotes); |
|
||||||
|
|
||||||
vm.startBroadcast(); |
|
||||||
|
|
||||||
MultisigIsm ism = config.deploy(owner); |
|
||||||
TestRecipient recipient = new TestRecipient(); |
|
||||||
recipient.setInterchainSecurityModule(address(ism)); |
|
||||||
console.log("TestRecipient deployed at address %s", address(recipient)); |
|
||||||
|
|
||||||
InterchainGasPaymaster igp = new InterchainGasPaymaster(); |
|
||||||
console.log( |
|
||||||
"InterchainGasPaymaster deployed at address %s", |
|
||||||
address(igp) |
|
||||||
); |
|
||||||
config.check(ism, owner); |
|
||||||
} |
|
||||||
} |
|
@ -1,44 +0,0 @@ |
|||||||
// SPDX-License-Identifier: UNLICENSED |
|
||||||
pragma solidity ^0.8.17; |
|
||||||
|
|
||||||
import "../lib/forge-std/src/Script.sol"; |
|
||||||
import "../lib/forge-std/src/console.sol"; |
|
||||||
|
|
||||||
import {BytesLib} from "../lib/BytesLib.sol"; |
|
||||||
import {ConfigLib} from "../lib/ConfigLib.sol"; |
|
||||||
import {Mailbox} from "@hyperlane-xyz/core/contracts/Mailbox.sol"; |
|
||||||
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol"; |
|
||||||
|
|
||||||
contract SendTestMessage is Script { |
|
||||||
using TypeCasts for address; |
|
||||||
using BytesLib for bytes; |
|
||||||
|
|
||||||
function run() public { |
|
||||||
string memory origin = vm.envString("ORIGIN"); |
|
||||||
string memory destination = vm.envString("DESTINATION"); |
|
||||||
address recipient = vm.envAddress("RECIPIENT"); |
|
||||||
string memory body = vm.envString("BODY"); |
|
||||||
Mailbox mailbox = ConfigLib |
|
||||||
.readHyperlaneDomainConfig(vm, origin) |
|
||||||
.mailbox; |
|
||||||
ConfigLib.HyperlaneDomainConfig memory config = ConfigLib |
|
||||||
.readHyperlaneDomainConfig(vm, destination); |
|
||||||
|
|
||||||
vm.startBroadcast(); |
|
||||||
bytes32 messageId = mailbox.dispatch( |
|
||||||
config.domainId, |
|
||||||
address(recipient).addressToBytes32(), |
|
||||||
abi.encode(body) |
|
||||||
); |
|
||||||
console.log( |
|
||||||
"Sent message with ID %s from %s to %s", |
|
||||||
vm.toString(messageId), |
|
||||||
origin, |
|
||||||
destination |
|
||||||
); |
|
||||||
console.log( |
|
||||||
"https://explorer.hyperlane.xyz/message/%s", |
|
||||||
string(abi.encodePacked(vm.toString(messageId)).slice(2, 64)) |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,18 @@ |
|||||||
|
import { HyperlanePermissionlessDeployer } from '../src/deployer'; |
||||||
|
|
||||||
|
async function main() { |
||||||
|
const deployer = await HyperlanePermissionlessDeployer.fromArgs(); |
||||||
|
try { |
||||||
|
await deployer.deploy(); |
||||||
|
} catch (e) { |
||||||
|
console.error(`Encountered error during deploy`); |
||||||
|
console.error(e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
main() |
||||||
|
.then(() => console.info('Deploy completed successfully')) |
||||||
|
.catch((e) => { |
||||||
|
console.error(e); |
||||||
|
process.exit(1); |
||||||
|
}); |
@ -0,0 +1,167 @@ |
|||||||
|
import { |
||||||
|
CoreFactories, |
||||||
|
coreFactories, |
||||||
|
DispatchedMessage, |
||||||
|
HyperlaneAddressesMap, |
||||||
|
HyperlaneApp, |
||||||
|
HyperlaneCore, |
||||||
|
HyperlaneIgp, |
||||||
|
MultiProvider, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { |
||||||
|
igpFactories, |
||||||
|
IgpFactories, |
||||||
|
} from '@hyperlane-xyz/sdk/dist/gas/contracts'; |
||||||
|
import { utils } from '@hyperlane-xyz/utils'; |
||||||
|
import { sleep } from '@hyperlane-xyz/utils/dist/src/utils'; |
||||||
|
import { ethers } from 'ethers'; |
||||||
|
import yargs from 'yargs'; |
||||||
|
import { |
||||||
|
assertBalances, |
||||||
|
assertBytes32, |
||||||
|
getMultiProvider, |
||||||
|
mergedContractAddresses, |
||||||
|
} from '../src/config'; |
||||||
|
|
||||||
|
export function getArgs(multiProvider: MultiProvider) { |
||||||
|
// Only accept chains for which we have both a connection and contract addresses
|
||||||
|
const { intersection } = multiProvider.intersect( |
||||||
|
Object.keys(mergedContractAddresses), |
||||||
|
); |
||||||
|
return yargs(process.argv.slice(2)) |
||||||
|
.describe('chains', 'chain to send message from') |
||||||
|
.choices('chains', intersection) |
||||||
|
.demandOption('chains') |
||||||
|
.array('chains') |
||||||
|
.describe('key', 'hexadecimal private key for transaction signing') |
||||||
|
.string('key') |
||||||
|
.coerce('key', assertBytes32) |
||||||
|
.demandOption('key') |
||||||
|
.describe('timeout', 'timeout in seconds') |
||||||
|
.number('timeout') |
||||||
|
.default('timeout', 10 * 60) |
||||||
|
.middleware(assertBalances(multiProvider, (argv) => argv.chains)).argv; |
||||||
|
} |
||||||
|
|
||||||
|
function coreFromAddressesMap( |
||||||
|
addressesMap: HyperlaneAddressesMap<CoreFactories>, |
||||||
|
_multiProvider: MultiProvider, |
||||||
|
): HyperlaneCore { |
||||||
|
const { contractsMap, multiProvider } = HyperlaneApp.fromAddressesMap( |
||||||
|
addressesMap, |
||||||
|
coreFactories, |
||||||
|
_multiProvider, |
||||||
|
); |
||||||
|
return new HyperlaneCore(contractsMap, multiProvider); |
||||||
|
} |
||||||
|
|
||||||
|
function igpFromAddressesMap( |
||||||
|
addressesMap: HyperlaneAddressesMap<IgpFactories>, |
||||||
|
_multiProvider: MultiProvider, |
||||||
|
): HyperlaneIgp { |
||||||
|
const { contractsMap, multiProvider } = HyperlaneApp.fromAddressesMap( |
||||||
|
addressesMap, |
||||||
|
igpFactories, |
||||||
|
_multiProvider, |
||||||
|
); |
||||||
|
return new HyperlaneIgp(contractsMap, multiProvider); |
||||||
|
} |
||||||
|
|
||||||
|
async function main() { |
||||||
|
let timedOut = false; |
||||||
|
const multiProvider = getMultiProvider(); |
||||||
|
let { chains, key, timeout } = await getArgs(multiProvider); |
||||||
|
const timeoutId = setTimeout(() => { |
||||||
|
timedOut = true; |
||||||
|
}, timeout * 1000); |
||||||
|
const signer = new ethers.Wallet(key); |
||||||
|
multiProvider.setSharedSigner(signer); |
||||||
|
const core = coreFromAddressesMap(mergedContractAddresses, multiProvider); |
||||||
|
const igp = igpFromAddressesMap(mergedContractAddresses, multiProvider); |
||||||
|
const messages: Set<DispatchedMessage> = new Set(); |
||||||
|
for (const origin of chains) { |
||||||
|
const mailbox = core.getContracts(origin).mailbox; |
||||||
|
const defaultIgp = |
||||||
|
igp.getContracts(origin).defaultIsmInterchainGasPaymaster; |
||||||
|
for (const destination of chains) { |
||||||
|
const destinationDomain = multiProvider.getDomainId(destination); |
||||||
|
if (origin === destination) continue; |
||||||
|
try { |
||||||
|
const recipient = mergedContractAddresses[destination] |
||||||
|
.testRecipient as string; |
||||||
|
if (!recipient) { |
||||||
|
throw new Error(`Unable to find TestRecipient for ${destination}`); |
||||||
|
} |
||||||
|
const messageTx = await mailbox.dispatch( |
||||||
|
destinationDomain, |
||||||
|
utils.addressToBytes32(recipient), |
||||||
|
'0xdeadbeef', |
||||||
|
); |
||||||
|
const messageReceipt = await multiProvider.handleTx(origin, messageTx); |
||||||
|
const dispatchedMessages = core.getDispatchedMessages(messageReceipt); |
||||||
|
if (dispatchedMessages.length !== 1) continue; |
||||||
|
const dispatchedMessage = dispatchedMessages[0]; |
||||||
|
console.log( |
||||||
|
`Sent message from ${origin} to ${recipient} on ${destination} with message ID ${dispatchedMessage.id}`, |
||||||
|
); |
||||||
|
// Make gas payment...
|
||||||
|
const gasAmount = 100_000; |
||||||
|
const value = await defaultIgp.quoteGasPayment( |
||||||
|
destinationDomain, |
||||||
|
gasAmount, |
||||||
|
); |
||||||
|
const paymentTx = await defaultIgp.payForGas( |
||||||
|
dispatchedMessage.id, |
||||||
|
destinationDomain, |
||||||
|
gasAmount, |
||||||
|
await multiProvider.getSignerAddress(origin), |
||||||
|
{ value }, |
||||||
|
); |
||||||
|
await paymentTx.wait(); |
||||||
|
messages.add(dispatchedMessage); |
||||||
|
} catch (e) { |
||||||
|
console.error( |
||||||
|
`Encountered error sending message from ${origin} to ${destination}`, |
||||||
|
); |
||||||
|
console.error(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
while (messages.size > 0 && !timedOut) { |
||||||
|
for (const message of messages.values()) { |
||||||
|
const origin = multiProvider.getChainName(message.parsed.origin); |
||||||
|
const destination = multiProvider.getChainName( |
||||||
|
message.parsed.destination, |
||||||
|
); |
||||||
|
const mailbox = core.getContracts(destination).mailbox; |
||||||
|
const delivered = await mailbox.delivered(message.id); |
||||||
|
if (delivered) { |
||||||
|
messages.delete(message); |
||||||
|
console.log( |
||||||
|
`Message from ${origin} to ${destination} with ID ${ |
||||||
|
message!.id |
||||||
|
} was delivered`,
|
||||||
|
); |
||||||
|
} else { |
||||||
|
console.log( |
||||||
|
`Message from ${origin} to ${destination} with ID ${ |
||||||
|
message!.id |
||||||
|
} has not yet been delivered`,
|
||||||
|
); |
||||||
|
} |
||||||
|
await sleep(5000); |
||||||
|
} |
||||||
|
} |
||||||
|
clearTimeout(timeoutId); |
||||||
|
if (timedOut) { |
||||||
|
console.error('Timed out waiting for messages to be delivered'); |
||||||
|
process.exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
main() |
||||||
|
.then(() => console.info('Testing complete')) |
||||||
|
.catch((e) => { |
||||||
|
console.error(e); |
||||||
|
process.exit(1); |
||||||
|
}); |
@ -0,0 +1,53 @@ |
|||||||
|
import { TestRecipient, TestRecipient__factory } from '@hyperlane-xyz/core'; |
||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
ChainName, |
||||||
|
HyperlaneDeployer, |
||||||
|
MultiProvider, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { types } from '@hyperlane-xyz/utils'; |
||||||
|
import debug from 'debug'; |
||||||
|
|
||||||
|
// Maps chain name to ISM address
|
||||||
|
export type TestRecipientConfig = { |
||||||
|
ism: types.Address; |
||||||
|
}; |
||||||
|
|
||||||
|
export type TestRecipientContracts = { |
||||||
|
testRecipient: TestRecipient; |
||||||
|
}; |
||||||
|
|
||||||
|
export type TestRecipientAddresses = { |
||||||
|
testRecipient: types.Address; |
||||||
|
}; |
||||||
|
|
||||||
|
export const testRecipientFactories = { |
||||||
|
testRecipient: new TestRecipient__factory(), |
||||||
|
}; |
||||||
|
|
||||||
|
export class HyperlaneTestRecipientDeployer extends HyperlaneDeployer< |
||||||
|
TestRecipientConfig, |
||||||
|
typeof testRecipientFactories |
||||||
|
> { |
||||||
|
constructor( |
||||||
|
multiProvider: MultiProvider, |
||||||
|
configMap: ChainMap<TestRecipientConfig>, |
||||||
|
factoriesOverride = testRecipientFactories, |
||||||
|
) { |
||||||
|
super(multiProvider, configMap, factoriesOverride, { |
||||||
|
logger: debug('hyperlane:TestRecipientDeployer'), |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async deployContracts( |
||||||
|
chain: ChainName, |
||||||
|
config: TestRecipientConfig, |
||||||
|
): Promise<TestRecipientContracts> { |
||||||
|
const testRecipient = await this.deployContract(chain, 'testRecipient', []); |
||||||
|
const tx = testRecipient.setInterchainSecurityModule(config.ism); |
||||||
|
await this.multiProvider.handleTx(chain, tx); |
||||||
|
return { |
||||||
|
testRecipient, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,159 @@ |
|||||||
|
import { |
||||||
|
buildAgentConfig, |
||||||
|
ChainMap, |
||||||
|
ChainName, |
||||||
|
CoreConfig, |
||||||
|
defaultMultisigIsmConfigs, |
||||||
|
GasOracleContractType, |
||||||
|
HyperlaneAddressesMap, |
||||||
|
HyperlaneAgentAddresses, |
||||||
|
MultiProvider, |
||||||
|
MultisigIsmConfig, |
||||||
|
multisigIsmVerificationCost, |
||||||
|
objFilter, |
||||||
|
objMerge, |
||||||
|
OverheadIgpConfig, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import { hyperlaneEnvironments } from '@hyperlane-xyz/sdk/dist/consts/environments'; |
||||||
|
import { types, utils } from '@hyperlane-xyz/utils'; |
||||||
|
import { ethers } from 'ethers'; |
||||||
|
import artifactAddresses from '../artifacts/addresses.json'; |
||||||
|
import { chains } from '../config/chains'; |
||||||
|
import { multisigIsmConfig } from '../config/multisig_ism'; |
||||||
|
import { readJSON } from './json'; |
||||||
|
|
||||||
|
export function getMultiProvider() { |
||||||
|
const multiProvider = new MultiProvider(); |
||||||
|
for (const metadata of Object.values(chains)) { |
||||||
|
multiProvider.addChain(metadata); |
||||||
|
} |
||||||
|
return multiProvider; |
||||||
|
} |
||||||
|
|
||||||
|
export function assertBytes32(value: string): string { |
||||||
|
if ( |
||||||
|
ethers.utils.isHexString(value) && |
||||||
|
ethers.utils.hexDataLength(value) == 32 |
||||||
|
) { |
||||||
|
return value; |
||||||
|
} |
||||||
|
throw new Error(`Invalid value ${value}, must be a 32 byte hex string`); |
||||||
|
} |
||||||
|
|
||||||
|
export function assertBalances( |
||||||
|
multiProvider: MultiProvider, |
||||||
|
chainsFunc: (argv: any) => ChainName[], |
||||||
|
): (argv: any) => Promise<void> { |
||||||
|
return async (argv: any) => { |
||||||
|
const chains = chainsFunc(argv); |
||||||
|
const signer = new ethers.Wallet(argv.key); |
||||||
|
const address = await signer.getAddress(); |
||||||
|
Promise.all( |
||||||
|
chains.map(async (chain: ChainName) => { |
||||||
|
const balance = await multiProvider |
||||||
|
.getProvider(chain) |
||||||
|
.getBalance(address); |
||||||
|
if (balance.isZero()) |
||||||
|
throw new Error(`${address} has no balance on ${chain}`); |
||||||
|
}), |
||||||
|
); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export function coerceAddressToBytes32(value: string): string { |
||||||
|
if (ethers.utils.isHexString(value)) { |
||||||
|
const length = ethers.utils.hexDataLength(value); |
||||||
|
if (length == 32) { |
||||||
|
return value; |
||||||
|
} else if (length == 20) { |
||||||
|
return utils.addressToBytes32(value); |
||||||
|
} |
||||||
|
} |
||||||
|
throw new Error(`Invalid value ${value}, must be a 20 or 32 byte hex string`); |
||||||
|
} |
||||||
|
|
||||||
|
export function buildCoreConfig( |
||||||
|
owner: types.Address, |
||||||
|
chains: ChainName[], |
||||||
|
): ChainMap<CoreConfig> { |
||||||
|
const configMap: ChainMap<CoreConfig> = {}; |
||||||
|
for (const local of chains) { |
||||||
|
const multisigIsmConfigs: ChainMap<MultisigIsmConfig> = {}; |
||||||
|
const mergedMultisigIsmConfig: ChainMap<MultisigIsmConfig> = objMerge( |
||||||
|
defaultMultisigIsmConfigs, |
||||||
|
multisigIsmConfig, |
||||||
|
); |
||||||
|
for (const remote of chains) { |
||||||
|
if (local === remote) continue; |
||||||
|
multisigIsmConfigs[remote] = mergedMultisigIsmConfig[remote]; |
||||||
|
} |
||||||
|
configMap[local] = { |
||||||
|
owner, |
||||||
|
multisigIsm: multisigIsmConfigs, |
||||||
|
}; |
||||||
|
} |
||||||
|
return configMap; |
||||||
|
} |
||||||
|
|
||||||
|
export function buildIgpConfig( |
||||||
|
owner: types.Address, |
||||||
|
chains: ChainName[], |
||||||
|
): ChainMap<OverheadIgpConfig> { |
||||||
|
const configMap: ChainMap<OverheadIgpConfig> = {}; |
||||||
|
for (const local of chains) { |
||||||
|
const overhead: ChainMap<number> = {}; |
||||||
|
const gasOracleType: ChainMap<GasOracleContractType> = {}; |
||||||
|
for (const remote of chains) { |
||||||
|
if (local === remote) continue; |
||||||
|
overhead[remote] = multisigIsmVerificationCost( |
||||||
|
multisigIsmConfig[remote].threshold, |
||||||
|
multisigIsmConfig[remote].validators.length, |
||||||
|
); |
||||||
|
gasOracleType[remote] = GasOracleContractType.StorageGasOracle; |
||||||
|
} |
||||||
|
configMap[local] = { |
||||||
|
owner, |
||||||
|
beneficiary: owner, |
||||||
|
gasOracleType, |
||||||
|
overhead, |
||||||
|
}; |
||||||
|
} |
||||||
|
return configMap; |
||||||
|
} |
||||||
|
|
||||||
|
export const sdkContractAddresses = { |
||||||
|
...hyperlaneEnvironments.testnet, |
||||||
|
...hyperlaneEnvironments.mainnet, |
||||||
|
}; |
||||||
|
|
||||||
|
export const mergedContractAddresses = objMerge( |
||||||
|
sdkContractAddresses, |
||||||
|
artifactAddresses, |
||||||
|
); |
||||||
|
|
||||||
|
export function buildOverriddenAgentConfig( |
||||||
|
chains: ChainName[], |
||||||
|
multiProvider: MultiProvider, |
||||||
|
startBlocks: ChainMap<number>, |
||||||
|
) { |
||||||
|
const localAddresses = readJSON('./artifacts', 'addresses.json'); |
||||||
|
const mergedAddresses: HyperlaneAddressesMap<any> = objMerge( |
||||||
|
sdkContractAddresses, |
||||||
|
localAddresses, |
||||||
|
); |
||||||
|
const filteredAddresses: ChainMap<HyperlaneAgentAddresses> = objFilter( |
||||||
|
mergedAddresses, |
||||||
|
(chain, v): v is HyperlaneAgentAddresses => |
||||||
|
chains.includes(chain) && |
||||||
|
!!v.mailbox && |
||||||
|
!!v.interchainGasPaymaster && |
||||||
|
!!v.validatorAnnounce, |
||||||
|
); |
||||||
|
|
||||||
|
return buildAgentConfig( |
||||||
|
chains, |
||||||
|
multiProvider, |
||||||
|
filteredAddresses, |
||||||
|
startBlocks, |
||||||
|
); |
||||||
|
} |
@ -0,0 +1,172 @@ |
|||||||
|
import { |
||||||
|
ChainMap, |
||||||
|
ChainName, |
||||||
|
CoreFactories, |
||||||
|
HyperlaneContractsMap, |
||||||
|
HyperlaneCoreDeployer, |
||||||
|
HyperlaneIgpDeployer, |
||||||
|
MultiProvider, |
||||||
|
objMap, |
||||||
|
objMerge, |
||||||
|
serializeContractsMap, |
||||||
|
} from '@hyperlane-xyz/sdk'; |
||||||
|
import yargs from 'yargs'; |
||||||
|
|
||||||
|
import { LegacyMultisigIsm } from '@hyperlane-xyz/core'; |
||||||
|
import { ethers } from 'ethers'; |
||||||
|
import { multisigIsmConfig } from '../config/multisig_ism'; |
||||||
|
import { startBlocks } from '../config/start_blocks'; |
||||||
|
import { |
||||||
|
assertBalances, |
||||||
|
assertBytes32, |
||||||
|
buildCoreConfig, |
||||||
|
buildIgpConfig, |
||||||
|
buildOverriddenAgentConfig, |
||||||
|
getMultiProvider, |
||||||
|
} from './config'; |
||||||
|
import { mergeJSON, writeJSON } from './json'; |
||||||
|
import { |
||||||
|
HyperlaneTestRecipientDeployer, |
||||||
|
TestRecipientConfig, |
||||||
|
} from './TestRecipientDeployer'; |
||||||
|
|
||||||
|
export function getArgs(multiProvider: MultiProvider) { |
||||||
|
// For each chain, we need:
|
||||||
|
// - ChainMetadata for the MultiProvider
|
||||||
|
// - A MultisigIsmConfig
|
||||||
|
const { intersection } = multiProvider.intersect( |
||||||
|
Object.keys(multisigIsmConfig), |
||||||
|
); |
||||||
|
|
||||||
|
return yargs(process.argv.slice(2)) |
||||||
|
.describe('local', 'The chain to deploy to') |
||||||
|
.choices('local', intersection) |
||||||
|
.demandOption('local') |
||||||
|
.array('remotes') |
||||||
|
.describe( |
||||||
|
'remotes', |
||||||
|
"The chains with which 'local' will be able to send and receive messages", |
||||||
|
) |
||||||
|
.choices('remotes', intersection) |
||||||
|
.demandOption('remotes') |
||||||
|
.describe('key', 'A hexadecimal private key for transaction signing') |
||||||
|
.string('key') |
||||||
|
.coerce('key', assertBytes32) |
||||||
|
.demandOption('key') |
||||||
|
.middleware( |
||||||
|
assertBalances(multiProvider, (argv) => argv.remotes.concat(argv.local)), |
||||||
|
) |
||||||
|
.describe('write-agent-config', 'Whether or not to write agent config') |
||||||
|
.default('write-agent-config', true) |
||||||
|
.boolean('write-agent-config').argv; |
||||||
|
} |
||||||
|
|
||||||
|
type MultisigIsmContracts = { |
||||||
|
multisigIsm: LegacyMultisigIsm; |
||||||
|
}; |
||||||
|
|
||||||
|
export class HyperlanePermissionlessDeployer { |
||||||
|
constructor( |
||||||
|
public readonly multiProvider: MultiProvider, |
||||||
|
public readonly signer: ethers.Signer, |
||||||
|
public readonly local: ChainName, |
||||||
|
public readonly remotes: ChainName[], |
||||||
|
public readonly writeAgentConfig?: boolean, |
||||||
|
) {} |
||||||
|
|
||||||
|
static async fromArgs(): Promise<HyperlanePermissionlessDeployer> { |
||||||
|
const multiProvider = getMultiProvider(); |
||||||
|
const { local, remotes, key, writeAgentConfig } = await getArgs( |
||||||
|
multiProvider, |
||||||
|
); |
||||||
|
const signer = new ethers.Wallet(key); |
||||||
|
multiProvider.setSharedSigner(signer); |
||||||
|
|
||||||
|
return new HyperlanePermissionlessDeployer( |
||||||
|
multiProvider, |
||||||
|
signer, |
||||||
|
local, |
||||||
|
remotes as unknown as string[], |
||||||
|
writeAgentConfig, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
get chains(): ChainName[] { |
||||||
|
return this.remotes.concat([this.local]); |
||||||
|
} |
||||||
|
|
||||||
|
async deploy(): Promise<void> { |
||||||
|
let contracts: HyperlaneContractsMap<CoreFactories> = {}; |
||||||
|
const owner = await this.signer.getAddress(); |
||||||
|
// First, deploy core contracts to the local chain
|
||||||
|
// NB: We create core configs for *all* chains because
|
||||||
|
// we also use coreDeployer to deploy MultisigIsms.
|
||||||
|
// Once we move that out to a HyperlaneIsmDeployer
|
||||||
|
// we can just do:
|
||||||
|
// const coreContracts = await coreDeployer.deploy();
|
||||||
|
const coreConfig = buildCoreConfig(owner, this.chains); |
||||||
|
const coreDeployer = new HyperlaneCoreDeployer( |
||||||
|
this.multiProvider, |
||||||
|
coreConfig, |
||||||
|
); |
||||||
|
const coreContracts: HyperlaneContractsMap<CoreFactories> = {}; |
||||||
|
coreContracts[this.local] = await coreDeployer.deployContracts( |
||||||
|
this.local, |
||||||
|
coreConfig[this.local], |
||||||
|
); |
||||||
|
contracts = objMerge(contracts, coreContracts); |
||||||
|
|
||||||
|
// Next, deploy MultisigIsms to the remote chains
|
||||||
|
// TODO: Would be cleaner if using HyperlaneIsmDeployer
|
||||||
|
const isms: ChainMap<MultisigIsmContracts> = {}; |
||||||
|
isms[this.local] = { |
||||||
|
multisigIsm: coreContracts[this.local].multisigIsm, |
||||||
|
}; |
||||||
|
for (const remote of this.remotes) { |
||||||
|
isms[remote] = { |
||||||
|
multisigIsm: await coreDeployer.deployLegacyMultisigIsm(remote), |
||||||
|
}; |
||||||
|
} |
||||||
|
contracts = objMerge(contracts, isms); |
||||||
|
|
||||||
|
// Next, deploy TestRecipients to all chains
|
||||||
|
const testRecipientConfig: ChainMap<TestRecipientConfig> = objMap( |
||||||
|
isms, |
||||||
|
(chain, ism) => { |
||||||
|
return { ism: ism.multisigIsm.address }; |
||||||
|
}, |
||||||
|
); |
||||||
|
const testRecipientDeployer = new HyperlaneTestRecipientDeployer( |
||||||
|
this.multiProvider, |
||||||
|
testRecipientConfig, |
||||||
|
); |
||||||
|
const testRecipients = await testRecipientDeployer.deploy(); |
||||||
|
contracts = objMerge(contracts, testRecipients); |
||||||
|
|
||||||
|
// Finally, deploy IGPs to all chains
|
||||||
|
// TODO: Reuse ProxyAdmin on local chain... right now *two* ProxyAdmins are deployed
|
||||||
|
const igpConfig = buildIgpConfig(owner, this.chains); |
||||||
|
const igpDeployer = new HyperlaneIgpDeployer(this.multiProvider, igpConfig); |
||||||
|
const igps = await igpDeployer.deploy(); |
||||||
|
contracts = objMerge(contracts, igps); |
||||||
|
|
||||||
|
const addresses = serializeContractsMap(contracts); |
||||||
|
// Write contract address artifacts
|
||||||
|
mergeJSON('./artifacts/', 'addresses.json', addresses); |
||||||
|
|
||||||
|
startBlocks[this.local] = await this.multiProvider |
||||||
|
.getProvider(this.local) |
||||||
|
.getBlockNumber(); |
||||||
|
|
||||||
|
if (this.writeAgentConfig) { |
||||||
|
const agentConfig = buildOverriddenAgentConfig( |
||||||
|
this.chains, |
||||||
|
this.multiProvider, |
||||||
|
startBlocks, |
||||||
|
); |
||||||
|
|
||||||
|
// Write AgentConfig artifacts
|
||||||
|
writeJSON('./artifacts/', 'agent_config.json', agentConfig); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
import { objMerge } from '@hyperlane-xyz/sdk'; |
||||||
|
import fs from 'fs'; |
||||||
|
import path from 'path'; |
||||||
|
|
||||||
|
export function writeJSON(directory: string, filename: string, obj: any) { |
||||||
|
if (!fs.existsSync(directory)) { |
||||||
|
fs.mkdirSync(directory, { recursive: true }); |
||||||
|
} |
||||||
|
fs.writeFileSync( |
||||||
|
path.join(directory, filename), |
||||||
|
JSON.stringify(obj, null, 2) + '\n', |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
export function mergeJSON(directory: string, filename: string, obj: any) { |
||||||
|
if (fs.existsSync(path.join(directory, filename))) { |
||||||
|
const previous = readJSON(directory, filename); |
||||||
|
writeJSON(directory, filename, objMerge(previous, obj)); |
||||||
|
} else { |
||||||
|
writeJSON(directory, filename, obj); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function readFileAtPath(filepath: string) { |
||||||
|
if (!fs.existsSync(filepath)) { |
||||||
|
throw Error(`file doesn't exist at ${filepath}`); |
||||||
|
} |
||||||
|
return fs.readFileSync(filepath, 'utf8'); |
||||||
|
} |
||||||
|
|
||||||
|
export function readJSONAtPath(filepath: string) { |
||||||
|
return JSON.parse(readFileAtPath(filepath)); |
||||||
|
} |
||||||
|
|
||||||
|
export function readJSON(directory: string, filename: string) { |
||||||
|
return readJSONAtPath(path.join(directory, filename)); |
||||||
|
} |
@ -0,0 +1,28 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"outDir": "./dist/", |
||||||
|
"rootDir": "./", |
||||||
|
"declaration": true, |
||||||
|
"declarationMap": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"forceConsistentCasingInFileNames": true, |
||||||
|
"incremental": false, |
||||||
|
"lib": ["es2015", "es5", "dom", "es2021"], |
||||||
|
"module": "commonjs", |
||||||
|
"moduleResolution": "node", |
||||||
|
"noEmitOnError": true, |
||||||
|
"noFallthroughCasesInSwitch": true, |
||||||
|
"noImplicitAny": true, |
||||||
|
"noImplicitReturns": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"preserveSymlinks": true, |
||||||
|
"preserveWatchOutput": true, |
||||||
|
"pretty": false, |
||||||
|
"resolveJsonModule": true, |
||||||
|
"sourceMap": true, |
||||||
|
"target": "es6", |
||||||
|
"strict": true |
||||||
|
}, |
||||||
|
"exclude": ["./node_modules/", "./dist/"], |
||||||
|
"include": ["./src/**/*.ts", "./config/**/*.ts", "./scripts/*.ts"] |
||||||
|
} |
Loading…
Reference in new issue