Skip to content

Commit

Permalink
Scripts to generate payloads needed to do administrative config chang…
Browse files Browse the repository at this point in the history
…es on dvn
  • Loading branch information
MohammadChavosh committed Jan 16, 2025
1 parent 26e6dc6 commit 56659e0
Show file tree
Hide file tree
Showing 9 changed files with 1,480 additions and 5 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,22 @@ 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/configChangePayloads/data` folder. The file names should be `dvn-addresses-<environment>.json` and `kms-keyids-<environment>.json` respectively. Take a look at existing testnet examples in the `scripts/configChangePayloads/data` folder to see how they need to be filled.
### Signatures for changing quorum
```
ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts -e <environment> -c <comma-separated-chain-names> --oldQuorum <number> --newQuorum <number>
# e.g. ts-node scripts/configChangePayloads/createSetQuorumSignatures.ts -e testnet -c bsc,avalanche,fantom --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 <environment> -c <comma-separated-chain-names> --q <quorum> --signerAddress <string> --shouldRevoke <0 or 1>
# e.g. ts-node scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts -e testnet -c bsc,avalanche,fantom -q 1 --signerAddress 0x85e4857b7f15bbbbbc72d933a6357d3c22a0bbc7 --shouldRevoke 1
```

## Troubleshooting

### 1. CDK Deploy failed and cannot redeploy because resource already exists
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
"@aws-sdk/client-iam": "^3.53.0",
"@aws-sdk/client-secrets-manager": "^3.50.0",
"@aws-sdk/client-sts": "^3.51.0",
"@layerzerolabs/lz-definitions": "3.0.44",
"@rumblefishdev/eth-signer-kms": "^2.3.0",
"args": "^5.0.3",
"aws-cdk-lib": "^2.62.1",
"aws-sdk": "^2.1308.0",
"command-line-args": "^5.2.1",
"constructs": "^10.0.0",
"axios": "^1.3.1",
Expand Down
120 changes: 120 additions & 0 deletions scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ethers } from 'ethers'
import fs from 'fs'
import path from 'path'
import { parse } from 'ts-command-line-args'

import { AwsKmsKey, getAwsKmsSigners } 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
* that will add or remove a singer from the DVN contract
*/

const args = parse({
environment: {
alias: 'e',
type: String,
defaultValue: 'mainnet',
description: 'environment',
},
chainNames: {
alias: 'c',
type: String,
description: 'comma separated list of chain names',
},
quorum: {
type: Number,
alias: 'q',
description: 'number of signatures required for quorum',
},
signerAddress: {
type: String,
description: 'public address of the signer',
},
shouldRevoke: {
type: Number, // Not a boolean to make it required in the command line, so users be explicit about it
description:
'set to 1 if you want to remove signer, set to 0 if you want to add signer',
},
})

const setSignerFunctionSig = 'function setSigner(address _signer, bool _active)'
const iface = new ethers.utils.Interface([setSignerFunctionSig])
const getCallData = (signerAddress: string, active: boolean) => {
return iface.encodeFunctionData('setSigner', [signerAddress, active])
}

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: AwsKmsKey[] = require(`./data/kms-keyids-${environment}.json`)
const signers = await getAwsKmsSigners(keyIds)

const availableChainNames = chainNames.split(',')

const results: { [chainName: string]: any } = {}
await Promise.all(
availableChainNames.map(async (chainName) => {
results[chainName] = results[chainName] || {}
const vId = getVId(chainName, environment)
const callData = getCallData(
signerAddress,
shouldRevoke === 1 ? false : true,
)

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: signaturesPayload,
callData,
expiration: EXPIRATION,
vid: vId,
},
info: {
signatures,
hashCallData: hash,
quorum,
signerAddress,
shouldRevoke: shouldRevoke === 1,
},
}
}),
)
fs.writeFileSync(FILE_PATH, JSON.stringify(results))
console.log(`Results written to: ${FILE_PATH}`)
}

main()
.then(() => {
process.exit(0)
})
.catch((err: any) => {
console.error(err)
process.exit(1)
})
110 changes: 110 additions & 0 deletions scripts/configChangePayloads/createSetQuorumSignatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { ethers } from 'ethers'
import fs from 'fs'
import path from 'path'
import { parse } from 'ts-command-line-args'

import { AwsKmsKey, getAwsKmsSigners } 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
* that will change the quorum of the DVN contract
*/

const args = parse({
environment: {
alias: 'e',
type: String,
defaultValue: 'mainnet',
description: 'environment',
},
chainNames: {
alias: 'c',
type: String,
description: 'comma separated list of chain names',
},
oldQuorum: {
type: Number,
description:
'old quorum, which is number of signatures required for change to happen',
},
newQuorum: {
type: Number,
description: 'new quorum',
},
})

const setQuorumFunctionSig = 'function setQuorum(uint64 _quorum)'
const iface = new ethers.utils.Interface([setQuorumFunctionSig])
const getCallData = (newQuorum: number) => {
return iface.encodeFunctionData('setQuorum', [newQuorum])
}

const main = async () => {
const { environment, chainNames, oldQuorum, newQuorum } = args

const dvnAddresses = require(`./data/dvn-addresses-${environment}.json`)

const keyIds: AwsKmsKey[] = require(`./data/kms-keyids-${environment}.json`)
const signers = await getAwsKmsSigners(keyIds)

const availableChainNames = chainNames.split(',')

const results: { [chainName: string]: any } = {}
await Promise.all(
availableChainNames.map(async (chainName) => {
results[chainName] = results[chainName] || {}
const vId = getVId(chainName, environment)
const callData = getCallData(newQuorum)

const hash = hashCallData(
dvnAddresses[chainName],
vId,
EXPIRATION,
callData,
)

const signatures = await getSignatures(signers, hash)
const signaturesPayload = getSignaturesPayload(
signatures,
oldQuorum,
)

results[chainName] = {
args: {
target: dvnAddresses[chainName],
signatures: signaturesPayload,
callData,
expiration: EXPIRATION,
vid: vId,
},
info: {
signatures,
hashCallData: hash,
oldQuorum,
newQuorum,
},
}
}),
)
fs.writeFileSync(FILE_PATH, JSON.stringify(results))
console.log(`Results written to: ${FILE_PATH}`)
}

main()
.then(() => {
process.exit(0)
})
.catch((err: any) => {
console.error(err)
process.exit(1)
})
5 changes: 5 additions & 0 deletions scripts/configChangePayloads/data/dvn-addresses-testnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"bsc": "0x0eE552262f7B562eFcED6DD4A7e2878AB897d405",
"avalanche": "0x9f0e79Aeb198750F963b6f30B99d87c6EE5A0467",
"fantom": "0xFffc92A6AbE6480AdC574901ebFDe108A7077Eb8"
}
10 changes: 10 additions & 0 deletions scripts/configChangePayloads/data/kms-keyids-testnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"keyId": "84561bb6-ab14-431e-b07a-88d2bfd1b5dc",
"region": "us-east-1"
},
{
"keyId": "0bc1c31b-6635-4311-bd63-5eb66419c4e7",
"region": "us-east-1"
}
]
26 changes: 26 additions & 0 deletions scripts/configChangePayloads/kms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { KMSSigner } from '@rumblefishdev/eth-signer-kms'
import AWS from 'aws-sdk'

/**
* Defines a AWS KMS Key.
*/
export interface AwsKmsKey {
keyId: string
region: string
}

export async function getAwsKmsSigners(
keysInfo: AwsKmsKey[],
): Promise<KMSSigner[]> {
return await Promise.all(
keysInfo.map(async (keyInfo: AwsKmsKey) => {
return new KMSSigner(
{} as any, // not used
keyInfo.keyId,
new AWS.KMS({
region: keyInfo.region,
}),
)
}),
)
}
58 changes: 58 additions & 0 deletions scripts/configChangePayloads/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { KMSSigner } from '@rumblefishdev/eth-signer-kms'
import { ethers } from 'ethers'

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: KMSSigner[],
hash: string,
): Promise<Signature[]> {
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),
)
}
Loading

0 comments on commit 56659e0

Please sign in to comment.