Skip to content
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
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
Loading