Skip to content

Commit

Permalink
Merge pull request #47 from ethereum-optimism/nick/broadcast-artifacts
Browse files Browse the repository at this point in the history
Generate Broadcast Artifacts
  • Loading branch information
nitaliano authored Jan 14, 2025
2 parents b308525 + c06e6fe commit 88fcaa1
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 4 deletions.
83 changes: 79 additions & 4 deletions packages/cli/src/deploy-create2/DeployCreate2Command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
Hex,
zeroAddress,
} from 'viem';
import {useConfig, useWaitForTransactionReceipt, useWriteContract} from 'wagmi';
import {useConfig, useTransaction, useWaitForTransactionReceipt, useWriteContract} from 'wagmi';
import {useForgeArtifact} from '@/queries/forgeArtifact';
import {ForgeArtifact} from '@/forge/readForgeArtifact';
import {CREATEX_ADDRESS, createXABI} from '@/contracts/createx/constants';
Expand All @@ -35,6 +35,9 @@ import {useCodeForChains} from '@/deploy-create2/useCodeForChains';
import {useRefetchCodeOnReceipt} from '@/deploy-create2/useRefetchCodeOnReceipt';
import {VerifyCommandInner} from '@/commands/verify';
import {useGasEstimation} from '@/hooks/useGasEstimation';
import { useBroadcastStore } from '@/stores/broadcastStore';
import { writeMultichainBroadcast } from '@/utils/broadcasts';
import { fromFoundryArtifactPath } from '@/forge/foundryProject';

// Prepares any required data or loading state if waiting
export const DeployCreate2Command = ({
Expand All @@ -57,7 +60,11 @@ export const DeployCreate2Command = ({
return <Spinner />;
}

const chains = options.chains.map(
// TODO: Fix option formatting between wizard and command
// Wizards = [ 'op', 'base' ]
// Command = [ 'op, base' ]
const flattenedChains = options.chains.flatMap(chain => chain.split(','));
const chains = flattenedChains.map(
chain => chainByIdentifier[`${options.network}/${chain}`]!,
);

Expand All @@ -79,6 +86,8 @@ const DeployCreate2CommandInner = ({
forgeArtifact: ForgeArtifact;
options: DeployCreateXCreate2Params;
}) => {
const { createBroadcast } = useBroadcastStore();

const [executionOption, setExecutionOption] =
useState<ExecutionOption | null>(
options.privateKey
Expand All @@ -104,6 +113,19 @@ const DeployCreate2CommandInner = ({
chainIds: chains.map(chain => chain.id),
});

useEffect(() => {
(async () => {
const { foundryProject, contractFileName } = await fromFoundryArtifactPath(options.forgeArtifactPath)

createBroadcast({
contractAddress: deterministicAddress,
contractName: contractFileName,
foundryProjectRoot: foundryProject.baseDir,
contractArguments: options.constructorArgs?.split(','),
});
})()
}, []);

return (
<Box flexDirection="column" gap={1}>
<Box flexDirection="column" gap={1}>
Expand Down Expand Up @@ -202,6 +224,21 @@ const CompletedOrVerify = ({
contractAddress: Address;
forgeArtifact: ForgeArtifact;
}) => {
const { broadcasts } = useBroadcastStore();
const [isGeneratingBroadcastArtifacts, setIsGeneratingBroadcastArtifacts] = useState(true);

useEffect(() => {
const multichainBroadcast = broadcasts[contractAddress];
if (multichainBroadcast) {
writeMultichainBroadcast(multichainBroadcast);
}
setIsGeneratingBroadcastArtifacts(false);
}, [broadcasts]);

if (isGeneratingBroadcastArtifacts) {
return <Spinner label="Generating broadcast artifacts" />;
}

if (!shouldVerify) {
return (
<Box>
Expand Down Expand Up @@ -409,12 +446,19 @@ const PrivateKeyExecution = ({
data: transactionHash,
} = useWriteContract();

const { addTransaction } = useBroadcastStore();

const {data: receipt, isLoading: isReceiptLoading} =
useWaitForTransactionReceipt({
hash: transactionHash,
chainId: chain.id,
});

const { data: transaction, isLoading: isTransactionLoading } = useTransaction({
hash: transactionHash,
chainId: chain.id,
});

useRefetchCodeOnReceipt(deterministicAddress, chain.id, receipt);

useEffect(() => {
Expand All @@ -428,6 +472,18 @@ const PrivateKeyExecution = ({
});
}, []);

useEffect(() => {
if (receipt && transaction) {
addTransaction({
chainId: chain.id,
hash: transaction.hash,
transaction: transaction,
receipt: receipt,
contractAddress: deterministicAddress,
});
}
}, [receipt, transaction]);

if (isPending) {
return <Spinner label="Deploying contract" />;
}
Expand All @@ -436,7 +492,7 @@ const PrivateKeyExecution = ({
return <Text>Error deploying contract: {error.message}</Text>;
}

if (isReceiptLoading) {
if (isReceiptLoading || isTransactionLoading) {
return <Spinner label="Waiting for receipt" />;
}

Expand All @@ -460,6 +516,7 @@ const ExternalSignerExecution = ({
baseSalt: Hex;
deterministicAddress: Address;
}) => {
const { addTransaction } = useBroadcastStore();
const encodedData = encodeFunctionData({
abi: createXABI,
args: [baseSalt, initCode],
Expand Down Expand Up @@ -488,9 +545,27 @@ const ExternalSignerExecution = ({
chainId: chain.id,
});

const { data: transaction, isLoading: isTransactionLoading } = useTransaction({
hash: taskEntryById[taskId]?.hash,
chainId: chain.id,
});

useRefetchCodeOnReceipt(deterministicAddress, chain.id, receipt);

if (isReceiptLoading) {

useEffect(() => {
if (receipt && transaction) {
addTransaction({
chainId: chain.id,
hash: transaction.hash,
transaction: transaction,
receipt: receipt,
contractAddress: deterministicAddress,
});
}
}, [receipt, transaction]);

if (isReceiptLoading || isTransactionLoading) {
return <Spinner label="Waiting for receipt" />;
}

Expand Down
87 changes: 87 additions & 0 deletions packages/cli/src/stores/broadcastStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Address, Hash, Transaction, TransactionReceipt } from "viem";
import { create } from "zustand";

export type CreateBroadcastParams = {
contractName: string;
contractAddress: Address;
contractArguments?: any[];
foundryProjectRoot: string;
}

export type AddTransactionParams = {
contractAddress: Address;
chainId: number;
hash: Hash;
transaction: Transaction;
receipt: TransactionReceipt;
}

export type MultichainBroadcast = {
name: string;
address: Address;
timestamp: number;
type: 'CREATE2';
contractArguments: any[];
foundryProjectRoot: string;
transactions: Record<number, {
chainId: number;
hash: Hash;
transaction: Transaction;
receipt: TransactionReceipt;
}>
}

export type BroadcastStore = {
broadcasts: Record<Address, MultichainBroadcast>;
createBroadcast: (params: CreateBroadcastParams) => void;
addTransaction: (params: AddTransactionParams) => void;
}

export const useBroadcastStore = create<BroadcastStore>()(
set => ({
broadcasts: {},
createBroadcast: (params: CreateBroadcastParams) => {
const {contractName, contractAddress, contractArguments, foundryProjectRoot} = params;

const timeInSecs = Math.floor(Date.now() / 1000);

const broadcast = {
name: contractName,
address: contractAddress,
timestamp: timeInSecs,
transactions: {},
type: 'CREATE2',
contractArguments,
foundryProjectRoot,
}

set(state => ({
...state,
broadcasts: {
...state.broadcasts,
[contractAddress]: broadcast,
},
}));
},
addTransaction: (params: AddTransactionParams) => {
const { contractAddress, chainId, hash, transaction, receipt } = params;

set(state => {
const broadcast = state.broadcasts[contractAddress];

if (!broadcast) {
return state;
}

broadcast.transactions[chainId] = {
chainId,
hash,
transaction,
receipt,
}

return state
})
}
})
);
124 changes: 124 additions & 0 deletions packages/cli/src/utils/broadcasts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import fs from 'fs';
import path from 'path';
import { MultichainBroadcast } from "@/stores/broadcastStore";
import { Address, Hash, Hex, numberToHex, TransactionReceipt } from "viem";

export type TransactionBroadcast = {
hash: Hash
transactionType: string
contractName: string
contractAddress: Address
function: string | null
arguments: any[]
transaction: {
from: Address
to: Address
value: Hex;
input: Hex;
nonce: Hex;
gas: Hex;
chainId: Hex;
};
additionalContracts: any[];
isFixedGasLimit: boolean;
}

export type Deployment = {
transactions: TransactionBroadcast[];
receipts: TransactionReceipt[];
timestamp: number;
chain: number;
}

export type RunArtifact = {
deployments: Deployment[];
timestamp: number;
}

const DEFAULT_OUTPUT_DIR = path.join('broadcast', 'multi');

export function writeMultichainBroadcast(multichainBroadcast: MultichainBroadcast) {
const { transactions, address, name, timestamp, type, contractArguments, foundryProjectRoot } = multichainBroadcast;

if (!Object.keys(transactions).length) {
return;
}

const outputDir = path.join(foundryProjectRoot, DEFAULT_OUTPUT_DIR);

const runArtifact = {
deployments: [],
timestamp,
} as RunArtifact

Object.keys(transactions).forEach(chainIdStr => {
const chainId = Number(chainIdStr);

if (!transactions[chainId]) {
return
}

const { hash, transaction, receipt } = transactions[chainId];

const transactionData = {
hash,
transactionType: type,
contractName: name,
contractAddress: address,
function: null,
arguments: contractArguments,
transaction: {
from: receipt.from,
to: receipt.to,
gas: transaction.gas,
value: transaction.value,
input: transaction.input,
nonce: numberToHex(transaction.nonce),
chainId: numberToHex(chainId),
additionalContracts: [],
isFixedGasLimit: false,
}
} as unknown as TransactionBroadcast

const receiptData = {
status: receipt.status === 'success' ? '0x1' : '0x0',
cumulativeGasUsed: receipt.cumulativeGasUsed,
logs: receipt.logs,
logsBloom: receipt.logsBloom,
type: transaction.typeHex,
transactionHash: receipt.transactionHash,
transactionIndex: numberToHex(receipt.transactionIndex),
blockHash: receipt.blockHash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed,
effectiveGasPrice: receipt.effectiveGasPrice,
blobGasPrice: receipt.blobGasPrice,
from: receipt.from,
to: receipt.to,
contractAddress: receipt.contractAddress,
} as unknown as TransactionReceipt

const deployment: Deployment = {
transactions: [transactionData],
receipts: [receiptData],
timestamp,
chain: chainId,
}

runArtifact.deployments.push(deployment);
})

const latestDirname = path.join(outputDir, `${name}-latest`);
const timestampDirname = path.join(outputDir, `${name}-${timestamp}`);
fs.mkdirSync(latestDirname, { recursive: true });
fs.mkdirSync(timestampDirname, { recursive: true });

const fileContents = JSON.stringify(
runArtifact,
(_, val) => typeof val === 'bigint' ? numberToHex(val) : val,
2,
);

fs.writeFileSync(path.join(latestDirname, 'run.json'), fileContents);
fs.writeFileSync(path.join(timestampDirname, 'run.json'), fileContents);
}

0 comments on commit 88fcaa1

Please sign in to comment.