Skip to content

Commit a5f46d7

Browse files
authored
feat(contract_manager): add deploy_evm_pulse_contracts script (#2439)
* 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
1 parent b4bf112 commit a5f46d7

File tree

8 files changed

+1462
-44
lines changed

8 files changed

+1462
-44
lines changed

contract_manager/scripts/common.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,26 @@ export async function deployIfNotCached(
3838
readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8")
3939
);
4040

41+
// Handle bytecode which can be either a string or an object with an 'object' property
42+
let bytecode = artifact["bytecode"];
43+
if (
44+
typeof bytecode === "object" &&
45+
bytecode !== null &&
46+
"object" in bytecode
47+
) {
48+
bytecode = bytecode.object;
49+
}
50+
51+
// Ensure bytecode starts with 0x
52+
if (!bytecode.startsWith("0x")) {
53+
bytecode = `0x${bytecode}`;
54+
}
55+
4156
console.log(`Deploying ${artifactName} on ${chain.getId()}...`);
4257
const addr = await chain.deploy(
4358
config.privateKey,
4459
artifact["abi"],
45-
artifact["bytecode"],
60+
bytecode,
4661
deployArgs,
4762
config.gasMultiplier,
4863
config.gasPriceMultiplier
@@ -322,3 +337,52 @@ export async function getOrDeployWormholeContract(
322337
(await deployWormholeContract(chain, config, cacheFile))
323338
);
324339
}
340+
341+
export interface DefaultAddresses {
342+
mainnet: string;
343+
testnet: string;
344+
}
345+
346+
export async function topupAccountsIfNecessary(
347+
chain: EvmChain,
348+
deploymentConfig: BaseDeployConfig,
349+
accounts: Array<[string, DefaultAddresses]>,
350+
minBalance = 0.01
351+
) {
352+
for (const [accountName, defaultAddresses] of accounts) {
353+
const accountAddress = chain.isMainnet()
354+
? defaultAddresses.mainnet
355+
: defaultAddresses.testnet;
356+
const web3 = chain.getWeb3();
357+
const balance = Number(
358+
web3.utils.fromWei(await web3.eth.getBalance(accountAddress), "ether")
359+
);
360+
console.log(`${accountName} balance: ${balance} ETH`);
361+
if (balance < minBalance) {
362+
console.log(
363+
`Balance is less than ${minBalance}. Topping up the ${accountName} address...`
364+
);
365+
const signer = web3.eth.accounts.privateKeyToAccount(
366+
deploymentConfig.privateKey
367+
);
368+
web3.eth.accounts.wallet.add(signer);
369+
const estimatedGas = await web3.eth.estimateGas({
370+
from: signer.address,
371+
to: accountAddress,
372+
value: web3.utils.toWei(`${minBalance}`, "ether"),
373+
});
374+
375+
const tx = await web3.eth.sendTransaction({
376+
from: signer.address,
377+
to: accountAddress,
378+
gas: estimatedGas * deploymentConfig.gasMultiplier,
379+
value: web3.utils.toWei(`${minBalance}`, "ether"),
380+
});
381+
382+
console.log(
383+
`Topped up the ${accountName} address. Tx: `,
384+
tx.transactionHash
385+
);
386+
}
387+
}
388+
}

contract_manager/scripts/deploy_evm_entropy_contracts.ts

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import {
1717
getWeb3Contract,
1818
getOrDeployWormholeContract,
1919
BaseDeployConfig,
20+
topupAccountsIfNecessary,
21+
DefaultAddresses,
2022
} from "./common";
21-
import Web3 from "web3";
2223

2324
interface DeploymentConfig extends BaseDeployConfig {
2425
type: DeploymentType;
@@ -123,50 +124,16 @@ async function deployEntropyContracts(
123124
);
124125
}
125126

126-
async function topupAccountsIfNecessary(
127+
async function topupEntropyAccountsIfNecessary(
127128
chain: EvmChain,
128129
deploymentConfig: DeploymentConfig
129130
) {
130-
for (const [accountName, defaultAddresses] of [
131+
const accounts: Array<[string, DefaultAddresses]> = [
131132
["keeper", ENTROPY_DEFAULT_KEEPER],
132133
["provider", ENTROPY_DEFAULT_PROVIDER],
133-
] as const) {
134-
const accountAddress = chain.isMainnet()
135-
? defaultAddresses.mainnet
136-
: defaultAddresses.testnet;
137-
const web3 = chain.getWeb3();
138-
const balance = Number(
139-
web3.utils.fromWei(await web3.eth.getBalance(accountAddress), "ether")
140-
);
141-
const MIN_BALANCE = 0.01;
142-
console.log(`${accountName} balance: ${balance} ETH`);
143-
if (balance < MIN_BALANCE) {
144-
console.log(
145-
`Balance is less than ${MIN_BALANCE}. Topping up the ${accountName} address...`
146-
);
147-
const signer = web3.eth.accounts.privateKeyToAccount(
148-
deploymentConfig.privateKey
149-
);
150-
web3.eth.accounts.wallet.add(signer);
151-
const estimatedGas = await web3.eth.estimateGas({
152-
from: signer.address,
153-
to: accountAddress,
154-
value: web3.utils.toWei(`${MIN_BALANCE}`, "ether"),
155-
});
156-
157-
const tx = await web3.eth.sendTransaction({
158-
from: signer.address,
159-
to: accountAddress,
160-
gas: estimatedGas * deploymentConfig.gasMultiplier,
161-
value: web3.utils.toWei(`${MIN_BALANCE}`, "ether"),
162-
});
163-
164-
console.log(
165-
`Topped up the ${accountName} address. Tx: `,
166-
tx.transactionHash
167-
);
168-
}
169-
}
134+
];
135+
136+
await topupAccountsIfNecessary(chain, deploymentConfig, accounts);
170137
}
171138

172139
async function main() {
@@ -189,7 +156,7 @@ async function main() {
189156
CACHE_FILE
190157
);
191158

192-
await topupAccountsIfNecessary(chain, deploymentConfig);
159+
await topupEntropyAccountsIfNecessary(chain, deploymentConfig);
193160

194161
console.log(
195162
`Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import yargs from "yargs";
2+
import { hideBin } from "yargs/helpers";
3+
import { EvmChain } from "../src/chains";
4+
import { DefaultStore } from "../src/store";
5+
import {
6+
DeploymentType,
7+
toDeploymentType,
8+
toPrivateKey,
9+
EvmPulseContract,
10+
PULSE_DEFAULT_PROVIDER,
11+
PULSE_DEFAULT_KEEPER,
12+
} from "../src";
13+
import {
14+
COMMON_DEPLOY_OPTIONS,
15+
deployIfNotCached,
16+
getWeb3Contract,
17+
getOrDeployWormholeContract,
18+
BaseDeployConfig,
19+
topupAccountsIfNecessary,
20+
DefaultAddresses,
21+
} from "./common";
22+
import fs from "fs";
23+
import path from "path";
24+
25+
interface DeploymentConfig extends BaseDeployConfig {
26+
type: DeploymentType;
27+
saveContract: boolean;
28+
}
29+
30+
const CACHE_FILE = ".cache-deploy-evm-pulse-contracts";
31+
32+
const parser = yargs(hideBin(process.argv))
33+
.scriptName("deploy_evm_pulse_contracts.ts")
34+
.usage(
35+
"Usage: $0 --std-output-dir <path/to/std-output-dir/> --private-key <private-key> --chain <chain> --default-provider <default-provider> --wormhole-addr <wormhole-addr>"
36+
)
37+
.options({
38+
...COMMON_DEPLOY_OPTIONS,
39+
chain: {
40+
type: "string",
41+
demandOption: true,
42+
desc: "Chain to upload the contract on. Can be one of the evm chains available in the store",
43+
},
44+
});
45+
46+
async function deployPulseContracts(
47+
chain: EvmChain,
48+
config: DeploymentConfig,
49+
executorAddr: string
50+
): Promise<string> {
51+
console.log("Deploying PulseUpgradeable on", chain.getId(), "...");
52+
53+
// Get the artifact and ensure bytecode is properly formatted
54+
const pulseArtifact = JSON.parse(
55+
fs.readFileSync(
56+
path.join(config.jsonOutputDir, "PulseUpgradeable.json"),
57+
"utf8"
58+
)
59+
);
60+
console.log("PulseArtifact bytecode type:", typeof pulseArtifact.bytecode);
61+
62+
const pulseImplAddr = await deployIfNotCached(
63+
CACHE_FILE,
64+
chain,
65+
config,
66+
"PulseUpgradeable",
67+
[]
68+
);
69+
70+
console.log("PulseUpgradeable implementation deployed at:", pulseImplAddr);
71+
72+
const pulseImplContract = getWeb3Contract(
73+
config.jsonOutputDir,
74+
"PulseUpgradeable",
75+
pulseImplAddr
76+
);
77+
78+
console.log("Preparing initialization data...");
79+
80+
const pulseInitData = pulseImplContract.methods
81+
.initialize(
82+
executorAddr, // owner
83+
executorAddr, // admin
84+
"1", // pythFeeInWei
85+
executorAddr, // pythAddress - using executor as a placeholder
86+
chain.isMainnet()
87+
? PULSE_DEFAULT_PROVIDER.mainnet
88+
: PULSE_DEFAULT_PROVIDER.testnet,
89+
true, // prefillRequestStorage
90+
3600 // exclusivityPeriodSeconds - 1 hour
91+
)
92+
.encodeABI();
93+
94+
console.log("Deploying ERC1967Proxy for Pulse...");
95+
96+
return await deployIfNotCached(
97+
CACHE_FILE,
98+
chain,
99+
config,
100+
"ERC1967Proxy",
101+
[pulseImplAddr, pulseInitData],
102+
// NOTE: we are deploying a ERC1967Proxy when deploying executor
103+
// we need to provide a different cache key. As the `artifactname`
104+
// is same in both case which means the cache key will be same
105+
`${chain.getId()}-ERC1967Proxy-PULSE1`
106+
);
107+
}
108+
109+
async function topupPulseAccountsIfNecessary(
110+
chain: EvmChain,
111+
deploymentConfig: DeploymentConfig
112+
) {
113+
const accounts: Array<[string, DefaultAddresses]> = [
114+
["keeper", PULSE_DEFAULT_KEEPER],
115+
["provider", PULSE_DEFAULT_PROVIDER],
116+
];
117+
118+
await topupAccountsIfNecessary(chain, deploymentConfig, accounts);
119+
}
120+
121+
async function main() {
122+
const argv = await parser.argv;
123+
124+
const chainName = argv.chain;
125+
const chain = DefaultStore.chains[chainName];
126+
if (!chain) {
127+
throw new Error(`Chain ${chainName} not found`);
128+
} else if (!(chain instanceof EvmChain)) {
129+
throw new Error(`Chain ${chainName} is not an EVM chain`);
130+
}
131+
132+
const deploymentConfig: DeploymentConfig = {
133+
type: toDeploymentType(argv.deploymentType),
134+
gasMultiplier: argv.gasMultiplier,
135+
gasPriceMultiplier: argv.gasPriceMultiplier,
136+
privateKey: toPrivateKey(argv.privateKey),
137+
jsonOutputDir: argv.stdOutputDir,
138+
saveContract: argv.saveContract,
139+
};
140+
141+
const wormholeContract = await getOrDeployWormholeContract(
142+
chain,
143+
deploymentConfig,
144+
CACHE_FILE
145+
);
146+
147+
await topupPulseAccountsIfNecessary(chain, deploymentConfig);
148+
149+
console.log(
150+
`Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`
151+
);
152+
153+
console.log(`Deploying pulse contracts on ${chain.getId()}...`);
154+
155+
const executorAddr = wormholeContract.address; // Using wormhole contract as executor for Pulse
156+
const pulseAddr = await deployPulseContracts(
157+
chain,
158+
deploymentConfig,
159+
executorAddr
160+
);
161+
162+
if (deploymentConfig.saveContract) {
163+
console.log("Saving the contract in the store...");
164+
const contract = new EvmPulseContract(chain, pulseAddr);
165+
DefaultStore.pulse_contracts[contract.getId()] = contract;
166+
DefaultStore.saveAllContracts();
167+
}
168+
169+
console.log(
170+
`✅ Deployed pulse contracts on ${chain.getId()} at ${pulseAddr}\n\n`
171+
);
172+
}
173+
174+
main();

0 commit comments

Comments
 (0)