From dc99ffed20e6615d665c04b3b92477bc9e1611cc Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Mon, 9 Mar 2026 10:45:10 +0100 Subject: [PATCH 01/18] Some basic EIP-7594 code clean-up --- src/explorations/eip-7594/MyC.vue | 42 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/explorations/eip-7594/MyC.vue b/src/explorations/eip-7594/MyC.vue index 468da53..0fc009f 100644 --- a/src/explorations/eip-7594/MyC.vue +++ b/src/explorations/eip-7594/MyC.vue @@ -1,6 +1,6 @@ ``` -See [Using E-Components](/contributing/e-components) for the full API reference. +The `useStandardPrecompileRun` helper covers the common EthereumJS pre/post hardfork comparison. For custom execution (different library, custom precompile, etc.), provide your own `run` function and `#result` slot — see [Available E-Components](/contributing/available-e-components) for the full API reference. ## Step 5: Register in the Registry diff --git a/docs/contributing/available-e-components.md b/docs/contributing/available-e-components.md index fe57124..6ab727d 100644 --- a/docs/contributing/available-e-components.md +++ b/docs/contributing/available-e-components.md @@ -4,47 +4,49 @@ This page lists all E-Components that are ready to use in your explorations. For ## Precompile Interface (`precompileInterfaceEC`) -A complete interface for exploring EVM precompiles. It handles: +An interface for exploring EVM precompiles. It handles input management while leaving execution and result display to the exploration: - Example selection and URL sharing - Hex data input with parsing and validation - Individual value inputs with byte length tracking -- Side-by-side pre/post hardfork comparison (running the precompile on two different EVM versions) -- Result display with gas cost and output data +- **Execution and result display** are provided by the exploration via the `run` prop and `#result` slot **Files:** ``` src/eComponents/precompileInterfaceEC/ ├── PrecompileInterfaceEC.vue # Main component -├── PrecompileInterfaceResultEC.vue # Result display (pre/post comparison) +├── PrecompileInterfaceResultEC.vue # Result display (reusable, e.g. pre/post comparison) ├── PrecompileValueInputEC.vue # Value input with byte length validation -├── usePrecompileState.ts # Composable: all state and logic +├── usePrecompileState.ts # Composable: input state and sync logic ├── types.ts # PrecompileConfig and PrecompileValueDef -└── run.ts # EVM precompile execution utility +└── run.ts # EVM precompile execution utility + useStandardPrecompileRun ``` **Used by:** [EIP-7951](https://github.com/feelyourprotocol/website/blob/main/src/explorations/eip-7951/MyC.vue) (secp256r1), [EIP-7883](https://github.com/feelyourprotocol/website/blob/main/src/explorations/eip-7883/MyC.vue) (ModExp gas cost) ### Basic Usage -A precompile exploration needs just a config object and a single component tag: +A precompile exploration provides a config for input layout, a `run` function for execution, and a `#result` slot for visualization. For the standard EthereumJS pre/post hardfork comparison, use the `useStandardPrecompileRun` helper: ```vue ``` +### Component Props + +| Prop | Required | Description | +|------|----------|-------------| +| `config` | Yes | Input configuration (see `PrecompileConfig` below) | +| `examples` | Yes | Example presets from `examples.ts` | +| `exploration` | Yes | Exploration metadata from `info.ts` | +| `run` | Yes | Execution function, called with the assembled hex data (without `0x`) on every valid input change | + ### `PrecompileConfig` Reference ```typescript interface PrecompileConfig { explorationId: string - precompileAddress: string - preHardfork: Hardfork - postHardfork: Hardfork defaultExample: string showBigInt?: boolean values: PrecompileValueDef[] @@ -77,9 +94,6 @@ interface PrecompileConfig { | Field | Required | Description | |-------|----------|-------------| | `explorationId` | Yes | Matches the exploration's `id` in `info.ts` | -| `precompileAddress` | Yes | Hex address of the precompile (e.g. `'05'` for ModExp) | -| `preHardfork` | Yes | Hardfork for the "before" comparison | -| `postHardfork` | Yes | Hardfork for the "after" comparison | | `defaultExample` | Yes | Key from `examples.ts` to load on initialization | | `showBigInt` | No | Show BigInt representations for values (default: per-value setting) | | `values` | Yes | Array of value definitions (see below) | @@ -137,3 +151,62 @@ const config: PrecompileConfig = { }, } ``` + +### Execution: `run` Prop and `#result` Slot + +The E-Component separates input management from execution. The exploration provides: + +1. A **`run` function** — called automatically with the assembled hex data on every valid input change +2. A **`#result` slot** — renders the execution results however the exploration needs + +#### Standard: `useStandardPrecompileRun` + +For the common pre/post hardfork comparison using the EthereumJS EVM, use the provided helper: + +```typescript +import { useStandardPrecompileRun } from '@/eComponents/precompileInterfaceEC/run' + +const { run, execResultPre, execResultPost } = useStandardPrecompileRun( + Hardfork.Prague, Hardfork.Osaka, '05', +) +``` + +This returns a `run` function ready to pass as a prop and two reactive refs for the results. Use `PrecompileInterfaceResultEC` in the `#result` slot for the standard gas + hex display. + +#### Custom Execution + +For explorations that need a different execution mechanism (custom precompile, different library, etc.), define your own `run` function and result state: + +```vue + + + +``` + +The `#result` slot template lives in the exploration's scope, so it naturally accesses your own refs and computed properties. diff --git a/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue b/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue index 6511333..b3fed22 100644 --- a/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue +++ b/src/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue @@ -7,7 +7,6 @@ import type { Examples } from '@/explorations/REGISTRY' import type { Exploration } from '@/explorations/REGISTRY' import { TOPICS } from '@/explorations/TOPICS' -import PrecompileInterfaceResultEC from './PrecompileInterfaceResultEC.vue' import PrecompileValueInputEC from './PrecompileValueInputEC.vue' import type { PrecompileConfig } from './types' import { usePrecompileState } from './usePrecompileState' @@ -16,6 +15,7 @@ const props = defineProps<{ config: PrecompileConfig examples: Examples exploration: Exploration + run: (data: string) => Promise }>() const topic = TOPICS[props.exploration.topic] @@ -26,15 +26,13 @@ const { hexVals, bigIntVals, byteLengths, - execResultPre, - execResultPost, inputValues, selectExample, shareURL, onDataInputFormChange, onValueInputFormChange, init, -} = usePrecompileState(props.config, props.examples) +} = usePrecompileState(props.config, props.examples, props.run) await init() @@ -62,10 +60,7 @@ await init() :bigIntVal="bigIntVals[val.index]" /> -
- - -
+ diff --git a/src/eComponents/precompileInterfaceEC/__tests__/usePrecompileState.spec.ts b/src/eComponents/precompileInterfaceEC/__tests__/usePrecompileState.spec.ts new file mode 100644 index 0000000..50a50ae --- /dev/null +++ b/src/eComponents/precompileInterfaceEC/__tests__/usePrecompileState.spec.ts @@ -0,0 +1,82 @@ +import { describe, expect, it, vi } from 'vitest' + +vi.mock('vue-router', () => ({ + useRoute: () => ({ query: {} }), + useRouter: () => ({ resolve: vi.fn(() => ({ href: '' })) }), +})) + +import type { Examples } from '@/explorations/REGISTRY' + +import type { PrecompileConfig } from '../types' +import { usePrecompileState } from '../usePrecompileState' + +const config: PrecompileConfig = { + explorationId: 'test', + defaultExample: 'ex1', + values: [{ title: 'A', urlParam: 'a', expectedLen: 4n }], +} + +const examples: Examples = { + ex1: { title: 'Example 1', values: ['deadbeef'] }, +} + +describe('usePrecompileState', () => { + it('calls run with assembled data on init', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + + await state.init() + + expect(run).toHaveBeenCalledWith('deadbeef') + }) + + it('calls run on data input change', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + await state.init() + run.mockClear() + + state.data.value = 'cafebabe' + await state.onDataInputFormChange() + + expect(run).toHaveBeenCalledWith('cafebabe') + }) + + it('calls run on value input change', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + await state.init() + run.mockClear() + + state.hexVals.value[0] = 'aabbccdd' + await state.onValueInputFormChange() + + expect(run).toHaveBeenCalledWith('aabbccdd') + }) + + it('clears example on manual data input', async () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + await state.init() + expect(state.example.value).toBe('ex1') + + state.data.value = 'cafebabe' + await state.onDataInputFormChange() + + expect(state.example.value).toBe('') + }) + + it('exposes input state without execution state', () => { + const run = vi.fn().mockResolvedValue(undefined) + const state = usePrecompileState(config, examples, run) + + expect(state.data).toBeDefined() + expect(state.hexVals).toBeDefined() + expect(state.bigIntVals).toBeDefined() + expect(state.byteLengths).toBeDefined() + expect(state.example).toBeDefined() + expect(state.inputValues).toBeDefined() + expect(state).not.toHaveProperty('execResultPre') + expect(state).not.toHaveProperty('execResultPost') + }) +}) diff --git a/src/eComponents/precompileInterfaceEC/run.ts b/src/eComponents/precompileInterfaceEC/run.ts index dd95ccd..2feca5b 100644 --- a/src/eComponents/precompileInterfaceEC/run.ts +++ b/src/eComponents/precompileInterfaceEC/run.ts @@ -1,4 +1,4 @@ -import type { Ref } from 'vue' +import { ref } from 'vue' import { Common, type Hardfork, Mainnet } from '@ethereumjs/common' import { createEVM, type ExecResult, getActivePrecompiles } from '@ethereumjs/evm' import { hexToBytes } from '@ethereumjs/util' @@ -8,9 +8,7 @@ export async function runPrecompile( preHF: Hardfork, postHF: Hardfork, precompile: string, - execResultPre: Ref, - execResultPost: Ref, -) { +): Promise<{ pre?: ExecResult; post: ExecResult }> { const gasLimit = BigInt(5000000) const commonPre = new Common({ chain: Mainnet, hardfork: preHF }) @@ -21,7 +19,7 @@ export async function runPrecompile( const evmPost = await createEVM({ common: commonPost }) const precompilePost = getActivePrecompiles(commonPost).get(precompile.padStart(40, '0'))! - // Pre-HF run + let pre: ExecResult | undefined if (precompilePre) { const callDataPre = { data: hexToBytes(`0x${data}`), @@ -29,15 +27,38 @@ export async function runPrecompile( common: commonPre, _EVM: evmPre, } - execResultPre.value = await precompilePre(callDataPre) + pre = await precompilePre(callDataPre) } - // Post-HF run const callDataPost = { data: hexToBytes(`0x${data}`), gasLimit, common: commonPost, _EVM: evmPost, } - execResultPost.value = await precompilePost(callDataPost) + const post = await precompilePost(callDataPost) + + return { pre, post } +} + +/** + * Convenience composable for the standard pre/post hardfork comparison pattern. + * Returns a `run` function compatible with the `PrecompileInterfaceEC` `run` prop, + * plus reactive refs for both results. + */ +export function useStandardPrecompileRun( + preHF: Hardfork, + postHF: Hardfork, + precompileAddress: string, +) { + const execResultPre = ref() + const execResultPost = ref() + + async function run(data: string) { + const results = await runPrecompile(data, preHF, postHF, precompileAddress) + execResultPre.value = results.pre + execResultPost.value = results.post + } + + return { run, execResultPre, execResultPost } } diff --git a/src/eComponents/precompileInterfaceEC/types.ts b/src/eComponents/precompileInterfaceEC/types.ts index d8fc29a..de3a0d6 100644 --- a/src/eComponents/precompileInterfaceEC/types.ts +++ b/src/eComponents/precompileInterfaceEC/types.ts @@ -1,5 +1,3 @@ -import type { Hardfork } from '@ethereumjs/common' - export interface PrecompileValueDef { title: string urlParam?: string @@ -11,9 +9,6 @@ export interface PrecompileValueDef { export interface PrecompileConfig { explorationId: string - precompileAddress: string - preHardfork: Hardfork - postHardfork: Hardfork defaultExample: string showBigInt?: boolean values: PrecompileValueDef[] diff --git a/src/eComponents/precompileInterfaceEC/usePrecompileState.ts b/src/eComponents/precompileInterfaceEC/usePrecompileState.ts index 9af1096..6a9b012 100644 --- a/src/eComponents/precompileInterfaceEC/usePrecompileState.ts +++ b/src/eComponents/precompileInterfaceEC/usePrecompileState.ts @@ -1,10 +1,8 @@ import { computed, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' -import type { ExecResult } from '@ethereumjs/evm' import type { Examples } from '@/explorations/REGISTRY' -import { runPrecompile } from './run' import type { PrecompileConfig } from './types' import { dataToValueInput, isValidByteInputForm, valueToDataInput } from './utils' @@ -18,12 +16,14 @@ function createState(config: PrecompileConfig) { lengthsMask: ref<(bigint | undefined)[]>(config.values.map((v) => v.expectedLen)), byteLengths: ref(config.values.map(() => 0n)), example: ref(''), - execResultPre: ref(), - execResultPost: ref(), } } -export function usePrecompileState(config: PrecompileConfig, examples: Examples) { +export function usePrecompileState( + config: PrecompileConfig, + examples: Examples, + run: (data: string) => Promise, +) { const { data, hexVals, @@ -31,8 +31,6 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) lengthsMask, byteLengths, example, - execResultPre, - execResultPost, } = createState(config) const router = useRouter() @@ -46,24 +44,13 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) // --- Data conversion --- - async function run() { - await runPrecompile( - data.value, - config.preHardfork, - config.postHardfork, - config.precompileAddress, - execResultPre, - execResultPost, - ) - } - async function data2Values() { if (isValidByteInputForm(data.value).length > 0) return if (config.parseData) { config.parseData(data.value, byteLengths.value) } dataToValueInput(data, hexVals, bigIntVals, byteLengths) - await run() + await run(data.value) } async function values2Data() { @@ -76,7 +63,7 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) data.value = config.assembleData ? config.assembleData(hexVals.value, byteLengths.value) : hexVals.value.join('') - await run() + await run(data.value) } // --- User interaction --- @@ -136,8 +123,6 @@ export function usePrecompileState(config: PrecompileConfig, examples: Examples) hexVals, bigIntVals, byteLengths, - execResultPre, - execResultPost, inputValues, selectExample, shareURL, diff --git a/src/explorations/eip-7883/MyC.vue b/src/explorations/eip-7883/MyC.vue index 2352a46..4e5ab43 100644 --- a/src/explorations/eip-7883/MyC.vue +++ b/src/explorations/eip-7883/MyC.vue @@ -3,17 +3,20 @@ import { Hardfork } from '@ethereumjs/common' import { hexToBigInt } from '@ethereumjs/util' import PrecompileInterfaceEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue' +import PrecompileInterfaceResultEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceResultEC.vue' +import { useStandardPrecompileRun } from '@/eComponents/precompileInterfaceEC/run' import type { PrecompileConfig } from '@/eComponents/precompileInterfaceEC/types' import { padHex, toHex } from '@/eComponents/precompileInterfaceEC/utils' import { examples } from './examples' import { INFO as exploration } from './info' +const { run, execResultPre, execResultPost } = useStandardPrecompileRun( + Hardfork.Prague, Hardfork.Osaka, '05', +) + const config: PrecompileConfig = { explorationId: 'eip-7883', - precompileAddress: '05', - preHardfork: Hardfork.Prague, - postHardfork: Hardfork.Osaka, defaultExample: 'simple', values: [ { title: 'Blen', expectedLen: 32n, initialHex: '00'.repeat(32), showInput: false }, @@ -39,5 +42,14 @@ const config: PrecompileConfig = { diff --git a/src/explorations/eip-7951/MyC.vue b/src/explorations/eip-7951/MyC.vue index b2c99f2..36e2aeb 100644 --- a/src/explorations/eip-7951/MyC.vue +++ b/src/explorations/eip-7951/MyC.vue @@ -2,16 +2,19 @@ import { Hardfork } from '@ethereumjs/common' import PrecompileInterfaceEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceEC.vue' +import PrecompileInterfaceResultEC from '@/eComponents/precompileInterfaceEC/PrecompileInterfaceResultEC.vue' +import { useStandardPrecompileRun } from '@/eComponents/precompileInterfaceEC/run' import type { PrecompileConfig } from '@/eComponents/precompileInterfaceEC/types' import { examples } from './examples' import { INFO as exploration } from './info' +const { run, execResultPre, execResultPost } = useStandardPrecompileRun( + Hardfork.Prague, Hardfork.Osaka, '100', +) + const config: PrecompileConfig = { explorationId: 'eip-7951', - precompileAddress: '100', - preHardfork: Hardfork.Prague, - postHardfork: Hardfork.Osaka, defaultExample: 'valid', showBigInt: false, values: [ @@ -25,5 +28,14 @@ const config: PrecompileConfig = { From 52df7fdbc9e1f01878199db01c65574c9d5e10d5 Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Mon, 9 Mar 2026 13:06:10 +0100 Subject: [PATCH 03/18] Small EIP-7594 exploration optimization --- src/explorations/eip-7594/MyC.vue | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/explorations/eip-7594/MyC.vue b/src/explorations/eip-7594/MyC.vue index 0fc009f..49aae2e 100644 --- a/src/explorations/eip-7594/MyC.vue +++ b/src/explorations/eip-7594/MyC.vue @@ -34,27 +34,23 @@ const example = ref('') const topic = TOPICS[exploration.topic] const hasResult = computed(() => commitment.value !== '') -function selectExample() { - if (example.value === '') { - return - } - data.value = examples[example.value]!.values[0] +function resetResults() { commitment.value = '' versionedHash.value = '' blobProof.value = '' cellProofs.value = [] - errorMsg.value = '' } +function selectExample() { + if (example.value === '') return + data.value = examples[example.value]!.values[0] + resetResults() +} + function onDataInputFormChange() { example.value = '' - errorMsg.value = '' - - commitment.value = '' - versionedHash.value = '' - blobProof.value = '' - cellProofs.value = [] + resetResults() } async function run() { From 5c251815624fab6c2375d1aef9bee28266ff538c Mon Sep 17 00:00:00 2001 From: Holger Drewes Date: Mon, 9 Mar 2026 13:50:30 +0100 Subject: [PATCH 04/18] Exploration/E-Component related test rework (more consistent, contributor-friendly and E2E -> unit) --- cypress/e2e/eip7594.cy.ts | 36 ---- cypress/e2e/eip7883_Precompile_R.cy.ts | 107 ---------- cypress/e2e/eip7951.cy.ts | 31 --- cypress/e2e/explorations.cy.ts | 61 ++++++ docs/contributing/adding-an-exploration.md | 135 +++++++++++-- .../__tests__/run.spec.ts | 60 ++++++ .../__tests__/utils.spec.ts | 185 ++++++++++++++++++ src/explorations/eip-7594/tests.spec.ts | 46 +++++ src/explorations/eip-7883/MyC.vue | 29 +-- src/explorations/eip-7883/config.ts | 29 +++ src/explorations/eip-7883/tests.spec.ts | 108 ++++++++++ src/explorations/eip-7951/MyC.vue | 15 +- src/explorations/eip-7951/config.ts | 14 ++ src/explorations/eip-7951/tests.spec.ts | 82 ++++++++ 14 files changed, 707 insertions(+), 231 deletions(-) delete mode 100644 cypress/e2e/eip7594.cy.ts delete mode 100644 cypress/e2e/eip7883_Precompile_R.cy.ts delete mode 100644 cypress/e2e/eip7951.cy.ts create mode 100644 cypress/e2e/explorations.cy.ts create mode 100644 src/eComponents/precompileInterfaceEC/__tests__/run.spec.ts create mode 100644 src/eComponents/precompileInterfaceEC/__tests__/utils.spec.ts create mode 100644 src/explorations/eip-7594/tests.spec.ts create mode 100644 src/explorations/eip-7883/config.ts create mode 100644 src/explorations/eip-7883/tests.spec.ts create mode 100644 src/explorations/eip-7951/config.ts create mode 100644 src/explorations/eip-7951/tests.spec.ts diff --git a/cypress/e2e/eip7594.cy.ts b/cypress/e2e/eip7594.cy.ts deleted file mode 100644 index ce38f7d..0000000 --- a/cypress/e2e/eip7594.cy.ts +++ /dev/null @@ -1,36 +0,0 @@ -describe('EIP-7594/PeerDAS Tests', () => { - it('initialization', () => { - cy.visit('/eip-7594-peerdas-data-availability-sampling') - - // Basic component display - cy.contains('h1', 'Feel Your Protocol') - cy.contains('h3', 'Peer Data Availability Sampling') - - // Values from inital example - cy.get('#eip-7594-c textarea', { timeout: 10000 }).should( - 'contain.value', - '0a0001fbfc0084bd8494e56454b36510b0adc8aaa1b60', - ) - - // Select different example - cy.get('#eip-7594-c .e-select').click() - cy.get('#eip-7594-c [role="option"]').eq(1).click() - cy.get('#eip-7594-c textarea', { timeout: 10000 }).should( - 'contain.value', - '00000000000000000000000000000000000000000', - ) - - cy.get('.run-button').click() - let text = - '0xa522f1be9ec4a02fcb6998b10306e94331311ac29bcaaae357d8d7fbc087a04b5b66dd7fa84cbebabcc45202b8fee57f' - cy.get('#eip-7594-c .4844-7594-box') - .find('table tr:first td:nth-child(2)') - .should('contain.text', text, { timeout: 25000 }) - text = - '0x8bd1e6e38b2b54735c6f0102022510cf2abc2e4c6c5a437cba9831662c9f112e61e2a6ced8ce63b3de18cb9cc99ae21e' - cy.get('#eip-7594-c .4844-box').find('p').should('contain.text', text, { timeout: 25000 }) - text = - '0x8d90ed38068f3561132d9264db8c8dfb5237af24a6e28c3d4e72d3ad8d51d97be5733ddc382c9718822cb29ccc26364e' - cy.get('#eip-7594-c .7594-box').find('p:first').should('contain.text', text, { timeout: 25000 }) - }) -}) diff --git a/cypress/e2e/eip7883_Precompile_R.cy.ts b/cypress/e2e/eip7883_Precompile_R.cy.ts deleted file mode 100644 index 018616e..0000000 --- a/cypress/e2e/eip7883_Precompile_R.cy.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * EIP-7823 is the representative EIP for the precompile component tests. - */ -describe('EIP-7823/Precompile Component Tests', () => { - let bytesLengthsExpected = - '0000000000000000000000000000000000000000000000000000000000000001' + - '0000000000000000000000000000000000000000000000000000000000000001' + - '0000000000000000000000000000000000000000000000000000000000000001' - const bytesValuesExpected = '030302' - let bytesExpected = bytesLengthsExpected + bytesValuesExpected - - it('initialization', () => { - cy.visit('/eip-7883-modexp-gas-cost-increase') - - cy.contains('h1', 'Feel Your Protocol') - cy.contains('h3', 'ModExp') - - cy.get('#eip-7883-c textarea', { timeout: 10000 }).should('have.value', bytesExpected) - cy.get('#eip-7883-c input').eq(0).should('have.value', '03') - cy.get('#eip-7883-c input').eq(1).should('have.value', '03') - cy.get('#eip-7883-c input').eq(2).should('have.value', '02') - - cy.get('.pre-hardfork').find('p').eq(0).should('include.text', '200 Gas') - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '500 Gas') - }) - - it('values -> data', () => { - cy.visit('/eip-7883-modexp-gas-cost-increase') - - // Simple initial case - bytesExpected = bytesLengthsExpected + '040402' - cy.get('#eip-7883-c').within(() => { - cy.get('input').eq(0).clear() - cy.get('input').eq(1).clear() - cy.get('input').eq(2).clear() - cy.get('input').eq(0).type('04') - cy.get('input').eq(1).type('04') - cy.get('input').eq(2).type('02') - }) - cy.get('textarea').should('have.value', bytesExpected) - - // Slightly modified values case - bytesLengthsExpected = - '0000000000000000000000000000000000000000000000000000000000000002' + - '0000000000000000000000000000000000000000000000000000000000000002' + - '0000000000000000000000000000000000000000000000000000000000000002' - bytesExpected = bytesLengthsExpected + '040404040202' - cy.get('#eip-7883-c').within(() => { - cy.get('input').eq(0).type('04') - cy.get('input').eq(1).type('04') - cy.get('input').eq(2).type('02') - }) - cy.get('textarea').should('have.value', bytesExpected) - - cy.get('#eip-7883-c').within(() => { - // Gas changing example - cy.get('input').eq(0).type('040404040404040404040404040404040404040404') - cy.get('input').eq(1).type('040404040404040404040404040404040404040404') - cy.get('input').eq(2).type('02') - - cy.get('.pre-hardfork').find('p').eq(0).should('include.text', '534 Gas') - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '2848 Gas') - }) - }) - - it('data -> values, URL sharing, EIP button', () => { - cy.visit('/eip-7883-modexp-gas-cost-increase') - - cy.get('#eip-7883-c').within(() => { - cy.window().then((win) => { - cy.stub(win, 'open').callsFake((url) => { - win.location.href = url // Redirect within the same tab - }) - }) - // data -> values - cy.get('textarea').clear() - cy.get('textarea').type(bytesExpected) // '040404040202' - cy.get('input').eq(0).should('have.value', '0404') - cy.get('input').eq(1).should('have.value', '0404') - cy.get('input').eq(2).should('have.value', '0202') - - // URL sharing - cy.get('.share-url-button').click() - cy.url().should('include', 'b=0404') - cy.url().should('include', 'e=0404') - cy.url().should('include', 'm=0202') - - cy.get('textarea').should('have.value', bytesExpected) - cy.get('input').eq(0).should('have.value', '0404') - cy.get('input').eq(1).should('have.value', '0404') - cy.get('input').eq(2).should('have.value', '0202') - - // examples - cy.get('.e-select').click() - cy.contains('[role="option"]', 'Simple').click() - cy.get('input').eq(0).should('have.value', '03') - cy.get('input').eq(1).should('have.value', '03') - cy.get('input').eq(2).should('have.value', '02') - - // EIP button - cy.get('.visit-exploration-button').invoke('removeAttr', 'target').click() - cy.origin('https://eips.ethereum.org', () => { - cy.url().should('eq', 'https://eips.ethereum.org/EIPS/eip-7883') - }) - }) - }) -}) diff --git a/cypress/e2e/eip7951.cy.ts b/cypress/e2e/eip7951.cy.ts deleted file mode 100644 index 342baf7..0000000 --- a/cypress/e2e/eip7951.cy.ts +++ /dev/null @@ -1,31 +0,0 @@ -describe('EIP-7951/secp256r1 Precompile Support', () => { - it('initialization', () => { - cy.visit('/eip-7951-secp256r1-precompile') - - // Basic component display - cy.contains('h1', 'Feel Your Protocol') - cy.contains('h3', 'secp256r1 Precompile Support') - - // Values from inital example - cy.get('#eip-7951-c textarea', { timeout: 10000 }).should( - 'contain.value', - '4dfb1eae8ed41e188b8a44a1109d982d01fc24bb85a933', - ) - const val = '3b91fedfb22f40063245c621036a040c159f02ae02e6d450ff9b53235e9232c4' - cy.get('#eip-7951-c input').eq(2).should('have.value', val) - - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '6900 Gas') - - // Select different example - cy.get('#eip-7951-c .e-select').click() - cy.contains('[role="option"]', 'Invalid (Wycheproof), r value too large').click() - cy.get('#eip-7951-c textarea', { timeout: 10000 }).should( - 'contain.value', - '532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25', - ) - cy.get('#eip-7951-c input') - .eq(2) - .should('have.value', 'ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc63254e') - cy.get('.post-hardfork').find('p').eq(0).should('include.text', '6900 Gas') - }) -}) diff --git a/cypress/e2e/explorations.cy.ts b/cypress/e2e/explorations.cy.ts new file mode 100644 index 0000000..7d5a6a3 --- /dev/null +++ b/cypress/e2e/explorations.cy.ts @@ -0,0 +1,61 @@ +describe('EIP-7883 ModExp', () => { + it('loads and displays exploration content', () => { + cy.visit('/eip-7883-modexp-gas-cost-increase') + cy.contains('h1', 'Feel Your Protocol') + cy.contains('h3', 'ModExp') + cy.get('#eip-7883-c', { timeout: 10000 }).should('exist') + }) + + it('loads default example with inputs', () => { + cy.visit('/eip-7883-modexp-gas-cost-increase') + cy.get('#eip-7883-c textarea', { timeout: 10000 }).should('not.have.value', '') + cy.get('#eip-7883-c input').should('have.length.gte', 3) + }) + + it('example selector shows available options', () => { + cy.visit('/eip-7883-modexp-gas-cost-increase') + cy.get('#eip-7883-c .e-select', { timeout: 10000 }).click() + cy.get('[role="option"]').should('have.length.gte', 2) + }) +}) + +describe('EIP-7594 PeerDAS', () => { + it('loads and displays exploration content', () => { + cy.visit('/eip-7594-peerdas-data-availability-sampling') + cy.contains('h1', 'Feel Your Protocol') + cy.contains('h3', 'Peer Data Availability Sampling') + cy.get('#eip-7594-c', { timeout: 10000 }).should('exist') + }) + + it('loads default example with blob data', () => { + cy.visit('/eip-7594-peerdas-data-availability-sampling') + cy.get('#eip-7594-c textarea', { timeout: 10000 }).should('not.have.value', '') + }) + + it('example selector shows available options', () => { + cy.visit('/eip-7594-peerdas-data-availability-sampling') + cy.get('#eip-7594-c .e-select', { timeout: 10000 }).click() + cy.get('[role="option"]').should('have.length.gte', 2) + }) +}) + +describe('EIP-7951 secp256r1', () => { + it('loads and displays exploration content', () => { + cy.visit('/eip-7951-secp256r1-precompile') + cy.contains('h1', 'Feel Your Protocol') + cy.contains('h3', 'secp256r1 Precompile Support') + cy.get('#eip-7951-c', { timeout: 10000 }).should('exist') + }) + + it('loads default example with inputs', () => { + cy.visit('/eip-7951-secp256r1-precompile') + cy.get('#eip-7951-c textarea', { timeout: 10000 }).should('not.have.value', '') + cy.get('#eip-7951-c input').should('have.length.gte', 5) + }) + + it('example selector shows available options', () => { + cy.visit('/eip-7951-secp256r1-precompile') + cy.get('#eip-7951-c .e-select', { timeout: 10000 }).click() + cy.get('[role="option"]').should('have.length.gte', 2) + }) +}) diff --git a/docs/contributing/adding-an-exploration.md b/docs/contributing/adding-an-exploration.md index 74e08d4..09fe053 100644 --- a/docs/contributing/adding-an-exploration.md +++ b/docs/contributing/adding-an-exploration.md @@ -8,10 +8,12 @@ An exploration folder looks like this: ``` src/explorations/eip-XXXX/ -├── info.ts # Metadata (required) -├── MyC.vue # Interactive widget (required) -├── examples.ts # Example presets (recommended) -└── data/ # Optional data files +├── info.ts # Metadata (required) +├── MyC.vue # Interactive widget (required) +├── examples.ts # Example presets (recommended) +├── tests.spec.ts # Unit tests (required) +├── config.ts # Precompile config (for precompile explorations) +└── data/ # Optional data files ``` ## Step 1: Create the Folder @@ -147,6 +149,24 @@ The `ExplorationC` wrapper renders the title, info link, intro text, and usage t If your exploration is about a precompile, you can use the Precompile Interface E-Component. It handles all input management while you provide the execution logic and result display: +First, define your precompile config in a separate `config.ts` file (this keeps the config testable): + +```typescript +// config.ts +import type { PrecompileConfig } from '@/eComponents/precompileInterfaceEC/types' + +export const config: PrecompileConfig = { + explorationId: 'eip-XXXX', + defaultExample: 'basic', + values: [ + { title: 'Input A', urlParam: 'a', expectedLen: 32n }, + { title: 'Input B', urlParam: 'b', expectedLen: 32n }, + ], +} +``` + +Then in `MyC.vue`: + ```vue