Skip to content

refactor(contract_manager): add docs to ton upgrade + use parseUrl #2160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions contract_manager/scripts/upgrade_ton_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ import fs from "fs";
import path from "path";
import { Cell } from "@ton/ton";

// This script upgrades the Pyth contract on TON after the governance has authorized the upgrade
// If you are starting over, the process is like the following:
// 1. create a governance proposal using generate_upgrade_ton_contract_proposal script
// 2. once approved and executed, relay it to TON using sync_governance_vaas script
// 3. upgrade the contract on TON using this script
const parser = yargs(hideBin(process.argv))
.usage(
"Upgrades the Pyth contract on TON and creates a governance proposal for it.\n" +
"Usage: $0 --network <mainnet|testnet> --contract <contract_name> --private-key <private_key>"
"Usage: $0 --contract <contract_name> --private-key <private_key>"
)
.options({
network: {
type: "string",
choices: ["mainnet", "testnet"],
description: "Network to deploy to",
demandOption: true,
},
contract: {
type: "string",
description: "Contract name",
Expand Down
57 changes: 34 additions & 23 deletions contract_manager/src/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ import {
import { keyPairFromSeed } from "@ton/crypto";
import { PythContract } from "@pythnetwork/pyth-ton-js";

/**
* Returns the chain rpc url with any environment variables replaced or throws an error if any are missing
*/
export function parseRpcUrl(rpcUrl: string): string {
const envMatches = rpcUrl.match(/\$ENV_\w+/);
if (envMatches) {
for (const envMatch of envMatches) {
const envName = envMatch.replace("$ENV_", "");
const envValue = process.env[envName];
if (!envValue) {
throw new Error(
`Missing env variable ${envName} required for this RPC: ${rpcUrl}`
);
}
rpcUrl = rpcUrl.replace(envMatch, envValue);
}
}
return rpcUrl;
}

export type ChainConfig = Record<string, string> & {
mainnet: boolean;
id: string;
Expand Down Expand Up @@ -352,23 +372,10 @@ export class EvmChain extends Chain {
}

/**
* Returns the chain rpc url with any environment variables replaced or throws an error if any are missing
* Returns a web3 provider for this chain
*/
getRpcUrl(): string {
const envMatches = this.rpcUrl.match(/\$ENV_\w+/);
if (envMatches) {
for (const envMatch of envMatches) {
const envName = envMatch.replace("$ENV_", "");
const envValue = process.env[envName];
if (!envValue) {
throw new Error(
`Missing env variable ${envName} required for chain ${this.id} rpc: ${this.rpcUrl}`
);
}
this.rpcUrl = this.rpcUrl.replace(envMatch, envValue);
}
}
return this.rpcUrl;
getWeb3(): Web3 {
return new Web3(parseRpcUrl(this.rpcUrl));
}

/**
Expand Down Expand Up @@ -419,7 +426,7 @@ export class EvmChain extends Chain {
}

async getGasPrice() {
const web3 = new Web3(this.getRpcUrl());
const web3 = this.getWeb3();
let gasPrice = await web3.eth.getGasPrice();
// some testnets have inaccuarte gas prices that leads to transactions not being mined, we double it since it's free!
if (!this.isMainnet()) {
Expand Down Expand Up @@ -459,7 +466,7 @@ export class EvmChain extends Chain {
gasMultiplier = 1,
gasPriceMultiplier = 1
): Promise<string> {
const web3 = new Web3(this.getRpcUrl());
const web3 = this.getWeb3();
const signer = web3.eth.accounts.privateKeyToAccount(privateKey);
web3.eth.accounts.wallet.add(signer);
const contract = new web3.eth.Contract(abi);
Expand Down Expand Up @@ -494,13 +501,13 @@ export class EvmChain extends Chain {
}

async getAccountAddress(privateKey: PrivateKey): Promise<string> {
const web3 = new Web3(this.getRpcUrl());
const web3 = this.getWeb3();
const signer = web3.eth.accounts.privateKeyToAccount(privateKey);
return signer.address;
}

async getAccountBalance(privateKey: PrivateKey): Promise<number> {
const web3 = new Web3(this.getRpcUrl());
const web3 = this.getWeb3();
const balance = await web3.eth.getBalance(
await this.getAccountAddress(privateKey)
);
Expand Down Expand Up @@ -757,15 +764,19 @@ export class TonChain extends Chain {
mainnet: boolean,
wormholeChainName: string,
nativeToken: TokenId | undefined,
public rpcUrl: string
private rpcUrl: string
) {
super(id, mainnet, wormholeChainName, nativeToken);
}

async getClient(): Promise<TonClient> {
// add apiKey if facing rate limit
// We are hacking rpcUrl to include the apiKey header which is a
// header that is used to bypass rate limits on the TON network
const [rpcUrl, apiKey] = parseRpcUrl(this.rpcUrl).split("#");

const client = new TonClient({
endpoint: this.rpcUrl,
endpoint: rpcUrl,
apiKey,
});
return client;
}
Expand Down
41 changes: 20 additions & 21 deletions contract_manager/src/contracts/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ import {
* with the deployedCode property generated by truffle builds
*/
export async function getCodeDigestWithoutAddress(
rpcUrl: string,
web3: Web3,
address: string
): Promise<string> {
const web3 = new Web3(rpcUrl);
const code = await web3.eth.getCode(address);
const strippedCode = code.replaceAll(
address.toLowerCase().replace("0x", ""),
Expand Down Expand Up @@ -70,7 +69,7 @@ export class EvmWormholeContract extends WormholeContract {
super();
}
getContract(): Contract {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
return new web3.eth.Contract(WORMHOLE_ABI, this.address);
}

Expand Down Expand Up @@ -99,7 +98,7 @@ export class EvmWormholeContract extends WormholeContract {
}

async upgradeGuardianSets(senderPrivateKey: PrivateKey, vaa: Buffer) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
const wormholeContract = new web3.eth.Contract(WORMHOLE_ABI, this.address);
const transactionObject = wormholeContract.methods.submitNewGuardianSet(
Expand Down Expand Up @@ -222,7 +221,7 @@ export class EvmEntropyContract extends Storable {
): Promise<Buffer> {
// Executor contract is the owner of entropy contract
const executorAddr = await this.getOwner();
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const executor = new web3.eth.Contract(EXECUTOR_ABI, executorAddr);
const data = executor.methods.upgradeTo(newImplementation).encodeABI();
return this.chain.generateExecutorPayload(executorAddr, executorAddr, data);
Expand Down Expand Up @@ -252,7 +251,7 @@ export class EvmEntropyContract extends Storable {
}

getContract() {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
return new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
}

Expand Down Expand Up @@ -327,7 +326,7 @@ export class EvmEntropyContract extends Storable {
sequenceNumber: number,
senderPrivateKey: PrivateKey
) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
// can not use `this.getContract()` because it uses another web3 instance without the wallet
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
Expand All @@ -343,7 +342,7 @@ export class EvmEntropyContract extends Storable {
}

generateUserRandomNumber() {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
return web3.utils.randomHex(32);
}

Expand All @@ -358,7 +357,7 @@ export class EvmEntropyContract extends Storable {
senderPrivateKey: PrivateKey,
withCallback?: boolean
) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const userCommitment = web3.utils.keccak256(userRandomNumber);
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
const fee = await contract.methods.getFee(provider).call();
Expand Down Expand Up @@ -392,7 +391,7 @@ export class EvmEntropyContract extends Storable {
sequenceNumber: string,
senderPrivateKey: PrivateKey
) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const contract = new web3.eth.Contract(EXTENDED_ENTROPY_ABI, this.address);
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
const transactionObject = contract.methods.reveal(
Expand Down Expand Up @@ -486,7 +485,7 @@ export class EvmExpressRelayContract extends Storable {
}

getContract() {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
return new web3.eth.Contract(EXPRESS_RELAY_ABI, this.address);
}
}
Expand All @@ -499,7 +498,7 @@ export class EvmExecutorContract {
}

async getWormholeContract(): Promise<EvmWormholeContract> {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
//Unfortunately, there is no public method to get the wormhole address
//Found 251 by using `forge build --extra-output storageLayout` and finding the slot for the wormhole variable.
let address = await web3.eth.getStorageAt(this.address, 251);
Expand All @@ -508,7 +507,7 @@ export class EvmExecutorContract {
}

getContract() {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
return new web3.eth.Contract(EXECUTOR_ABI, this.address);
}

Expand Down Expand Up @@ -542,7 +541,7 @@ export class EvmExecutorContract {
senderPrivateKey: PrivateKey,
vaa: Buffer
) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
const executorContract = new web3.eth.Contract(EXECUTOR_ABI, this.address);
const transactionObject = executorContract.methods.execute(
Expand Down Expand Up @@ -589,7 +588,7 @@ export class EvmPriceFeedContract extends PriceFeedContract {
}

getContract() {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
return pythContract;
}
Expand All @@ -599,12 +598,12 @@ export class EvmPriceFeedContract extends PriceFeedContract {
*/
async getCode(): Promise<string> {
// TODO: handle proxy contracts
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
return web3.eth.getCode(this.address);
}

async getImplementationAddress(): Promise<string> {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
// bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) according to EIP-1967
const storagePosition =
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
Expand All @@ -617,11 +616,11 @@ export class EvmPriceFeedContract extends PriceFeedContract {
* Returns the keccak256 digest of the contract bytecode
*/
async getCodeDigestWithoutAddress(): Promise<string> {
return getCodeDigestWithoutAddress(this.chain.getRpcUrl(), this.address);
return getCodeDigestWithoutAddress(this.chain.getWeb3(), this.address);
}

async getTotalFee(): Promise<TokenQty> {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const amount = BigInt(await web3.eth.getBalance(this.address));
return {
amount,
Expand Down Expand Up @@ -712,7 +711,7 @@ export class EvmPriceFeedContract extends PriceFeedContract {
}

async executeUpdatePriceFeed(senderPrivateKey: PrivateKey, vaas: Buffer[]) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
const priceFeedUpdateData = vaas.map((vaa) => "0x" + vaa.toString("hex"));
Expand All @@ -732,7 +731,7 @@ export class EvmPriceFeedContract extends PriceFeedContract {
senderPrivateKey: PrivateKey,
vaa: Buffer
) {
const web3 = new Web3(this.chain.getRpcUrl());
const web3 = this.chain.getWeb3();
const { address } = web3.eth.accounts.wallet.add(senderPrivateKey);
const pythContract = new web3.eth.Contract(EXTENDED_PYTH_ABI, this.address);
const transactionObject = pythContract.methods.executeGovernanceInstruction(
Expand Down
8 changes: 6 additions & 2 deletions contract_manager/store/chains/TonChains.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
- id: ton_testnet
wormholeChainName: ton_testnet
mainnet: false
rpcUrl: https://testnet.toncenter.com/api/v2/jsonRPC
# using # is a hack so we can later separate the key from the URL and pass
# it as a header
rpcUrl: https://testnet.toncenter.com/api/v2/#$ENV_TON_TESTNET_API_KEY
type: TonChain
- id: ton_mainnet
wormholeChainName: ton_mainnet
mainnet: true
rpcUrl: https://toncenter.com/api/v2/jsonRPC
# using # is a hack so we can later separate the key from the URL and pass
# it as a header
rpcUrl: https://toncenter.com/api/v2/jsonRPC#$ENV_TON_MAINNET_API_KEY
type: TonChain
Loading