Skip to content

Commit

Permalink
[contract_manager] Add some nice-to-have features (#1650)
Browse files Browse the repository at this point in the history
* gr

* some stuff

* comment

* gr

* cleanup
  • Loading branch information
jayantk authored Jun 5, 2024
1 parent 046ba8a commit 1b8fee9
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 32 deletions.
15 changes: 11 additions & 4 deletions contract_manager/scripts/check_proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ async function main() {
if (instruction.governanceAction instanceof EvmExecute) {
// Note: it only checks for upgrade entropy contracts right now
console.log(
`Verifying EVMExecute Contract on ${instruction.governanceAction.targetChainId}`
`Verifying EVMExecute on ${instruction.governanceAction.targetChainId}`
);
for (const chain of Object.values(DefaultStore.chains)) {
if (
Expand All @@ -153,7 +153,8 @@ async function main() {
const callAddress = instruction.governanceAction.callAddress;
const calldata = instruction.governanceAction.calldata;

// currently executor is only being used by the entropy contract
// TODO: If we add additional EVM contracts using the executor, we need to
// add some logic here to identify what kind of contract is at the call address.
const contract = new EvmEntropyContract(chain, callAddress);
const owner = await contract.getOwner();

Expand All @@ -169,10 +170,13 @@ async function main() {
continue;
}

// TODO: This logic assumes we are calling upgradeTo on the contract at callAddress.
// In the future, this logic may need to be generalized to support calling other functions.
const invokedMethod = "upgradeTo(address)";
const calldataHex = calldata.toString("hex");
const web3 = new Web3();
const methodSignature = web3.eth.abi
.encodeFunctionSignature("upgradeTo(address)")
.encodeFunctionSignature(invokedMethod)
.replace("0x", "");

let newImplementationAddress: string | undefined = undefined;
Expand All @@ -196,7 +200,10 @@ async function main() {
);
// this should be the same keccak256 of the deployedCode property generated by truffle
console.log(
`${chain.getId()} new implementation address:${newImplementationAddress} digest:${newImplementationCode}`
`${chain.getId()} call ${invokedMethod} with arguments (${newImplementationAddress}) on ${contract.getType()} at address:${callAddress} from executor:${executorAddress}.`
);
console.log(
`${chain.getId()} new implementation address:${newImplementationAddress} has code digest:${newImplementationCode}`
);
}
}
Expand Down
73 changes: 51 additions & 22 deletions contract_manager/scripts/upgrade_evm_entropy_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { DefaultStore, loadHotWallet, toPrivateKey } from "../src";
import { readFileSync } from "fs";
import { PythCluster } from "@pythnetwork/client/lib/cluster";

import {
COMMON_UPGRADE_OPTIONS,
Expand All @@ -27,6 +28,18 @@ const parser = yargs(hideBin(process.argv))
},
});

// Override these URLs to use a different RPC node for mainnet / testnet.
// TODO: extract these RPCs to a config file (?)
const RPCS = {
"mainnet-beta": "https://api.mainnet-beta.solana.com",
testnet: "https://api.testnet.solana.com",
devnet: "https://api.devnet.solana.com",
} as Record<PythCluster, string>;

function registry(cluster: PythCluster): string {
return RPCS[cluster];
}

async function main() {
const argv = await parser.argv;
const cacheFile =
Expand All @@ -45,40 +58,56 @@ async function main() {

console.log("Using cache file", cacheFile);

// Try to deploy on every chain, then collect any failures at the end. This logic makes it simpler to
// identify deployment problems (e.g., not enough gas) on every chain where they occur.
const payloads: Buffer[] = [];
const failures: string[] = [];
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
if (selectedChains.includes(contract.chain)) {
const artifact = JSON.parse(readFileSync(argv["std-output"], "utf8"));
console.log("Deploying contract to", contract.chain.getId());
const address = await runIfNotCached(
`deploy-${contract.chain.getId()}`,
() => {
return contract.chain.deploy(
toPrivateKey(argv["private-key"]),
artifact["abi"],
artifact["bytecode"],
[],
2
);
}
);
console.log(
`Deployed contract at ${address} on ${contract.chain.getId()}`
);
const payload =
argv["contract-type"] === "executor"
? await contract.generateUpgradeExecutorContractsPayload(address)
: await contract.generateUpgradeEntropyContractPayload(address);
try {
const address = await runIfNotCached(
`deploy-${contract.chain.getId()}`,
() => {
return contract.chain.deploy(
toPrivateKey(argv["private-key"]),
artifact["abi"],
artifact["bytecode"],
[],
2
);
}
);
console.log(
`Deployed contract at ${address} on ${contract.chain.getId()}`
);
const payload =
argv["contract-type"] === "executor"
? await contract.generateUpgradeExecutorContractsPayload(address)
: await contract.generateUpgradeEntropyContractPayload(address);

console.log(payload.toString("hex"));
payloads.push(payload);
console.log(payload.toString("hex"));
payloads.push(payload);
} catch (e) {
console.log(`error deploying: ${e}`);
failures.push(contract.chain.getId());
}
}
}

if (failures.length > 0) {
throw new Error(
`Some chains could not be deployed: ${failures.join(
", "
)}. Scroll up to see the errors from each chain.`
);
}

console.log("Using vault at for proposal", vault.getId());
const wallet = await loadHotWallet(argv["ops-key-path"]);
console.log("Using wallet ", wallet.publicKey.toBase58());
await vault.connect(wallet);
vault.connect(wallet, registry);
const proposal = await vault.proposeWormholeMessage(payloads);
console.log("Proposal address", proposal.address.toBase58());
}
Expand Down
19 changes: 13 additions & 6 deletions contract_manager/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,19 @@ export class EvmChain extends Chain {
);
}

const deployedContract = await deployTx.send({
from: signer.address,
gas,
gasPrice: gasPrice.toString(),
});
return deployedContract.options.address;
try {
const deployedContract = await deployTx.send({
from: signer.address,
gas,
gasPrice: gasPrice.toString(),
});
return deployedContract.options.address;
} catch (e) {
// RPC errors often have useful information in the non-primary message field. Log the whole error
// to simplify identifying the problem.
console.log(`Error deploying contract: ${JSON.stringify(e)}`);
throw e;
}
}

async getAccountAddress(privateKey: PrivateKey): Promise<string> {
Expand Down
8 changes: 8 additions & 0 deletions contract_manager/src/governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class SubmittedWormholeMessage {
* Inspects the transaction logs to find the sequence number
* @param signature signature of the transaction to inspect
* @param cluster the cluster the transaction was submitted to
* @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
*/
static async fromTransactionSignature(
signature: string,
Expand Down Expand Up @@ -152,6 +153,11 @@ export class WormholeEmitter {
return this.wallet.publicKey;
}

/**
* Send a wormhole message containing payload through wormhole.
* @param payload the contents of the message
* @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
*/
async sendMessage(
payload: Buffer,
registry: SolanaRpcRegistry = getPythClusterApiUrl
Expand Down Expand Up @@ -299,6 +305,7 @@ export class Vault extends Storable {
* Connects the vault to a wallet that can be used to submit proposals
* The wallet should be a multisig signer of the vault
* @param wallet
* @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
*/
public connect(
wallet: Wallet,
Expand All @@ -314,6 +321,7 @@ export class Vault extends Storable {

/**
* Gets the emitter address of the vault
* @param registry registry of RPC nodes to use for each solana network. Defaults to the Solana public RPCs if not provided.
*/
public async getEmitter(registry: SolanaRpcRegistry = getPythClusterApiUrl) {
const squad = SquadsMesh.endpoint(
Expand Down

0 comments on commit 1b8fee9

Please sign in to comment.