Skip to content
Open
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
9 changes: 8 additions & 1 deletion components/Editor/ExecutionState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EthereumContext } from 'context/ethereumContext'

import { toKeyIndex } from 'util/string'

import { MemoryPane } from 'components/Editor/MemoryPane'
import { StackBox } from 'components/ui'

type RowProps = {
Expand Down Expand Up @@ -51,7 +52,13 @@ const ExecutionState = () => {
return (
<div>
<dl className="text-2xs">
<ExecutionStateRow label="Memory" value={memory} />
<dt className="mb-1 text-gray-500 dark:text-gray-400 font-medium uppercase">
Memory
</dt>
<dd className="font-mono mb-2">
<MemoryPane words={memory} />
</dd>

<ExecutionStateRow label="Stack" value={stack} />

<dt className="mb-1 text-gray-500 dark:text-gray-400 font-medium uppercase">
Expand Down
42 changes: 42 additions & 0 deletions components/Editor/MemoryPane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react'

import cn from 'classnames'
import { IMemoryWord } from 'types'

type Props = {
words: IMemoryWord[] | undefined
}

const toPrefixedHex = (hex: string) => (hex ? `0x${hex}` : '')

export const MemoryPane: React.FC<Props> = ({ words }) => {
const rows =
!words || words.length === 0 ? [{ offset: '00', data: '' }] : words
const offsetWidth = Math.max(...rows.map((row) => row.offset.length))

return (
<div
className="inline-block border border-gray-600 dark:border-gray-700 rounded-sm w-full overflow-hidden font-mono text-tiny"
style={{ minHeight: 26 }}
>
{rows.map((row, index) => (
<div
key={`${row.offset}-${index}`}
className={cn('flex items-start px-2 py-1', {
'border-t border-gray-600/40 dark:border-gray-700/60': index > 0,
})}
>
<span
className="shrink-0 pr-3 text-gray-500 dark:text-gray-400 tabular-nums select-none"
style={{ width: `${offsetWidth + 1}ch` }}
>
{row.offset}
</span>
<span className="min-w-0 flex-1 break-all text-gray-300 tracking-tight">
{row.data ? toPrefixedHex(row.data) : '\u00a0'}
</span>
</div>
))}
</div>
)
}
13 changes: 8 additions & 5 deletions context/ethereumContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
calculateOpcodeDynamicFee,
calculatePrecompiledDynamicFee,
} from 'util/gas'
import { toHex, fromBuffer } from 'util/string'
import { toHex, fromBuffer, formatEvmMemory } from 'util/string'

let vm: VM
let common: Common
Expand Down Expand Up @@ -546,7 +546,8 @@ export const EthereumProvider: React.FC<{}> = ({ children }) => {
while (!result.done) {
// Convert full address (e.g., "0000...0001" or "0000...0100") to short form
// Format: 0x01-0x0f (2 digits), 0x10-0x11 (2 digits), 0x100 (3 digits)
const addressString = '0x' + result.value.slice(2).replace(/^0+(?=..)/, '')
const addressString =
'0x' + result.value.slice(2).replace(/^0+(?=..)/, '')

if (!meta[addressString]) {
result = addressIterator.next()
Expand Down Expand Up @@ -778,9 +779,11 @@ export const EthereumProvider: React.FC<{}> = ({ children }) => {
programCounter: pc,
stack: stack.map((value) => value.toString(16)).reverse(),
totalGas: totalGasSpent.toString(),
memory: fromBuffer(Buffer.from(memory)).substring(
0,
Number(memoryWordCount) * 64,
memory: formatEvmMemory(
fromBuffer(Buffer.from(memory)).substring(
0,
Number(memoryWordCount) * 64,
),
),
transientStorage,
storage,
Expand Down
7 changes: 6 additions & 1 deletion types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,17 @@ export interface ITransientStorage {
value: string
}

export interface IMemoryWord {
offset: string
data: string
}

export interface IExecutionState {
programCounter: number | undefined
stack: string[]
storage: IStorage[]
transientStorage: ITransientStorage[]
memory: string | undefined
memory: IMemoryWord[] | undefined
totalGas: string | undefined
currentGas: string | undefined
returnValue: string | undefined
Expand Down
41 changes: 41 additions & 0 deletions util/string.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import hljs from 'highlight.js/lib/core'
import hljsDefineSolidity from 'highlightjs-solidity'
import { IMemoryWord } from 'types'

import hljsDefineBytecode from '../bytecode.js'
import hljsDefineMnemonic from '../mnemonic.js'
Expand Down Expand Up @@ -69,6 +70,46 @@ export const fromBuffer = (buf: Buffer) => {
return result
}

const EVM_MEMORY_WORD_BYTES = 32
const EVM_MEMORY_WORD_HEX_LENGTH = EVM_MEMORY_WORD_BYTES * 2

const normalizeMemoryWordHex = (wordHex: string) =>
wordHex
.padEnd(EVM_MEMORY_WORD_HEX_LENGTH, '0')
.slice(0, EVM_MEMORY_WORD_HEX_LENGTH)

/**
* Splits EVM memory into 32-byte words keyed by byte offset (00, 20, 40, …).
*/
export const formatEvmMemory = (hex: string): IMemoryWord[] => {
if (!hex || hex.length === 0) {
return [{ offset: '00', data: '' }]
}

const totalBytes = hex.length / 2
const offsetWidth = Math.max(
2,
Math.max(0, totalBytes - 1).toString(16).length,
)
const words: IMemoryWord[] = []

for (
let hexIndex = 0;
hexIndex < hex.length;
hexIndex += EVM_MEMORY_WORD_HEX_LENGTH
) {
const byteOffset = hexIndex / 2
const offset = byteOffset.toString(16).padStart(offsetWidth, '0')
const wordHex = hex.slice(hexIndex, hexIndex + EVM_MEMORY_WORD_HEX_LENGTH)
words.push({
offset,
data: normalizeMemoryWordHex(wordHex),
})
}

return words
}

/**
* Creates a RC key from string prefix and index.
*/
Expand Down