Skip to content

Commit

Permalink
iteration on contract deploys
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquim-verges committed Feb 22, 2024
1 parent f30f2b7 commit a9b2b15
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 27 deletions.
13 changes: 5 additions & 8 deletions packages/thirdweb/src/contract/deployment/deploy-from-uri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ export async function prepareDeployTransactionFromUri(
const chainId = options.chain.id;
const isNetworkEnabled = !!(
extendedMetadata?.networksForDeployment?.networksEnabled?.includes(
// TODO: align with chainId being bigint
Number(chainId),
chainId,
) || extendedMetadata?.networksForDeployment?.allNetworks
);

Expand Down Expand Up @@ -76,12 +75,10 @@ export async function prepareDeployTransactionFromUri(
throw new Error(`Contract bytecode is invalid.\n\n${bytecode}`);
}

const constructorAbi = compilerMetadata.abi.find(
(abi) => abi.type === "constructor",
) as AbiConstructor;
if (!constructorAbi) {
throw new Error("No constructor found in the contract ABI");
}
const constructorAbi =
(compilerMetadata.abi.find(
(abi) => abi.type === "constructor",
) as AbiConstructor) || [];

return prepareDirectDeployTransaction({
bytecode,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { SharedDeployOptions } from "./types.js";
import type { FullPublishMetadata } from "./utils/deploy-metadata.js";

/**
* @internal
*/
export async function prepareDeployTransactionViaAutoFactory(
args: SharedDeployOptions & {
extendedMetadata: FullPublishMetadata;
},
) {
// TODO
console.log(args);
}

Check warning on line 14 in packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts#L1-L14

Added lines #L1 - L14 were not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type { Abi } from "abitype";
import type { SharedDeployOptions } from "./types.js";
import { getContract } from "../contract.js";
import { prepareContractCall } from "../../transaction/prepare-contract-call.js";
import { encode } from "../../transaction/actions/encode.js";
import { keccakId } from "../../utils/any-evm/keccak-id.js";
import { getRpcClient } from "../../rpc/rpc.js";
import { eth_blockNumber } from "../../rpc/actions/eth_blockNumber.js";
import { toHex } from "../../utils/encoding/hex.js";

/**
* Prepares a deploy transaction via a proxy factory.
* @param args - The arguments for deploying the contract.
* @example
* ```ts
* import { prepareDeployTransactionViaCloneFactory } from "thirdweb/contract";
* import { ethereum } from "thirdweb/chains";
*
* const tx = await prepareDeployTransactionViaCloneFactory({
* client,
* chain: ethereum,
* factoryAddress: "0x...",
* implementationAddress: "0x...",
* implementationAbi: abi,
* initializerFunction: "initialize",
* initializerArgs: [123, "hello"],
* });
* ```
* @returns A prepared deployment transaction ready to be sent.
*/
export async function prepareDeployTransactionViaCloneFactory(
args: SharedDeployOptions & {
factoryAddress: string;
implementationAddress: string;
implementationAbi: Abi;
initializerFunction: string;
initializerArgs: unknown[];
saltForProxyDeploy?: string;
},
) {
const {
client,
chain,
factoryAddress,
implementationAddress,
implementationAbi,
initializerFunction,
initializerArgs,
saltForProxyDeploy,
} = args;
const factory = getContract({
client,
chain,
address: factoryAddress,
});
return prepareContractCall({
contract: factory,
method:
"function deployProxyByImplementation(address _implementation, bytes memory _data, bytes32 _salt) returns (address deployedProxy)",
params: async () => {
const implementation = getContract({
client,
chain,
address: implementationAddress,
abi: implementationAbi,
});
const initializerTransaction = prepareContractCall({
contract: implementation,
method: initializerFunction,
params: initializerArgs,
});
const rpcRequest = getRpcClient({
client,
chain,
});
const blockNumber = await eth_blockNumber(rpcRequest);
const salt = saltForProxyDeploy
? keccakId(saltForProxyDeploy)
: toHex(blockNumber, {
size: 32,
});
const encoded = await encode(initializerTransaction);
return [implementationAddress, encoded, salt] as const;
},
});
}

Check warning on line 86 in packages/thirdweb/src/contract/deployment/deploy-via-clone-factory.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/contract/deployment/deploy-via-clone-factory.ts#L1-L86

Added lines #L1 - L86 were not covered by tests
40 changes: 21 additions & 19 deletions packages/thirdweb/src/contract/deployment/utils/deploy-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,33 @@ export async function fetchDeployMetadata(
): Promise<FetchDeployMetadataResult> {
const [compilerMetadata, extendedMetadata] = await Promise.all([
fetchPreDeployMetadata(options),
fetchExtendedReleaseMetadata(options).catch(() => undefined),
fetchPublishedMetadata(options).catch(() => undefined),
]);
return { compilerMetadata, extendedMetadata };
}

// helpers
async function fetchExtendedReleaseMetadata(
/**
* Fetches the published metadata.
* @param options - The options for fetching the published metadata.
* @internal
*/
async function fetchPublishedMetadata(
options: FetchDeployMetadataOptions,
): Promise<FullPublishMetadata> {
return JSON.parse(
await (
await download({
uri: options.uri,
client: options.client,
})
).text(),
);
return download({
uri: options.uri,
client: options.client,
}).then((r) => r.json());
}

async function fetchPreDeployMetadata(
options: FetchDeployMetadataOptions,
): Promise<PreDeployMetadata> {
const rawMeta = JSON.parse(
await (await download({ uri: options.uri, client: options.client })).text(),
) as RawPredeployMetadata;
const rawMeta: RawPredeployMetadata = await download({
uri: options.uri,
client: options.client,
}).then((r) => r.json());
const [deployBytecode, parsedMeta] = await Promise.all([
download({ uri: rawMeta.bytecodeUri, client: options.client }).then(
(res) => res.text() as Promise<Hex>,
Expand All @@ -68,7 +70,7 @@ const CONTRACT_METADATA_TIMEOUT_SEC = 2 * 1000;

async function fetchContractMetadata(
options: FetchDeployMetadataOptions,
): Promise<PublishedMetadata> {
): Promise<ParsedPredeployMetadata> {
// short timeout to avoid hanging on unpinned contract metadata CIDs
const metadata = await (
await download({
Expand All @@ -84,7 +86,7 @@ async function fetchContractMetadata(
return formatCompilerMetadata(metadata);
}

function formatCompilerMetadata(metadata: any): PublishedMetadata {
function formatCompilerMetadata(metadata: any): ParsedPredeployMetadata {
const abi = metadata.output.abi;
const compilationTarget = metadata.settings.compilationTarget;
const targets = Object.keys(compilationTarget);
Expand Down Expand Up @@ -120,7 +122,7 @@ type RawPredeployMetadata = {
[key: string]: any;
};

type PublishedMetadata = {
type ParsedPredeployMetadata = {
name: string;
abi: Abi;
metadata: Record<string, unknown>;
Expand All @@ -134,13 +136,13 @@ type PublishedMetadata = {
isPartialAbi?: boolean;
};

type PreDeployMetadata = Prettify<
PublishedMetadata & {
export type PreDeployMetadata = Prettify<
ParsedPredeployMetadata & {
bytecode: Hex;
}
>;

type FullPublishMetadata = {
export type FullPublishMetadata = {
name: string;
version: string;
metadataUri: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { polygon } from "../../../chains/chain-definitions/polygon.js";
import type { ThirdwebClient } from "../../../client/client.js";
import { readContract } from "../../../transaction/read-contract.js";
import { getContract } from "../../contract.js";

const ContractPublisherAddress = "0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808"; // Polygon only
export const THIRDWEB_DEPLOYER = "0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024";

/**
* @internal
*/
export async function fetchPublishedContract(args: {
client: ThirdwebClient;
contractId: string;
publisherAddress?: string;
}) {
const { client, publisherAddress, contractId } = args;
const contractPublisher = getContract({
client,
chain: polygon,
address: ContractPublisherAddress,
});
// TODO support mutliple contract versions
return readContract({
contract: contractPublisher,
method: GET_PUBLISHED_CONTRACT_ABI,
params: [publisherAddress || THIRDWEB_DEPLOYER, contractId],
});
}

const GET_PUBLISHED_CONTRACT_ABI = {
inputs: [
{
internalType: "address",
name: "_publisher",
type: "address",
},
{
internalType: "string",
name: "_contractId",
type: "string",
},
],
name: "getPublishedContract",
outputs: [
{
components: [
{
internalType: "string",
name: "contractId",
type: "string",
},
{
internalType: "uint256",
name: "publishTimestamp",
type: "uint256",
},
{
internalType: "string",
name: "publishMetadataUri",
type: "string",
},
{
internalType: "bytes32",
name: "bytecodeHash",
type: "bytes32",
},
{
internalType: "address",
name: "implementation",
type: "address",
},
],
internalType: "struct IContractPublisher.CustomContractInstance",
name: "published",
type: "tuple",
},
],
stateMutability: "view",
type: "function",
} as const;

Check warning on line 81 in packages/thirdweb/src/contract/deployment/utils/fetch-published-contract.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/contract/deployment/utils/fetch-published-contract.ts#L1-L81

Added lines #L1 - L81 were not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { AbiConstructor } from "abitype";
import type { ThirdwebClient } from "../../../client/client.js";
import { getInitBytecodeWithSalt } from "../../../utils/any-evm/get-init-bytecode-with-salt.js";
import { fetchDeployMetadata } from "./deploy-metadata.js";
import { fetchPublishedContract } from "./fetch-published-contract.js";
import { encodeAbiParameters } from "viem";
import { computeDeploymentAddress } from "../../../utils/any-evm/compute-deployment-address.js";
import { getCreate2FactoryAddress } from "../../../utils/any-evm/create-2-factory.js";
import type { Chain } from "../../../chains/types.js";

/**
* Predicts the implementation address of any published contract
* @param args - The arguments for predicting the address of a published contract.
* @param args.client - The Thirdweb client.
* @param args.chain - The chain to predict the address on.
* @param args.contractId - The ID of the contract to predict the address of.
* @param args.constructorParams - The parameters for the contract constructor.
* @example
* ```ts
* import { predictPublishedContractAddress } from "thirdweb/contract";
*
* const address = await predictPublishedContractAddress({
* client,
* chain,
* contractId,
* constructorParams,
* });
* ```
* @returns A promise that resolves to the predicted address of the contract.
*/
export async function predictPublishedContractAddress(args: {
client: ThirdwebClient;
chain: Chain;
contractId: string;
constructorParams: unknown[]; // TODO automate contract params from known inputs
}): Promise<string> {
const { client, chain, contractId, constructorParams } = args;
const contractModel = await fetchPublishedContract({
client,
contractId,
});
const [{ compilerMetadata }, create2FactoryAddress] = await Promise.all([
fetchDeployMetadata({
client,
uri: contractModel.publishMetadataUri,
}),
getCreate2FactoryAddress({
client,
chain,
}),
]);
const bytecode = compilerMetadata.bytecode;
const constructorAbi =
(compilerMetadata.abi.find(
(abi) => abi.type === "constructor",
) as AbiConstructor) || [];
const encodedArgs = encodeAbiParameters(
constructorAbi.inputs,
constructorParams,
);
const initBytecodeWithsalt = getInitBytecodeWithSalt({
bytecode,
encodedArgs,
});
return computeDeploymentAddress({
bytecode,
encodedArgs,
create2FactoryAddress,
salt: initBytecodeWithsalt,
});
}

Check warning on line 71 in packages/thirdweb/src/contract/deployment/utils/predict-published-contract-address.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/contract/deployment/utils/predict-published-contract-address.ts#L1-L71

Added lines #L1 - L71 were not covered by tests

0 comments on commit a9b2b15

Please sign in to comment.