Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 36 additions & 12 deletions scripts/deploy-predeploys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,19 @@ function formatEther(wei: bigint): string {
async function gasEstimateOnL2(params: {
deployments: PreSignedDeployment[]
rpcUrl: string
}): Promise<GasEstimateResult[]> {
}): Promise<{ results: GasEstimateResult[]; chainGasPrice: bigint }> {
const { deployments, rpcUrl } = params
const provider = new JsonRpcProvider(rpcUrl)
const results: GasEstimateResult[] = []

// Fetch the chain's current gas price so we can produce accurate cost estimates.
// Pre-signed txs embed a gas price (e.g. 100 gwei) that may differ wildly from the
// L2's actual pricing — especially on zkSync-style chains with a different gas model.
const feeData = await provider.getFeeData()
const chainGasPrice = feeData.gasPrice ?? BigInt(0)

console.log('\n[L2] Running gas estimation...')
console.log(` Chain gas price: ${formatUnits(chainGasPrice, 'gwei')} gwei`)

for (const deployment of deployments) {
console.log(`\n ${deployment.label}`)
Expand Down Expand Up @@ -256,8 +263,13 @@ async function gasEstimateOnL2(params: {
console.log(` ⚠ Gas estimation failed: ${err.message}`)
}

const estimatedCost = estimatedGasUsed !== null ? estimatedGasUsed * txGasPrice : null
const expectedLeftover = estimatedCost !== null ? maxCost - estimatedCost : null
// Use the chain's actual gas price for cost estimates instead of the gas price
// embedded in the pre-signed tx. On L2s (especially zkSync-style chains) the
// gas model differs from L1 — estimateGas returns L2 gas units which should be
// priced at the L2 gas price, not the 100 gwei baked into the legacy tx.
const effectiveGasPrice = chainGasPrice > BigInt(0) ? chainGasPrice : txGasPrice
const estimatedCost = estimatedGasUsed !== null ? estimatedGasUsed * effectiveGasPrice : null
const expectedLeftover = estimatedCost !== null && maxCost > estimatedCost ? maxCost - estimatedCost : null

results.push({
key: deployment.key,
Expand All @@ -276,7 +288,7 @@ async function gasEstimateOnL2(params: {
})
}

return results
return { results, chainGasPrice }
}

async function deployOnL2(params: {
Expand Down Expand Up @@ -332,22 +344,31 @@ async function deployOnL2(params: {
continue
}

// Estimate actual gas cost for this deployment and use that instead of the
// hardcoded fundingAmount, so we don't over-fund the deployer address.
// The node requires the deployer to hold at least gasLimit × txGasPrice to
// accept the pre-signed transaction. We use gas estimation only to decide
// whether we can send *less* than the hardcoded amount — but we must never
// go below the node's minimum (gasLimit × txGasPrice).
const parsedTx = Transaction.from(deployment.rawTransaction)
const txGasPrice = parsedTx.gasPrice ?? BigInt(0)
const txGasLimit = BigInt(parsedTx.gasLimit)
const nodeMinimum = txGasPrice * txGasLimit
let requiredWei: bigint

try {
const feeData = await provider.getFeeData()
const chainGasPrice = feeData.gasPrice ?? BigInt(0)
const effectiveGasPrice = chainGasPrice > BigInt(0) ? chainGasPrice : txGasPrice

const estimatedGas = await provider.estimateGas({
from: deployment.fundingAddress,
data: parsedTx.data,
value: parsedTx.value
})
// Apply the configurable buffer on top of the estimate
const bufferedGas = estimatedGas * BigInt(100 + gasBuffer) / BigInt(100)
requiredWei = bufferedGas * txGasPrice
const estimatedCost = bufferedGas * effectiveGasPrice
// Never fund below the node's minimum acceptance threshold
requiredWei = estimatedCost > nodeMinimum ? estimatedCost : nodeMinimum
console.log(` Gas estimate: ${estimatedGas.toLocaleString()} (+ ${gasBuffer}% buffer → funding ${formatEther(requiredWei)} ETH vs hardcoded ${deployment.fundingAmount} ETH)`)
} catch (err: any) {
// Fall back to the hardcoded amount if estimation fails
Expand Down Expand Up @@ -493,7 +514,7 @@ async function main() {

// Gas estimate mode: connect to L2, estimate actual costs, check deployer nonces
if (options.gasEstimate) {
const estimates = await gasEstimateOnL2({
const { results: estimates, chainGasPrice } = await gasEstimateOnL2({
deployments,
rpcUrl: options.l2Rpc!
})
Expand Down Expand Up @@ -533,13 +554,16 @@ async function main() {
}

console.log(` Gas price (tx): ${formatUnits(est.txGasPrice, 'gwei')} gwei`)
console.log(` Gas price (chain): ${formatUnits(chainGasPrice, 'gwei')} gwei`)
console.log(` Gas limit (tx): ${est.txGasLimit.toLocaleString()}`)

if (est.estimatedGasUsed !== null && est.estimatedCost !== null && est.expectedLeftover !== null) {
if (est.estimatedGasUsed !== null && est.estimatedCost !== null) {
if (!est.alreadyDeployed) totalEstimated += est.estimatedCost
console.log(` Estimated gas: ${est.estimatedGasUsed.toLocaleString()} used`)
console.log(` Estimated cost: ${formatEther(est.estimatedCost)} ETH`)
console.log(` Expected refund: ${formatEther(est.expectedLeftover)} ETH left in deployer after tx`)
console.log(` Estimated cost: ${formatEther(est.estimatedCost)} ETH (estimatedGas × chain gas price)`)
if (est.expectedLeftover !== null) {
console.log(` Expected refund: ${formatEther(est.expectedLeftover)} ETH left in deployer after tx`)
}
} else if (est.estimateError) {
if (!est.alreadyDeployed) totalEstimated += est.maxCost // fall back to max cost if estimation failed
console.log(` Gas estimate: ⚠ Failed (${est.estimateError})`)
Expand All @@ -553,7 +577,7 @@ async function main() {
console.log(` Hardcoded total: ${formatEther(totalHardcoded)} ETH`)
console.log(` Min required: ${formatEther(totalMaxCost)} ETH (sum of gasPrice × gasLimit per tx)`)
if (totalEstimated > BigInt(0)) {
console.log(` Estimated actual: ${formatEther(totalEstimated)} ETH (likely spend based on gas estimation)`)
console.log(` Estimated actual: ${formatEther(totalEstimated)} ETH (based on chain gas price: ${formatUnits(chainGasPrice, 'gwei')} gwei)`)
}
const hardcodedSavings = totalHardcoded - totalMaxCost
if (hardcodedSavings > BigInt(0)) {
Expand Down