[aptos] Aptos cleanup (#994)
* Better errors * Better variable typing * Upgrade cli with more straightforward flow * Remove unnecessary configs and distinguish between deployer contract and signer * Convert the named-addresses argument to 3 separate options with default values * Add README
This commit is contained in:
parent
07b01118d2
commit
2782c6bf64
|
@ -47,7 +47,7 @@ export class SubmittedWormholeMessage {
|
|||
constructor(
|
||||
public emitter: PublicKey,
|
||||
public sequenceNumber: number,
|
||||
public cluster: string
|
||||
public cluster: PythCluster
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
@ -107,8 +107,7 @@ export class SubmittedWormholeMessage {
|
|||
* @param waitingSeconds how long to wait before giving up
|
||||
*/
|
||||
async fetchVaa(waitingSeconds: number = 1): Promise<Buffer> {
|
||||
let rpcUrl =
|
||||
WORMHOLE_API_ENDPOINT[this.cluster as keyof typeof WORMHOLE_API_ENDPOINT];
|
||||
let rpcUrl = WORMHOLE_API_ENDPOINT[this.cluster];
|
||||
|
||||
let startTime = Date.now();
|
||||
while (Date.now() - startTime < waitingSeconds * 1000) {
|
||||
|
@ -176,6 +175,10 @@ export class WormholeEmitter {
|
|||
this.cluster = asPythCluster(cluster);
|
||||
}
|
||||
|
||||
public getEmitter() {
|
||||
return this.wallet.publicKey;
|
||||
}
|
||||
|
||||
async sendMessage(payload: Buffer) {
|
||||
const provider = new AnchorProvider(
|
||||
new Connection(getPythClusterApiUrl(this.cluster), "confirmed"),
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
# Pre-requisites
|
||||
|
||||
Install aptos cli with the same version specified in the ci workflows.
|
||||
|
||||
All the commands which submit transactions require an environment variable for the private key to be set.
|
||||
Depending on the network, this can be either `APTOS_LOCALNET_KEY`, `APTOS_TESTNET_KEY` or `APTOS_MAINNET_KEY`.
|
||||
|
||||
# Deploying from scratch
|
||||
|
||||
In addition to the wormhole dependency we depend on the deployer contract that facilitates the ownership of package upgrade
|
||||
capability. You can read more about it [here](https://github.com/wormhole-foundation/wormhole/blob/5255e933d68629f0643207b0f9d3fa797af5cbf7/aptos/deployer/sources/deployer.move).
|
||||
|
||||
Assuming the wormhole and deployer contracts are already deployed, we can deploy the pyth oracle with the following command:
|
||||
|
||||
```bash
|
||||
npm run cli deploy-pyth ../contracts <seed> -n testnet
|
||||
```
|
||||
|
||||
`seed` can be any random string that is used for determining a specific contract address based on the seed value and the signer address.
|
||||
|
||||
You can manually specify the address of wormhole and deployer contracts with `--wormhole` and `--deployer` flags.
|
||||
This requires the addresses to be empty in the `Move.toml` file for the pyth package:
|
||||
|
||||
```toml
|
||||
[addresses]
|
||||
pyth = "_"
|
||||
deployer = "_"
|
||||
wormhole = "_"
|
||||
```
|
||||
|
||||
### Initializing pyth
|
||||
|
||||
You can run the following to initialize the pyth contract, the following is a sample (testnet) config:
|
||||
|
||||
```bash
|
||||
npm run cli init-pyth <seed> -n testnet \
|
||||
--stale-price-threshold 60 \
|
||||
--update-fee 1 \
|
||||
--governance-emitter-chain-id 1 \
|
||||
--governance-emitter-address 63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385 \
|
||||
--data-source-chain-ids 1 \
|
||||
--data-source-chain-ids 26 \
|
||||
--data-source-chain-ids 26 \
|
||||
--data-source-emitter-addresses f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0 \
|
||||
--data-source-emitter-addresses a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6 \
|
||||
--data-source-emitter-addresses e101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71
|
||||
```
|
||||
|
||||
Note that the `data-source-chain-ids` are paired with `data-source-emitter-addresses` and their order matters.
|
||||
|
||||
# Upgrade process:
|
||||
|
||||
The following steps are needed to upgrade our aptos contracts:
|
||||
|
||||
- Generate the hash for the new contract build
|
||||
- Create a governance proposal, proposing the aptos package to be upgraded to this specific hash
|
||||
- Approve and execute the governance proposal
|
||||
- Run the upgrade transaction and publish the new package
|
||||
|
||||
## Generating the new contract hash:
|
||||
|
||||
Run the following command to generate the new hash, this will assume the default deployed addresses of deployer, wormhole, and pyth, but you can override them if necessary.
|
||||
|
||||
```bash
|
||||
npm run cli hash-contracts ../contracts
|
||||
```
|
||||
|
||||
## Upgrading the contract
|
||||
|
||||
To upgrade the contract after the governance vaa was executed run:
|
||||
|
||||
```bash
|
||||
npm run cli upgrade ../contracts
|
||||
```
|
|
@ -5,6 +5,7 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"cli": "ts-node src/cli.ts",
|
||||
"build": "npx tsc && pkg . --targets node14-linux-x64 --output build/pyth"
|
||||
},
|
||||
"author": "",
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import { builder } from "./commands/aptos";
|
||||
|
||||
yargs(hideBin(process.argv)).commandDir("commands").strict().argv;
|
||||
builder(yargs(hideBin(process.argv))).argv;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Argv } from "yargs";
|
||||
import { spawnSync } from "child_process";
|
||||
import { BCS, AptosClient, AptosAccount, TxnBuilderTypes } from "aptos";
|
||||
import type { CommandBuilder } from "yargs";
|
||||
import { AptosAccount, AptosClient, BCS, TxnBuilderTypes } from "aptos";
|
||||
import fs from "fs";
|
||||
import sha3 from "js-sha3";
|
||||
import { ethers } from "ethers";
|
||||
|
@ -14,18 +14,29 @@ interface Network {
|
|||
endpoint: string;
|
||||
// Private key of the network
|
||||
key: string | undefined;
|
||||
// Address of the Pyth deployer contract
|
||||
deployer: string;
|
||||
// The Pyth deployer contract seed used to generate the derived address of the Pyth contract
|
||||
pythDeployerSeed: string;
|
||||
}
|
||||
|
||||
const network = {
|
||||
const NETWORK_OPTION = {
|
||||
alias: "n",
|
||||
describe: "network",
|
||||
type: "string",
|
||||
choices: [LOCALNET, TESTNET, MAINNET],
|
||||
required: true,
|
||||
demandOption: true,
|
||||
} as const;
|
||||
const DEPLOYER_OPTION = {
|
||||
describe: "deployer contract address deployed in the network",
|
||||
type: "string",
|
||||
default: "0xb31e712b26fd295357355f6845e77c888298636609e93bc9b05f0f604049f434",
|
||||
} as const;
|
||||
const WORMHOLE_OPTION = {
|
||||
describe: "wormhole contract address deployed in the network",
|
||||
type: "string",
|
||||
default: "0x5bc11445584a763c1fa7ed39081f1b920954da14e04b32440cba863d03e19625",
|
||||
} as const;
|
||||
const PYTH_OPTION = {
|
||||
describe: "pyth contract address deployed in the network",
|
||||
type: "string",
|
||||
default: "0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387",
|
||||
} as const;
|
||||
|
||||
interface Package {
|
||||
|
@ -45,9 +56,6 @@ const networks = new Map<string, Network>([
|
|||
{
|
||||
key: process.env["APTOS_LOCALNET_KEY"],
|
||||
endpoint: "http://0.0.0.0:8080",
|
||||
deployer:
|
||||
"0x277fa055b6a73c42c0662d5236c65c864ccbf2d4abd21f174a30c8b786eab84b",
|
||||
pythDeployerSeed: "pyth",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -55,10 +63,6 @@ const networks = new Map<string, Network>([
|
|||
{
|
||||
key: process.env["APTOS_TESTNET_KEY"],
|
||||
endpoint: "https://fullnode.testnet.aptoslabs.com/v1",
|
||||
deployer:
|
||||
"0xb31e712b26fd295357355f6845e77c888298636609e93bc9b05f0f604049f434",
|
||||
// A Wormhole redeploy meant we had to use different seeds for testnet and mainnet
|
||||
pythDeployerSeed: "pyth-a8d0d",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -66,16 +70,10 @@ const networks = new Map<string, Network>([
|
|||
{
|
||||
key: process.env["APTOS_MAINNET_KEY"],
|
||||
endpoint: "https://fullnode.mainnet.aptoslabs.com/v1",
|
||||
deployer:
|
||||
"0xb31e712b26fd295357355f6845e77c888298636609e93bc9b05f0f604049f434",
|
||||
pythDeployerSeed: "pyth-a8d0d",
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
export const command: string = "aptos <command>";
|
||||
|
||||
export const builder: CommandBuilder = (yargs) =>
|
||||
export const builder: (args: Argv<any>) => Argv<any> = (yargs) =>
|
||||
yargs
|
||||
.command(
|
||||
"deploy <package-dir> <account>",
|
||||
|
@ -85,7 +83,7 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
.positional("package-dir", { type: "string" })
|
||||
.positional("account", { type: "string" })
|
||||
.option("named-addresses", { type: "string" })
|
||||
.option("network", network);
|
||||
.option("network", NETWORK_OPTION);
|
||||
},
|
||||
async (argv) => {
|
||||
const artefact = serializePackage(
|
||||
|
@ -104,27 +102,40 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
}
|
||||
)
|
||||
.command(
|
||||
"deploy-resource <package-dir> <seed>",
|
||||
"Deploy a package using a resource account",
|
||||
"deploy-pyth <package-dir> <seed>",
|
||||
"Deploy the pyth package using a resource account.",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.positional("package-dir", { type: "string" })
|
||||
.positional("seed", { type: "string" })
|
||||
.option("named-addresses", { type: "string" })
|
||||
.option("network", network);
|
||||
.option("deployer", DEPLOYER_OPTION)
|
||||
.option("wormhole", WORMHOLE_OPTION)
|
||||
.option("network", NETWORK_OPTION);
|
||||
},
|
||||
async (argv) => {
|
||||
const artefact = serializePackage(
|
||||
buildPackage(argv["package-dir"]!, argv["named-addresses"])
|
||||
const sender = getSender(argv.network);
|
||||
const derivedAddress = generateDerivedAddress(
|
||||
sender.address().toString(),
|
||||
argv.seed!
|
||||
);
|
||||
|
||||
const namedAddresses = `wormhole=${argv.wormhole},deployer=${argv.deployer},pyth=0x${derivedAddress}`;
|
||||
console.log("Building the package with the following named addresses:");
|
||||
console.log(`Wormhole=${argv.wormhole}`);
|
||||
console.log(`Deployer=${argv.deployer}`);
|
||||
console.log(`Pyth=${derivedAddress}`);
|
||||
const artifact = serializePackage(
|
||||
buildPackage(argv["package-dir"]!, namedAddresses)
|
||||
);
|
||||
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
TxnBuilderTypes.EntryFunction.natural(
|
||||
networks.get(argv.network)!.deployer + "::deployer",
|
||||
argv.deployer + "::deployer",
|
||||
"deploy_derived",
|
||||
[],
|
||||
[
|
||||
artefact.meta,
|
||||
artefact.bytecodes,
|
||||
artifact.meta,
|
||||
artifact.bytecodes,
|
||||
BCS.bcsSerializeBytes(Buffer.from(argv["seed"]!, "ascii")),
|
||||
]
|
||||
)
|
||||
|
@ -134,20 +145,15 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
}
|
||||
)
|
||||
.command(
|
||||
"derived-address <seed>",
|
||||
"Generate the derived address for the given deployer seed",
|
||||
"derived-address <seed> <signer>",
|
||||
"Generate the derived address for the given seed and sender address",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.positional("seed", { type: "string" })
|
||||
.option("network", network);
|
||||
.positional("seed", { type: "string", demandOption: true })
|
||||
.positional("signer", { type: "string", demandOption: true });
|
||||
},
|
||||
async (argv) => {
|
||||
console.log(
|
||||
generateDerivedAddress(
|
||||
networks.get(argv.network)!.deployer,
|
||||
argv.seed!
|
||||
)
|
||||
);
|
||||
console.log(generateDerivedAddress(argv.signer, argv.seed));
|
||||
}
|
||||
)
|
||||
.command(
|
||||
|
@ -155,29 +161,29 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
"Init Wormhole core contract",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.option("network", network)
|
||||
.option("network", NETWORK_OPTION)
|
||||
.option("chain-id", {
|
||||
describe: "Chain id",
|
||||
type: "number",
|
||||
default: 22,
|
||||
required: false,
|
||||
demandOption: false,
|
||||
})
|
||||
.option("governance-chain-id", {
|
||||
describe: "Governance chain id",
|
||||
type: "number",
|
||||
default: 1,
|
||||
required: false,
|
||||
demandOption: false,
|
||||
})
|
||||
.option("governance-address", {
|
||||
describe: "Governance address",
|
||||
type: "string",
|
||||
default:
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000004",
|
||||
required: false,
|
||||
demandOption: false,
|
||||
})
|
||||
.option("guardian-address", {
|
||||
alias: "g",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
describe: "Initial guardian's address",
|
||||
type: "string",
|
||||
});
|
||||
|
@ -202,8 +208,9 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
BCS.bcsSerializeBytes(Buffer.from(governance_address, "hex")),
|
||||
guardian_addresses_serializer.getBytes(),
|
||||
];
|
||||
const sender = getSender(argv.network);
|
||||
const wormholeAddress = generateDerivedAddress(
|
||||
networks.get(argv.network)!.deployer!,
|
||||
sender.address().hex(),
|
||||
"wormhole"
|
||||
);
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
|
@ -219,40 +226,41 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
}
|
||||
)
|
||||
.command(
|
||||
"init-pyth",
|
||||
"init-pyth <seed>",
|
||||
"Init Pyth contract",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.option("network", network)
|
||||
.positional("seed", { type: "string", demandOption: true })
|
||||
.option("network", NETWORK_OPTION)
|
||||
.option("stale-price-threshold", {
|
||||
describe: "Stale price threshold",
|
||||
type: "number",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
})
|
||||
.option("governance-emitter-chain-id", {
|
||||
describe: "Governance emitter chain id",
|
||||
type: "number",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
})
|
||||
.option("governance-emitter-address", {
|
||||
describe: "Governance emitter address",
|
||||
type: "string",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
})
|
||||
.option("update-fee", {
|
||||
describe: "Update fee",
|
||||
type: "number",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
})
|
||||
.option("data-source-chain-ids", {
|
||||
describe: "Data source chain IDs",
|
||||
type: "array",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
})
|
||||
.option("data-source-emitter-addresses", {
|
||||
describe: "Data source emitter addresses",
|
||||
type: "array",
|
||||
required: true,
|
||||
demandOption: true,
|
||||
});
|
||||
},
|
||||
async (argv) => {
|
||||
|
@ -266,8 +274,8 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
dataSourceChainIdsSerializer.serializeU32AsUleb128(
|
||||
argv["data-source-chain-ids"].length
|
||||
);
|
||||
argv["data-source-chain-ids"].forEach((chain_id) =>
|
||||
dataSourceChainIdsSerializer.serializeU64(chain_id as number)
|
||||
argv["data-source-chain-ids"].forEach((chain_id: number) =>
|
||||
dataSourceChainIdsSerializer.serializeU64(chain_id)
|
||||
);
|
||||
|
||||
const dataSourceEmitterAddressesSerializer = new BCS.Serializer();
|
||||
|
@ -289,9 +297,10 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
dataSourceEmitterAddressesSerializer.getBytes(),
|
||||
BCS.bcsSerializeUint64(update_fee),
|
||||
];
|
||||
const sender = getSender(argv.network);
|
||||
const pythAddress = generateDerivedAddress(
|
||||
networks.get(argv.network)!.deployer!,
|
||||
networks.get(argv.network)!.pythDeployerSeed!
|
||||
sender.address().hex(),
|
||||
argv.seed
|
||||
);
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
TxnBuilderTypes.EntryFunction.natural(
|
||||
|
@ -307,17 +316,20 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
)
|
||||
.command(
|
||||
"hash-contracts <package-dir>",
|
||||
"Hash contract bytecodes for upgrade",
|
||||
"Hash contract bytecodes for upgrade, the named addresses should be the same as the currently deployed ones",
|
||||
(yargs) => {
|
||||
return yargs
|
||||
.positional("package-dir", {
|
||||
type: "string",
|
||||
required: true,
|
||||
})
|
||||
.option("named-addresses", { type: "string" });
|
||||
.option("deployer", DEPLOYER_OPTION)
|
||||
.option("wormhole", WORMHOLE_OPTION)
|
||||
.option("pyth", PYTH_OPTION);
|
||||
},
|
||||
(argv) => {
|
||||
const p = buildPackage(argv["package-dir"]!, argv["named-addresses"]);
|
||||
const namedAddresses = `wormhole=${argv.wormhole},deployer=${argv.deployer},pyth=${argv.pyth}`;
|
||||
const p = buildPackage(argv["package-dir"]!, namedAddresses);
|
||||
const b = serializePackage(p);
|
||||
console.log(Buffer.from(b.codeHash).toString("hex"));
|
||||
}
|
||||
|
@ -331,18 +343,18 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
type: "string",
|
||||
required: true,
|
||||
})
|
||||
.option("network", network)
|
||||
.option("named-addresses", { type: "string" });
|
||||
.option("network", NETWORK_OPTION)
|
||||
.option("deployer", DEPLOYER_OPTION)
|
||||
.option("wormhole", WORMHOLE_OPTION)
|
||||
.option("pyth", PYTH_OPTION);
|
||||
},
|
||||
async (argv) => {
|
||||
const namedAddresses = `wormhole=${argv.wormhole},deployer=${argv.deployer},pyth=${argv.pyth}`;
|
||||
const artefact = serializePackage(
|
||||
buildPackage(argv["package-dir"]!, argv["named-addresses"])
|
||||
buildPackage(argv["package-dir"]!, namedAddresses)
|
||||
);
|
||||
|
||||
let pythAddress = generateDerivedAddress(
|
||||
networks.get(argv.network)!.deployer!,
|
||||
networks.get(argv.network)!.pythDeployerSeed!
|
||||
);
|
||||
let pythAddress = argv.pyth;
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
TxnBuilderTypes.EntryFunction.natural(
|
||||
`${pythAddress}::contract_upgrade`,
|
||||
|
@ -355,42 +367,24 @@ export const builder: CommandBuilder = (yargs) =>
|
|||
await executeTransaction(argv.network, txPayload);
|
||||
}
|
||||
)
|
||||
.command(
|
||||
"execute-governance <vaa-bytes>",
|
||||
"Execute a governance instruction on the Pyth contract",
|
||||
(_yargs) => {
|
||||
return yargs
|
||||
.positional("vaa-bytes", {
|
||||
type: "string",
|
||||
})
|
||||
.option("network", network);
|
||||
},
|
||||
async (argv) => {
|
||||
let pythAddress = generateDerivedAddress(
|
||||
networks.get(argv.network)!.deployer!,
|
||||
networks.get(argv.network)!.pythDeployerSeed!
|
||||
);
|
||||
const txPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
|
||||
TxnBuilderTypes.EntryFunction.natural(
|
||||
`${pythAddress}::governance`,
|
||||
"execute_governance_instruction",
|
||||
[],
|
||||
[BCS.bcsSerializeBytes(Buffer.from(argv.vaaBytes!, "hex"))]
|
||||
)
|
||||
);
|
||||
.demandCommand();
|
||||
|
||||
await executeTransaction(argv.network, txPayload);
|
||||
}
|
||||
function getSender(network: string) {
|
||||
if (networks.get(network)!.key === undefined) {
|
||||
throw new Error(
|
||||
`No key for network ${network}. Please set the APTOS_${network.toUpperCase()}_KEY environment variable.`
|
||||
);
|
||||
|
||||
}
|
||||
return new AptosAccount(
|
||||
new Uint8Array(Buffer.from(networks.get(network)!.key!, "hex"))
|
||||
);
|
||||
}
|
||||
async function executeTransaction(
|
||||
network: string,
|
||||
txPayload: TxnBuilderTypes.TransactionPayloadEntryFunction
|
||||
) {
|
||||
const client = new AptosClient(networks.get(network)!.endpoint);
|
||||
const sender = new AptosAccount(
|
||||
new Uint8Array(Buffer.from(networks.get(network)!.key!, "hex"))
|
||||
);
|
||||
const sender = getSender(network);
|
||||
console.log(
|
||||
await client.generateSignSubmitWaitForTransaction(sender, txPayload, {
|
||||
maxGasAmount: BigInt(30000),
|
||||
|
@ -413,15 +407,12 @@ function hexStringToByteArray(hexString: string) {
|
|||
return byteArray;
|
||||
}
|
||||
|
||||
function generateDerivedAddress(
|
||||
deployer_address: string,
|
||||
seed: string
|
||||
): string {
|
||||
function generateDerivedAddress(signer_address: string, seed: string): string {
|
||||
let derive_resource_account_scheme = Buffer.alloc(1);
|
||||
derive_resource_account_scheme.writeUInt8(255);
|
||||
return sha3.sha3_256(
|
||||
Buffer.concat([
|
||||
hexStringToByteArray(deployer_address),
|
||||
hexStringToByteArray(signer_address),
|
||||
Buffer.from(seed, "ascii"),
|
||||
derive_resource_account_scheme,
|
||||
])
|
||||
|
|
Loading…
Reference in New Issue