Skip to content

Commit

Permalink
fix(core): adjust gas heuristics to support large contracts on Scroll
Browse files Browse the repository at this point in the history
  • Loading branch information
sam-goldman committed Feb 10, 2024
1 parent 2406ced commit 373c3fa
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 12 deletions.
7 changes: 7 additions & 0 deletions .changeset/green-cups-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sphinx-labs/contracts': patch
'@sphinx-labs/plugins': patch
'@sphinx-labs/core': patch
---

Adjust gas heuristics to support large contracts on Scroll
12 changes: 8 additions & 4 deletions packages/contracts/contracts/foundry/Sphinx.sol
Original file line number Diff line number Diff line change
Expand Up @@ -388,10 +388,14 @@ abstract contract Sphinx {
// more likely to be "warm" (i.e. cheaper) than the production environment, where
// transactions may be split between batches.
//
// We chose to multiply the gas by 1.3 because multiplying it by a higher number could
// make a very large transaction unexecutable on-chain. Since the 1.3x multiplier
// doesn't impact small transactions very much, we add a constant amount of 20k too.
gasEstimates[i] = 20_000 + ((startGas - finalGas) * 13) / 10;
// Collecting the user's transactions in the same process as this function does not
// impact the Merkle leaf gas fields because we use `vm.snapshot`/`vm.revertTo`. Also,
// state changes on one fork do not impact the gas cost on other forks.
//
// We chose to multiply the gas by 1.1 because multiplying it by a higher number could
// make a very large transaction unexecutable on-chain. Since the 1.1x multiplier
// doesn't impact small transactions very much, we add a constant amount of 60k too.
gasEstimates[i] = 60_000 + ((startGas - finalGas) * 11) / 10;
}

vm.stopPrank();
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/actions/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export const executeBatchActions = async (
const executionReceipts: ethers.TransactionReceipt[] = []
const batches: SphinxLeafWithProof[][] = []

const maxGasLimit = getMaxGasLimit(blockGasLimit)
const maxGasLimit = getMaxGasLimit(blockGasLimit, chainId)

// Pull the Merkle root state from the contract so we're guaranteed to be up to date.
const activeRoot = await sphinxModule.activeMerkleRoot()
Expand Down Expand Up @@ -532,7 +532,7 @@ export const executeActionsViaSigner: ExecuteActions = async (
* Here are the data points in the format (<CALLDATA_LENGTH>, <GAS_ESTIMATE>):
* (0,10202),(1000,10592),(100000,67166),(500000,676492),(1000000,2296456).
*
* After summing these values, we multiply by 1.1 to ensure that the heuristic overestimates the
* After summing these values, we include a buffer to ensure that the heuristic overestimates the
* amount of gas. This ensures we don't underestimate the gas, which would otherwise be a concern
* because we don't incorporate the cost of executing the `DELEGATECALL` on the `SphinxModuleProxy`,
* or the cost of potentially returning data from the `exec` function on the `ManagedService`. This
Expand Down Expand Up @@ -566,7 +566,7 @@ export const estimateGasViaManagedService: EstimateGas = (
const estimate =
21_000 + callDataGas + estimateModuleExecutionGas(batch) + managedServiceGas

return Math.round(estimate * 1.1)
return Math.round(estimate * 1.05 + 50_000) // Include a buffer
}

/**
Expand All @@ -580,7 +580,7 @@ export const estimateGasViaManagedService: EstimateGas = (
* zero-byte of calldata.
* 3. The estimated cost of executing the logic in the `SphinxModule`.
*
* After summing these values, we multiply by 1.1 to ensure that the heuristic overestimates the
* After summing these values, we include a buffer to ensure that the heuristic overestimates the
* amount of gas. This ensures we don't underestimate the gas, which would otherwise be a concern
* because we don't incorporate the cost of executing the `DELEGATECALL` on the `SphinxModuleProxy`.
* This buffer also makes our estimate closer to the value that would be returned by the
Expand All @@ -597,7 +597,7 @@ export const estimateGasViaSigner: EstimateGas = (moduleAddress, batch) => {

const estimate = 21_000 + callDataGas + estimateModuleExecutionGas(batch)

return Math.round(estimate * 1.1)
return Math.round(estimate * 1.05 + 50_000) // Include a buffer
}

/**
Expand Down
14 changes: 13 additions & 1 deletion packages/core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1436,7 +1436,19 @@ export const getCreate3Salt = (
* which transactions to include. As a result, we restrict our total gas usage to a fraction of the
* block gas limit.
*/
export const getMaxGasLimit = (blockGasLimit: bigint): bigint => {
export const getMaxGasLimit = (
blockGasLimit: bigint,
chainId: bigint
): bigint => {
// On Scroll and Scroll Sepolia, set the max gas limit to be 80% of the block gas limit. We set a
// higher value for these networks because their block gas limit is low (10 million), which means
// a lower max gas limit could cause large contract deployments to be unexecutable. Transactions
// that use ~8.5M gas were executed quickly on Scroll Sepolia, so an 80% limit shouldn't
// meaningfully impact execution speed.
if (chainId === BigInt(534351) || chainId === BigInt(534352)) {
return (blockGasLimit * BigInt(8)) / BigInt(10)
}

return blockGasLimit / BigInt(2)
}

Expand Down
4 changes: 3 additions & 1 deletion packages/plugins/src/foundry/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
decodeDeterministicDeploymentProxyData,
Create2ActionInput,
ActionInputType,
getNetworkNameForChainId,
} from '@sphinx-labs/core'
import { AbiCoder, ConstructorFragment, ethers } from 'ethers'
import {
Expand Down Expand Up @@ -158,8 +159,9 @@ export const makeParsedConfig = (
const gas = gasEstimates[i].toString()

if (BigInt(gas) > maxAllowedGasPerLeaf) {
const networkName = getNetworkNameForChainId(BigInt(chainId))
throw new Error(
`Estimated gas for a transaction is too close to the block gas limit.`
`Estimated gas for a transaction is too close to the block gas limit on ${networkName}.`
)
}

Expand Down
7 changes: 6 additions & 1 deletion packages/plugins/test/mocha/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,12 @@ export const makeDeployment = async (
},
arbitraryChain: false,
accountAccesses,
gasEstimates: new Array(numActionInputs).fill(BigInt(5_000_000)),
// We set the Merkle leaf gas fields to 6 million to ensure that a very large contract
// deployment can fit in a batch. This is important to check on networks like Scroll which
// have low block gas limits. (Scroll's block gas limit is 10 million). A Merkle leaf gas
// field of 6 million corresponds to a contract at the max size limit with a couple dozen
// SSTOREs in its constructor.
gasEstimates: new Array(numActionInputs).fill(BigInt(6_000_000)),
sphinxLibraryVersion: CONTRACTS_LIBRARY_VERSION,
}

Expand Down

0 comments on commit 373c3fa

Please sign in to comment.