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. */