feat: handle salt for deterministic deploys (#4528)

## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR focuses on adding support for deterministic contract deployment using a salt parameter.

### Detailed summary
- Added support for passing a `salt` parameter for deterministic contract deployment
- Updated functions to handle the `salt` parameter for deterministic deploys
- Modified tests to include deterministic deployment scenarios

> The following files were skipped due to too many changes: `packages/thirdweb/src/extensions/prebuilts/deploy-published.ts`, `packages/thirdweb/src/contract/deployment/deploy-with-abi.ts`, `packages/thirdweb/test/src/abis/proxy.ts`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
joaquim-verges
2024-09-11 05:16:55 +00:00
parent beb894a355
commit c96e2d9777
13 changed files with 248 additions and 62 deletions
+38
View File
@@ -0,0 +1,38 @@
---
"thirdweb": minor
---
Handle salt parameter for deterministic deploys of published contracts
You can now pass a salt parameter to the `deployPublishedContract` function to deploy a contract deterministically.
```ts
const address = await deployPublishedContract({
client,
chain,
account,
contractId: "Airdrop",
contractParams: {
defaultAdmin: "0x...",
contractURI: "ipfs://...",
},
salt: "test", // <--- deterministic deploy
});
```
This also works for unpublished contracts, via the `deployContract` function.
```ts
const address = await deployContract({
client,
chain,
account,
bytecode: "0x...",
abi: contractAbi,
constructorParams: {
param1: "value1",
param2: 123,
},
salt: "test", // <--- deterministic deploy
});
```
@@ -1,5 +1,10 @@
import type { Abi } from "abitype";
import { prepareTransaction } from "../../transaction/prepare-transaction.js";
import { computeDeploymentInfoFromContractId } from "../../utils/any-evm/compute-published-contract-deploy-info.js";
import {
computeDeploymentInfoFromBytecode,
computeDeploymentInfoFromContractId,
} from "../../utils/any-evm/compute-published-contract-deploy-info.js";
import type { Hex } from "../../utils/encoding/hex.js";
import type { Prettify } from "../../utils/type-utils.js";
import type { ClientAndChain } from "../../utils/types.js";
import { computeCreate2FactoryAddress } from "./utils/create-2-factory.js";
@@ -56,3 +61,31 @@ export function prepareDeterministicDeployTransaction(
},
});
}
/**
* @internal
*/
export function prepareDeterministicDeployTransactionFromBytecode(
options: ClientAndChain & {
abi: Abi;
bytecode: Hex;
constructorParams?: Record<string, unknown>;
salt?: string;
},
) {
const { client, chain } = options;
return prepareTransaction({
client,
chain,
to: () =>
computeCreate2FactoryAddress({
client,
chain,
}),
data: async () => {
const infraContractInfo =
await computeDeploymentInfoFromBytecode(options);
return infraContractInfo.initBytecodeWithsalt;
},
});
}
@@ -70,6 +70,7 @@ export async function deployViaAutoFactory(
account,
cloneFactoryContract,
initializeTransaction,
salt,
} = options;
if (isZkSyncChain(chain)) {
@@ -79,6 +80,7 @@ export async function deployViaAutoFactory(
account,
cloneFactoryContract,
initializeTransaction,
salt,
});
}
@@ -87,6 +89,7 @@ export async function deployViaAutoFactory(
client,
cloneFactoryContract,
initializeTransaction,
salt,
});
const receipt = await sendAndConfirmTransaction({
transaction: tx,
@@ -1,11 +1,10 @@
import type {
AbiConstructor,
AbiParameter,
AbiParametersToPrimitiveTypes,
} from "abitype";
import type { Abi, AbiConstructor } from "abitype";
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
import { prepareTransaction } from "../../transaction/prepare-transaction.js";
import { encodeAbiParameters } from "../../utils/abi/encodeAbiParameters.js";
import { normalizeFunctionParams } from "../../utils/abi/normalizeFunctionParams.js";
import { computeDeploymentAddress } from "../../utils/any-evm/compute-deployment-address.js";
import { computeDeploymentInfoFromBytecode } from "../../utils/any-evm/compute-published-contract-deploy-info.js";
import { ensureBytecodePrefix } from "../../utils/bytecode/prefix.js";
import { concatHex } from "../../utils/encoding/helpers/concat-hex.js";
import { type Hex, isHex } from "../../utils/encoding/hex.js";
@@ -16,16 +15,11 @@ import type { Account } from "../../wallets/interfaces/wallet.js";
/**
* @extension DEPLOY
*/
export type PrepareDirectDeployTransactionOptions<
TConstructor extends AbiConstructor,
TParams = AbiParametersToPrimitiveTypes<TConstructor["inputs"]>,
> = Prettify<
export type PrepareDirectDeployTransactionOptions = Prettify<
ClientAndChain & {
constructorAbi: TConstructor | undefined;
abi: Abi;
bytecode: Hex;
constructorParams: TParams extends readonly AbiParameter[]
? TParams
: readonly unknown[];
constructorParams?: Record<string, unknown>;
}
>;
@@ -51,13 +45,16 @@ export type PrepareDirectDeployTransactionOptions<
* ```
* @extension DEPLOY
*/
export function prepareDirectDeployTransaction<
const TConstructor extends AbiConstructor,
>(options: PrepareDirectDeployTransactionOptions<TConstructor>) {
export function prepareDirectDeployTransaction(
options: PrepareDirectDeployTransactionOptions,
) {
const bytecode = ensureBytecodePrefix(options.bytecode);
if (!isHex(bytecode)) {
throw new Error(`Contract bytecode is invalid.\n\n${bytecode}`);
}
const constructorAbi = options.abi.find(
(abi) => abi.type === "constructor",
) as AbiConstructor | undefined;
// prepare the tx
return prepareTransaction({
chain: options.chain,
@@ -66,8 +63,8 @@ export function prepareDirectDeployTransaction<
data: concatHex([
bytecode,
encodeAbiParameters(
options.constructorAbi?.inputs || [], // Leave an empty array if there's no constructor
options.constructorParams,
constructorAbi?.inputs || [], // Leave an empty array if there's no constructor
normalizeFunctionParams(constructorAbi, options.constructorParams),
),
]),
});
@@ -85,20 +82,41 @@ export function prepareDirectDeployTransaction<
* client,
* chain,
* bytecode: "0x...",
* constructorAbi: {
* inputs: [{ type: "uint256", name: "value" }],
* type: "constructor",
* abi: contractAbi,
* constructorParams: {
* param1: "value1",
* param2: 123,
* },
* constructorParams: [123],
* salt, // passing salt enables deterministic deploys
* });
* ```
* @extension DEPLOY
*/
export async function deployContract<const TConstructor extends AbiConstructor>(
options: PrepareDirectDeployTransactionOptions<TConstructor> & {
export async function deployContract(
options: PrepareDirectDeployTransactionOptions & {
account: Account;
salt?: string;
},
) {
if (options.salt !== undefined) {
// Deploy with CREATE2 if salt is provided
const info = await computeDeploymentInfoFromBytecode(options);
sendAndConfirmTransaction({
account: options.account,
transaction: prepareTransaction({
chain: options.chain,
client: options.client,
to: info.create2FactoryAddress,
data: info.initBytecodeWithsalt,
}),
});
return computeDeploymentAddress({
bytecode: options.bytecode,
encodedArgs: info.encodedArgs,
create2FactoryAddress: info.create2FactoryAddress,
});
}
const deployTx = prepareDirectDeployTransaction(options);
const receipt = await sendAndConfirmTransaction({
account: options.account,
@@ -8,6 +8,7 @@ import { normalizeFunctionParams } from "../../../utils/abi/normalizeFunctionPar
import { CONTRACT_DEPLOYER_ADDRESS } from "../../../utils/any-evm/zksync/constants.js";
import type { Hex } from "../../../utils/encoding/hex.js";
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
import { zkDeployContractDeterministic } from "./zkDeployDeterministic.js";
/**
* @internal
@@ -17,8 +18,14 @@ export async function zkDeployContract(
abi: Abi;
bytecode: Hex;
params?: Record<string, unknown>;
salt?: string;
},
) {
if (options.salt !== undefined) {
// if a salt is provided, use the deterministic deployer
return zkDeployContractDeterministic(options);
}
const data = encodeDeployData({
abi: options.abi,
bytecode: options.bytecode,
@@ -48,6 +48,7 @@ export async function zkDeployProxy(
_logic: implementationAddress,
_data: await encode(options.initializeTransaction),
},
salt: options.salt,
});
// return address of proxy
@@ -52,6 +52,7 @@ export type DeployModularContractOptions = Prettify<
params: ModularContractParams;
modules?: ModuleInstaller[];
publisher?: string;
salt?: string;
}
>;
@@ -97,6 +98,7 @@ export async function deployModularContract(
core,
params: coreParams,
modules = [],
salt,
} = options;
const contractId = getContractId(core);
const { cloneFactoryContract, implementationContract } =
@@ -147,6 +149,7 @@ export async function deployModularContract(
account,
cloneFactoryContract,
initializeTransaction,
salt,
});
}
@@ -26,6 +26,21 @@ describe.runIf(process.env.TW_SECRET_KEY)(
expect(address.length).toBe(42);
});
it("should deploy a published direct deploy contract deterministically", async () => {
const address = await deployPublishedContract({
client: TEST_CLIENT,
chain: ANVIL_CHAIN,
account: TEST_ACCOUNT_A,
contractId: "AccountFactory",
contractParams: {
defaultAdmin: TEST_ACCOUNT_A.address,
entrypoint: ENTRYPOINT_ADDRESS_v0_6,
},
salt: "test",
});
expect(address).toBe("0x75fc461544723c0dac7b46256027878e82ddc2cb");
});
it("should deploy a published autofactory contract", async () => {
const address = await deployPublishedContract({
client: TEST_CLIENT,
@@ -41,6 +56,21 @@ describe.runIf(process.env.TW_SECRET_KEY)(
expect(address.length).toBe(42);
});
it("should deploy a published autofactory contract deterministically", async () => {
const address = await deployPublishedContract({
client: TEST_CLIENT,
chain: ANVIL_CHAIN,
account: TEST_ACCOUNT_A,
contractId: "Airdrop",
contractParams: {
defaultAdmin: TEST_ACCOUNT_A.address,
contractURI: "",
},
salt: "test",
});
expect(address).toBe("0x777151741260F8d4098dD492e45FdB536F442672");
});
// TODO: Replace these tests' live contracts with mocks
it("should deploy a published contract with no constructor", async () => {
const address = await deployPublishedContract({
@@ -65,5 +95,28 @@ describe.runIf(process.env.TW_SECRET_KEY)(
expect(address).toBeDefined();
expect(address.length).toBe(42);
});
it("should deploy a published contract with no fuzzy params", async () => {
const address = await deployPublishedContract({
client: TEST_CLIENT,
chain: ANVIL_CHAIN,
account: TEST_ACCOUNT_A,
contractId: "DropERC1155",
contractParams: {
defaultAdmin: TEST_ACCOUNT_A.address, // defaultAdmin
name: "test", // name
symbol: "test", // symbol
contractURI: "", // contractURI
trustedForwarders: [], // trustedForwarders
saleRecipient: TEST_ACCOUNT_A.address, // saleRecipient
royaltyRecipient: TEST_ACCOUNT_A.address, // royaltyRecipient
royaltyBps: 0n, // royaltyBps
platformFeeBps: 0n, // platformFeeBps
platformFeeRecipient: TEST_ACCOUNT_A.address, // platformFeeRecipient
},
});
expect(address).toBeDefined();
expect(address.length).toBe(42);
});
},
);
@@ -1,4 +1,4 @@
import type { AbiConstructor, AbiFunction } from "abitype";
import type { AbiFunction } from "abitype";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import { type ThirdwebContract, getContract } from "../../contract/contract.js";
@@ -31,6 +31,7 @@ export type DeployPublishedContractOptions = {
publisher?: string;
version?: string;
implementationConstructorParams?: Record<string, unknown>;
salt?: string;
};
/**
@@ -64,6 +65,7 @@ export async function deployPublishedContract(
publisher,
version,
implementationConstructorParams,
salt,
} = options;
const deployMetadata = await fetchPublishedContractMetadata({
client,
@@ -79,6 +81,7 @@ export async function deployPublishedContract(
client,
initializeParams: contractParams,
implementationConstructorParams,
salt,
});
}
@@ -96,6 +99,7 @@ export type DeployContractfromDeployMetadataOptions = {
deployMetadata: FetchDeployMetadataResult;
initializeParams?: Record<string, unknown>;
}[];
salt?: string;
};
/**
@@ -103,7 +107,7 @@ export type DeployContractfromDeployMetadataOptions = {
*/
export async function deployContractfromDeployMetadata(
options: DeployContractfromDeployMetadataOptions,
) {
): Promise<string> {
const {
client,
account,
@@ -112,6 +116,7 @@ export async function deployContractfromDeployMetadata(
deployMetadata,
implementationConstructorParams,
modules,
salt,
} = options;
switch (deployMetadata?.deployType) {
case "standard": {
@@ -121,6 +126,7 @@ export async function deployContractfromDeployMetadata(
chain,
compilerMetadata: deployMetadata,
contractParams: initializeParams,
salt,
});
}
case "autoFactory": {
@@ -157,6 +163,7 @@ export async function deployContractfromDeployMetadata(
account,
cloneFactoryContract,
initializeTransaction,
salt,
});
}
case "customFactory": {
@@ -185,14 +192,14 @@ export async function deployContractfromDeployMetadata(
params: normalizeFunctionParams(method, initializeParams),
});
// asumption here is that the factory address returns the deployed proxy address
const address = simulateTransaction({
const address = await simulateTransaction({
transaction: deployTx,
});
await sendAndConfirmTransaction({
transaction: deployTx,
account,
});
return address;
return address as string;
}
case undefined: {
// Default to standard deployment if none was specified
@@ -202,6 +209,7 @@ export async function deployContractfromDeployMetadata(
chain,
compilerMetadata: deployMetadata,
contractParams: initializeParams,
salt,
});
}
default:
@@ -216,8 +224,10 @@ async function directDeploy(options: {
chain: Chain;
compilerMetadata: CompilerMetadata;
contractParams?: Record<string, unknown>;
}) {
const { account, client, chain, compilerMetadata, contractParams } = options;
salt?: string;
}): Promise<string> {
const { account, client, chain, compilerMetadata, contractParams, salt } =
options;
if (isZkSyncChain(chain)) {
return zkDeployContract({
@@ -227,22 +237,21 @@ async function directDeploy(options: {
bytecode: compilerMetadata.bytecode,
abi: compilerMetadata.abi,
params: contractParams,
salt,
});
}
const { deployContract } = await import(
"../../contract/deployment/deploy-with-abi.js"
);
const constructorAbi = compilerMetadata.abi.find(
(i) => i.type === "constructor",
) as AbiConstructor | undefined;
return deployContract({
account,
client,
chain,
bytecode: compilerMetadata.bytecode,
constructorAbi,
constructorParams: normalizeFunctionParams(constructorAbi, contractParams),
abi: compilerMetadata.abi,
constructorParams: contractParams,
salt,
});
}
@@ -322,10 +331,7 @@ async function getInitializeTransaction(options: {
chain,
address: implementationContract.address,
}),
method: resolveMethod(
metadata.factoryDeploymentData?.implementationInitializerFunction ||
"initialize",
),
method: initializeFunction,
params: normalizeFunctionParams(initializeFunction, initializeParams),
});
return initializeTransaction;
@@ -35,7 +35,7 @@ describe("sendEip712Transaction", () => {
client: TEST_CLIENT,
chain: zkSyncSepolia, // TODO make zksync fork chain work
account: TEST_ACCOUNT_A,
contractId: "DropERC1155",
contractId: "DropERC721",
contractParams: {
defaultAdmin: TEST_ACCOUNT_A.address, // defaultAdmin
name: "test", // name
@@ -1,10 +1,12 @@
import type { AbiConstructor } from "abitype";
import type { Abi, AbiConstructor } from "abitype";
import type { Chain } from "../../chains/types.js";
import type { ThirdwebClient } from "../../client/client.js";
import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js";
import { computeCreate2FactoryAddress } from "../../contract/deployment/utils/create-2-factory.js";
import { encodeAbiParameters } from "../abi/encodeAbiParameters.js";
import { normalizeFunctionParams } from "../abi/normalizeFunctionParams.js";
import { ensureBytecodePrefix } from "../bytecode/prefix.js";
import type { Hex } from "../encoding/hex.js";
import type { FetchDeployMetadataResult } from "./deploy-metadata.js";
import { getInitBytecodeWithSalt } from "./get-init-bytecode-with-salt.js";
@@ -46,15 +48,33 @@ export async function computeDeploymentInfoFromMetadata(args: {
constructorParams?: Record<string, unknown>;
salt?: string;
}) {
const { client, chain, contractMetadata, constructorParams, salt } = args;
return computeDeploymentInfoFromBytecode({
client: args.client,
chain: args.chain,
abi: args.contractMetadata.abi,
bytecode: args.contractMetadata.bytecode,
constructorParams: args.constructorParams,
salt: args.salt,
});
}
export async function computeDeploymentInfoFromBytecode(args: {
client: ThirdwebClient;
chain: Chain;
abi: Abi;
bytecode: Hex;
constructorParams?: Record<string, unknown>;
salt?: string;
}) {
const { client, chain, constructorParams, salt } = args;
const create2FactoryAddress = await computeCreate2FactoryAddress({
client,
chain,
});
const bytecode = contractMetadata.bytecode;
const constructorAbi = contractMetadata.abi.find(
(abi) => abi.type === "constructor",
) as AbiConstructor | undefined;
const bytecode = ensureBytecodePrefix(args.bytecode);
const constructorAbi = args.abi.find((abi) => abi.type === "constructor") as
| AbiConstructor
| undefined;
const encodedArgs = encodeAbiParameters(
constructorAbi?.inputs ?? [],
normalizeFunctionParams(constructorAbi, constructorParams),
@@ -1,4 +1,4 @@
import type { AbiConstructor } from "abitype";
import type { Abi } from "abitype";
import { describe, expect, it } from "vitest";
import {
DUMMY_BYTECODE,
@@ -46,8 +46,7 @@ describe("Resolve implementation", async () => {
chain: ANVIL_CHAIN,
account: TEST_ACCOUNT_A,
bytecode: DUMMY_BYTECODE,
constructorAbi: {} as AbiConstructor,
constructorParams: [],
abi: [],
});
const proxyAddress = await deployContract({
@@ -55,8 +54,11 @@ describe("Resolve implementation", async () => {
chain: ANVIL_CHAIN,
account: TEST_ACCOUNT_A,
bytecode: ERC1967_PROXY_BYTECODE,
constructorAbi: ERC1967_PROXY_CONSTRUCTOR_ABI as AbiConstructor,
constructorParams: [implementationAddress, ""],
abi: ERC1967_PROXY_CONSTRUCTOR_ABI as Abi,
constructorParams: {
logic: implementationAddress,
data: "",
},
});
const proxy = getContract({
+12 -10
View File
@@ -1,12 +1,14 @@
export const ERC1967_PROXY_BYTECODE =
"0x60806040526040516104e13803806104e1833981016040819052610022916102de565b61002e82826000610035565b50506103fb565b61003e83610061565b60008251118061004b5750805b1561005c5761005a83836100a1565b505b505050565b61006a816100cd565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606100c683836040518060600160405280602781526020016104ba60279139610180565b9392505050565b6001600160a01b0381163b61013f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080856001600160a01b03168560405161019d91906103ac565b600060405180830381855af49150503d80600081146101d8576040519150601f19603f3d011682016040523d82523d6000602084013e6101dd565b606091505b5090925090506101ef868383876101f9565b9695505050505050565b60608315610268578251600003610261576001600160a01b0385163b6102615760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610136565b5081610272565b610272838361027a565b949350505050565b81511561028a5781518083602001fd5b8060405162461bcd60e51b815260040161013691906103c8565b634e487b7160e01b600052604160045260246000fd5b60005b838110156102d55781810151838201526020016102bd565b50506000910152565b600080604083850312156102f157600080fd5b82516001600160a01b038116811461030857600080fd5b60208401519092506001600160401b038082111561032557600080fd5b818501915085601f83011261033957600080fd5b81518181111561034b5761034b6102a4565b604051601f8201601f19908116603f01168101908382118183101715610373576103736102a4565b8160405282815288602084870101111561038c57600080fd5b61039d8360208301602088016102ba565b80955050505050509250929050565b600082516103be8184602087016102ba565b9190910192915050565b60208152600082518060208401526103e78160408501602087016102ba565b601f01601f19169190910160400192915050565b60b1806104096000396000f3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6058565b565b600060537f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b3660008037600080366000845af43d6000803e8080156076573d6000f35b3d6000fdfea264697066735822122078e56a32ce2547a0a7b53ad6597602b4207bd6211f94b4294d5ab7b60e4c0b3f64736f6c63430008170033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564"
export const ERC1967_PROXY_CONSTRUCTOR_ABI = {
type: "constructor",
inputs: [
{ name: "_logic", type: "address", internalType: "address" },
{ name: "_data", type: "bytes", internalType: "bytes" },
],
stateMutability: "payable",
}
"0x60806040526040516104e13803806104e1833981016040819052610022916102de565b61002e82826000610035565b50506103fb565b61003e83610061565b60008251118061004b5750805b1561005c5761005a83836100a1565b505b505050565b61006a816100cd565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606100c683836040518060600160405280602781526020016104ba60279139610180565b9392505050565b6001600160a01b0381163b61013f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b6060600080856001600160a01b03168560405161019d91906103ac565b600060405180830381855af49150503d80600081146101d8576040519150601f19603f3d011682016040523d82523d6000602084013e6101dd565b606091505b5090925090506101ef868383876101f9565b9695505050505050565b60608315610268578251600003610261576001600160a01b0385163b6102615760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610136565b5081610272565b610272838361027a565b949350505050565b81511561028a5781518083602001fd5b8060405162461bcd60e51b815260040161013691906103c8565b634e487b7160e01b600052604160045260246000fd5b60005b838110156102d55781810151838201526020016102bd565b50506000910152565b600080604083850312156102f157600080fd5b82516001600160a01b038116811461030857600080fd5b60208401519092506001600160401b038082111561032557600080fd5b818501915085601f83011261033957600080fd5b81518181111561034b5761034b6102a4565b604051601f8201601f19908116603f01168101908382118183101715610373576103736102a4565b8160405282815288602084870101111561038c57600080fd5b61039d8360208301602088016102ba565b80955050505050509250929050565b600082516103be8184602087016102ba565b9190910192915050565b60208152600082518060208401526103e78160408501602087016102ba565b601f01601f19169190910160400192915050565b60b1806104096000396000f3fe608060405236601057600e6013565b005b600e5b601f601b6021565b6058565b565b600060537f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b3660008037600080366000845af43d6000803e8080156076573d6000f35b3d6000fdfea264697066735822122078e56a32ce2547a0a7b53ad6597602b4207bd6211f94b4294d5ab7b60e4c0b3f64736f6c63430008170033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564";
export const ERC1967_PROXY_CONSTRUCTOR_ABI = [
{
type: "constructor",
inputs: [
{ name: "_logic", type: "address", internalType: "address" },
{ name: "_data", type: "bytes", internalType: "bytes" },
],
stateMutability: "payable",
},
];
export const DUMMY_BYTECODE = "0x60ff60005260146000f3" // deploys contract with 0xff bytecode
export const DUMMY_BYTECODE = "0x60ff60005260146000f3"; // deploys contract with 0xff bytecode