UNPKG

@openzeppelin/hardhat-upgrades

Version:

[![Docs](https://img.shields.io/badge/docs-%F0%9F%93%84-blue)](https://docs.openzeppelin.com/upgrades-plugins/hardhat-upgrades) [![NPM Package](https://img.shields.io/npm/v/@openzeppelin/hardhat-upgrades.svg)](https://www.npmjs.org/package/@openzeppelin/h

126 lines • 6.93 kB
import { encodeAbiParameters, encodeDeployData, encodeFunctionData as viemEncodeFunctionData, getAddress, getContractAddress, toFunctionSignature, } from 'viem'; import { UpgradesError } from '@openzeppelin/upgrades-core'; import { waitForReceipt } from '../engine/receipt.js'; /** * Builds the client-neutral engine binding for the viem API. All transactions are signed and * broadcast through the given wallet client, so viem local accounts (private key, mnemonic, KMS, * hardware) are supported natively — viem's own `sendTransaction` branches on the account type. */ export function makeViemBinding(hre, connection, walletClient, exec) { const provider = connection.provider; const overrides = { ...(exec.value !== undefined ? { value: exec.value } : {}), ...(exec.gas !== undefined ? { gas: exec.gas } : {}), ...(exec.gasPrice !== undefined ? { gasPrice: exec.gasPrice } : {}), ...(exec.maxFeePerGas !== undefined ? { maxFeePerGas: exec.maxFeePerGas } : {}), ...(exec.maxPriorityFeePerGas !== undefined ? { maxPriorityFeePerGas: exec.maxPriorityFeePerGas } : {}), }; const waitOpts = { timeout: exec.timeout, pollingInterval: exec.pollingInterval }; const send = (request) => { if (walletClient === undefined) { // Read-only bindings (validation, force-import) never reach here. throw new UpgradesError('Broken invariant: a transaction was sent without a wallet client'); } return walletClient.sendTransaction({ account: walletClient.account, ...request, ...overrides, }); }; const broadcastDeploy = (info, args, nonce) => send({ data: encodeDeployData({ abi: info.abi, bytecode: info.bytecode, ...(args.length > 0 ? { args: args } : {}), }), ...(nonce !== undefined ? { nonce } : {}), }); return { hre, provider, encodeConstructorArgs(info, args) { // Validate the constructor argument count up front and ABI-encode just the arguments (no // bytecode) for the version hash. viem's `encodeDeployData` silently returns the bytecode // unchanged when no args are passed, so a contract with required constructor parameters // deployed without `constructorArgs` would otherwise be encoded with no arguments instead of // failing — `encodeAbiParameters` throws on a length mismatch, matching the ethers binding. const inputs = constructorInputs(info.abi); if (inputs.length !== args.length) { throw new UpgradesError(`Expected ${inputs.length} constructor argument(s) but got ${args.length}`, () => "Provide the implementation contract's constructor arguments with the `constructorArgs` option."); } if (inputs.length === 0) { return '0x'; } return encodeAbiParameters(inputs, args); }, encodeFunctionData(abi, fn, args) { if (fn.includes('(')) { const item = findBySignature(abi, fn); if (item === undefined) { throw new UpgradesError(`The contract has no function matching the signature: ${fn}`); } return viemEncodeFunctionData({ abi: [item], functionName: item.name, args: args }); } return viemEncodeFunctionData({ abi: abi, functionName: fn, args: args }); }, hasFunction(abi, fn) { if (fn.includes('(')) { return findBySignature(abi, fn) !== undefined; } return abi.some(item => item.type === 'function' && item.name === fn); }, formatManifestAbi(abi) { return abi; }, async getSignerAddress() { return walletClient?.account?.address; }, async deployUnconfirmed(info, args) { // Determine the eventual address before broadcasting: read the sender's pending nonce, send the // deployment with that explicit nonce, and compute the CREATE address from `(from, nonce)` // locally — the address depends only on the sender and nonce, not on the bytecode or arguments. // This mirrors how the ethers binding deploys, and unlike querying the chain for the transaction // after broadcasting it leaves no step that can fail between the (irreversible) broadcast and // returning the record, so the engine always records the deployment before it is mined. // `@openzeppelin/upgrades-core` then confirms it outside the manifest lock, so the lock is not // held while the contract mines. const account = walletClient?.account; if (account === undefined) { // Read-only bindings (validation, force-import) never reach here. throw new UpgradesError('Broken invariant: a transaction was sent without a wallet client'); } const publicClient = await connection.viem.getPublicClient(); const nonce = await publicClient.getTransactionCount({ address: account.address, blockTag: 'pending' }); const txHash = await broadcastDeploy(info, args, nonce); return { address: getContractAddress({ from: account.address, nonce: BigInt(nonce) }), txHash }; }, async deploy(info, args) { // Wait for the receipt so the returned address is immediately usable. Used for deployments the // engine does not confirm itself (the beacon contract); proxies go through `deployProxy`. const txHash = await broadcastDeploy(info, args); const receipt = await waitForReceipt(provider, txHash, waitOpts); if (receipt.contractAddress === undefined || receipt.contractAddress === null) { throw new UpgradesError(`Deployment transaction ${txHash} did not create a contract`); } return { address: getAddress(receipt.contractAddress), txHash }; }, deployProxy(info, args) { // The viem API has no `proxyFactory`/`deployFunction` escape hatches, so proxies deploy the // same way as any other contract. return this.deploy(info, args); }, async sendTransaction(tx) { const txHash = await send({ to: tx.to, data: tx.data }); await waitForReceipt(provider, txHash, waitOpts); return { txHash }; }, }; } function findBySignature(abi, signature) { return abi.find((item) => item.type === 'function' && toFunctionSignature(item) === signature); } function constructorInputs(abi) { const constructor = abi.find((item) => item.type === 'constructor'); return constructor?.inputs ?? []; } //# sourceMappingURL=viem-binding.js.map