Skip to content
Merged
Show file tree
Hide file tree
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
100 changes: 82 additions & 18 deletions src/pages/ethereum/execution/gas-profiler/SimulatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ import { useNetwork } from '@/hooks/useNetwork';
import { useThemeColors } from '@/hooks/useThemeColors';
import { GasScheduleDrawer } from './components/GasScheduleDrawer';
import { BlockSimulationResultsV2 } from './components/BlockSimulationResultsV2';
import { GlamsterdamPresetModal } from './components/GlamsterdamPresetModal';
import { useGasSchedule } from './hooks/useGasSchedule';
import type { GasSchedule, BlockSimulationResult, CallError } from './SimulatePage.types';
import { GLAMSTERDAM_PRESET } from './SimulatePage.types';

// Register ECharts components
echarts.use([LineChart, BarChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);
Expand Down Expand Up @@ -167,6 +169,7 @@ interface ApiBlockSimulationResponse {
simulatedReverts: number;
originalErrors: CallError[] | null;
simulatedErrors: CallError[] | null;
error?: string;
}>;
opcodeBreakdown: Record<
string,
Expand Down Expand Up @@ -195,6 +198,7 @@ function transformApiResponse(response: ApiBlockSimulationResponse): BlockSimula
simulatedReverts: tx.simulatedReverts,
originalErrors: tx.originalErrors ?? [],
simulatedErrors: tx.simulatedErrors ?? [],
error: tx.error,
})),
opcodeBreakdown: response.opcodeBreakdown,
};
Expand All @@ -218,6 +222,9 @@ export function SimulatePage(): JSX.Element {
// Drawer state
const [drawerOpen, setDrawerOpen] = useState(false);

// Preset modal state
const [presetModalOpen, setPresetModalOpen] = useState(false);

// Selection state
const [selectedBlockIndex, setSelectedBlockIndex] = useState<number | null>(null);

Expand Down Expand Up @@ -297,6 +304,12 @@ export function SimulatePage(): JSX.Element {
}).length;
}, [gasSchedule, gasScheduleDefaults]);

// Check if Glamsterdam preset is currently applied
const isGlamsterdamApplied = useMemo(
() => Object.entries(GLAMSTERDAM_PRESET).every(([key, value]) => gasSchedule[key] === value),
[gasSchedule]
);

// Aggregate stats from completed results
const aggregateStats = useMemo(() => calculateAggregateStats(simState.results), [simState.results]);

Expand Down Expand Up @@ -601,6 +614,30 @@ export function SimulatePage(): JSX.Element {

const modifiedParamNames = useMemo(() => modifiedEntries.map(([key]) => key), [modifiedEntries]);

// Pending simulate flag - triggers simulation on next render after preset is applied
const [pendingSimulate, setPendingSimulate] = useState(false);

// Auto-simulate after preset application
useEffect(() => {
if (pendingSimulate) {
setPendingSimulate(false);
handleSimulate();
}
}, [pendingSimulate, handleSimulate]);

// Handle preset modal "Apply & Simulate"
const handlePresetApplyAndSimulate = useCallback((presetValues: Record<string, number>) => {
setGasSchedule(presetValues as GasSchedule);
setGasWarning(false);
setPendingSimulate(true);
}, []);

// Handle preset modal "Cancel" - resets gas schedule to empty
const handlePresetCancel = useCallback(() => {
setGasSchedule({});
setGasWarning(false);
}, []);

const isRunning = simState.status === 'running';
const hasResults = simState.results.length > 0;

Expand Down Expand Up @@ -680,24 +717,42 @@ export function SimulatePage(): JSX.Element {
{/* Gas Schedule */}
<div>
<label className="mb-1.5 block text-xs text-muted">Gas Schedule</label>
<button
type="button"
onClick={() => setDrawerOpen(true)}
disabled={!gasScheduleDefaults || isRunning}
className={clsx(
'flex items-center gap-2 rounded-xs border px-3 py-2 text-sm font-medium transition-colors',
modifiedCount > 0
? 'border-primary/30 bg-primary/10 text-primary'
: 'border-border bg-surface text-muted hover:text-foreground',
(!gasScheduleDefaults || isRunning) && 'cursor-not-allowed opacity-50'
)}
>
<AdjustmentsHorizontalIcon className="size-4" />
Configure
{modifiedCount > 0 && (
<span className="rounded-full bg-primary px-1.5 py-0.5 text-xs text-white">{modifiedCount}</span>
)}
</button>
<div className="flex gap-2">
<button
type="button"
onClick={() => setPresetModalOpen(true)}
disabled={!gasScheduleDefaults || isRunning}
className={clsx(
'flex items-center gap-1.5 rounded-xs border px-3 py-2 text-sm font-medium transition-colors',
isGlamsterdamApplied
? 'border-primary/30 bg-primary/10 text-primary'
: 'border-border bg-surface text-muted hover:border-primary/30 hover:bg-primary/10 hover:text-primary',
(!gasScheduleDefaults || isRunning) && 'cursor-not-allowed opacity-50'
)}
title="Apply Glamsterdam (EIP-8007) gas schedule preset"
>
<BeakerIcon className="size-4" />
Glamsterdam
</button>
<button
type="button"
onClick={() => setDrawerOpen(true)}
disabled={!gasScheduleDefaults || isRunning}
className={clsx(
'flex items-center gap-2 rounded-xs border px-3 py-2 text-sm font-medium transition-colors',
modifiedCount > 0
? 'border-primary/30 bg-primary/10 text-primary'
: 'border-border bg-surface text-muted hover:text-foreground',
(!gasScheduleDefaults || isRunning) && 'cursor-not-allowed opacity-50'
)}
>
<AdjustmentsHorizontalIcon className="size-4" />
Configure
{modifiedCount > 0 && (
<span className="rounded-full bg-primary px-1.5 py-0.5 text-xs text-white">{modifiedCount}</span>
)}
</button>
</div>
</div>

{/* Actions */}
Expand Down Expand Up @@ -1169,6 +1224,15 @@ export function SimulatePage(): JSX.Element {
onChange={handleScheduleChange}
/>
)}

{/* Glamsterdam Preset Modal */}
<GlamsterdamPresetModal
open={presetModalOpen}
onClose={() => setPresetModalOpen(false)}
onApplyAndSimulate={handlePresetApplyAndSimulate}
onCancel={handlePresetCancel}
defaults={gasScheduleDefaults ?? null}
/>
</Container>
);
}
42 changes: 42 additions & 0 deletions src/pages/ethereum/execution/gas-profiler/SimulatePage.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ export const GAS_PARAMETER_GROUPS: GasParameterGroup[] = [
{ key: 'PC_MODEXP_MIN_GAS', label: 'MODEXP Min Gas', min: 0, max: 2000, step: 10 },
{ key: 'PC_BN254_PAIRING_BASE', label: 'BN254 Pairing Base', min: 0, max: 200000, step: 5000 },
{ key: 'PC_BN254_PAIRING_PER_PAIR', label: 'BN254 Pairing /Pair', min: 0, max: 200000, step: 1000 },
{ key: 'PC_BLAKE2F_BASE', label: 'BLAKE2F Base', min: 0, max: 1000, step: 10 },
{ key: 'PC_BLAKE2F_PER_ROUND', label: 'BLAKE2F /Round', min: 0, max: 20, step: 1 },
{ key: 'PC_BLS12_PAIRING_CHECK_BASE', label: 'BLS12 Pairing Base', min: 0, max: 200000, step: 5000 },
{ key: 'PC_BLS12_PAIRING_CHECK_PER_PAIR', label: 'BLS12 Pairing /Pair', min: 0, max: 200000, step: 1000 },
Expand All @@ -317,6 +318,45 @@ export const GAS_PARAMETER_GROUPS: GasParameterGroup[] = [
},
];

/**
* Glamsterdam (EIP-8007) gas schedule preset.
* Applies all gas parameter changes proposed for the Glamsterdam fork.
*
* EIP-7904: Compute repricing (opcodes + precompiles)
* EIP-7976: Calldata floor cost increase
* EIP-2780: Transaction base cost reduction
* EIP-8038: State access repricing (TBD - using current values as placeholders)
*/
export const GLAMSTERDAM_PRESET: Record<string, number> = {
// EIP-7904: Compute repricing
DIV: 15,
SDIV: 20,
MOD: 12,
MULMOD: 11,
KECCAK256: 45,
PC_BLAKE2F_BASE: 170,
PC_BLAKE2F_PER_ROUND: 2,
PC_BLS12_G1ADD: 643,
PC_BLS12_G2ADD: 765,
PC_BN254_ADD: 314,
PC_BN254_PAIRING_PER_PAIR: 34103,
PC_KZG_POINT_EVALUATION: 89363,

// EIP-7976: Calldata floor
TX_FLOOR_PER_TOKEN: 15,

// EIP-2780: TX base cost
TX_BASE: 4500,

// EIP-8038: State access repricing (TBD - current values)
SLOAD_COLD: 2100,
SLOAD_WARM: 100,
SSTORE_RESET: 2900,
CALL_COLD: 2600,
TX_ACCESS_LIST_ADDR: 2400,
TX_ACCESS_LIST_KEY: 1900,
};

/**
* Summary of gas usage (original or simulated)
*/
Expand Down Expand Up @@ -362,6 +402,8 @@ export interface TxSummary {
originalErrors: CallError[];
/** Errors from nested calls in simulated execution */
simulatedErrors: CallError[];
/** Pre-execution error (e.g. "intrinsic gas too low") when execution fails before the EVM runs */
error?: string;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ function ExecutionHealthSection({
subtitle:
totalTransactions > 0 && divergenceSummary.divergedCount > 0
? `${((divergenceSummary.divergedCount / totalTransactions) * 100).toFixed(1)}% of txs`
: undefined,
: 'all paths unchanged',
accentColor: `${divergedColor}33`,
},
{
Expand Down Expand Up @@ -622,8 +622,9 @@ function TransactionImpactView({
all: transactions.length,
diverged: transactions.filter(t => t.diverged).length,
status: transactions.filter(t => t.originalStatus !== t.simulatedStatus).length,
errors: transactions.filter(t => (t.originalErrors?.length ?? 0) > 0 || (t.simulatedErrors?.length ?? 0) > 0)
.length,
errors: transactions.filter(
t => (t.originalErrors?.length ?? 0) > 0 || (t.simulatedErrors?.length ?? 0) > 0 || !!t.error
).length,
}),
[transactions]
);
Expand All @@ -639,7 +640,7 @@ function TransactionImpactView({
txs = txs.filter(t => t.originalStatus !== t.simulatedStatus);
break;
case 'errors':
txs = txs.filter(t => (t.originalErrors?.length ?? 0) > 0 || (t.simulatedErrors?.length ?? 0) > 0);
txs = txs.filter(t => (t.originalErrors?.length ?? 0) > 0 || (t.simulatedErrors?.length ?? 0) > 0 || !!t.error);
break;
}

Expand Down Expand Up @@ -746,8 +747,9 @@ function TransactionImpactView({
{filteredAndSorted.slice(0, visibleCount).map(tx => {
const statusChanged = tx.originalStatus !== tx.simulatedStatus;
const hasErrors = (tx.originalErrors?.length ?? 0) > 0 || (tx.simulatedErrors?.length ?? 0) > 0;
const hasPreExecError = !!tx.error;
const isExpanded = expandedTxs.has(tx.hash);
const isNotable = statusChanged || tx.diverged || hasErrors;
const isNotable = statusChanged || tx.diverged || hasErrors || hasPreExecError;
const absDelta = Math.abs(tx.simulatedGas - tx.originalGas);

return (
Expand Down Expand Up @@ -869,6 +871,11 @@ function TransactionImpactView({
</div>
</dl>

{/* Pre-execution error (e.g. intrinsic gas too low) */}
{hasPreExecError && (
<Alert variant="error" title="Pre-execution error" description={tx.error} className="mt-3" />
)}

{/* Error cards (if any) */}
{hasErrors && (
<div className="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
Expand Down
Loading