Skip to content

Commit

Permalink
feat(contract_manager): add deploy_evm_pulse_contracts script (#2439)
Browse files Browse the repository at this point in the history
* feat(pulse): implement withdrawFees function and update ABI for Pulse contracts

* feat(deploy): enhance bytecode handling and add default provider option for Pulse contract deployment

* add hardcoded default provider and keeper

* refactor(deploy): simplify ERC1967Proxy deployment logic and remove unused code

* feat(deploy): extract common functions
  • Loading branch information
cctdaniel authored Mar 6, 2025
1 parent b4bf112 commit a5f46d7
Show file tree
Hide file tree
Showing 8 changed files with 1,462 additions and 44 deletions.
66 changes: 65 additions & 1 deletion contract_manager/scripts/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,26 @@ export async function deployIfNotCached(
readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8")
);

// Handle bytecode which can be either a string or an object with an 'object' property
let bytecode = artifact["bytecode"];
if (
typeof bytecode === "object" &&
bytecode !== null &&
"object" in bytecode
) {
bytecode = bytecode.object;
}

// Ensure bytecode starts with 0x
if (!bytecode.startsWith("0x")) {
bytecode = `0x${bytecode}`;
}

console.log(`Deploying ${artifactName} on ${chain.getId()}...`);
const addr = await chain.deploy(
config.privateKey,
artifact["abi"],
artifact["bytecode"],
bytecode,
deployArgs,
config.gasMultiplier,
config.gasPriceMultiplier
Expand Down Expand Up @@ -322,3 +337,52 @@ export async function getOrDeployWormholeContract(
(await deployWormholeContract(chain, config, cacheFile))
);
}

export interface DefaultAddresses {
mainnet: string;
testnet: string;
}

export async function topupAccountsIfNecessary(
chain: EvmChain,
deploymentConfig: BaseDeployConfig,
accounts: Array<[string, DefaultAddresses]>,
minBalance = 0.01
) {
for (const [accountName, defaultAddresses] of accounts) {
const accountAddress = chain.isMainnet()
? defaultAddresses.mainnet
: defaultAddresses.testnet;
const web3 = chain.getWeb3();
const balance = Number(
web3.utils.fromWei(await web3.eth.getBalance(accountAddress), "ether")
);
console.log(`${accountName} balance: ${balance} ETH`);
if (balance < minBalance) {
console.log(
`Balance is less than ${minBalance}. Topping up the ${accountName} address...`
);
const signer = web3.eth.accounts.privateKeyToAccount(
deploymentConfig.privateKey
);
web3.eth.accounts.wallet.add(signer);
const estimatedGas = await web3.eth.estimateGas({
from: signer.address,
to: accountAddress,
value: web3.utils.toWei(`${minBalance}`, "ether"),
});

const tx = await web3.eth.sendTransaction({
from: signer.address,
to: accountAddress,
gas: estimatedGas * deploymentConfig.gasMultiplier,
value: web3.utils.toWei(`${minBalance}`, "ether"),
});

console.log(
`Topped up the ${accountName} address. Tx: `,
tx.transactionHash
);
}
}
}
49 changes: 8 additions & 41 deletions contract_manager/scripts/deploy_evm_entropy_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import {
getWeb3Contract,
getOrDeployWormholeContract,
BaseDeployConfig,
topupAccountsIfNecessary,
DefaultAddresses,
} from "./common";
import Web3 from "web3";

interface DeploymentConfig extends BaseDeployConfig {
type: DeploymentType;
Expand Down Expand Up @@ -123,50 +124,16 @@ async function deployEntropyContracts(
);
}

async function topupAccountsIfNecessary(
async function topupEntropyAccountsIfNecessary(
chain: EvmChain,
deploymentConfig: DeploymentConfig
) {
for (const [accountName, defaultAddresses] of [
const accounts: Array<[string, DefaultAddresses]> = [
["keeper", ENTROPY_DEFAULT_KEEPER],
["provider", ENTROPY_DEFAULT_PROVIDER],
] as const) {
const accountAddress = chain.isMainnet()
? defaultAddresses.mainnet
: defaultAddresses.testnet;
const web3 = chain.getWeb3();
const balance = Number(
web3.utils.fromWei(await web3.eth.getBalance(accountAddress), "ether")
);
const MIN_BALANCE = 0.01;
console.log(`${accountName} balance: ${balance} ETH`);
if (balance < MIN_BALANCE) {
console.log(
`Balance is less than ${MIN_BALANCE}. Topping up the ${accountName} address...`
);
const signer = web3.eth.accounts.privateKeyToAccount(
deploymentConfig.privateKey
);
web3.eth.accounts.wallet.add(signer);
const estimatedGas = await web3.eth.estimateGas({
from: signer.address,
to: accountAddress,
value: web3.utils.toWei(`${MIN_BALANCE}`, "ether"),
});

const tx = await web3.eth.sendTransaction({
from: signer.address,
to: accountAddress,
gas: estimatedGas * deploymentConfig.gasMultiplier,
value: web3.utils.toWei(`${MIN_BALANCE}`, "ether"),
});

console.log(
`Topped up the ${accountName} address. Tx: `,
tx.transactionHash
);
}
}
];

await topupAccountsIfNecessary(chain, deploymentConfig, accounts);
}

async function main() {
Expand All @@ -189,7 +156,7 @@ async function main() {
CACHE_FILE
);

await topupAccountsIfNecessary(chain, deploymentConfig);
await topupEntropyAccountsIfNecessary(chain, deploymentConfig);

console.log(
`Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`
Expand Down
174 changes: 174 additions & 0 deletions contract_manager/scripts/deploy_evm_pulse_contracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { EvmChain } from "../src/chains";
import { DefaultStore } from "../src/store";
import {
DeploymentType,
toDeploymentType,
toPrivateKey,
EvmPulseContract,
PULSE_DEFAULT_PROVIDER,
PULSE_DEFAULT_KEEPER,
} from "../src";
import {
COMMON_DEPLOY_OPTIONS,
deployIfNotCached,
getWeb3Contract,
getOrDeployWormholeContract,
BaseDeployConfig,
topupAccountsIfNecessary,
DefaultAddresses,
} from "./common";
import fs from "fs";
import path from "path";

interface DeploymentConfig extends BaseDeployConfig {
type: DeploymentType;
saveContract: boolean;
}

const CACHE_FILE = ".cache-deploy-evm-pulse-contracts";

const parser = yargs(hideBin(process.argv))
.scriptName("deploy_evm_pulse_contracts.ts")
.usage(
"Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain> --default-provider <default-provider> --wormhole-addr <wormhole-addr>"
)
.options({
...COMMON_DEPLOY_OPTIONS,
chain: {
type: "string",
demandOption: true,
desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
},
});

async function deployPulseContracts(
chain: EvmChain,
config: DeploymentConfig,
executorAddr: string
): Promise<string> {
console.log("Deploying PulseUpgradeable on", chain.getId(), "...");

// Get the artifact and ensure bytecode is properly formatted
const pulseArtifact = JSON.parse(
fs.readFileSync(
path.join(config.jsonOutputDir, "PulseUpgradeable.json"),
"utf8"
)
);
console.log("PulseArtifact bytecode type:", typeof pulseArtifact.bytecode);

const pulseImplAddr = await deployIfNotCached(
CACHE_FILE,
chain,
config,
"PulseUpgradeable",
[]
);

console.log("PulseUpgradeable implementation deployed at:", pulseImplAddr);

const pulseImplContract = getWeb3Contract(
config.jsonOutputDir,
"PulseUpgradeable",
pulseImplAddr
);

console.log("Preparing initialization data...");

const pulseInitData = pulseImplContract.methods
.initialize(
executorAddr, // owner
executorAddr, // admin
"1", // pythFeeInWei
executorAddr, // pythAddress - using executor as a placeholder
chain.isMainnet()
? PULSE_DEFAULT_PROVIDER.mainnet
: PULSE_DEFAULT_PROVIDER.testnet,
true, // prefillRequestStorage
3600 // exclusivityPeriodSeconds - 1 hour
)
.encodeABI();

console.log("Deploying ERC1967Proxy for Pulse...");

return await deployIfNotCached(
CACHE_FILE,
chain,
config,
"ERC1967Proxy",
[pulseImplAddr, pulseInitData],
// NOTE: we are deploying a ERC1967Proxy when deploying executor
// we need to provide a different cache key. As the `artifactname`
// is same in both case which means the cache key will be same
`${chain.getId()}-ERC1967Proxy-PULSE1`
);
}

async function topupPulseAccountsIfNecessary(
chain: EvmChain,
deploymentConfig: DeploymentConfig
) {
const accounts: Array<[string, DefaultAddresses]> = [
["keeper", PULSE_DEFAULT_KEEPER],
["provider", PULSE_DEFAULT_PROVIDER],
];

await topupAccountsIfNecessary(chain, deploymentConfig, accounts);
}

async function main() {
const argv = await parser.argv;

const chainName = argv.chain;
const chain = DefaultStore.chains[chainName];
if (!chain) {
throw new Error(`Chain ${chainName} not found`);
} else if (!(chain instanceof EvmChain)) {
throw new Error(`Chain ${chainName} is not an EVM chain`);
}

const deploymentConfig: DeploymentConfig = {
type: toDeploymentType(argv.deploymentType),
gasMultiplier: argv.gasMultiplier,
gasPriceMultiplier: argv.gasPriceMultiplier,
privateKey: toPrivateKey(argv.privateKey),
jsonOutputDir: argv.stdOutputDir,
saveContract: argv.saveContract,
};

const wormholeContract = await getOrDeployWormholeContract(
chain,
deploymentConfig,
CACHE_FILE
);

await topupPulseAccountsIfNecessary(chain, deploymentConfig);

console.log(
`Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`
);

console.log(`Deploying pulse contracts on ${chain.getId()}...`);

const executorAddr = wormholeContract.address; // Using wormhole contract as executor for Pulse
const pulseAddr = await deployPulseContracts(
chain,
deploymentConfig,
executorAddr
);

if (deploymentConfig.saveContract) {
console.log("Saving the contract in the store...");
const contract = new EvmPulseContract(chain, pulseAddr);
DefaultStore.pulse_contracts[contract.getId()] = contract;
DefaultStore.saveAllContracts();
}

console.log(
`✅ Deployed pulse contracts on ${chain.getId()} at ${pulseAddr}\n\n`
);
}

main();
Loading

0 comments on commit a5f46d7

Please sign in to comment.