diff --git a/README.md b/README.md index 148ce0a..63d920b 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,28 @@ Response: { ``` +## Getting signatures to change DVN onchain configs + +### Setup + +Depending on the environment (i.e testnet/mainnet), fill in the appropriate information regarding DVN addresses and KMS key ids in the `scripts/data` folder. The file names should be `dvn-addresses-.json` and `kms-keyids-.json` respectively. Take a look at existing testnet examples in the `scripts/data` folder to see how they need to be filled. + +### Signatures for changing quorum + +``` +ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts -e -c --oldQuorum --newQuorum +# e.g. ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts -e testnet -c bsc,avalanche --oldQuorum 2 --newQuorum 1 +``` + +### Signatures for adding/removing a signer + +When adding a signer, you need to set `--shouldRevoke` arg as 0, when removing, you need to set it as 1. + +``` +ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts -e -c --q --signerAddress --shouldRevoke <0 or 1> +# e.g. ts-node scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts -e testnet -c bsc,avalanche -q 1 --signerAddress 0x85e4857b7f15bbbbbc72d933a6357d3c22a0bbc7 --shouldRevoke 1 +``` + ## Troubleshooting ### 1. Error creating KeyRing diff --git a/scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts b/scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts index 3a2cac0..234a1a0 100644 --- a/scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts +++ b/scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts @@ -1,15 +1,19 @@ import { ethers } from 'ethers' -import { GcpKmsSigner } from 'ethers-gcp-kms-signer' import fs from 'fs' import path from 'path' import { parse } from 'ts-command-line-args' -import { getChainIdForNetwork } from '@layerzerolabs/lz-definitions' - -import { GcpKmsKey } from './kms' +import { getGcpKmsSigners } from './kms' +import { + getSignatures, + getSignaturesPayload, + getVId, + hashCallData, +} from './utils' const PATH = path.join(__dirname) const FILE_PATH = `${PATH}/signer-change-payloads.json` +const EXPIRATION = Date.now() + 7 * 24 * 60 * 60 * 1000 // 1 week expiration from now /** * This script creates signature payloads to be submitted by an Admin of the DVN contract @@ -45,76 +49,49 @@ const args = parse({ }) const setSignerFunctionSig = 'function setSigner(address _signer, bool _active)' -const EXPIRATION = Date.now() + 7 * 24 * 60 * 60 * 1000 // 1 week expiration from now - const iface = new ethers.utils.Interface([setSignerFunctionSig]) - const getCallData = (signerAddress: string, active: boolean) => { return iface.encodeFunctionData('setSigner', [signerAddress, active]) } -const hashCallData = (target: string, callData: string, vId: string) => { - return ethers.utils.keccak256( - ethers.utils.solidityPack( - ['uint32', 'address', 'uint', 'bytes'], - [vId, target, EXPIRATION, callData], - ), - ) -} - -interface Signature { - signature: string - address: string -} - const main = async () => { const { environment, chainNames, quorum, signerAddress, shouldRevoke } = args if (shouldRevoke !== 0 && shouldRevoke !== 1) { throw new Error('shouldRevoke must be 0 or 1') } + const dvnAddresses = require(`./data/dvn-addresses-${environment}.json`) + const keyIds = require(`./data/kms-keyids-${environment}.json`) - const signers = await Promise.all( - keyIds.map(async (credentials: GcpKmsKey) => { - return new GcpKmsSigner(credentials) - }), - ) + const signers = await getGcpKmsSigners(keyIds) + const availableChainNames = chainNames.split(',') const results: { [chainName: string]: any } = {} await Promise.all( availableChainNames.map(async (chainName) => { results[chainName] = results[chainName] || {} - const vId = getChainIdForNetwork(chainName, environment, '2') + const vId = getVId(chainName, environment) const callData = getCallData( signerAddress, shouldRevoke === 1 ? false : true, ) - const hash = hashCallData(dvnAddresses[chainName], callData, vId) - // sign - const signatures = await Promise.all( - signers.map(async (signer) => ({ - signature: await signer.signMessage( - ethers.utils.arrayify(hash), - ), - address: await signer.getAddress(), - })), - ) - signatures.sort((a: Signature, b: Signature) => - a.address.localeCompare(b.address), - ) - const signaturesForQuorum = signatures.slice(0, quorum) - const signaturePayload = ethers.utils.solidityPack( - signaturesForQuorum.map(() => 'bytes'), - signaturesForQuorum.map((s: Signature) => s.signature), + const hash = hashCallData( + dvnAddresses[chainName], + vId, + EXPIRATION, + callData, ) + const signatures = await getSignatures(signers, hash) + const signaturesPayload = getSignaturesPayload(signatures, quorum) + results[chainName] = { args: { target: dvnAddresses[chainName], - signatures: signaturePayload, + signatures: signaturesPayload, callData, expiration: EXPIRATION, vid: vId, diff --git a/scripts/configChangePayloads/createSetQuorumSignatures.ts b/scripts/configChangePayloads/createSetQuorumSignatures.ts index 4cf5f66..3f877a7 100644 --- a/scripts/configChangePayloads/createSetQuorumSignatures.ts +++ b/scripts/configChangePayloads/createSetQuorumSignatures.ts @@ -1,15 +1,19 @@ import { ethers } from 'ethers' -import { GcpKmsSigner } from 'ethers-gcp-kms-signer' import fs from 'fs' import path from 'path' import { parse } from 'ts-command-line-args' -import { getChainIdForNetwork } from '@layerzerolabs/lz-definitions' - -import { GcpKmsKey } from './kms' +import { GcpKmsKey, getGcpKmsSigners } from './kms' +import { + getSignatures, + getSignaturesPayload, + getVId, + hashCallData, +} from './utils' const PATH = path.join(__dirname) const FILE_PATH = `${PATH}/quorum-change-payloads.json` +const EXPIRATION = Date.now() + 7 * 24 * 60 * 60 * 1000 // 1 week expiration from now /** * This script creates signature payloads to be submitted by an Admin of the DVN contract @@ -40,69 +44,45 @@ const args = parse({ }) const setQuorumFunctionSig = 'function setQuorum(uint64 _quorum)' -const EXPIRATION = Date.now() + 7 * 24 * 60 * 60 * 1000 // 1 week expiration from now - const iface = new ethers.utils.Interface([setQuorumFunctionSig]) - const getCallData = (newQuorum: number) => { return iface.encodeFunctionData('setQuorum', [newQuorum]) } -const hashCallData = (target: string, callData: string, vId: string) => { - return ethers.utils.keccak256( - ethers.utils.solidityPack( - ['uint32', 'address', 'uint', 'bytes'], - [vId, target, EXPIRATION, callData], - ), - ) -} - -interface Signature { - signature: string - address: string -} - const main = async () => { const { environment, chainNames, oldQuorum, newQuorum } = args + const dvnAddresses = require(`./data/dvn-addresses-${environment}.json`) - const keyIds = require(`./data/kms-keyids-${environment}.json`) - const signers = await Promise.all( - keyIds.map(async (credentials: GcpKmsKey) => { - return new GcpKmsSigner(credentials) - }), - ) + + const keyIds: GcpKmsKey[] = require(`./data/kms-keyids-${environment}.json`) + const signers = await getGcpKmsSigners(keyIds) + const availableChainNames = chainNames.split(',') const results: { [chainName: string]: any } = {} await Promise.all( availableChainNames.map(async (chainName) => { results[chainName] = results[chainName] || {} - const vId = getChainIdForNetwork(chainName, environment, '2') + const vId = getVId(chainName, environment) const callData = getCallData(newQuorum) - const hash = hashCallData(dvnAddresses[chainName], callData, vId) - // sign - const signatures = await Promise.all( - signers.map(async (signer) => ({ - signature: await signer.signMessage( - ethers.utils.arrayify(hash), - ), - address: await signer.getAddress(), - })), - ) - signatures.sort((a: Signature, b: Signature) => - a.address.localeCompare(b.address), + const hash = hashCallData( + dvnAddresses[chainName], + vId, + EXPIRATION, + callData, ) - const signaturesForQuorum = signatures.slice(0, oldQuorum) - const signaturePayload = ethers.utils.solidityPack( - signaturesForQuorum.map(() => 'bytes'), - signaturesForQuorum.map((s: Signature) => s.signature), + + const signatures = await getSignatures(signers, hash) + const signaturesPayload = getSignaturesPayload( + signatures, + oldQuorum, ) results[chainName] = { args: { target: dvnAddresses[chainName], - signatures: signaturePayload, + signatures: signaturesPayload, callData, expiration: EXPIRATION, vid: vId, diff --git a/scripts/configChangePayloads/kms.ts b/scripts/configChangePayloads/kms.ts index 9257c4e..14e78ec 100644 --- a/scripts/configChangePayloads/kms.ts +++ b/scripts/configChangePayloads/kms.ts @@ -1,3 +1,5 @@ +import { GcpKmsSigner } from 'ethers-gcp-kms-signer' + /** * Defines a GCP KMS Key. */ @@ -8,3 +10,13 @@ export interface GcpKmsKey { keyId: string keyVersion: string } + +export async function getGcpKmsSigners( + keyIds: GcpKmsKey[], +): Promise { + return await Promise.all( + keyIds.map(async (credentials: GcpKmsKey) => { + return new GcpKmsSigner(credentials) + }), + ) +} diff --git a/scripts/configChangePayloads/utils.ts b/scripts/configChangePayloads/utils.ts new file mode 100644 index 0000000..cf489f1 --- /dev/null +++ b/scripts/configChangePayloads/utils.ts @@ -0,0 +1,58 @@ +import { ethers } from 'ethers' +import { GcpKmsSigner } from 'ethers-gcp-kms-signer' + +import { getChainIdForNetwork } from '@layerzerolabs/lz-definitions' + +export interface Signature { + signature: string + address: string +} + +export function getVId(chainName: string, environment: string): string { + // By convention the vid is always the endpointV1 chainId + if (['solana', 'ton', 'initia', 'movement'].includes(chainName)) { + const eid = getChainIdForNetwork(chainName, environment, '302') + return (parseInt(eid) % 30000).toString() + } + return getChainIdForNetwork(chainName, environment, '2') +} + +export function hashCallData( + target: string, + vId: string, + expiration: number, + callData: string, +): string { + return ethers.utils.keccak256( + ethers.utils.solidityPack( + ['uint32', 'address', 'uint', 'bytes'], + [vId, target, expiration, callData], + ), + ) +} + +export async function getSignatures( + signers: GcpKmsSigner[], + hash: string, +): Promise { + return await Promise.all( + signers.map(async (signer) => ({ + signature: await signer.signMessage(ethers.utils.arrayify(hash)), + address: await signer.getAddress(), + })), + ) +} + +export function getSignaturesPayload( + signatures: Signature[], + quorum: number, +): string { + signatures.sort((a: Signature, b: Signature) => + a.address.localeCompare(b.address), + ) + const signaturesForQuorum = signatures.slice(0, quorum) + return ethers.utils.solidityPack( + signaturesForQuorum.map(() => 'bytes'), + signaturesForQuorum.map((s: Signature) => s.signature), + ) +}