diff --git a/components/Editor/ExecutionState.tsx b/components/Editor/ExecutionState.tsx
index e076864d..dfc03015 100644
--- a/components/Editor/ExecutionState.tsx
+++ b/components/Editor/ExecutionState.tsx
@@ -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 = {
@@ -51,7 +52,13 @@ const ExecutionState = () => {
return (
-
+ -
+ Memory
+
+ -
+
+
+
-
diff --git a/components/Editor/MemoryPane.tsx b/components/Editor/MemoryPane.tsx
new file mode 100644
index 00000000..7475197a
--- /dev/null
+++ b/components/Editor/MemoryPane.tsx
@@ -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 = ({ words }) => {
+ const rows =
+ !words || words.length === 0 ? [{ offset: '00', data: '' }] : words
+ const offsetWidth = Math.max(...rows.map((row) => row.offset.length))
+
+ return (
+
+ {rows.map((row, index) => (
+
0,
+ })}
+ >
+
+ {row.offset}
+
+
+ {row.data ? toPrefixedHex(row.data) : '\u00a0'}
+
+
+ ))}
+
+ )
+}
diff --git a/context/ethereumContext.tsx b/context/ethereumContext.tsx
index dabf5711..53aaadde 100644
--- a/context/ethereumContext.tsx
+++ b/context/ethereumContext.tsx
@@ -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
@@ -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()
@@ -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,
diff --git a/types/index.ts b/types/index.ts
index 21e9723b..c0408cfd 100644
--- a/types/index.ts
+++ b/types/index.ts
@@ -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
diff --git a/util/string.ts b/util/string.ts
index 1ff1debc..ae9ef456 100644
--- a/util/string.ts
+++ b/util/string.ts
@@ -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'
@@ -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.
*/