├── public ├── robots.txt ├── favicon.ico ├── logo-dark.png ├── logo-light.png └── logo-dark-tight.png ├── .prettierignore ├── src ├── chains │ ├── arbitrum │ │ ├── accountTypes.ts │ │ ├── vm │ │ │ ├── predeploys.ts │ │ │ └── opcodes │ │ │ │ ├── stack │ │ │ │ └── push0.ts │ │ │ │ ├── environment │ │ │ │ ├── caller.ts │ │ │ │ └── origin.ts │ │ │ │ ├── block │ │ │ │ ├── prevrandao.ts │ │ │ │ ├── number.ts │ │ │ │ ├── coinbase.ts │ │ │ │ └── blockhash.ts │ │ │ │ └── index.ts │ │ ├── nodes.ts │ │ └── index.ts │ ├── optimism │ │ ├── accountTypes.ts │ │ ├── vm │ │ │ ├── precompiles.ts │ │ │ └── opcodes │ │ │ │ ├── stack │ │ │ │ └── push0.ts │ │ │ │ ├── index.ts │ │ │ │ ├── block │ │ │ │ ├── coinbase.ts │ │ │ │ └── prevrandao.ts │ │ │ │ └── environment │ │ │ │ ├── origin.ts │ │ │ │ └── caller.ts │ │ ├── index.ts │ │ ├── nodes.ts │ │ ├── signatureTypes.ts │ │ ├── hardforks.ts │ │ ├── deployedContracts.ts │ │ └── eips.ts │ ├── mainnet │ │ ├── vm │ │ │ ├── predeploys.ts │ │ │ └── opcodes │ │ │ │ ├── keccak │ │ │ │ └── index.ts │ │ │ │ ├── log │ │ │ │ └── index.ts │ │ │ │ ├── storage │ │ │ │ ├── index.ts │ │ │ │ └── sload.ts │ │ │ │ ├── memory │ │ │ │ ├── index.ts │ │ │ │ ├── msize.ts │ │ │ │ ├── mstore8.ts │ │ │ │ └── mstore.ts │ │ │ │ ├── stack │ │ │ │ ├── index.ts │ │ │ │ ├── pop.ts │ │ │ │ ├── dup.ts │ │ │ │ ├── push.ts │ │ │ │ └── swap.ts │ │ │ │ ├── comparison │ │ │ │ ├── index.ts │ │ │ │ ├── iszero.ts │ │ │ │ ├── gt.ts │ │ │ │ ├── lt.ts │ │ │ │ ├── eq.ts │ │ │ │ ├── sgt.ts │ │ │ │ └── slt.ts │ │ │ │ ├── controlFlow │ │ │ │ ├── index.ts │ │ │ │ ├── jumpdest.ts │ │ │ │ ├── stop.ts │ │ │ │ ├── gas.ts │ │ │ │ ├── pc.ts │ │ │ │ ├── jump.ts │ │ │ │ └── jumpi.ts │ │ │ │ ├── bitwise │ │ │ │ ├── index.ts │ │ │ │ ├── not.ts │ │ │ │ ├── and.ts │ │ │ │ ├── or.ts │ │ │ │ ├── xor.ts │ │ │ │ ├── byte.ts │ │ │ │ ├── shr.ts │ │ │ │ ├── shl.ts │ │ │ │ └── sar.ts │ │ │ │ ├── block │ │ │ │ ├── index.ts │ │ │ │ ├── number.ts │ │ │ │ ├── gaslimit.ts │ │ │ │ ├── timestamp.ts │ │ │ │ ├── coinbase.ts │ │ │ │ ├── chainid.ts │ │ │ │ ├── blockhash.ts │ │ │ │ └── prevrandao.ts │ │ │ │ ├── arithmetic │ │ │ │ ├── index.ts │ │ │ │ ├── div.ts │ │ │ │ ├── mod.ts │ │ │ │ ├── sub.ts │ │ │ │ ├── add.ts │ │ │ │ ├── signextend.ts │ │ │ │ ├── mul.ts │ │ │ │ ├── addmod.ts │ │ │ │ ├── sdiv.ts │ │ │ │ ├── exp.ts │ │ │ │ ├── smod.ts │ │ │ │ └── mulmod.ts │ │ │ │ ├── system │ │ │ │ ├── invalid.ts │ │ │ │ ├── index.ts │ │ │ │ ├── selfdestruct.ts │ │ │ │ └── return.ts │ │ │ │ ├── environment │ │ │ │ ├── basefee.ts │ │ │ │ ├── gasprice.ts │ │ │ │ ├── address.ts │ │ │ │ ├── calldatasize.ts │ │ │ │ ├── callvalue.ts │ │ │ │ ├── origin.ts │ │ │ │ ├── caller.ts │ │ │ │ ├── selfbalance.ts │ │ │ │ ├── codesize.ts │ │ │ │ ├── balance.ts │ │ │ │ ├── returndatasize.ts │ │ │ │ ├── index.ts │ │ │ │ ├── extcodesize.ts │ │ │ │ ├── calldataload.ts │ │ │ │ └── extcodehash.ts │ │ │ │ └── index.ts │ │ ├── accountTypes.ts │ │ ├── index.ts │ │ ├── nodes │ │ │ ├── consensus.ts │ │ │ └── execution.ts │ │ ├── hardforks.ts │ │ └── signatureTypes.ts │ └── index.ts ├── types │ ├── accountType.ts │ ├── signatureType.ts │ ├── predeploy.ts │ ├── node.ts │ ├── mempool.ts │ ├── index.ts │ ├── precompile.ts │ ├── deployedContract.ts │ ├── chain.ts │ ├── eip.ts │ └── opcode.ts ├── components │ ├── diff │ │ ├── utils │ │ │ ├── RenderDiff.tsx │ │ │ ├── format.tsx │ │ │ ├── Collapsible.tsx │ │ │ ├── Abi.tsx │ │ │ └── Markdown.tsx │ │ ├── DiffSignatureTypes.tsx │ │ ├── DiffPredeploys.tsx │ │ └── DiffAccountTypes.tsx │ ├── layout │ │ ├── ExternalLink.tsx │ │ ├── ThemeSwitcher.tsx │ │ ├── Header.tsx │ │ ├── Layout.tsx │ │ ├── Head.tsx │ │ └── Footer.tsx │ ├── ui │ │ ├── Tooltip.tsx │ │ ├── LoadingSpinner.tsx │ │ ├── Toggle.tsx │ │ └── Copyable.tsx │ └── ChainDiffSelector.tsx ├── pages │ ├── _app.tsx │ └── index.tsx ├── styles │ └── globals.css └── lib │ ├── constants.ts │ ├── opcodes.ts │ └── utils.ts ├── postcss.config.js ├── next.config.js ├── tailwind.config.js ├── script ├── optimism-difficulty.sh └── json-abi-to-viem-human-readable.ts ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── package.json ├── README.md └── CONTRIBUTING.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | Allow: /api/og/* 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shafu0x/evm-diff/main/public/favicon.ico -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | pnpm-lock.yaml 3 | public/prism-light.css 4 | public/prism-dark.css -------------------------------------------------------------------------------- /public/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shafu0x/evm-diff/main/public/logo-dark.png -------------------------------------------------------------------------------- /public/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shafu0x/evm-diff/main/public/logo-light.png -------------------------------------------------------------------------------- /src/chains/arbitrum/accountTypes.ts: -------------------------------------------------------------------------------- 1 | export { accountTypes } from '@/chains/mainnet/accountTypes'; 2 | -------------------------------------------------------------------------------- /src/chains/optimism/accountTypes.ts: -------------------------------------------------------------------------------- 1 | export { accountTypes } from '@/chains/mainnet/accountTypes'; 2 | -------------------------------------------------------------------------------- /public/logo-dark-tight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shafu0x/evm-diff/main/public/logo-dark-tight.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/predeploys.ts: -------------------------------------------------------------------------------- 1 | import { Predeploy } from '@/types'; 2 | 3 | export const predeploys: Predeploy[] = []; 4 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/predeploys.ts: -------------------------------------------------------------------------------- 1 | import { Predeploy } from '@/types'; 2 | 3 | export const predeploys: Predeploy[] = []; 4 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | images: { 3 | remotePatterns: [ 4 | { 5 | protocol: 'https', 6 | hostname: 'icons.llamao.fi', 7 | }, 8 | ], 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/keccak/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { keccak } from './keccak'; 3 | 4 | export const opcodes: Record = { 5 | [keccak.number]: keccak, 6 | }; 7 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/log/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { opcodes as logOpcodes } from './log'; 3 | 4 | export const opcodes: Record> = { 5 | ...logOpcodes, 6 | }; 7 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/precompiles.ts: -------------------------------------------------------------------------------- 1 | import { precompiles as mainnetPrecompiles } from '@/chains/mainnet/vm/precompiles'; 2 | import { Precompile } from '@/types'; 3 | 4 | export const precompiles: Precompile[] = mainnetPrecompiles; 5 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/stack/push0.ts: -------------------------------------------------------------------------------- 1 | import { push0 as baseOpcode } from '@/chains/mainnet/vm/opcodes/stack/push'; 2 | import { Opcode } from '@/types'; 3 | 4 | export const push0: Opcode = { 5 | ...baseOpcode, 6 | supportedHardforks: ['ArbOS 11'], 7 | }; 8 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/opcodes/stack/push0.ts: -------------------------------------------------------------------------------- 1 | import { push0 as baseOpcode } from '@/chains/mainnet/vm/opcodes/stack/push'; 2 | import { Opcode } from '@/types'; 3 | 4 | export const push0: Opcode = { 5 | ...baseOpcode, 6 | supportedHardforks: ['Canyon'], 7 | }; 8 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/storage/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { sload } from './sload'; 3 | import { sstore } from './sstore'; 4 | 5 | export const opcodes: Record = { 6 | [sload.number]: sload, 7 | [sstore.number]: sstore, 8 | }; 9 | -------------------------------------------------------------------------------- /src/types/accountType.ts: -------------------------------------------------------------------------------- 1 | export type AccountType = { 2 | name: string; 3 | description: string; 4 | references: string[]; 5 | properties: { 6 | canBatchTxs: boolean; 7 | canInitiateTxs: boolean; 8 | hasCode: boolean; 9 | hasKeyPair: boolean; 10 | hasStorage: boolean; 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], 4 | darkMode: 'class', 5 | plugins: [require('@tailwindcss/forms')], 6 | theme: { 7 | extend: { 8 | colors: { 9 | zinc: { 10 | 0: '#fcfcfc', 11 | 1000: '#121212', 12 | }, 13 | }, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/memory/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { mload } from './mload'; 3 | import { msize } from './msize'; 4 | import { mstore } from './mstore'; 5 | import { mstore8 } from './mstore8'; 6 | 7 | export const opcodes: Record> = { 8 | [mload.number]: mload, 9 | [msize.number]: msize, 10 | [mstore.number]: mstore, 11 | [mstore8.number]: mstore8, 12 | }; 13 | -------------------------------------------------------------------------------- /src/types/signatureType.ts: -------------------------------------------------------------------------------- 1 | export type SignatureType = { 2 | prefixByte: number; 3 | description: string; 4 | // The data that is RLP encoded and signed to generate a signed transaction. 5 | signedData: string[] | undefined; 6 | // Some signature types are used to sign transactions, others are used to sign data. 7 | signs: 'transaction' | 'data' | undefined; 8 | references: string[]; 9 | notes?: string[]; 10 | }; 11 | -------------------------------------------------------------------------------- /script/optimism-difficulty.sh: -------------------------------------------------------------------------------- 1 | for i in {1..100}; do 2 | cast call 0xca11bde05977b3631167028862be2a173976ca11 "aggregate((address,bytes)[])(uint256, bytes[])" "[(0xca11bde05977b3631167028862be2a173976ca11,0x72425d9d)]" --rpc-url $MAINNET_RPC_URL 3 | cast call 0xca11bde05977b3631167028862be2a173976ca11 "aggregate((address,bytes)[])(uint256, bytes[])" "[(0xca11bde05977b3631167028862be2a173976ca11,0x72425d9d)]" --rpc-url $OPTIMISM_RPC_URL 4 | echo "---" 5 | done -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "printWidth": 100, 7 | "tabWidth": 2, 8 | "useTabs": false, 9 | "importOrder": ["^react(.*)", "next/(.*)", "", "@/(.*)", "^[./]"], 10 | "importOrderSortSpecifiers": true, 11 | "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"], 12 | "pluginSearchDirs": false 13 | } 14 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/stack/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { opcodes as dupOpcodes } from './dup'; 3 | import { pop } from './pop'; 4 | import { push0, opcodes as pushOpcodes } from './push'; 5 | import { opcodes as swapOpcodes } from './swap'; 6 | 7 | export const opcodes: Record = { 8 | ...dupOpcodes, 9 | [pop.number]: pop, 10 | [push0.number]: push0, 11 | ...pushOpcodes, 12 | ...swapOpcodes, 13 | }; 14 | -------------------------------------------------------------------------------- /src/chains/index.ts: -------------------------------------------------------------------------------- 1 | import { arbitrum } from '@/chains/arbitrum'; 2 | import { mainnet } from '@/chains/mainnet'; 3 | import { optimism } from '@/chains/optimism'; 4 | 5 | export const chains = { arbitrum, mainnet, optimism }; 6 | 7 | export const getChainById = (chainId: string | number | bigint | undefined) => { 8 | if (!chainId) return undefined; 9 | chainId = BigInt(chainId); 10 | return Object.values(chains).find((chain) => BigInt(chain.metadata.id) === chainId); 11 | }; 12 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { eq } from './eq'; 3 | import { gt } from './gt'; 4 | import { iszero } from './iszero'; 5 | import { lt } from './lt'; 6 | import { sgt } from './sgt'; 7 | import { slt } from './slt'; 8 | 9 | export const opcodes: Record = { 10 | [eq.number]: eq, 11 | [gt.number]: gt, 12 | [iszero.number]: iszero, 13 | [lt.number]: lt, 14 | [sgt.number]: sgt, 15 | [slt.number]: slt, 16 | }; 17 | -------------------------------------------------------------------------------- /script/json-abi-to-viem-human-readable.ts: -------------------------------------------------------------------------------- 1 | // Given a JSON ABI on the clipboard, replace the clipboard contents with a viem human-readable ABI. 2 | // Example usage: 3 | // bun script/json-abi-to-viem-human-readable.ts 4 | import { formatAbi } from 'abitype'; 5 | import clipboardy from 'clipboardy'; 6 | 7 | const abi = clipboardy.readSync(); 8 | const formattedAbi = formatAbi(JSON.parse(abi)); 9 | clipboardy.writeSync(JSON.stringify(formattedAbi)); 10 | console.log('✅ Copied formatted ABI to clipboard'); 11 | -------------------------------------------------------------------------------- /src/components/diff/utils/RenderDiff.tsx: -------------------------------------------------------------------------------- 1 | // Shows content if present, or a message if there's no diff. 2 | export const RenderDiff = ({ content }: { content: JSX.Element }) => { 3 | const children = content.props.children as (boolean | null | JSX.Element)[]; 4 | const isEmpty = children.every((child) => child === false); 5 | 6 | const EmptyDiff = () => ( 7 |
No differences found.
8 | ); 9 | 10 | return !isEmpty ? content : ; 11 | }; 12 | -------------------------------------------------------------------------------- /src/components/layout/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | type Props = { 2 | href: string; 3 | className?: string; 4 | } & ( 5 | | { text: string; children?: never } 6 | | { text?: never; children: JSX.Element } 7 | | { text?: never; children: React.ReactNode } 8 | ); 9 | 10 | export const ExternalLink = ({ href, className, text, children }: Props) => { 11 | return ( 12 | 13 | {text || children} 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/types/predeploy.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | type PredeployBase = { 4 | address: Address; 5 | name: string; 6 | description: string; 7 | deprecated: boolean; 8 | references: string[]; 9 | }; 10 | 11 | type StandardPredeploy = PredeployBase & { 12 | logicAbi: string[]; 13 | }; 14 | 15 | type ProxiedPredeploy = PredeployBase & { 16 | proxyAbi: string[]; 17 | logicAbi: string[]; 18 | logicAddress: Address; 19 | }; 20 | 21 | export type Predeploy = StandardPredeploy | ProxiedPredeploy; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { gas } from './gas'; 3 | import { jump } from './jump'; 4 | import { jumpdest } from './jumpdest'; 5 | import { jumpi } from './jumpi'; 6 | import { pc } from './pc'; 7 | import { stop } from './stop'; 8 | 9 | export const opcodes: Record> = { 10 | [gas.number]: gas, 11 | [jump.number]: jump, 12 | [jumpdest.number]: jumpdest, 13 | [jumpi.number]: jumpi, 14 | [pc.number]: pc, 15 | [stop.number]: stop, 16 | }; 17 | -------------------------------------------------------------------------------- /src/types/node.ts: -------------------------------------------------------------------------------- 1 | export type Node = { 2 | name: string; 3 | description: string; 4 | type: NodeType; 5 | language: Language; 6 | syncStrategy?: SyncStrategy[]; // only for execution nodes. 7 | forkOf?: string; 8 | repository: string; 9 | documentation: string; 10 | }; 11 | 12 | export enum NodeType { 13 | Execution, 14 | Consensus, 15 | } 16 | 17 | export enum Language { 18 | Java, 19 | Go, 20 | CSharp, 21 | Rust, 22 | TypeScript, 23 | Nim, 24 | } 25 | 26 | export enum SyncStrategy { 27 | Snap, 28 | Fast, 29 | Full, 30 | } 31 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/environment/caller.ts: -------------------------------------------------------------------------------- 1 | import { caller as baseOpcode } from '@/chains/optimism/vm/opcodes/environment/caller'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { references: _references, ...opcode } = baseOpcode; 5 | export const caller: Omit = { 6 | ...opcode, 7 | references: [ 8 | '[Arbitrum Differences from Solidity on Ethereum](https://developer.arbitrum.io/solidity-support)', 9 | '[L1 to L2 Messaging, Address Aliasing](https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing)', 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/environment/origin.ts: -------------------------------------------------------------------------------- 1 | import { origin as baseOpcode } from '@/chains/optimism/vm/opcodes/environment/origin'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { references: _references, ...opcode } = baseOpcode; 5 | export const origin: Omit = { 6 | ...opcode, 7 | references: [ 8 | '[Arbitrum Differences from Solidity on Ethereum](https://developer.arbitrum.io/solidity-support)', 9 | '[L1 to L2 Messaging, Address Aliasing](https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing)', 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { and } from './and'; 3 | import { byte } from './byte'; 4 | import { not } from './not'; 5 | import { or } from './or'; 6 | import { sar } from './sar'; 7 | import { shl } from './shl'; 8 | import { shr } from './shr'; 9 | import { xor } from './xor'; 10 | 11 | export const opcodes: Record = { 12 | [and.number]: and, 13 | [byte.number]: byte, 14 | [not.number]: not, 15 | [or.number]: or, 16 | [sar.number]: sar, 17 | [shl.number]: shl, 18 | [shr.number]: shr, 19 | [xor.number]: xor, 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/ui/Tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { InformationCircleIcon } from '@heroicons/react/20/solid'; 2 | 3 | export const Tooltip = ({ 4 | text, 5 | showIcon = true, 6 | width = 24, 7 | }: { 8 | text: string; 9 | showIcon?: boolean; 10 | width?: number; 11 | }) => { 12 | return ( 13 |
14 | {showIcon && } 15 | 16 | {text} 17 | 18 |
19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { blockhash } from './blockhash'; 3 | import { coinbase } from './coinbase'; 4 | import { gaslimit } from './gaslimit'; 5 | import { number } from './number'; 6 | import { prevrandao } from './prevrandao'; 7 | import { timestamp } from './timestamp'; 8 | 9 | export const opcodes: Record = { 10 | [blockhash.number]: blockhash, 11 | [coinbase.number]: coinbase, 12 | [prevrandao.number]: prevrandao, 13 | [gaslimit.number]: gaslimit, 14 | [number.number]: number, 15 | [timestamp.number]: timestamp, 16 | }; 17 | -------------------------------------------------------------------------------- /src/types/mempool.ts: -------------------------------------------------------------------------------- 1 | export type Mempool = { 2 | name: string; 3 | description: string; 4 | rpcUrl: string; 5 | references: string[]; 6 | notes?: string[]; 7 | // All properties are private, and a missing field indicates that the property is unknown. 8 | properties: { 9 | isPrivate?: boolean; 10 | tracksIpAddress?: boolean; 11 | refundsMev?: boolean; 12 | includesFailedTxs?: boolean; 13 | canSpecifyBuilders?: boolean; 14 | isFree?: boolean; 15 | configurable?: boolean; 16 | txLifespan?: number; // Seconds until kicked from mempool. 17 | rateLimit?: string; 18 | burstRateLimit?: string; 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/block/prevrandao.ts: -------------------------------------------------------------------------------- 1 | import { prevrandao as baseOpcode } from '@/chains/mainnet/vm/opcodes/block/prevrandao'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, notes: _notes, ...opcode } = baseOpcode; 5 | export const prevrandao: Omit = { 6 | ...opcode, 7 | outputs: [{ name: 'constant', description: 'The constant `1`.' }], 8 | examples: [{ output: '1' }], 9 | description: 'Returns the constant `1`.', 10 | references: [ 11 | '[Arbitrum Differences from Solidity on Ethereum](https://developer.arbitrum.io/solidity-support)', 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { Chain } from './chain'; 2 | export type { Variable, Example, Opcode } from './opcode'; 3 | export type { Precompile } from './precompile'; 4 | export type { Predeploy } from './predeploy'; 5 | export type { AccountType } from './accountType'; 6 | export type { SignatureType } from './signatureType'; 7 | export type { Mempool } from './mempool'; 8 | export type { 9 | DeployedContract, 10 | StandardDeployedContract, 11 | ProxiedDeployedContract, 12 | } from './deployedContract'; 13 | export { DeployedContractKind } from './deployedContract'; 14 | export type { Node } from './node'; 15 | export { NodeType, Language, SyncStrategy } from './node'; 16 | -------------------------------------------------------------------------------- /src/chains/arbitrum/nodes.ts: -------------------------------------------------------------------------------- 1 | import { Language, Node, NodeType } from '@/types'; 2 | 3 | const nitro: Node = { 4 | name: 'nitro', 5 | description: 6 | 'Nitro is the latest iteration of the Arbitrum technology. It is a fully integrated, complete layer 2 optimistic rollup system, including fraud proofs, the sequencer, the token bridges, advanced calldata compression, and more.', 7 | type: NodeType.Execution, 8 | language: Language.Go, 9 | repository: 'https://github.com/OffchainLabs/nitro', 10 | documentation: 'https://docs.arbitrum.io/', 11 | }; 12 | 13 | export const executionNodes: Node[] = [nitro]; 14 | export const consensusNodes: Node[] = []; // Arbitrum does not have any consensus nodes. 15 | -------------------------------------------------------------------------------- /src/types/precompile.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | type PrecompileParam = { 4 | byteStart: number | string; 5 | byteLength: number | string; 6 | name: string; 7 | description: string; 8 | }; 9 | 10 | export type PrecompileBase = { 11 | address: Address; 12 | name: string; 13 | description: string; 14 | minGas?: number; 15 | deprecated: boolean; 16 | references: string[]; 17 | notes?: string[]; 18 | }; 19 | 20 | export type PrecompileInputOutput = PrecompileBase & { 21 | input: PrecompileParam[]; 22 | output: PrecompileParam[]; 23 | }; 24 | 25 | export type PrecompileAbi = PrecompileBase & { 26 | logicAbi: string[]; 27 | }; 28 | 29 | export type Precompile = PrecompileInputOutput | PrecompileAbi; 30 | -------------------------------------------------------------------------------- /src/components/layout/ThemeSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { MoonIcon, SunIcon } from '@heroicons/react/20/solid'; 3 | import { useTheme } from 'next-themes'; 4 | 5 | export const ThemeSwitcher = ({ className = '' }: { className?: string }) => { 6 | const { resolvedTheme, setTheme } = useTheme(); 7 | 8 | const toggleTheme = useCallback(() => { 9 | setTheme(resolvedTheme === 'light' ? 'dark' : 'light'); 10 | }, [resolvedTheme, setTheme]); 11 | 12 | return ( 13 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/opcodes/index.ts: -------------------------------------------------------------------------------- 1 | import { opcodes as mainnetOpcodes } from '@/chains/mainnet/vm/opcodes'; 2 | import { Opcode } from '@/types'; 3 | import { coinbase } from './block/coinbase'; 4 | import { prevrandao } from './block/prevrandao'; 5 | import { caller } from './environment/caller'; 6 | import { origin } from './environment/origin'; 7 | import { push0 } from './stack/push0'; 8 | 9 | export const opcodes: Record> = { 10 | ...mainnetOpcodes, 11 | 12 | // Block. 13 | ...{ [coinbase.number]: coinbase }, 14 | ...{ [prevrandao.number]: prevrandao }, 15 | 16 | // Environment. 17 | ...{ [caller.number]: caller }, 18 | ...{ [origin.number]: origin }, 19 | 20 | // Stack. 21 | ...{ [push0.number]: push0 }, 22 | }; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/types/deployedContract.ts: -------------------------------------------------------------------------------- 1 | import { Address } from 'viem'; 2 | 3 | export enum DeployedContractKind { 4 | WrappedNativeAsset, 5 | Utility, 6 | } 7 | 8 | type DeployedContractBase = { 9 | name: string; 10 | description: string; 11 | kind: DeployedContractKind; 12 | tokenName?: string; 13 | tokenSymbol?: string; 14 | address: Address; 15 | deploymentInstructions?: string; 16 | references: string[]; 17 | notes?: string[]; 18 | }; 19 | 20 | export type StandardDeployedContract = DeployedContractBase & { 21 | logicAbi: string[]; 22 | }; 23 | 24 | export type ProxiedDeployedContract = DeployedContractBase & { 25 | proxyAbi: string[]; 26 | logicAbi: string[]; 27 | logicAddress: Address; 28 | }; 29 | 30 | export type DeployedContract = StandardDeployedContract | ProxiedDeployedContract; 31 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/jumpdest.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const jumpdest: Omit = { 6 | number: 0x5b, 7 | name: 'jumpdest', 8 | description: 'Mark a valid destination for jumps', 9 | minGas: 1, 10 | notes: [ 11 | 'Mark a valid destination for JUMP or JUMPI. This operation has no effect on machine state during execution.', 12 | ], 13 | references: [ 14 | evmCodesOpcodesLink(0x5b), 15 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 149), 16 | ], 17 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 18 | }; 19 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { add } from './add'; 3 | import { addmod } from './addmod'; 4 | import { div } from './div'; 5 | import { exp } from './exp'; 6 | import { mod } from './mod'; 7 | import { mul } from './mul'; 8 | import { mulmod } from './mulmod'; 9 | import { sdiv } from './sdiv'; 10 | import { signextend } from './signextend'; 11 | import { smod } from './smod'; 12 | import { sub } from './sub'; 13 | 14 | export const opcodes: Record = { 15 | [add.number]: add, 16 | [addmod.number]: addmod, 17 | [div.number]: div, 18 | [exp.number]: exp, 19 | [mod.number]: mod, 20 | [mul.number]: mul, 21 | [mulmod.number]: mulmod, 22 | [sdiv.number]: sdiv, 23 | [signextend.number]: signextend, 24 | [smod.number]: smod, 25 | [sub.number]: sub, 26 | }; 27 | -------------------------------------------------------------------------------- /src/types/chain.ts: -------------------------------------------------------------------------------- 1 | import { Chain as Metadata } from '@wagmi/chains'; 2 | import { AccountType } from './accountType'; 3 | import { DeployedContract } from './deployedContract'; 4 | import { EIP } from './eip'; 5 | import { Mempool } from './mempool'; 6 | import { Node } from './node'; 7 | import { Opcode } from './opcode'; 8 | import { Precompile } from './precompile'; 9 | import { Predeploy } from './predeploy'; 10 | import { SignatureType } from './signatureType'; 11 | 12 | export type Chain = { 13 | metadata: Metadata; 14 | precompiles: Precompile[]; 15 | predeploys: Predeploy[]; 16 | signatureTypes: SignatureType[]; 17 | accountTypes: AccountType[]; 18 | opcodes: Partial[]; 19 | mempools: Mempool[]; 20 | deployedContracts: DeployedContract[]; 21 | eips: EIP[]; 22 | executionNodes: Node[]; 23 | consensusNodes: Node[]; 24 | }; 25 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/block/number.ts: -------------------------------------------------------------------------------- 1 | import { number as baseOpcode } from '@/chains/mainnet/vm/opcodes/block/number'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, ...opcode } = baseOpcode; 5 | export const number: Omit = { 6 | ...opcode, 7 | description: 8 | 'Returns an estimate of the L1 block number at which the Sequencer received the transaction.', 9 | outputs: [ 10 | { 11 | name: 'blockNumber', 12 | description: 13 | 'Estimate of the L1 block number at which the Sequencer received the transaction.', 14 | }, 15 | ], 16 | references: [ 17 | '[Arbitrum Differences from Solidity on Ethereum](https://developer.arbitrum.io/solidity-support)', 18 | '[Arbitrum Block Numbers and Time](https://developer.arbitrum.io/time)', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/system/invalid.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const invalid: Omit = { 6 | number: 0xfe, 7 | name: 'invalid', 8 | description: 'Designated invalid instruction', 9 | notes: [ 10 | 'Equivalent to any other opcode not present in this reference, but guaranteed to remain an invalid instruction.', 11 | 'Equivalent to REVERT (since Byzantium fork) with 0,0 as stack parameters, except that all the gas given to the current context is consumed.', 12 | 'All the remaining gas in this context is consumed.', 13 | ], 14 | references: [evmCodesOpcodesLink(0xfe)], 15 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 16 | }; 17 | -------------------------------------------------------------------------------- /src/components/ui/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | export const LoadingSpinner = () => { 2 | return ( 3 |
4 | 11 | 19 | 24 | 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/number.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const number: Opcode = { 6 | number: 0x43, 7 | name: 'number', 8 | description: "Get the block's number", 9 | minGas: 2, 10 | outputs: [ 11 | { 12 | name: 'blockNumber', 13 | description: 'The current block number', 14 | }, 15 | ], 16 | examples: [ 17 | { 18 | output: '1636704767', 19 | }, 20 | ], 21 | errorCases: ['Not enough gas', 'Stack overflow'], 22 | references: [ 23 | evmCodesOpcodesLink(0x43), 24 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 126), 25 | ], 26 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 27 | }; 28 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/gaslimit.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const gaslimit: Opcode = { 6 | number: 0x45, 7 | name: 'gaslimit', 8 | description: "Get the block's gas limit", 9 | minGas: 2, 10 | outputs: [ 11 | { 12 | name: 'gasLimit', 13 | description: 'The current block gas limit', 14 | }, 15 | ], 16 | examples: [ 17 | { 18 | output: '0xffffffffffff', 19 | }, 20 | ], 21 | errorCases: ['Not enough gas', 'Stack overflow'], 22 | references: [ 23 | evmCodesOpcodesLink(0x45), 24 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 190), 25 | ], 26 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 27 | }; 28 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/opcodes/block/coinbase.ts: -------------------------------------------------------------------------------- 1 | import { coinbase as baseOpcode } from '@/chains/mainnet/vm/opcodes/block/coinbase'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, ...opcode } = baseOpcode; 5 | export const coinbase: Omit = { 6 | ...opcode, 7 | description: 8 | 'Returns `0x4200000000000000000000000000000000000011`, the address of the Sequencer Fee Vault.', 9 | outputs: [ 10 | { 11 | name: 'address', 12 | description: 'The address of the Sequencer Fee Vault.', 13 | }, 14 | ], 15 | examples: [ 16 | { 17 | output: '0x4200000000000000000000000000000000000011', 18 | }, 19 | ], 20 | references: [ 21 | '[Differences between Ethereum and OP Mainnet: Opcode Differences](https://community.optimism.io/docs/developers/build/differences/#opcode-differences)', 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/timestamp.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const timestamp: Opcode = { 6 | number: 0x42, 7 | name: 'timestamp', 8 | description: "Get the block's timestamp", 9 | minGas: 2, 10 | outputs: [ 11 | { 12 | name: 'timestamp', 13 | description: 'unix timestamp of the current block', 14 | }, 15 | ], 16 | examples: [ 17 | { 18 | output: '1636704767', 19 | }, 20 | ], 21 | errorCases: ['Not enough gas', 'Stack overflow'], 22 | references: [ 23 | evmCodesOpcodesLink(0x42), 24 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 93), 25 | ], 26 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 27 | }; 28 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/stop.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const stop: Omit = { 6 | number: 0x00, 7 | name: 'stop', 8 | description: 'Halts execution', 9 | minGas: 0, 10 | notes: [ 11 | 'Exits the current context successfully. When a call is executed on an address with no code and the EVM tries to read the code data, the default value is returned, 0, which corresponds to this instruction and halts the execution.', 12 | ], 13 | references: [ 14 | evmCodesOpcodesLink(0x00), 15 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.ControlFlow, 23), 16 | ], 17 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 18 | }; 19 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/system/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { call } from './call'; 3 | import { callcode } from './callcode'; 4 | import { create } from './create'; 5 | import { create2 } from './create2'; 6 | import { delegatecall } from './delegatecall'; 7 | import { invalid } from './invalid'; 8 | import { _return } from './return'; 9 | import { revert } from './revert'; 10 | import { selfdestruct } from './selfdestruct'; 11 | import { staticcall } from './staticcall'; 12 | 13 | export const opcodes: Record> = { 14 | [call.number]: call, 15 | [callcode.number]: callcode, 16 | [create.number]: create, 17 | [create2.number]: create2, 18 | [delegatecall.number]: delegatecall, 19 | [invalid.number]: invalid, 20 | [_return.number]: _return, 21 | [revert.number]: revert, 22 | [selfdestruct.number]: selfdestruct, 23 | [staticcall.number]: staticcall, 24 | }; 25 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/index.ts: -------------------------------------------------------------------------------- 1 | import { opcodes as mainnetOpcodes } from '@/chains/mainnet/vm/opcodes'; 2 | import { Opcode } from '@/types'; 3 | import { blockhash } from './block/blockhash'; 4 | import { coinbase } from './block/coinbase'; 5 | import { number } from './block/number'; 6 | import { prevrandao } from './block/prevrandao'; 7 | import { caller } from './environment/caller'; 8 | import { origin } from './environment/origin'; 9 | import { push0 } from './stack/push0'; 10 | 11 | export const opcodes: Record> = { 12 | ...mainnetOpcodes, 13 | 14 | // block 15 | ...{ [blockhash.number]: blockhash }, 16 | ...{ [coinbase.number]: coinbase }, 17 | ...{ [prevrandao.number]: prevrandao }, 18 | ...{ [number.number]: number }, 19 | 20 | // environment 21 | ...{ [caller.number]: caller }, 22 | ...{ [origin.number]: origin }, 23 | 24 | // stack 25 | ...{ [push0.number]: push0 }, 26 | }; 27 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/coinbase.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const coinbase: Opcode = { 6 | number: 0x41, 7 | name: 'coinbase', 8 | description: "Get the block's beneficiary address", 9 | minGas: 2, 10 | outputs: [ 11 | { 12 | name: 'address', 13 | description: "The miner's 20-byte address", 14 | }, 15 | ], 16 | examples: [ 17 | { 18 | output: '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4', 19 | }, 20 | ], 21 | errorCases: ['Not enough gas.', 'Stack overflow.'], 22 | references: [ 23 | evmCodesOpcodesLink(0x41), 24 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 60), 25 | ], 26 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 27 | }; 28 | -------------------------------------------------------------------------------- /src/types/eip.ts: -------------------------------------------------------------------------------- 1 | export type EIP = { 2 | number: number; 3 | title: string; 4 | category: EIPCategory; 5 | status: EIPState; // The status should always be `Final` for now. 6 | activeHardforks: string[]; 7 | deprecated?: boolean; 8 | // Some EIPs have parameters, such as EIP-1559, but these parameters may not be the same on all 9 | // chains. This field is intended to list the names and values of any parameters that exist. 10 | parameters?: EIPParameter[]; 11 | notes?: string[]; 12 | references: string[]; 13 | }; 14 | 15 | // EIPCategory defines if the EIP is execution or consensus related. 16 | export enum EIPCategory { 17 | Execution, 18 | Consensus, 19 | } 20 | 21 | export enum EIPState { 22 | Draft, 23 | Review, 24 | LastCall, 25 | Final, 26 | Stagnant, 27 | Withdrawn, 28 | Living, 29 | } 30 | 31 | export type EIPParameter = { 32 | name: string; 33 | value: string | number | bigint | boolean; 34 | }; 35 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | }, 6 | extends: [ 7 | 'next/core-web-vitals', 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:react/recommended', 11 | ], 12 | overrides: [ 13 | { 14 | env: { 15 | node: true, 16 | }, 17 | files: ['.eslintrc.{js,cjs}'], 18 | parserOptions: { 19 | sourceType: 'script', 20 | }, 21 | }, 22 | ], 23 | parser: '@typescript-eslint/parser', 24 | parserOptions: { 25 | ecmaVersion: 'latest', 26 | sourceType: 'module', 27 | }, 28 | plugins: ['@typescript-eslint', 'react'], 29 | rules: { 30 | 'react/react-in-jsx-scope': 'off', 31 | // Allow unused variables that start with an underscore. 32 | '@typescript-eslint/no-unused-vars': [ 33 | 'error', 34 | { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, 35 | ], 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type { AppProps } from 'next/app'; 3 | import { Open_Sans } from 'next/font/google'; 4 | import { Analytics } from '@vercel/analytics/react'; 5 | import { ThemeProvider } from 'next-themes'; 6 | import { Head } from '@/components/layout/Head'; 7 | import { Layout } from '@/components/layout/Layout'; 8 | import '@/styles/globals.css'; 9 | 10 | const font = Open_Sans({ subsets: ['latin'] }); 11 | 12 | function App({ Component, pageProps }: AppProps) { 13 | const [mounted, setMounted] = React.useState(false); 14 | React.useEffect(() => setMounted(true), []); 15 | return ( 16 | 17 | 18 | {mounted && ( 19 | 20 |
21 | 22 | 23 |
24 |
25 | )} 26 |
27 | ); 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/chainid.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const chainid: Opcode = { 11 | number: 0x46, 12 | name: 'chainid', 13 | description: 'Get the chain ID', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'chainId', 18 | description: 'The chain id of the network', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '1', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink('%27CHAINID%27_'), 27 | errorCases: ['Not enough gas', 'Stack overflow'], 28 | references: [ 29 | evmCodesOpcodesLink(0x46), 30 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 222), 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Istanbul), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/basefee.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const basefee: Opcode = { 11 | number: 0x48, 12 | name: 'basefee', 13 | description: 'Get the base fee', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'baseFee', 18 | description: 'The base fee in wei.', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '10', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink('%27BASEFEE%27_'), 27 | errorCases: ['Not enough gas', 'Stack overflow'], 28 | references: [ 29 | evmCodesOpcodesLink(0x48), 30 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 517), 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.London), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/opcodes/block/prevrandao.ts: -------------------------------------------------------------------------------- 1 | import { prevrandao as baseOpcode } from '@/chains/mainnet/vm/opcodes/block/prevrandao'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, notes: _notes, ...opcode } = baseOpcode; 5 | export const prevrandao: Omit = { 6 | ...opcode, 7 | outputs: [ 8 | { 9 | name: 'random', 10 | description: 11 | "The random output of the L1 beacon chain's oracle from approximately 5 L1 blocks ago.", 12 | }, 13 | ], 14 | description: 15 | "Returns the random output of the L1 beacon chain's randomness oracle. This value lags behind the L1 block's prevrandao value by approximately 5 L1 blocks, and is updated when the `L1BlockInfo` predeploy is updated.", 16 | references: [ 17 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/derivation.md#building-individual-payload-attributes', 18 | 'https://github.com/mds1/evm-diff/issues/21', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/chains/mainnet/accountTypes.ts: -------------------------------------------------------------------------------- 1 | import { AccountType } from '@/types'; 2 | 3 | const ethereumAccounts = '[Ethereum Accounts](https://ethereum.org/en/developers/docs/accounts/)'; 4 | 5 | export const eoa: AccountType = { 6 | name: 'EOA', 7 | description: 'An externally-owned account (EOA) is controlled by a private key.', 8 | references: [ethereumAccounts], 9 | properties: { 10 | canBatchTxs: false, 11 | canInitiateTxs: true, 12 | hasCode: false, 13 | hasKeyPair: true, 14 | hasStorage: false, 15 | }, 16 | }; 17 | 18 | export const contract: AccountType = { 19 | name: 'Contract Account', 20 | description: 'A smart contract deployed to the network, controlled by code.', 21 | references: [ethereumAccounts], 22 | properties: { 23 | canBatchTxs: true, 24 | canInitiateTxs: false, 25 | hasCode: true, 26 | hasKeyPair: false, 27 | hasStorage: true, 28 | }, 29 | }; 30 | 31 | export const accountTypes = { 32 | [eoa.name]: eoa, 33 | [contract.name]: contract, 34 | }; 35 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { ChainDiffSelector } from '@/components/ChainDiffSelector'; 2 | import { SITE_DESCRIPTION } from '@/lib/constants'; 3 | 4 | const Home = () => { 5 | return ( 6 | <> 7 |
8 |
9 |

10 | {SITE_DESCRIPTION} 11 |

12 |

13 | Compare execution layer differences between chains in a friendly format 14 |

15 |
16 | 17 |
18 |
19 |
20 | 21 | ); 22 | }; 23 | 24 | export default Home; 25 | -------------------------------------------------------------------------------- /src/chains/arbitrum/index.ts: -------------------------------------------------------------------------------- 1 | import { arbitrum as arbitrumMetadata } from '@wagmi/chains'; 2 | import { sortedArrayByField, sortedArrayByFields } from '@/lib/utils'; 3 | import { Chain } from '@/types'; 4 | import { accountTypes } from './accountTypes'; 5 | import { deployedContracts } from './deployedContracts'; 6 | import { consensusNodes, executionNodes } from './nodes'; 7 | import { signatureTypes } from './signatureTypes'; 8 | import { opcodes } from './vm/opcodes'; 9 | import { precompiles } from './vm/precompiles'; 10 | import { predeploys } from './vm/predeploys'; 11 | 12 | export const arbitrum: Chain = { 13 | metadata: arbitrumMetadata, 14 | precompiles, 15 | predeploys, 16 | signatureTypes: sortedArrayByField(signatureTypes, 'prefixByte'), 17 | accountTypes: sortedArrayByField(accountTypes, 'name'), 18 | opcodes: sortedArrayByField(opcodes, 'number'), 19 | mempools: [], 20 | deployedContracts: sortedArrayByFields(deployedContracts, ['kind', 'name']), 21 | eips: [], 22 | executionNodes, 23 | consensusNodes, 24 | }; 25 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/gasprice.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const gasprice: Opcode = { 11 | number: 0x3a, 12 | name: 'gasprice', 13 | description: 'Get price of gas in current environment', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'price', 18 | description: 'The gas price in wei per gas', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '10', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink('%27GASPRICE%27_'), 27 | errorCases: ['Not enough gas', 'Stack overflow'], 28 | references: [ 29 | evmCodesOpcodesLink(0x3a), 30 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 301), 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/stack/pop.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { CURRENT_MAINNET_HARDFORK } from '@/lib/constants'; 3 | import { 4 | OpcodeGroup, 5 | ethSpecsOpcodeSrc, 6 | evmCodesOpcodesLink, 7 | evmCodesPlaygroundLink, 8 | } from '@/lib/opcodes'; 9 | import { Opcode } from '@/types'; 10 | 11 | export const pop: Opcode = { 12 | number: 0x50, 13 | name: 'pop', 14 | description: 'Remove item from stack', 15 | minGas: 2, 16 | inputs: [ 17 | { 18 | name: 'y', 19 | description: 'A stack item', 20 | }, 21 | ], 22 | examples: [ 23 | { 24 | input: '125985', 25 | }, 26 | ], 27 | playgroundLink: evmCodesPlaygroundLink('%27PUSH3%20125985%5CnPOP%27_'), 28 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 29 | references: [ 30 | evmCodesOpcodesLink(0x50), 31 | ethSpecsOpcodeSrc(CURRENT_MAINNET_HARDFORK, OpcodeGroup.Stack, 26), 32 | ], 33 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 34 | }; 35 | -------------------------------------------------------------------------------- /src/chains/optimism/index.ts: -------------------------------------------------------------------------------- 1 | import { optimism as optimismMetadata } from '@wagmi/chains'; 2 | import { sortedArrayByField, sortedArrayByFields } from '@/lib/utils'; 3 | import { Chain } from '@/types'; 4 | import { accountTypes } from './accountTypes'; 5 | import { deployedContracts } from './deployedContracts'; 6 | import { eips } from './eips'; 7 | import { consensusNodes, executionNodes } from './nodes'; 8 | import { signatureTypes } from './signatureTypes'; 9 | import { opcodes } from './vm/opcodes'; 10 | import { precompiles } from './vm/precompiles'; 11 | import { predeploys } from './vm/predeploys'; 12 | 13 | export const optimism: Chain = { 14 | metadata: optimismMetadata, 15 | precompiles, 16 | predeploys, 17 | signatureTypes: sortedArrayByField(signatureTypes, 'prefixByte'), 18 | accountTypes: sortedArrayByField(accountTypes, 'name'), 19 | opcodes: sortedArrayByField(opcodes, 'number'), 20 | mempools: [], 21 | deployedContracts: sortedArrayByFields(deployedContracts, ['kind', 'name']), 22 | eips, 23 | executionNodes, 24 | consensusNodes, 25 | }; 26 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/address.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const address: Opcode = { 11 | number: 0x30, 12 | name: 'address', 13 | description: 'Get address of currently executing account', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'address', 18 | description: 'The 20-byte address of the current account', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '0x9bbfed6889322e016e0a02ee459d306fc19545d8', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink('%27ADDRESS%27_'), 27 | errorCases: ['Not enough gas', 'Stack overflow'], 28 | references: [ 29 | evmCodesOpcodesLink(0x30), 30 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 40), 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/calldatasize.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const calldatasize: Opcode = { 11 | number: 0x36, 12 | name: 'calldatasize', 13 | description: 'Get size of input data in current environment', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'size ', 18 | description: 'The byte size of the calldata', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '1', 24 | calldata: '0xFF', 25 | }, 26 | ], 27 | playgroundLink: evmCodesPlaygroundLink('%27CALLDATASIZE%27_&callData=0xFF'), 28 | errorCases: ['Not enough gas', 'Stack overflow'], 29 | references: [ 30 | evmCodesOpcodesLink(0x36), 31 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 189), 32 | ], 33 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 34 | }; 35 | -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | .hyperlink { 7 | @apply cursor-pointer text-green-600 no-underline hover:text-green-700 dark:text-green-500 dark:hover:text-green-400; 8 | } 9 | 10 | .text-primary { 11 | @apply text-zinc-900 dark:text-zinc-300; 12 | } 13 | 14 | .text-secondary { 15 | @apply text-zinc-500 dark:text-zinc-400; 16 | } 17 | 18 | .text-hover { 19 | @apply hover:text-zinc-500 dark:hover:text-zinc-300; 20 | } 21 | 22 | .text-accent { 23 | @apply text-green-600 dark:text-green-500; 24 | } 25 | 26 | .bg-primary { 27 | @apply bg-zinc-100 dark:bg-zinc-800; 28 | } 29 | 30 | .bg-secondary { 31 | @apply bg-zinc-50 dark:bg-zinc-900/80; 32 | } 33 | 34 | .button { 35 | @apply rounded-md bg-green-600 px-3 py-1.5 text-sm font-semibold leading-6 text-zinc-50 shadow-sm hover:bg-green-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600 dark:bg-green-500 dark:text-zinc-900 dark:hover:bg-green-400; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/callvalue.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const callvalue: Opcode = { 11 | number: 0x34, 12 | name: 'callvalue', 13 | description: 'Get deposited value by the instruction/transaction responsible for this execution', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'value', 18 | description: 'The value of the current call in wei', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '123456789', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink('%27CALLVALUE%27_&callValue=123456789'), 27 | errorCases: ['Not enough gas', 'Stack overflow'], 28 | references: [ 29 | evmCodesOpcodesLink(0x34), 30 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 140), 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/optimism/nodes.ts: -------------------------------------------------------------------------------- 1 | import { Language, Node, NodeType } from '@/types'; 2 | import { nethermind, reth } from '../mainnet/nodes/execution'; 3 | 4 | // Execution nodes. 5 | const opGeth: Node = { 6 | name: 'op-geth', 7 | description: 8 | 'Official Golang execution layer implementation of the Ethereum protocol. It implements the Execution-Layer, with minimal changes for a secure Ethereum-equivalent application environment.', 9 | type: NodeType.Execution, 10 | language: Language.Go, 11 | forkOf: 'geth', 12 | repository: 'https://github.com/ethereum-optimism/op-geth', 13 | documentation: 'https://op-geth.optimism.io/', 14 | }; 15 | 16 | const opErigon: Node = { 17 | name: 'op-erigon', 18 | description: 'Optimism implementation on the efficiency frontier', 19 | type: NodeType.Execution, 20 | language: Language.Go, 21 | forkOf: 'erigon', 22 | repository: 'https://github.com/testinprod-io/op-erigon', 23 | documentation: 'https://op-erigon.testinprod.io/', 24 | }; 25 | 26 | export const executionNodes: Node[] = [opGeth, opErigon, reth, nethermind]; 27 | export const consensusNodes: Node[] = []; 28 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/block/coinbase.ts: -------------------------------------------------------------------------------- 1 | import { coinbase as baseOpcode } from '@/chains/mainnet/vm/opcodes/block/coinbase'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, ...opcode } = baseOpcode; 5 | export const coinbase: Omit = { 6 | ...opcode, 7 | description: 8 | "Returns the designated internal address `0xA4b000000000000000000073657175656e636572` if the message was posted by a sequencer. If it's a delayed message, it returns the address of the delayed message's poster.", 9 | outputs: [ 10 | { 11 | name: 'address', 12 | description: 13 | "The L1 Batch Poster's address if the message was posted by a sequencer, or the address of the delayed message's poster if it's a delayed message.", 14 | }, 15 | ], 16 | examples: [ 17 | { 18 | output: '0xA4b000000000000000000073657175656e636572', 19 | }, 20 | ], 21 | references: [ 22 | '[Arbitrum Differences from Solidity on Ethereum](https://docs.arbitrum.io/for-devs/concepts/differences-between-arbitrum-ethereum/solidity-support)', 23 | ], 24 | }; 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matt Solomon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/origin.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const origin: Opcode = { 11 | number: 0x32, 12 | name: 'origin', 13 | description: 'Get execution origination address', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'address', 18 | description: 19 | 'The 20-byte address of the sender of the transaction. It can only be an account without code.', 20 | }, 21 | ], 22 | examples: [ 23 | { 24 | output: '0xbe862ad9abfe6f22bcb087716c7d89a26051f74c', 25 | }, 26 | ], 27 | playgroundLink: evmCodesPlaygroundLink('%27ORIGIN%27_'), 28 | errorCases: ['Not enough gas', 'Stack overflow'], 29 | references: [ 30 | evmCodesOpcodesLink(0x32), 31 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 93), 32 | ], 33 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 34 | }; 35 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/caller.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const caller: Opcode = { 11 | number: 0x33, 12 | name: 'caller', 13 | description: 'Get caller address', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'address', 18 | description: 19 | "The 20-byte address of the caller's account. This is the account that did the last call (except delegate call).", 20 | }, 21 | ], 22 | examples: [ 23 | { 24 | output: '0xbe862ad9abfe6f22bcb087716c7d89a26051f74c', 25 | }, 26 | ], 27 | playgroundLink: evmCodesPlaygroundLink('%27CALLER%27_'), 28 | errorCases: ['Not enough gas', 'Stack overflow'], 29 | references: [ 30 | evmCodesOpcodesLink(0x33), 31 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 117), 32 | ], 33 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 34 | }; 35 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { opcodes as arithmeticOpcodes } from './arithmetic'; 3 | import { opcodes as bitwiseOpcodes } from './bitwise'; 4 | import { opcodes as blockOpcodes } from './block'; 5 | import { opcodes as comparisonOpcodes } from './comparison'; 6 | import { opcodes as controlFlowOpcodes } from './controlFlow'; 7 | import { opcodes as environmentOpcodes } from './environment'; 8 | import { opcodes as keccakOpcodes } from './keccak'; 9 | import { opcodes as logOpcodes } from './log'; 10 | import { opcodes as memoryOpcodes } from './memory'; 11 | import { opcodes as stackOpcodes } from './stack'; 12 | import { opcodes as storageOpcodes } from './storage'; 13 | import { opcodes as systemOpcodes } from './system'; 14 | 15 | export const opcodes: Record> = { 16 | ...arithmeticOpcodes, 17 | ...bitwiseOpcodes, 18 | ...blockOpcodes, 19 | ...comparisonOpcodes, 20 | ...controlFlowOpcodes, 21 | ...environmentOpcodes, 22 | ...keccakOpcodes, 23 | ...logOpcodes, 24 | ...memoryOpcodes, 25 | ...stackOpcodes, 26 | ...storageOpcodes, 27 | ...systemOpcodes, 28 | }; 29 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/opcodes/environment/origin.ts: -------------------------------------------------------------------------------- 1 | import { origin as baseOpcode } from '@/chains/mainnet/vm/opcodes/environment/origin'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, ...opcode } = baseOpcode; 5 | export const origin: Omit = { 6 | ...opcode, 7 | description: 8 | 'If the transaction is an L1 ⇒ L2 transaction, then `tx.origin` is set to the aliased address of the address that triggered the L1 ⇒ L2 transaction. Otherwise, this opcode behaves normally.', 9 | outputs: [ 10 | { 11 | name: 'address', 12 | description: 13 | 'The 20-byte address of the sender of the transaction, or the aliased address for L1 ⇒ L2 transactions. It can only be an account without code.', 14 | }, 15 | ], 16 | references: [ 17 | '[Differences between Ethereum and OP Mainnet: Opcode Differences](https://community.optimism.io/docs/developers/build/differences/#opcode-differences)', 18 | '[Differences between Ethereum and OP Mainnet: Address Aliasing](https://community.optimism.io/docs/developers/build/differences/#address-aliasing)', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/chains/mainnet/index.ts: -------------------------------------------------------------------------------- 1 | import { mainnet as mainnetMetadata } from '@wagmi/chains'; 2 | import { sortedArrayByField, sortedArrayByFields } from '@/lib/utils'; 3 | import { Chain } from '@/types'; 4 | import { accountTypes } from './accountTypes'; 5 | import { deployedContracts } from './deployedContracts'; 6 | import { eips } from './eips'; 7 | import { mempools } from './mempools'; 8 | import { consensusNodes } from './nodes/consensus'; 9 | import { executionNodes } from './nodes/execution'; 10 | import { signatureTypes } from './signatureTypes'; 11 | import { opcodes } from './vm/opcodes'; 12 | import { precompiles } from './vm/precompiles'; 13 | import { predeploys } from './vm/predeploys'; 14 | 15 | export const mainnet: Chain = { 16 | metadata: mainnetMetadata, 17 | precompiles, 18 | predeploys, 19 | signatureTypes: sortedArrayByField(signatureTypes, 'prefixByte'), 20 | accountTypes: sortedArrayByField(accountTypes, 'name'), 21 | opcodes: sortedArrayByField(opcodes, 'number'), 22 | mempools: sortedArrayByField(mempools, 'name'), 23 | deployedContracts: sortedArrayByFields(deployedContracts, ['kind', 'name']), 24 | eips, 25 | executionNodes, 26 | consensusNodes, 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/constants.ts: -------------------------------------------------------------------------------- 1 | // -------- Website data -------- 2 | export const SITE_NAME = 'EVM Diff'; 3 | export const SITE_DESCRIPTION = 'Diff EVM-compatible chains'; 4 | export const SITE_URL = 5 | process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://www.evmdiff.com'; 6 | export const OG_ENDPOINT = '/api/og'; // For reference, see `src/pages/og.tsx`. 7 | export const COMPANY_NAME = 'Matt Solomon'; 8 | export const COMPANY_URL = 'https://twitter.com/msolomon44'; 9 | export const GITHUB_URL = 'https://github.com/mds1/evm-diff'; 10 | export const TWITTER_URL = 'https://twitter.com/msolomon44'; 11 | 12 | // -------- Data and References -------- 13 | export { CURRENT_MAINNET_HARDFORK } from '@/chains/mainnet/hardforks'; 14 | 15 | // All opcodes are linked to their implementation in https://github.com/ethereum/execution-specs. 16 | // To avoid broken links, our links are referenced to a commit hash (there's no releases or tags). 17 | export const ETHEREUM_EXECUTION_SPECS_URL = 'https://github.com/ethereum/execution-specs'; 18 | export const ETHEREUM_EXECUTION_SPECS_COMMIT_ID = '87f5e4f5ec03c6a23b2d2cd909482664f870fd1e'; 19 | export const EVM_OPCODES_URL = 'https://www.evm.codes'; 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: pnpm/action-setup@v2 17 | with: 18 | version: latest 19 | 20 | - name: Use Node.js 18.x 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 18.x 24 | cache: 'pnpm' 25 | 26 | - name: Install dependencies 27 | run: pnpm install 28 | 29 | # This runs `next lint` before building. 30 | - name: Build 31 | run: pnpm build 32 | 33 | fmt: 34 | runs-on: ubuntu-latest 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | - uses: pnpm/action-setup@v2 39 | with: 40 | version: latest 41 | 42 | - name: Use Node.js 18.x 43 | uses: actions/setup-node@v4 44 | with: 45 | node-version: 18.x 46 | cache: 'pnpm' 47 | 48 | - name: Install dependencies 49 | run: pnpm install 50 | 51 | - name: Check formatting 52 | run: pnpm fmt:check 53 | -------------------------------------------------------------------------------- /src/chains/arbitrum/vm/opcodes/block/blockhash.ts: -------------------------------------------------------------------------------- 1 | import { blockhash as baseOpcode } from '@/chains/mainnet/vm/opcodes/block/blockhash'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, ...opcode } = baseOpcode; 5 | export const blockhash: Omit = { 6 | ...opcode, 7 | description: 8 | 'Returns a cryptographically insecure, pseudo-random hash for `x` within the range `block.number - 256 <= x < block.number`. If `x` is outside of this range, `blockhash(x)` will return 0. This includes `blockhash(block.number)`, which always returns 0 just like on Ethereum. The hashes returned do not come from L1.', 9 | outputs: [ 10 | { 11 | name: 'hash', 12 | description: 13 | 'The pseudo-random hash for the input block number, or 0 if the block number is not in the valid range', 14 | }, 15 | ], 16 | examples: [ 17 | { 18 | input: '17813636', 19 | output: '0xfe4f20b10608dbb75f84782733dd434832c50192993f7389386dfa40f6feda4b', 20 | }, 21 | ], 22 | references: [ 23 | '[Arbitrum Differences from Solidity on Ethereum](https://developer.arbitrum.io/solidity-support)', 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/selfbalance.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const selfbalance: Opcode = { 11 | number: 0x47, 12 | name: 'selfbalance', 13 | description: 'Get balance of currently executing account', 14 | minGas: 5, 15 | outputs: [ 16 | { 17 | name: 'balance', 18 | description: 'The balance of the current account in wei.', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '9', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink('%27SELFBALANCE%27_'), 27 | errorCases: ['Not enough gas', 'Stack overflow'], 28 | notes: [ 29 | 'Semantically equivalent of calling BALANCE with ADDRESS as parameter, but with a reduced gas cost.', 30 | ], 31 | references: [ 32 | evmCodesOpcodesLink(0x47), 33 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 491), 34 | ], 35 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Istanbul), 36 | }; 37 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/not.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const not: Opcode = { 11 | number: 0x19, 12 | name: 'not', 13 | description: 'Bitwise NOT operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The binary value', 19 | }, 20 | ], 21 | outputs: [ 22 | { 23 | name: '~a', 24 | description: 'The bitwise NOT result', 25 | }, 26 | ], 27 | examples: [ 28 | { 29 | input: '0', 30 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 31 | }, 32 | ], 33 | playgroundLink: evmCodesPlaygroundLink('%27PUSH1%200%5CnNOT%27_'), 34 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 35 | references: [ 36 | evmCodesOpcodesLink(0x19), 37 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 97), 38 | ], 39 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 40 | }; 41 | -------------------------------------------------------------------------------- /src/chains/optimism/signatureTypes.ts: -------------------------------------------------------------------------------- 1 | import { signatureTypes as mainnetSignatureTypes } from '@/chains/mainnet/signatureTypes'; 2 | import { SignatureType } from '@/types'; 3 | 4 | const depositTx: SignatureType = { 5 | prefixByte: 0x7e, 6 | description: 'An L2 transaction that was derived from L1 and included in a L2 block', 7 | signedData: [ 8 | '`keccak256(0x7E || rlp([sourceHash, from, to, mint, value, gas, isSystemTx, data]))`', 9 | ], 10 | signs: 'transaction', 11 | references: [ 12 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/deposits.md#the-deposited-transaction-type', 13 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/glossary.md#deposited-transaction', 14 | ], 15 | notes: [ 16 | `There are two kinds of deposited transactions: 17 | - [L1 attributes deposited transaction][l1-attr-deposit], which submits the L1 block's attributes to the [L1 Attributes Predeployed Contract][l1-attr-predeploy]. 18 | - [User-deposited transactions][user-deposited], which are transactions derived from an L1 call to the [deposit contract][deposit-contract].`, 19 | ], 20 | }; 21 | 22 | export const signatureTypes = { 23 | ...mainnetSignatureTypes, 24 | ...{ [depositTx.prefixByte]: depositTx }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/gas.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const gas: Omit = { 11 | number: 0x5a, 12 | name: 'gas', 13 | description: 14 | 'Get the amount of available gas, including the corresponding reduction for the cost of this instruction', 15 | minGas: 2, 16 | outputs: [ 17 | { 18 | name: 'gas', 19 | description: 'The remaining gas (after this instruction).', 20 | }, 21 | ], 22 | playgroundLink: evmCodesPlaygroundLink( 23 | '%27GASyPUSH3%2021000zCosqof~transactionyGASLIMITzGaskhaqwjgivenko~context__zResulqis~amounqof%20gjused%20upko%20andbncluding~GASbnstruction%27~khe%20z%20%2F%2F%20y%5Cnqt%20k%20tjas%20b%20i_ySUB%01_bjkqyz~_' 24 | ), 25 | errorCases: ['Not enough gas', 'Stack overflow'], 26 | references: [ 27 | evmCodesOpcodesLink(0x5a), 28 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 125), 29 | ], 30 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 31 | }; 32 | -------------------------------------------------------------------------------- /src/types/opcode.ts: -------------------------------------------------------------------------------- 1 | export type Variable = { 2 | name: string; 3 | description: string; 4 | expression?: string; 5 | variables?: Variable[]; 6 | }; 7 | 8 | type Memory = { 9 | before: string; 10 | after: string; 11 | }; 12 | 13 | export type Storage = { 14 | before: Record; 15 | after: Record; 16 | }; 17 | 18 | export type Example = { 19 | description?: string; 20 | input?: string | string[]; 21 | output?: string | string[]; 22 | memory?: Memory; 23 | storage?: Storage; 24 | calldata?: string; 25 | code?: string; 26 | returndata?: string; 27 | }; 28 | 29 | type ComputationCost = Partial & Required>; 30 | 31 | export type GasComputation = { 32 | staticGasCost: ComputationCost; 33 | dynamicGasCost: ComputationCost; 34 | refunds?: string; 35 | }; 36 | 37 | export type Opcode = { 38 | number: number; 39 | name: string; 40 | description: string; 41 | minGas: number; 42 | gasComputation?: GasComputation; 43 | inputs?: Variable[]; 44 | outputs?: Variable[]; 45 | examples: Example[]; 46 | playgroundLink?: string; 47 | errorCases: string[]; 48 | notes?: string[]; 49 | references: string[]; 50 | supportedHardforks: string[]; 51 | }; 52 | -------------------------------------------------------------------------------- /src/components/ui/Toggle.tsx: -------------------------------------------------------------------------------- 1 | import { Switch } from '@headlessui/react'; 2 | import { classNames } from '@/lib/utils'; 3 | 4 | type Props = { 5 | enabled: boolean; 6 | setEnabled: (enabled: boolean) => void; 7 | label: string; 8 | }; 9 | 10 | export const Toggle = ({ enabled, setEnabled, label }: Props) => { 11 | return ( 12 | 13 | 21 | 29 | 30 | {label} 31 | 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/chains/optimism/vm/opcodes/environment/caller.ts: -------------------------------------------------------------------------------- 1 | import { caller as baseOpcode } from '@/chains/mainnet/vm/opcodes/environment/caller'; 2 | import { Opcode } from '@/types'; 3 | 4 | const { supportedHardforks: _supportedHardforks, ...opcode } = baseOpcode; 5 | export const caller: Omit = { 6 | ...opcode, 7 | description: 8 | 'If the transaction is an L1 ⇒ L2 transaction, and this is the initial call (rather than an internal transaction from one contract to another), then `msg.sender` is set to the aliased address of the address that triggered the L1 ⇒ L2 transaction. Otherwise, this opcode behaves normally.', 9 | outputs: [ 10 | { 11 | name: 'address', 12 | description: 13 | "The 20-byte address of the caller's account, or the aliased address for L1 ⇒ L2 transactions. This is the account that did the last call (except delegate call).", 14 | }, 15 | ], 16 | references: [ 17 | '[Opcode Differences between Ethereum and OP Mainnet]([Differences between Ethereum and OP Mainnet: Opcode Differences](https://community.optimism.io/docs/developers/build/differences/#opcode-differences))', 18 | '[Differences between Ethereum and OP Mainnet: Address Aliasing](https://community.optimism.io/docs/developers/build/differences/#address-aliasing)', 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/codesize.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const codesize: Opcode = { 11 | number: 0x38, 12 | name: 'codesize', 13 | description: 'Get size of code running in current environment', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'size', 18 | description: 'The byte size of the code', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '32', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink( 27 | '%27%2F%2F%20Add%20som~instructions%20to%20increas~th~cod~sizeyPUSH29%200yPOPyyCODESIZE%27~e%20y%5Cn%01y~_' 28 | ), 29 | errorCases: ['Not enough gas', 'Stack overflow'], 30 | notes: [ 31 | 'Each instruction occupies one byte. In the case of a PUSH instruction, the bytes that need to be pushed are encoded after that, it thus increases the codesize accordingly.', 32 | ], 33 | references: [ 34 | evmCodesOpcodesLink(0x38), 35 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 247), 36 | ], 37 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/layout/Header.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | import { useTheme } from 'next-themes'; 4 | import logoDark from 'public/logo-dark.png'; 5 | import logoLight from 'public/logo-light.png'; 6 | import { COMPANY_NAME } from '@/lib/constants'; 7 | import { ExternalLink } from './ExternalLink'; 8 | 9 | export const Header = () => { 10 | const { resolvedTheme } = useTheme(); 11 | const logo = resolvedTheme === 'light' ? logoDark.src : logoLight.src; 12 | return ( 13 |
14 |
15 | This site is under active development. Check out{' '} 16 | {' '} 21 | to help contribute. 22 |
23 |
24 |
25 |
26 | 27 | {COMPANY_NAME} 28 | logo 29 | 30 |
31 |
32 |
33 |
34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/blockhash.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const blockhash: Opcode = { 6 | number: 0x40, 7 | name: 'blockhash', 8 | description: 'Get the hash of one of the 256 most recent complete blocks.', 9 | minGas: 20, 10 | inputs: [ 11 | { 12 | name: 'blockNumber', 13 | description: 14 | 'The block number to get the hash from. Valid range is the last 256 blocks (not including the current one). Current block number can be queried with NUMBER.', 15 | }, 16 | ], 17 | outputs: [ 18 | { 19 | name: 'hash', 20 | description: 21 | 'The hash of the chosen block, or 0 if the block number is not in the valid range', 22 | }, 23 | ], 24 | examples: [ 25 | { 26 | input: '17813636', 27 | output: '0x3204feac1c276343f84e44df04f8fcddbb80eee246ee0533026511e4c4bbf4b6', 28 | }, 29 | ], 30 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 31 | references: [ 32 | evmCodesOpcodesLink(0x40), 33 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 22), 34 | ], 35 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 36 | }; 37 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/block/prevrandao.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { OpcodeGroup, ethSpecsOpcodeSrc, evmCodesOpcodesLink } from '@/lib/opcodes'; 3 | import { Opcode } from '@/types'; 4 | 5 | export const prevrandao: Opcode = { 6 | number: 0x44, 7 | name: 'prevrandao', 8 | description: 9 | "Get the random output of the beacon chain's randomness oracle for the previous block.", 10 | minGas: 2, 11 | outputs: [ 12 | { 13 | name: 'random', 14 | description: "The random output of the beacon chain's oracle", 15 | }, 16 | ], 17 | examples: [ 18 | { 19 | output: '0xce124dee50136f3f93f19667fb4198c6b94eecbacfa300469e5280012757be94', 20 | }, 21 | ], 22 | notes: [ 23 | "Prior to the Paris hardfork, this opcode was named `difficulty` and returned the block's current difficulty. From Paris onwards, the `difficulty` opcode became `prevrandao`.", 24 | ], 25 | // TODO: add the evm.codes playground link once available 26 | errorCases: ['Not enough gas.', 'Stack overflow.'], 27 | references: [ 28 | evmCodesOpcodesLink(0x44), 29 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Block, 158), 30 | 'https://eips.ethereum.org/EIPS/eip-4399', 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/iszero.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const iszero: Opcode = { 11 | number: 0x15, 12 | name: 'iszero', 13 | description: 'Simple NOT (or non-zero )operator', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'An integer value', 19 | }, 20 | ], 21 | outputs: [ 22 | { 23 | name: 'a == 0', 24 | description: 25 | 'The result of the zero equality comparison: 1 if the a is equal to O and 0 otherwise', 26 | }, 27 | ], 28 | examples: [ 29 | { 30 | input: '10', 31 | output: '0', 32 | }, 33 | { 34 | input: '0', 35 | output: '1', 36 | }, 37 | ], 38 | playgroundLink: evmCodesPlaygroundLink( 39 | '%27~1y1wzz~2yw%27~%2F%2F%20Example%20z%5CnyzPUSH1%20w0zISZERO%01wyz~_' 40 | ), 41 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 42 | references: [ 43 | evmCodesOpcodesLink(0x15), 44 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Comparison, 155), 45 | ], 46 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 47 | }; 48 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/and.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const and: Opcode = { 11 | number: 0x16, 12 | name: 'and', 13 | description: 'Bitwise AND operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first binary value', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second binary value', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a & b', 28 | description: 'The bitwise AND result', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['0xF', '0xF'], 34 | output: '0xF', 35 | }, 36 | { 37 | input: ['0xFF', '0'], 38 | output: '0', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27z1~xF~xwyyz2~~xFw%27~yPUSH1%200z%2F%2F%20Example%20y%5CnwFyAND%01wyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x16), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 22), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/or.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const or: Opcode = { 11 | number: 0x17, 12 | name: 'or', 13 | description: 'Bitwise OR operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first binary value', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second binary value', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a | b', 28 | description: 'The bitwise OR result', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['0xF0', '0xFF'], 34 | output: '0xF', 35 | }, 36 | { 37 | input: ['0xFF', '0xFF'], 38 | output: '0xFF', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27z1~~0yORyyz2~F~FyOR%27~yPUSH1%200xFz%2F%2F%20Example%20y%5Cn%01yz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x17), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 47), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/xor.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const xor: Opcode = { 11 | number: 0x18, 12 | name: 'xor', 13 | description: 'Bitwise XOR operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first binary value', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second binary value', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a ^ b', 28 | description: 'The bitwise XOR result', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['0xF0', '0xFF'], 34 | output: '0xF', 35 | }, 36 | { 37 | input: ['0xFF', '0xFF'], 38 | output: '0', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27z1~~0wyyz2~F~Fw%27~yPUSH1%200xFz%2F%2F%20Example%20y%5CnwyXOR%01wyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x18), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 72), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evm-diff", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint --fix", 10 | "fmt": "prettier --write .", 11 | "fmt:check": "prettier --check ." 12 | }, 13 | "dependencies": { 14 | "@headlessui/react": "^1.7.16", 15 | "@heroicons/react": "^2.0.17", 16 | "@vercel/analytics": "^1.0.1", 17 | "@vercel/og": "^0.5.10", 18 | "@wagmi/chains": "^0.3.1", 19 | "next": "^13.4.12", 20 | "next-themes": "^0.2.1", 21 | "react": "18.2.0", 22 | "react-dom": "18.2.0", 23 | "react-markdown": "^8.0.7", 24 | "viem": "^1.5.0" 25 | }, 26 | "devDependencies": { 27 | "@tailwindcss/forms": "^0.5.3", 28 | "@trivago/prettier-plugin-sort-imports": "^4.1.1", 29 | "@types/node": "18.16.3", 30 | "@types/react": "18.2.5", 31 | "@types/react-dom": "18.2.3", 32 | "@typescript-eslint/eslint-plugin": "^6.1.0", 33 | "@typescript-eslint/parser": "^6.1.0", 34 | "abitype": "^0.9.6", 35 | "autoprefixer": "10.4.14", 36 | "clipboardy": "^3.0.0", 37 | "eslint": "8.39.0", 38 | "eslint-config-next": "13.4.12", 39 | "eslint-plugin-react": "^7.32.2", 40 | "postcss": "8.4.23", 41 | "prettier": "^2.8.8", 42 | "prettier-plugin-tailwindcss": "^0.2.8", 43 | "tailwindcss": "3.3.2", 44 | "typescript": "5.0.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/memory/msize.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const msize: Omit = { 11 | number: 0x59, 12 | name: 'msize', 13 | description: 'Get the size of active memory in bytes', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'size', 18 | description: 'The current memory size in bytes (higher offset accessed until now + 1)', 19 | }, 20 | ], 21 | playgroundLink: evmCodesPlaygroundLink( 22 | '%27jInitiallygkufirst~1mkx39upart%20of%20third~3ms%27~mqPOPqjNow%20size%20is%20v%20%2F%2F%20uqMLOADvRead%20q%5Cnm%20wordkqPUSH1gjMSIZEvg%200%01gjkmquv~_' 23 | ), 24 | errorCases: ['Not enough gas', 'Stack overflow'], 25 | notes: [ 26 | 'The memory is always fully accessible. What this instruction tracks is the highest offset that was accessed in the current execution. A first write or read to a bigger offset will trigger a memory expansion, which will cost gas. The size is always a multiple of a word (32 bytes).', 27 | ], 28 | references: [ 29 | evmCodesOpcodesLink(0x59), 30 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 120), 31 | ], 32 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/gt.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const gt: Opcode = { 11 | number: 0x11, 12 | name: 'gt', 13 | description: 'Greater-than comparison', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The left side integer', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The right side integer', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a > b', 28 | description: 29 | 'The result of the greater-than comparison: 1 if the left side is bigger and 0 otherwise', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '9'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['10', '10'], 39 | output: '0', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27z1~9wyyz2~10w%27~yPUSH1%20z%2F%2F%20Example%20y%5Cnw~10yGT%01wyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x11), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Comparison, 75), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/lt.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const lt: Opcode = { 11 | number: 0x10, 12 | name: 'lt', 13 | description: 'Less-than comparison', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The left side integer', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The right side integer', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a < b', 28 | description: 29 | 'The result of the less-than comparison: 1 if the left side is smaller and 0 otherwise', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['9', '10'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['10', '10'], 39 | output: '0', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27z1w~9yLTyyz2wwyLT%27~yPUSH1%20z%2F%2F%20Example%20y%5Cnw~10%01wyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x10), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Comparison, 22), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/div.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const div: Opcode = { 11 | number: 0x04, 12 | name: 'div', 13 | description: 'Multiplication operation', 14 | minGas: 5, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The integer numerator', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The integer denominator', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a // b', 28 | description: 29 | 'The integer result of the integer division. If the denominator is 0, the result will be 0.', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '10'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['1', '2'], 39 | output: '0', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27z1~10~10wyyz2~2~1w%27~yPUSH1%20z%2F%2F%20Example%20y%5CnwyDIV%01wyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x04), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 111), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/mod.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const mod: Opcode = { 11 | number: 0x06, 12 | name: 'mod', 13 | description: 'Modulo remainder operation', 14 | minGas: 5, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The integer numerator', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The integer denominator', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a % b', 28 | description: 29 | 'The integer result of the integer modulo. If the denominator is 0, the result will be 0.', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '3'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['17', '5'], 39 | output: '2', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27z1~3~10wyyz2~5~17w%27~yPUSH1%20z%2F%2F%20Example%20y%5CnwyMOD%01wyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x06), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 174), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/eq.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const eq: Opcode = { 11 | number: 0x14, 12 | name: 'eq', 13 | description: 'Equality comparison', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The left side integer', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The right side integer', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a == b', 28 | description: 29 | 'The result of the equality comparison: 1 if the left side is equal to the right side and 0 otherwise', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '10'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['10', '5'], 39 | output: '0', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27z1~10wyyz2~5w%27~yPUSH1%20z%2F%2F%20Example%20y%5Cnw~10yEQ%01wyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x14), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Comparison, 128), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/lib/opcodes.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork } from '@/chains/mainnet/hardforks'; 2 | import { 3 | ETHEREUM_EXECUTION_SPECS_COMMIT_ID, 4 | ETHEREUM_EXECUTION_SPECS_URL, 5 | EVM_OPCODES_URL, 6 | } from './constants'; 7 | 8 | export enum OpcodeGroup { 9 | Arithmetic = 'arithmetic', 10 | Bitwise = 'bitwise', 11 | Block = 'block', 12 | Comparison = 'comparison', 13 | ControlFlow = 'control_flow', 14 | Environment = 'environment', 15 | Keccak = 'keccak', 16 | Log = 'log', 17 | Memory = 'memory', 18 | Stack = 'stack', 19 | Storage = 'storage', 20 | System = 'system', 21 | } 22 | 23 | // Returns a hex string (without the '0x' prefix) padded to 2 characters. 24 | const formatOpcodeNumber = (n: number): string => n.toString(16).padStart(2, '0'); 25 | 26 | // Returns a link to the Ethereum execution specs for the given hardfork, opcode, and line number. 27 | export const ethSpecsOpcodeSrc = ( 28 | hardfork: MainnetHardfork, 29 | group: OpcodeGroup, 30 | line: number 31 | ): string => 32 | `${ETHEREUM_EXECUTION_SPECS_URL}/blob/${ETHEREUM_EXECUTION_SPECS_COMMIT_ID}/src/ethereum/${MainnetHardfork[ 33 | hardfork 34 | ].toLowerCase()}/vm/instructions/${group}.py#L${line}`; 35 | 36 | export const evmCodesOpcodesLink = (opcodeNumber: number): string => 37 | `${EVM_OPCODES_URL}/#${formatOpcodeNumber(opcodeNumber)}`; 38 | 39 | export const evmCodesPlaygroundLink = (codeParam: string): string => 40 | `${EVM_OPCODES_URL}/playground?unit=Wei&codeType=Mnemonic&code=${codeParam}`; 41 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/sub.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const sub: Opcode = { 11 | number: 0x03, 12 | name: 'sub', 13 | description: 'Subtraction operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first integer value', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second integer value to subtract to the first', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a - b', 28 | description: 'The integer result of the subtraction modulo 2**256', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['10', '10'], 34 | output: '0', 35 | }, 36 | { 37 | input: ['0', '1'], 38 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27z1~10~1wyyz2~1~w%27~yPUSH1%20z%2F%2F%20Example%20y%5Cnw0ySUB%01wyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x03), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 57), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/add.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const add: Opcode = { 11 | number: 0x01, 12 | name: 'add', 13 | description: 'Addition operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first integer value to add', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second integer value to add', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a + b', 28 | description: 'The integer result of the addition modulo 2**256', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['10', '20'], 34 | output: '30', 35 | }, 36 | { 37 | input: ['0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', '1'], 38 | output: '0', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27y1z0z0twwy2v32%200xsssszt%27~uuuuzv1%201y%2F%2F%20Example%20w%5CnvwPUSHuFFtwADDs~~%01stuvwyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x01), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 30), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/pc.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const pc: Omit = { 11 | number: 0x58, 12 | name: 'pc', 13 | description: 14 | 'Get the value of the program counter prior to the increment corresponding to this instruction', 15 | minGas: 2, 16 | outputs: [ 17 | { 18 | name: 'counter', 19 | description: 'The PC of this instruction in the current program', 20 | }, 21 | ], 22 | playgroundLink: evmCodesPlaygroundLink( 23 | '%27~0x~1xJUMPDESTzesq2x~3xPUSH1%201%20l4x~6%20%7Bpreviouminstructionmtakem2%20bytes%7D%27~PCwwwlz%20%2F%2F%20Offx%5Cnw%20%20qt%20ms%20lzseq%01lmqwxz~_' 24 | ), 25 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 26 | notes: [ 27 | 'The program counter (PC) is a byte offset in the deployed code. It indicates which instruction will be executed next. When an ADD is executed, for example, the PC is incremented by 1, since the instruction is 1 byte. The PUSH instructions are bigger than one byte, and so will increment the counter accordingly.', 28 | ], 29 | references: [ 30 | evmCodesOpcodesLink(0x58), 31 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 101), 32 | ], 33 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 34 | }; 35 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/byte.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const byte: Opcode = { 11 | number: 0x1a, 12 | name: 'byte', 13 | description: 'Retrieve single byte from word', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'i', 18 | description: 'The byte offset starting from the most significant byte', 19 | }, 20 | { 21 | name: 'x', 22 | description: 'The 32-byte value', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'y', 28 | description: 29 | 'The indicated byte at the least significant position. If the byte offset is out of range, the result is 0.', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['31', '0xFF'], 35 | output: '0xFF', 36 | }, 37 | { 38 | input: ['30', '0xFF00'], 39 | output: '0xFF00', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27z1~0xFF~31vyyz2w2%200xFF00~30v%27~w1%20z%2F%2F%20Example%20y%5CnwyPUSHvyBYTE%01vwyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x1a), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 121), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/signextend.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const signextend: Opcode = { 11 | number: 0x0b, 12 | name: 'signextend', 13 | description: "Extend length of two's complement signed integer", 14 | minGas: 5, 15 | inputs: [ 16 | { 17 | name: 'b', 18 | description: 'The size in byte minus 1 of the integer to sign extend', 19 | }, 20 | { 21 | name: 'x', 22 | description: 'The integer value to sign extend', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'y', 28 | description: 'The integer result of the sign extend', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['0', '0xFF'], 34 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 35 | }, 36 | { 37 | input: ['0', '0x7F'], 38 | output: '0x7F', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27z1~xFywwz2~x7y%27~wPUSH1%200z%2F%2F%20Example%20yF~wSIGNEXTENDw%5Cn%01wyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x0b), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 329), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/sgt.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const sgt: Opcode = { 11 | number: 0x13, 12 | name: 'sgt', 13 | description: 'Signed greater-than comparison', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The left side signed integer', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The right side signed integer', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a > b', 28 | description: 29 | 'The result of the signed greater-than comparison: 1 if the left side is bigger and 0 otherwise', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['10', '10'], 39 | output: '0', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27y1v32%200xssssz9twwy2z10z10t%27~uuuuzv1%20y%2F%2F%20Example%20w%5CnvwPUSHuFFtwSGTs~~%01stuvwyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x13), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Comparison, 102), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/balance.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const balance: Opcode = { 11 | number: 0x31, 12 | name: 'balance', 13 | description: 'Get balance of given account', 14 | minGas: 100, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '0', 18 | }, 19 | dynamicGasCost: { 20 | expression: '100 if the accessed address is warm, 2600 otherwise', 21 | }, 22 | }, 23 | inputs: [ 24 | { 25 | name: 'address', 26 | description: 'The 20-byte address of the account to check', 27 | }, 28 | ], 29 | outputs: [ 30 | { 31 | name: 'balance', 32 | description: 33 | "The balance of the given account in wei. Returns 0 if the account doesn't exist.", 34 | }, 35 | ], 36 | examples: [ 37 | { 38 | input: '0x9bbfed6889322e016e0a02ee459d306fc19545d8', 39 | output: '125985', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27%2F%2F%20Read%20current%20contract%20balance%5CnADDRESS%5CnBALANCE%27_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x31), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 63), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/mul.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const mul: Opcode = { 11 | number: 0x02, 12 | name: 'mul', 13 | description: 'Multiplication operation', 14 | minGas: 5, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first integer value to multiply', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second integer value to multiply', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a * b', 28 | description: 'The integer result of the multiplication modulo 2**256', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['10', '10'], 34 | output: '100', 35 | }, 36 | { 37 | input: ['0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', '2'], 38 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27y1z10z10twwy2v32%200xssssz2t%27~uuuuzv1%20y%2F%2F%20Example%20w%5CnvwPUSHuFFtwMULs~~%01stuvwyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x02), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 84), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/shr.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const shr: Opcode = { 11 | number: 0x1c, 12 | name: 'shr', 13 | description: 'Right shift operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'shift', 18 | description: 'The number of bits to shift to the right', 19 | }, 20 | { 21 | name: 'value', 22 | description: 'The 32 bytes to shift', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'value >> shift', 28 | description: 'The shifted value. If shift is bigger than 255, returns 0.', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['1', '2'], 34 | output: '1', 35 | }, 36 | { 37 | input: ['4', '0xFF'], 38 | output: '0xF', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27z1~2~1wyyz2~0xFF~4w%27~yPUSH1%20z%2F%2F%20Example%20y%5CnwySHR%01wyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | notes: [ 46 | 'Shift the bits towards the least significant one. The bits moved before the first one are discarded, the new bits are set to 0.', 47 | ], 48 | references: [ 49 | evmCodesOpcodesLink(0x1c), 50 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 185), 51 | ], 52 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Constantinople), 53 | }; 54 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/returndatasize.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const returndatasize: Opcode = { 11 | number: 0x3d, 12 | name: 'returndatasize', 13 | description: 'Get size of output data from the previous call from the current environment', 14 | minGas: 2, 15 | outputs: [ 16 | { 17 | name: 'size ', 18 | description: 'The byte size of the return data from the last executed sub context', 19 | }, 20 | ], 21 | examples: [ 22 | { 23 | output: '32', 24 | }, 25 | ], 26 | playgroundLink: evmCodesPlaygroundLink( 27 | '%27gCJWthat%20cJX-ichB.(*Y7F7jjjjjjjjjjF)KY*6Q527*F6Q5260208~32KYq06020526029800~64KmmgC!_X-ith_WcodVabove~77))mCREATE%20gPuts_new%20X%20addres.on_stackmmgCall_deployed%20X))))mDUP5G4%200xj*mSTATICCALLmmgNow-Vshould%20havVourB%20data%20sizVof%2032mRETURNDATASIZEm%27~G1%20qQQQm%5Cnj***g%2F%2F%20_%20thVYG(0xXcontractWconstructor%20Ve%20Q000KmMSTOREJ!.a%20GmPUSHB%20return86QF3qqqq.s%20-%20w*FF)~0(32%20!reate%01!()*-.8BGJKQVWXY_gjmq~_' 28 | ), 29 | errorCases: ['Not enough gas', 'Stack overflow'], 30 | notes: ['A sub context can be created with CALL, CALLCODE, DELEGATECALL or STATICCALL'], 31 | references: [ 32 | evmCodesOpcodesLink(0x3d), 33 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 384), 34 | ], 35 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Byzantium), 36 | }; 37 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/index.ts: -------------------------------------------------------------------------------- 1 | import { Opcode } from '@/types'; 2 | import { address } from './address'; 3 | import { balance } from './balance'; 4 | import { basefee } from './basefee'; 5 | import { calldatacopy } from './calldatacopy'; 6 | import { calldataload } from './calldataload'; 7 | import { calldatasize } from './calldatasize'; 8 | import { caller } from './caller'; 9 | import { callvalue } from './callvalue'; 10 | import { codecopy } from './codecopy'; 11 | import { codesize } from './codesize'; 12 | import { extcodecopy } from './extcodecopy'; 13 | import { extcodehash } from './extcodehash'; 14 | import { extcodesize } from './extcodesize'; 15 | import { gasprice } from './gasprice'; 16 | import { origin } from './origin'; 17 | import { returndatacopy } from './returndatacopy'; 18 | import { returndatasize } from './returndatasize'; 19 | import { selfbalance } from './selfbalance'; 20 | 21 | export const opcodes: Record = { 22 | [address.number]: address, 23 | [balance.number]: balance, 24 | [basefee.number]: basefee, 25 | [calldatacopy.number]: calldatacopy, 26 | [calldataload.number]: calldataload, 27 | [calldatasize.number]: calldatasize, 28 | [caller.number]: caller, 29 | [callvalue.number]: callvalue, 30 | [codecopy.number]: codecopy, 31 | [codesize.number]: codesize, 32 | [extcodecopy.number]: extcodecopy, 33 | [extcodehash.number]: extcodehash, 34 | [extcodesize.number]: extcodesize, 35 | [gasprice.number]: gasprice, 36 | [origin.number]: origin, 37 | [returndatacopy.number]: returndatacopy, 38 | [returndatasize.number]: returndatasize, 39 | [selfbalance.number]: selfbalance, 40 | }; 41 | -------------------------------------------------------------------------------- /src/chains/optimism/hardforks.ts: -------------------------------------------------------------------------------- 1 | // List of Optimism's past Bedrock hard forks. 2 | // https://docs.optimism.io/builders/node-operators/network-upgrades/overview#activations 3 | // https://github.com/ethereum-optimism/specs/blob/main/specs/superchain-upgrades.md#post-bedrock-network-upgrades 4 | export enum OptimismHardfork { 5 | Regolith, 6 | Canyon, 7 | } 8 | 9 | export const CURRENT_OPTIMISM_HARDFORK = OptimismHardfork.Canyon; 10 | 11 | // Retrieve all the hard forks from the starting hard fork to the current mainnet hard fork. 12 | export const getOptimismHardforksFrom = (startingHardfork: OptimismHardfork): string[] => 13 | getOptimismHardforksFromTo(startingHardfork, CURRENT_OPTIMISM_HARDFORK); 14 | 15 | // Retrieve an array of hardforks from a starting hardfork to an ending hardfork (inclusive). 16 | export const getOptimismHardforksFromTo = ( 17 | start: OptimismHardfork, 18 | end: OptimismHardfork 19 | ): string[] => { 20 | if (start > end) { 21 | throw new Error( 22 | `Error: the starting hard fork ${OptimismHardfork[start]} (index: ${start}) occurred after the ending hard fork ${OptimismHardfork[end]} (index: ${end}). Arguments are wrong or must have been reversed.` 23 | ); 24 | } 25 | 26 | // Create an array made of all the enum key indexes following by all the stringified keys. 27 | // For example, if you had an enum with two keys A and B, you would get ['0', '1', 'A', 'B']. 28 | // Then, we only keep the slice with the values (e.g. ['A', 'B']). 29 | const array = Object.keys(OptimismHardfork); 30 | const length = array.length / 2; 31 | const keys = array.slice(length); 32 | return keys.slice(start, end + 1); 33 | }; 34 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/comparison/slt.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const slt: Opcode = { 11 | number: 0x12, 12 | name: 'slt', 13 | description: 'Signed less-than comparison', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The left side signed integer', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The right side signed integer', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a < b', 28 | description: 29 | 'The result of the signed less-than comparison: 1 if the left side is smaller and 0 otherwise', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', '0'], 35 | output: '1', 36 | }, 37 | { 38 | input: ['10', '10'], 39 | output: '0', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27y1z9v32%200xsssstwwy2z10z10t%27~uuuuzv1%20y%2F%2F%20Example%20w%5CnvwPUSHuFFtwSLTs~~%01stuvwyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | notes: ['All values are treated as two’s complement signed 256-bit integers.'], 47 | references: [ 48 | evmCodesOpcodesLink(0x12), 49 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Comparison, 49), 50 | ], 51 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 52 | }; 53 | -------------------------------------------------------------------------------- /src/chains/optimism/deployedContracts.ts: -------------------------------------------------------------------------------- 1 | import { deployedContracts as mainnetDeployedContracts } from '@/chains/mainnet/deployedContracts'; 2 | 3 | const optimismDeployedContracts = { ...mainnetDeployedContracts }; 4 | optimismDeployedContracts['Wrapped Native Token'] = { 5 | ...optimismDeployedContracts['Wrapped Native Token'], 6 | address: '0x4200000000000000000000000000000000000006', 7 | references: [ 8 | ...optimismDeployedContracts['Wrapped Native Token'].references, 9 | '[Optimism Docs: What is ETH? WETH?](https://help.optimism.io/hc/en-us/articles/4417948883611-What-is-ETH-WETH-How-do-they-interact-)', 10 | ], 11 | logicAbi: [ 12 | 'event Approval(address indexed src, address indexed guy, uint256 wad)', 13 | 'event Deposit(address indexed dst, uint256 wad)', 14 | 'event Transfer(address indexed src, address indexed dst, uint256 wad)', 15 | 'event Withdrawal(address indexed src, uint256 wad)', 16 | 'fallback()', 17 | 'function allowance(address, address) view returns (uint256)', 18 | 'function approve(address guy, uint256 wad) returns (bool)', 19 | 'function balanceOf(address) view returns (uint256)', 20 | 'function decimals() view returns (uint8)', 21 | 'function deposit() payable', 22 | 'function name() view returns (string)', 23 | 'function symbol() view returns (string)', 24 | 'function totalSupply() view returns (uint256)', 25 | 'function transfer(address dst, uint256 wad) returns (bool)', 26 | 'function transferFrom(address src, address dst, uint256 wad) returns (bool)', 27 | 'function withdraw(uint256 wad)', 28 | ], 29 | }; 30 | 31 | export const deployedContracts = optimismDeployedContracts; 32 | -------------------------------------------------------------------------------- /src/chains/mainnet/nodes/consensus.ts: -------------------------------------------------------------------------------- 1 | import { Language, Node, NodeType } from '@/types'; 2 | 3 | const lighthouse: Node = { 4 | name: 'lighthouse', 5 | description: 'Ethereum consensus client in Rust.', 6 | type: NodeType.Consensus, 7 | language: Language.Rust, 8 | repository: 'https://github.com/sigp/lighthouse', 9 | documentation: 'https://lighthouse-book.sigmaprime.io/', 10 | }; 11 | 12 | const lodestar: Node = { 13 | name: 'lodestar', 14 | description: 'TypeScript Implementation of Ethereum Consensus.', 15 | type: NodeType.Consensus, 16 | language: Language.TypeScript, 17 | repository: 'https://github.com/ChainSafe/lodestar', 18 | documentation: 'https://lodestar.chainsafe.io/', 19 | }; 20 | 21 | const nimbus: Node = { 22 | name: 'nimbus', 23 | description: 'Nim implementation of the Ethereum Beacon Chain.', 24 | type: NodeType.Consensus, 25 | language: Language.Nim, 26 | repository: 'https://github.com/status-im/nimbus-eth2', 27 | documentation: 'https://nimbus.guide/', 28 | }; 29 | 30 | const prysm: Node = { 31 | name: 'prysm', 32 | description: 'Go implementation of Ethereum proof of stake.', 33 | type: NodeType.Consensus, 34 | language: Language.Go, 35 | repository: 'https://github.com/prysmaticlabs/prysm', 36 | documentation: 'https://prysmaticlabs.com/', 37 | }; 38 | 39 | const teku: Node = { 40 | name: 'teku', 41 | description: 'Java Implementation of the Ethereum 2.0 Beacon Chain.', 42 | type: NodeType.Consensus, 43 | language: Language.Java, 44 | repository: 'https://github.com/Consensys/teku', 45 | documentation: 'https://docs.teku.consensys.io/', 46 | }; 47 | 48 | export const consensusNodes: Node[] = [lighthouse, lodestar, nimbus, prysm, teku]; 49 | -------------------------------------------------------------------------------- /src/chains/mainnet/hardforks.ts: -------------------------------------------------------------------------------- 1 | export enum MainnetHardfork { 2 | Frontier, 3 | Homestead, 4 | DaoFork, 5 | TangerineWhistle, 6 | SpuriousDragon, 7 | Byzantium, 8 | Constantinople, 9 | // The Petersburg hard fork doesn't exist in ethereum/execution-specs so we do not reference it. 10 | Istanbul, 11 | MuirGlacier, 12 | Berlin, 13 | London, 14 | ArrowGlacier, 15 | GrayGlacier, 16 | Paris, 17 | Shanghai, 18 | } 19 | 20 | export const CURRENT_MAINNET_HARDFORK = MainnetHardfork.Shanghai; 21 | 22 | // Retrieve all the hard forks from the starting hard fork to the current mainnet hard fork. 23 | export const getMainnetHardforksFrom = (startingHardfork: MainnetHardfork): string[] => 24 | getMainnetHardforksFromTo(startingHardfork, CURRENT_MAINNET_HARDFORK); 25 | 26 | // Retrieve an array of hardforks from a starting hardfork to an ending hardfork (inclusive). 27 | export const getMainnetHardforksFromTo = ( 28 | start: MainnetHardfork, 29 | end: MainnetHardfork 30 | ): string[] => { 31 | if (start > end) { 32 | throw new Error( 33 | `Error: the starting hard fork ${MainnetHardfork[start]} (index: ${start}) occured after the ending hard fork ${MainnetHardfork[end]} (index: ${end}). Arguments are wrong or must have been reversed.` 34 | ); 35 | } 36 | 37 | // Create an array made of all the enum key indexes following by all the stringified keys. 38 | // For example, if you had an enum with two keys A and B, you would get ['0', '1', 'A', 'B']. 39 | // Then, we only keep the slice with the values (e.g. ['A', 'B']). 40 | const array = Object.keys(MainnetHardfork); 41 | const length = array.length / 2; 42 | const keys = array.slice(length); 43 | return keys.slice(start, end + 1); 44 | }; 45 | -------------------------------------------------------------------------------- /src/components/layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/router'; 2 | import { Footer } from '@/components/layout/Footer'; 3 | import { Header } from '@/components/layout/Header'; 4 | import { classNames } from '@/lib/utils'; 5 | 6 | interface Props { 7 | children: JSX.Element; 8 | } 9 | 10 | export const Layout = ({ children }: Props) => { 11 | const { route } = useRouter(); 12 | const isHome = route === '/'; 13 | 14 | return ( 15 |
16 |
17 |
18 | {children} 19 |
20 |
21 | 50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/ui/Copyable.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { ClipboardDocumentIcon } from '@heroicons/react/24/outline'; 3 | import { classNames, copyToClipboard } from '@/lib/utils'; 4 | import { Tooltip } from './Tooltip'; 5 | 6 | // When content is a string, `textToCopy` is optional. 7 | interface ContentStringProps { 8 | content: string; 9 | textToCopy?: string; 10 | Icon?: typeof ClipboardDocumentIcon; 11 | className?: string; 12 | } 13 | 14 | // When text is a JSX element, `textToCopy` is required. 15 | interface ContentElementProps { 16 | content: JSX.Element; 17 | textToCopy: string; 18 | Icon?: typeof ClipboardDocumentIcon; 19 | className?: string; 20 | } 21 | 22 | export const Copyable = ({ 23 | content, 24 | textToCopy, 25 | Icon = ClipboardDocumentIcon, 26 | className = '', 27 | }: ContentStringProps | ContentElementProps) => { 28 | const [isShowing, setIsShowing] = useState(false); 29 | 30 | const onCopy = (text: string) => { 31 | setIsShowing(true); 32 | copyToClipboard(text); 33 | setTimeout(() => setIsShowing(false), 1000); 34 | }; 35 | 36 | return ( 37 |
38 |
39 | {content} 40 | onCopy(String(textToCopy || content))} 42 | className='ml-2 h-4 cursor-pointer opacity-0 transition-opacity group-hover:opacity-100' 43 | /> 44 |
45 |
48 | 49 |
50 |
51 | ); 52 | }; 53 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/shl.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const shl: Opcode = { 11 | number: 0x1b, 12 | name: 'shl', 13 | description: 'Left shift operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'shift', 18 | description: 'The number of bits to shift to the left', 19 | }, 20 | { 21 | name: 'value', 22 | description: 'The 32 bytes to shift', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'value << shift', 28 | description: 'The shifted value. If shift is bigger than 255, returns 0.', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['1', '1'], 34 | output: '2', 35 | }, 36 | { 37 | input: ['4', '0xFF00000000000000000000000000000000000000000000000000000000000000'], 38 | output: '0xF000000000000000000000000000000000000000000000000000000000000000', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27y1z1z1swwy2v32%200xFFuuuuutz4s%27~tttzv1%20y%2F%2F%20Example%20w%5CnvwPUSHu~~t00swSHL%01stuvwyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | notes: [ 46 | 'Shift the bits towards the least significant one. The bits moved before the first one are discarded, the new bits are set to 0.', 47 | ], 48 | references: [ 49 | evmCodesOpcodesLink(0x1b), 50 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 157), 51 | ], 52 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Constantinople), 53 | }; 54 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/extcodesize.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const extcodesize: Opcode = { 11 | number: 0x3b, 12 | name: 'extcodesize', 13 | description: "Get size of an account's code", 14 | minGas: 100, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '0', 18 | }, 19 | dynamicGasCost: { 20 | expression: '100 if the accessed address is warm, 2600 otherwise', 21 | }, 22 | }, 23 | inputs: [ 24 | { 25 | name: 'address', 26 | description: 'The 20-byte address of the contract to query', 27 | }, 28 | ], 29 | outputs: [ 30 | { 31 | name: 'size', 32 | description: 'The byte size of the code', 33 | }, 34 | ], 35 | examples: [ 36 | { 37 | input: '0x43a61f3f4c73ea0d444c5c1c1a8544067a86219b', 38 | output: '32', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27gCQYtha*cQ8%20GJ%20a_-deW7...p0KWJ6l5260206lF3NNNNN0p32KjjgCBm8mY-dVabovep41p0p0jCREATE%20gPutsmnew)addresLjjgThVaddres_iL%2C%20wVcan%20querymsizejEXTCODESIZE%27~JJJFp91%20m%20thVl000j%5Cng%2F%2F)-ntrac*_s%20Y-nstructor%20W9G0xVe%20QB_a%20NlllL_onmstackKjMSTOREJFFG32%20Breate9jPUSH8fwith.~~~-co*t%20)%20f%01)*-.89BGJKLNQVWY_fgjlmp~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | references: [ 46 | evmCodesOpcodesLink(0x3b), 47 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 324), 48 | ], 49 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/addmod.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const addmod: Opcode = { 11 | number: 0x08, 12 | name: 'addmod', 13 | description: 'Modulo addition operation', 14 | minGas: 8, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first integer value to add', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second integer value to add', 23 | }, 24 | { 25 | name: 'N', 26 | description: 'The integer denominator', 27 | }, 28 | ], 29 | outputs: [ 30 | { 31 | name: '(a + b) % N', 32 | description: 33 | 'The integer result of the addition followed by a modulo. If the denominator is 0, the result will be 0.', 34 | }, 35 | ], 36 | examples: [ 37 | { 38 | input: ['10', '10', '8'], 39 | output: '4', 40 | }, 41 | { 42 | input: ['0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', '2', '2'], 43 | output: '1', 44 | }, 45 | ], 46 | playgroundLink: evmCodesPlaygroundLink( 47 | '%27y1z8z10z10vwwy2z2z2u32%200xssssv%27~ttttzu1%20y%2F%2F%20Example%20w%5CnvwADDMODuwPUSHtFFs~~%01stuvwyz~_' 48 | ), 49 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 50 | notes: ['All intermediate calculations of this operation are not subject to the 2**256 modulo'], 51 | references: [ 52 | evmCodesOpcodesLink(0x08), 53 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 234), 54 | ], 55 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 56 | }; 57 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/bitwise/sar.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const sar: Opcode = { 11 | number: 0x1d, 12 | name: 'sar', 13 | description: 'Arithmetic (signed) right shift operation', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'shift', 18 | description: 'The number of bits to shift to the right', 19 | }, 20 | { 21 | name: 'value', 22 | description: 'The 32 bytes to shift', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'value >> shift', 28 | description: 'The shifted value', 29 | }, 30 | ], 31 | examples: [ 32 | { 33 | input: ['1', '2'], 34 | output: '1', 35 | }, 36 | { 37 | input: ['4', '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0'], 38 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27y1z2z1twwy2v32%200xuuu0z4t%27~FFFFFFFzv1%20y%2F%2F%20Example%20w%5CnvwPUSHu~~~twSAR%01tuvwyz~_' 43 | ), 44 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 45 | notes: [ 46 | 'Shift the bits towards the least significant one. The bits moved before the first one are discarded, the new bits are set to 0 if the previous most significant bit was 0, otherwise the new bits are set to 1.', 47 | ], 48 | references: [ 49 | evmCodesOpcodesLink(0x1d), 50 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Bitwise, 213), 51 | ], 52 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Constantinople), 53 | }; 54 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/calldataload.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const calldataload: Opcode = { 11 | number: 0x35, 12 | name: 'calldataload', 13 | description: 'Get input data of current environment', 14 | minGas: 3, 15 | inputs: [ 16 | { 17 | name: 'i', 18 | description: 'The byte offset in the calldata', 19 | }, 20 | ], 21 | outputs: [ 22 | { 23 | name: 'data[i]', 24 | description: 25 | 'The 32-byte value starting from the given offset of the calldata. All bytes after the end of the calldata are set to 0.', 26 | }, 27 | ], 28 | examples: [ 29 | { 30 | input: '0', 31 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 32 | calldata: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 33 | }, 34 | { 35 | input: '31', 36 | output: '0xFF00000000000000000000000000000000000000000000000000000000000000', 37 | calldata: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 38 | }, 39 | ], 40 | playgroundLink: evmCodesPlaygroundLink( 41 | '%27~1w0yzz~2w31y%27~%2F%2F%20Example%20z%5CnyzCALLDATALOADwzPUSH1%20%01wyz~_&callData=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 42 | ), 43 | errorCases: ['Not enough gas', 'Not enough values on stack'], 44 | references: [ 45 | evmCodesOpcodesLink(0x35), 46 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 163), 47 | ], 48 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 49 | }; 50 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/sdiv.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const sdiv: Opcode = { 11 | number: 0x05, 12 | name: 'sdiv', 13 | description: 'Signed integer division operation (truncated)', 14 | minGas: 5, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The integer numerator', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The integer denominator', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a // b', 28 | description: 29 | 'The integer result of the signed integer division. If the denominator is 0, the result will be 0.', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '10'], 35 | output: '1', 36 | }, 37 | { 38 | input: [ 39 | '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE', 40 | '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 41 | ], 42 | output: '2', 43 | }, 44 | ], 45 | playgroundLink: evmCodesPlaygroundLink( 46 | '%27y1vvszzy2wrFwrEs%27~uuuz%5Cny%2F%2F%20Example%20wt32%200xr~vt1%2010uFFFtzPUSHszSDIVr~~~%01rstuvwyz~_' 47 | ), 48 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 49 | notes: [ 50 | 'All values are treated as two’s complement signed 256-bit integers. Note the overflow semantic when −2**255 is negated.', 51 | ], 52 | references: [ 53 | evmCodesOpcodesLink(0x05), 54 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 141), 55 | ], 56 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 57 | }; 58 | -------------------------------------------------------------------------------- /src/components/ChainDiffSelector.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import { chains } from '@/chains'; 4 | import { ChainDiffSelectorChainCombobox } from '@/components/ui/ChainDiffSelectorChainCombobox'; 5 | import { Chain } from '@/types'; 6 | 7 | export const ChainDiffSelector = () => { 8 | const router = useRouter(); 9 | const [base, setBase] = useState(chains.mainnet); 10 | const [target, setTarget] = useState(chains.optimism); 11 | const chainsArray: Chain[] = Object.values(chains); 12 | 13 | const onSubmit = (e: React.FormEvent) => { 14 | e.preventDefault(); 15 | router.push({ 16 | pathname: '/diff', 17 | query: { base: base.metadata.id, target: target.metadata.id }, 18 | }); 19 | }; 20 | 21 | return ( 22 | <> 23 |
24 |
25 |
26 |
27 | 33 | 39 |
40 | 43 |
44 | 45 |
46 |
47 |
48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/exp.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const exp: Opcode = { 11 | number: 0x0a, 12 | name: 'exp', 13 | description: 'Exponential operation', 14 | minGas: 10, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '10', 18 | }, 19 | dynamicGasCost: { 20 | expression: '50 * exponent_byte_size', 21 | variables: [ 22 | { 23 | name: 'exponent_byte_size', 24 | description: 'The exponent representation in bytes', 25 | }, 26 | ], 27 | }, 28 | }, 29 | inputs: [ 30 | { 31 | name: 'a', 32 | description: 'The integer base', 33 | }, 34 | { 35 | name: 'exponent', 36 | description: 'The integer exponent', 37 | }, 38 | ], 39 | outputs: [ 40 | { 41 | name: 'a ** exponent', 42 | description: 'The integer result of the exponential operation modulo 2**256', 43 | }, 44 | ], 45 | examples: [ 46 | { 47 | input: ['10', '2'], 48 | output: '100', 49 | }, 50 | { 51 | input: ['2', '2'], 52 | output: '4', 53 | }, 54 | ], 55 | playgroundLink: evmCodesPlaygroundLink( 56 | '%27z1~2~10wyyz2~2~2w%27~yPUSH1%20z%2F%2F%20Example%20y%5CnwyEXP%01wyz~_' 57 | ), 58 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 59 | notes: ['All intermediate calculations of this operation are not subject to the 2**256 modulo'], 60 | references: [ 61 | evmCodesOpcodesLink(0x0a), 62 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 296), 63 | ], 64 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 65 | }; 66 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/smod.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const smod: Opcode = { 11 | number: 0x07, 12 | name: 'smod', 13 | description: 'Signed modulo remainder operation', 14 | minGas: 5, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The integer numerator', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The integer denominator', 23 | }, 24 | ], 25 | outputs: [ 26 | { 27 | name: 'a % b', 28 | description: 29 | 'The integer result of the signed integer modulo. If the denominator is 0, the result will be 0.', 30 | }, 31 | ], 32 | examples: [ 33 | { 34 | input: ['10', '3'], 35 | output: '1', 36 | }, 37 | { 38 | input: [ 39 | '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8', 40 | '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD', 41 | ], 42 | output: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE', 43 | }, 44 | ], 45 | playgroundLink: evmCodesPlaygroundLink( 46 | '%27y1s3s10tzzy2wrDwr8t%27~uuuz%5Cny%2F%2F%20Example%20wv32%200xr~vzPUSHuFFFtzSMODsv1%20r~~~%01rstuvwyz~_' 47 | ), 48 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 49 | notes: [ 50 | 'All values are treated as two’s complement signed 256-bit integers. Note the overflow semantic when −2**255 is negated.', 51 | ], 52 | references: [ 53 | evmCodesOpcodesLink(0x07), 54 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 174), 55 | ], 56 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 57 | }; 58 | -------------------------------------------------------------------------------- /src/components/diff/utils/format.tsx: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork } from '@/chains/mainnet/hardforks'; 2 | import { CURRENT_OPTIMISM_HARDFORK, OptimismHardfork } from '@/chains/optimism/hardforks'; 3 | import { CURRENT_MAINNET_HARDFORK } from '@/lib/constants'; 4 | import { classNames, toUppercase } from '@/lib/utils'; 5 | 6 | export const formatHardfork = (array: string[]): JSX.Element => { 7 | if (!array || array.length == 0) { 8 | return

No information provided on supported hard forks.

; 9 | } 10 | 11 | const first = array[0]; 12 | const last = array[array.length - 1]; 13 | const currentMainnetHardforkName = MainnetHardfork[CURRENT_MAINNET_HARDFORK]; 14 | const currentOptimismHardforkName = OptimismHardfork[CURRENT_OPTIMISM_HARDFORK]; 15 | if (array.length == 1) { 16 | const supportedText = 17 | first === currentMainnetHardforkName || first === currentOptimismHardforkName 18 | ? `Supported since ${first} hard fork.` 19 | : `Supported only in ${first} hard fork.`; 20 | return ( 21 |

22 | {supportedText} 23 |

24 | ); 25 | } 26 | 27 | const supportedText = 28 | last === currentMainnetHardforkName || last === currentOptimismHardforkName 29 | ? `Supported since ${first} hard fork.` 30 | : `Supported between ${first} and ${last} hard forks.`; 31 | return ( 32 |

33 | {supportedText} 34 |

35 | ); 36 | }; 37 | 38 | export const formatStringList = (title: string, array: string[] | undefined): JSX.Element => { 39 | if (array === undefined || array.length === 0) return <>; 40 | return ( 41 | <> 42 |

{toUppercase(title)}

43 |
    44 | {array.map((v, id) => ( 45 |
  • {toUppercase(v)}
  • 46 | ))} 47 |
48 | 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/environment/extcodehash.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const extcodehash: Opcode = { 11 | number: 0x3f, 12 | name: 'extcodehash', 13 | description: "Get hash of an account's code", 14 | minGas: 100, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '0', 18 | }, 19 | dynamicGasCost: { 20 | expression: '100 if the accessed address is warm, 2600 otherwise', 21 | }, 22 | }, 23 | inputs: [ 24 | { 25 | name: 'address', 26 | description: 'The 20-byte address of the account', 27 | }, 28 | ], 29 | outputs: [ 30 | { 31 | name: 'hash', 32 | description: 33 | "The hash of the chosen account's code, the empty hash (0xc5d24601...) if the account has no code, or 0 if the account does not exist or has been destroyed.", 34 | }, 35 | ], 36 | examples: [ 37 | { 38 | input: '0x43a61f3f4c73ea0d444c5c1c1a8544067a86219b', 39 | output: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470', 40 | }, 41 | ], 42 | playgroundLink: evmCodesPlaygroundLink( 43 | '%27yClmthaWcl_%204%20FFZjYdeg3%200x63FFFFFFFF60005260046000F3q0~MSTORE~~yCfz_zmYdeZboveq13q0q0~CREATE%20yPutsznewVaddresjonzstack~~yGetzhash~EXTCODEHASH%27~%5Cnz%20the%20y%2F%2F%20qgVYntracWmYnstructor%20lfja%20js%20g~PUSH1freate_pwithZ%20aYcoWt%20V%20p%01VWYZ_fgjlmpqyz~_' 44 | ), 45 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 46 | references: [ 47 | evmCodesOpcodesLink(0x3f), 48 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Environment, 444), 49 | ], 50 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Constantinople), 51 | }; 52 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/jump.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const jump: Omit = { 11 | number: 0x56, 12 | name: 'jump', 13 | description: 'Alter the program counter', 14 | minGas: 8, 15 | inputs: [ 16 | { 17 | name: 'counter', 18 | description: 19 | 'The byte offset in the deployed code where execution will continue from. Must be a JUMPDEST instruction.', 20 | }, 21 | ], 22 | playgroundLink: evmCodesPlaygroundLink( 23 | '%27wWZjump%20overqinvalid%20and%20jusXgoYoqpushk4x0_%20%20%20x2%20%7Bprevious%20instruction%20occupies%202%20bytes%7DzINVALIDx3_DEST~4k1x5%27~%20wOffseXz%5Cnx%20~w%2F%2F%20qYhZkzPUSH1%20_zJUMPZe%20Y%20tXt%20%01XYZ_kqwxz~_' 24 | ), 25 | errorCases: [ 26 | 'Not enough gas', 27 | 'Not enough values on the stack', 28 | 'Counter offset is not a JUMPDEST. The error is generated even if the JUMP would not have been done.', 29 | ], 30 | notes: [ 31 | 'The program counter (PC) is a byte offset in the deployed code. It indicates which instruction will be executed next. When an ADD is executed, for example, the PC is incremented by 1, since the instruction is 1 byte. The PUSH instructions are bigger than one byte, and so will increment the counter accordingly. The JUMP instruction alters the program counter, thus breaking the linear path of the execution to another point in the deployed code. It is used to implement functionalities like functions.', 32 | ], 33 | references: [ 34 | evmCodesOpcodesLink(0x56), 35 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 45), 36 | ], 37 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 38 | }; 39 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/storage/sload.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const sload: Opcode = { 11 | number: 0x54, 12 | name: 'sload', 13 | description: 'Load word from storage', 14 | minGas: 100, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '0', 18 | }, 19 | dynamicGasCost: { 20 | expression: '100 if the accessed address is warm, 2100 otherwise', 21 | }, 22 | }, 23 | inputs: [ 24 | { 25 | name: 'key', 26 | description: 'The 32-byte key in storage', 27 | }, 28 | ], 29 | outputs: [ 30 | { 31 | name: 'value', 32 | description: 33 | 'The 32-byte value corresponding to that key. 0 if that key was never written before', 34 | }, 35 | ], 36 | examples: [ 37 | { 38 | input: '0', 39 | output: '46', 40 | storage: { 41 | before: { 42 | '0': '46', 43 | }, 44 | after: { 45 | '0': '46', 46 | }, 47 | }, 48 | }, 49 | { 50 | input: '1', 51 | output: '0', 52 | storage: { 53 | before: { 54 | '0': '46', 55 | }, 56 | after: { 57 | '0': '46', 58 | }, 59 | }, 60 | }, 61 | ], 62 | playgroundLink: evmCodesPlaygroundLink( 63 | '%27wSet%20up%20thrstatez46z0~SSTOREy1z0vy2z1v~%27~%5Cnz~PUSH1%20y~~wExamplrw%2F%2F%20v~SLOADre%20%01rvwyz~_' 64 | ), 65 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 66 | references: [ 67 | evmCodesOpcodesLink(0x54), 68 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Storage, 32), 69 | ], 70 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 71 | }; 72 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/arithmetic/mulmod.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const mulmod: Opcode = { 11 | number: 0x09, 12 | name: 'mulmod', 13 | description: 'Modulo multiplication operation', 14 | minGas: 8, 15 | inputs: [ 16 | { 17 | name: 'a', 18 | description: 'The first integer value to multiply', 19 | }, 20 | { 21 | name: 'b', 22 | description: 'The second integer value to multiply', 23 | }, 24 | { 25 | name: 'N', 26 | description: 'The integer denominator', 27 | }, 28 | ], 29 | outputs: [ 30 | { 31 | name: '(a + b) % N', 32 | description: 33 | 'The integer result of the multiplication followed by a modulo. If the denominator is 0, the result will be 0.', 34 | }, 35 | ], 36 | examples: [ 37 | { 38 | input: ['10', '10', '8'], 39 | output: '4', 40 | }, 41 | { 42 | input: [ 43 | '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 44 | '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 45 | '12', 46 | ], 47 | output: '9', 48 | }, 49 | ], 50 | playgroundLink: evmCodesPlaygroundLink( 51 | '%27y1v8v10v10twwy2v12usust%27~rrrrzwPUSHy%2F%2F%20Example%20w%5Cnvz1%20uz32%200xstwMULMODs~~~~rFF%01rstuvwyz~_' 52 | ), 53 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 54 | notes: ['All intermediate calculations of this operation are not subject to the 2**256 modulo'], 55 | references: [ 56 | evmCodesOpcodesLink(0x08), 57 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 265), 58 | ], 59 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 60 | }; 61 | -------------------------------------------------------------------------------- /src/components/layout/Head.tsx: -------------------------------------------------------------------------------- 1 | import NextHead from 'next/head'; 2 | import { NextRouter, useRouter } from 'next/router'; 3 | import { getChainById } from '@/chains'; 4 | import { 5 | COMPANY_URL, 6 | OG_ENDPOINT, 7 | SITE_DESCRIPTION, 8 | SITE_NAME, 9 | SITE_URL, 10 | TWITTER_URL, 11 | } from '@/lib/constants'; 12 | 13 | // Function to generate the query parameter string based on base and target values. 14 | const getRouteData = (router: NextRouter) => { 15 | const defaultRouteData = { title: SITE_NAME, imageUrl: '' }; 16 | const path = router.pathname; 17 | if (path === '/diff') { 18 | const { base, target } = router.query; 19 | if (!base || !target) defaultRouteData; 20 | 21 | const baseTitle = getChainById(base as string)?.metadata.name; 22 | const targetTitle = getChainById(target as string)?.metadata.name; 23 | if (!baseTitle || !targetTitle) return defaultRouteData; 24 | 25 | const title = `${baseTitle} vs ${targetTitle} | ${SITE_NAME}`; 26 | const imageUrl = `?base=${base}&target=${target}`; 27 | return { title, imageUrl }; 28 | } 29 | return defaultRouteData; 30 | }; 31 | 32 | export const Head = () => { 33 | const router = useRouter(); 34 | const { title, imageUrl } = getRouteData(router); 35 | const imgUrl = `${SITE_URL}${OG_ENDPOINT}${imageUrl}`; 36 | 37 | return ( 38 | 39 | {title} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/components/diff/utils/Collapsible.tsx: -------------------------------------------------------------------------------- 1 | // This component is used to display the references for a section in a diff 2 | import { Disclosure } from '@headlessui/react'; 3 | import { ChevronRightIcon } from '@heroicons/react/20/solid'; 4 | import { Markdown } from '@/components/diff/utils/Markdown'; 5 | import { classNames } from '@/lib/utils'; 6 | 7 | export const Collapsible = ({ 8 | kind, 9 | contents, 10 | className, 11 | title, 12 | }: { 13 | kind: 'references' | 'notes' | 'custom'; 14 | contents: string[] | string | JSX.Element | undefined; 15 | className?: string; 16 | title?: string; 17 | }) => { 18 | if (contents === undefined) return <>; 19 | const refs = Array.isArray(contents) ? contents : [contents]; 20 | 21 | let headerTitle = 'unknown'; 22 | if (title) headerTitle = title; 23 | else if (kind === 'references') headerTitle = 'References'; 24 | else if (kind === 'notes') headerTitle = 'Notes'; 25 | 26 | const panelContent = 27 | kind === 'custom' ? ( 28 | contents 29 | ) : ( 30 |
    31 | {refs.map((reference) => { 32 | return ( 33 |
  1. 34 | 35 |
  2. 36 | ); 37 | })} 38 |
39 | ); 40 | 41 | return ( 42 |
43 | 44 | {({ open }) => ( 45 | <> 46 | 52 | {headerTitle} 53 | 56 | 57 | {panelContent} 58 | 59 | )} 60 | 61 |
62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/controlFlow/jumpi.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const jumpi: Omit = { 11 | number: 0x57, 12 | name: 'jumpi', 13 | description: 'Conditionally alter the program counter', 14 | minGas: 10, 15 | inputs: [ 16 | { 17 | name: 'counter', 18 | description: 19 | 'The byte offset in the deployed code where execution will continue from. Must be a JUMPDEST instruction.', 20 | }, 21 | { 22 | name: 'b', 23 | description: 24 | 'The program counter will be altered with the new value only if this value is different from 0. Otherwise, the program counter is simply incremented and the next instruction will be executed.', 25 | }, 26 | ], 27 | playgroundLink: evmCodesPlaygroundLink( 28 | '%27qFirstk%20noYjump%2C%20secondkw0%20XRY0w10z2~h4~W_z5w12z7~h9~Z0gINVALIDK11gZ2w_z13%27~%20%7Bprevious%20instruction%20occupiR%202%20bytR%7DgzXseYwgWq%2F%2F%20k%20example%20doRhQI%20%20Kg%5Cn_1%20ZQDESTz1Yt%20X%20qOffWPUSH_ResQJUMPK%20z%01KQRWXYZ_ghkqwz~_' 29 | ), 30 | errorCases: [ 31 | 'Not enough gas', 32 | 'Not enough values on the stack', 33 | 'Counter offset is not a JUMPDEST. The error is generated even if the JUMP would not have been done.', 34 | ], 35 | notes: [ 36 | 'The program counter (PC) is a byte offset in the deployed code. It indicates which instruction will be executed next. When an ADD is executed, for example, the PC is incremented by 1, since the instruction is 1 byte. The PUSH instructions are bigger than one byte, and so will increment the counter accordingly. The JUMPI instruction may alter the program counter, thus breaking the linear path of the execution to another point in the deployed code. It is used to implement functionalities like loops and conditions.', 37 | ], 38 | references: [ 39 | evmCodesOpcodesLink(0x57), 40 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Arithmetic, 70), 41 | ], 42 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 43 | }; 44 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { pad } from 'viem'; 2 | import { Chain } from '@/types'; 3 | 4 | // Takes an arbitrary number of class names, filtering out any falsey values. 5 | export const classNames = (...classes: (string | boolean)[]) => classes.filter(Boolean).join(' '); 6 | 7 | // Copies the provided text to the clipboard 8 | export const copyToClipboard = (text: string) => { 9 | navigator.clipboard.writeText(text).then( 10 | () => {}, 11 | (err) => console.error('Could not copy text to clipboard: ', err) 12 | ); 13 | }; 14 | 15 | // Given a `record` (i.e. an object), return an array of its values sorted by the given `field`. 16 | // Make sure the field is a number or string or the sort behavior based on `>` and `<` may be 17 | // undefined. 18 | export const sortedArrayByField = ( 19 | record: Record, 20 | field: K 21 | ): U[] => { 22 | return (Object.values(record) as U[]).sort((a, b) => { 23 | if (a[field] > b[field]) return 1; 24 | if (a[field] < b[field]) return -1; 25 | return 0; 26 | }); 27 | }; 28 | 29 | // Given a `record` (i.e. an object), return an array of its values sorted by the given `fields`, 30 | // in the order specified. 31 | // Make sure the field is a number or string or the sort behavior based on `>` and `<` may be 32 | // undefined. 33 | export const sortedArrayByFields = ( 34 | record: Record, 35 | fields: K[] 36 | ): U[] => { 37 | return (Object.values(record) as U[]).sort((a, b) => { 38 | for (const field of fields) { 39 | if (a[field] > b[field]) return 1; 40 | if (a[field] < b[field]) return -1; 41 | } 42 | return 0; 43 | }); 44 | }; 45 | 46 | // Returns a hex string with a leading `0x` and padded to 2 characters. 47 | export const formatPrefixByte = (prefix: number) => { 48 | return pad(`0x${prefix.toString(16).toUpperCase()}`, { size: 1 }); 49 | }; 50 | 51 | export const toUppercase = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); 52 | 53 | export const chainLogoUrl = (chain: Chain) => { 54 | if (chain.metadata.id === 42161) return 'https://icons.llamao.fi/icons/chains/rsz_arbitrum.jpg'; 55 | return `https://icons.llamao.fi/icons/chains/rsz_${chain.metadata.name.toLowerCase()}.jpg`; 56 | }; 57 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/system/selfdestruct.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const selfdestruct: Omit = { 11 | number: 0xff, 12 | name: 'selfdestruct', 13 | description: 'Halt execution and register account for later deletion', 14 | minGas: 5000, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '5000', 18 | }, 19 | dynamicGasCost: { 20 | expression: 'positive_balance + cold_address', 21 | variables: [ 22 | { 23 | name: 'positive_balance', 24 | description: 25 | 'If a positive balance is sent to an empty account, the dynamic gas is 25000. An account is empty if its balance is 0, its nonce is 0 and it has no code.', 26 | }, 27 | { 28 | name: 'cold_address', 29 | description: 'If `address` is cold, there is an additional dynamic cost of 2600', 30 | }, 31 | ], 32 | }, 33 | }, 34 | inputs: [ 35 | { 36 | name: 'address', 37 | description: 38 | 'The account to send the current balance to (see BALANCE or SELFBALANCE since Istanbul fork)', 39 | }, 40 | ], 41 | playgroundLink: evmCodesPlaygroundLink( 42 | '%27ureatekcvhagwrites%20inkslotq14bx64p1p055p052p5601BF3jzMSTORE~14~18jzCREATEzzuvries%20to%20modify%20state%2C%20failsjjjjzDUP5q2bxFFFFzSTATICCALL%27~q1%20z%5Cnvontracgtu%2F%2F%20CqzPUSHp600k%20a%20j~0gt%20b%200%01bgjkpquvz~_' 43 | ), 44 | errorCases: [ 45 | 'Not enough gas', 46 | 'Not enough values on the stack', 47 | 'The current execution context is from a STATICCALL (since Byzantium fork)', 48 | ], 49 | notes: [ 50 | 'The current account is registered to be destroyed, and will be at the end of the current transaction. The transfer of the current balance to the given account cannot fail. In particular, the destination account code (if any) is not executed, or, if the account does not exist, the balance is still added to the given address.', 51 | ], 52 | references: [ 53 | evmCodesOpcodesLink(0xff), 54 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.System, 481), 55 | ], 56 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Constantinople), 57 | }; 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EVM Diff 2 | 3 | Diff EVM-compatible chains in a friendly format. 4 | 5 | > [!NOTE] 6 | > This site is under active development. Check out the [issues](https://github.com/mds1/evm-diff/issues) if you'd like to contribute. 7 | 8 | ## Overview 9 | 10 | There are lots of EVM-compatible chains, and they can differ in various, subtle ways. 11 | Developers write contracts intended to be deployed on multiple chains and therefore need to be aware of these differences. 12 | 13 | Currently finding this information can be tedious because: 14 | 15 | - It requires checking each chain's documentation. 16 | - May require digging through node implementations. 17 | - Chains compare themselves to Ethereum, but not to each other. 18 | 19 | As L2s aim to scale horizontally with more chains (such as the [Optimism Superchain](https://app.optimism.io/superchain) and new [Arbitrum chains](https://docs.arbitrum.foundation/new-arb-chains)), developers will want to compare those chains against both the "base" optimism/arbitrum chains, and Ethereum mainnet. 20 | 21 | Sites like [op-geth](https://op-geth.optimism.io/) are excellent for comparing the actual code, but for smart contract and application developers, this is too low-level to be easily digestible. 22 | 23 | EVM Diff aims to solve these problems, by allowing you to diff the execution-level specifications of EVM-compatible chains in an easy-to-read format. 24 | 25 | ## Status 26 | 27 | This project is in the early stages of development, and should not yet be relied on. 28 | 29 | The initial goal is to have a full diff between mainnet, Optimism, and Arbitrum, and then expand to other chains from there. Starting with these chains seems ideal because they have good documentation and they are the most popular L2s (by [TVL](https://l2beat.com/scaling/tvl)). 30 | 31 | See the open [issues](https://github.com/mds1/evm-diff/issues) for current needs, and feel free to create new issues for bugs, feature requests, or other ideas. 32 | 33 | If you want to contribute, please see [CONTRIBUTING.md](./CONTRIBUTING.md). 34 | **Some issues have bounties attached**, which will be paid out on Optimism in OP tokens.[^1] Please be sure to read [CONTRIBUTING.md](./CONTRIBUTING.md) to understand how the bounty process works. 35 | 36 | [^1]: Thanks to Optimism RPGF, as these bounties were made possible by an [RPGF grant](https://optimism.mirror.xyz/Upn_LtV2-3SviXgX_PE_LyA7YI00jQyoM1yf55ltvvI) I received for [Multicall3](https://github.com/mds1/multicall). 37 | -------------------------------------------------------------------------------- /src/chains/mainnet/nodes/execution.ts: -------------------------------------------------------------------------------- 1 | import { Language, Node, NodeType, SyncStrategy } from '@/types'; 2 | 3 | const besu: Node = { 4 | name: 'besu', 5 | description: 'An enterprise-grade Java-based, Apache 2.0 licensed Ethereum client.', 6 | type: NodeType.Execution, 7 | language: Language.Java, 8 | syncStrategy: [SyncStrategy.Snap, SyncStrategy.Fast, SyncStrategy.Full], 9 | repository: 'https://github.com/hyperledger/besu', 10 | documentation: 'https://besu.hyperledger.org/', 11 | }; 12 | 13 | const coregeth: Node = { 14 | name: 'core-geth', 15 | description: 'A highly configurable Go implementation of the Ethereum protocol.', 16 | type: NodeType.Execution, 17 | language: Language.Go, 18 | syncStrategy: [SyncStrategy.Snap, SyncStrategy.Full], 19 | forkOf: 'geth', 20 | repository: 'https://github.com/etclabscore/core-geth', 21 | documentation: 'https://etclabscore.github.io/core-geth/', 22 | }; 23 | 24 | const erigon: Node = { 25 | name: 'erigon', 26 | description: 'Ethereum implementation on the efficiency frontier.', 27 | type: NodeType.Execution, 28 | language: Language.Go, 29 | syncStrategy: [SyncStrategy.Full], 30 | forkOf: 'geth', 31 | repository: 'https://github.com/ledgerwatch/erigon', 32 | documentation: 'https://erigon.gitbook.io/erigon/', 33 | }; 34 | 35 | const geth: Node = { 36 | name: 'geth', 37 | description: 'Official Go implementation of the Ethereum protocol.', 38 | type: NodeType.Execution, 39 | language: Language.Go, 40 | syncStrategy: [SyncStrategy.Snap, SyncStrategy.Full], 41 | repository: 'https://github.com/ethereum/go-ethereum', 42 | documentation: 'https://geth.ethereum.org/', 43 | }; 44 | 45 | export const nethermind: Node = { 46 | name: 'nethermind', 47 | description: 'A robust execution client for Ethereum node operators.', 48 | type: NodeType.Execution, 49 | language: Language.CSharp, 50 | syncStrategy: [SyncStrategy.Snap, SyncStrategy.Fast, SyncStrategy.Full], 51 | repository: 'https://github.com/NethermindEth/nethermind', 52 | documentation: 'https://docs.nethermind.io/', 53 | }; 54 | 55 | export const reth: Node = { 56 | name: 'reth', 57 | description: 58 | 'Modular, contributor-friendly and blazing-fast implementation of the Ethereum protocol, in Rust.', 59 | type: NodeType.Execution, 60 | language: Language.Rust, 61 | syncStrategy: [SyncStrategy.Full], 62 | repository: 'https://github.com/paradigmxyz/reth', 63 | documentation: 'https://paradigmxyz.github.io/reth/', 64 | }; 65 | 66 | export const executionNodes: Node[] = [besu, coregeth, erigon, geth, nethermind, reth]; 67 | -------------------------------------------------------------------------------- /src/components/diff/DiffSignatureTypes.tsx: -------------------------------------------------------------------------------- 1 | import { Collapsible } from '@/components/diff/utils/Collapsible'; 2 | import { Markdown } from '@/components/diff/utils/Markdown'; 3 | import { RenderDiff } from '@/components/diff/utils/RenderDiff'; 4 | import { Copyable } from '@/components/ui/Copyable'; 5 | import { formatPrefixByte } from '@/lib/utils'; 6 | import { SignatureType } from '@/types'; 7 | 8 | type Props = { 9 | base: SignatureType[]; 10 | target: SignatureType[]; 11 | onlyShowDiff: boolean; 12 | }; 13 | 14 | const formatSigType = (contents: SignatureType | undefined) => { 15 | if (!contents) return

Not present

; 16 | return ( 17 | <> 18 |
19 | 20 |
21 | {contents.signedData?.map((data) => ( 22 |
23 | 24 |
25 | ))} 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export const DiffSignatureTypes = ({ base, target, onlyShowDiff }: Props) => { 33 | // Generate a sorted list of all transaction types from both base and target. 34 | const allPrefixBytes = [...base.map((t) => t.prefixByte), ...target.map((t) => t.prefixByte)]; 35 | const prefixBytes = [...new Set(allPrefixBytes.sort((a, b) => a - b))]; 36 | 37 | const diffContent = ( 38 | <> 39 |

40 | The || symbol indicates concatenation 41 |

42 | {prefixBytes.map((prefix) => { 43 | const baseSigType = base.find((s) => s.prefixByte === prefix); 44 | const targetSigType = target.find((s) => s.prefixByte === prefix); 45 | 46 | const isEqual = JSON.stringify(baseSigType) === JSON.stringify(targetSigType); 47 | const showSigType = !isEqual || !onlyShowDiff; 48 | 49 | return ( 50 | showSigType && ( 51 |
55 |
56 | 57 |
58 |
{formatSigType(baseSigType)}
59 |
{formatSigType(targetSigType)}
60 |
61 | ) 62 | ); 63 | })} 64 | 65 | ); 66 | 67 | return ; 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/diff/utils/Abi.tsx: -------------------------------------------------------------------------------- 1 | import { Address, getAddress } from 'viem'; 2 | import { Collapsible } from '@/components/diff/utils/Collapsible'; 3 | import { Copyable } from '@/components/ui/Copyable'; 4 | 5 | const formatAddress = (addr: Address) => { 6 | addr = getAddress(addr); 7 | return {`${addr.slice(0, 6)}...${addr.slice(-4)}`}; 8 | }; 9 | 10 | type StandardContractAbi = { 11 | logicAbi: string[]; 12 | }; 13 | 14 | type ProxiedContractAbi = { 15 | proxyAbi: string[]; 16 | logicAbi: string[]; 17 | logicAddress: Address; 18 | }; 19 | 20 | type ContractAbi = StandardContractAbi | ProxiedContractAbi; 21 | 22 | export const Abi = ({ contract }: { contract: ContractAbi }) => { 23 | const hasProxyAbi = 'proxyAbi' in contract && contract.proxyAbi.length > 0; 24 | const hasLogicAbi = 'logicAbi' in contract && contract.logicAbi.length > 0; 25 | const hasLogicAddress = 'logicAddress' in contract && contract.logicAddress.length > 0; 26 | 27 | let proxyAbi = <>; 28 | let logicAbi = <>; 29 | 30 | if (!hasProxyAbi) { 31 | proxyAbi =
ABI not found.
; 32 | } else if (hasProxyAbi) { 33 | proxyAbi = ( 34 |
    35 | {contract.proxyAbi.map((sig) => ( 36 |
  • 37 | {sig} 38 |
  • 39 | ))} 40 |
41 | ); 42 | } 43 | 44 | if (!hasLogicAbi) { 45 | logicAbi =
ABI not found.
; 46 | } else if (hasLogicAbi) { 47 | logicAbi = ( 48 |
    49 | {contract.logicAbi.map((sig) => ( 50 |
  • 51 | {sig} 52 |
  • 53 | ))} 54 |
55 | ); 56 | } 57 | 58 | const proxyAbiContent = ( 59 | <> 60 |
Proxy Contract ABI
61 | {proxyAbi} 62 |
Logic Contract ABI
63 |
64 | {hasLogicAddress && ( 65 |
66 | {/* For some reason the text is not horizontally aligned so we manually add some margin to fix it */} 67 |
68 | Implementation at 69 |
70 | 74 |
75 | )} 76 |
77 | {logicAbi} 78 | 79 | ); 80 | 81 | return ( 82 | 83 | ); 84 | }; 85 | -------------------------------------------------------------------------------- /src/chains/optimism/eips.ts: -------------------------------------------------------------------------------- 1 | import { EIP, EIPCategory } from '@/types/eip'; 2 | import { 3 | eip4399 as eip1399OnMainnet, 4 | eip1559 as eip1559OnMainnet, 5 | eip4895 as eip4895OnMainnet, 6 | eips as ethereumEIPs, 7 | } from '../mainnet/eips'; 8 | import { OptimismHardfork, getOptimismHardforksFrom } from './hardforks'; 9 | 10 | const hardforksFromCanyon: string[] = getOptimismHardforksFrom(OptimismHardfork.Canyon); 11 | const eip1559OnOptimism: EIP = { 12 | ...eip1559OnMainnet, 13 | activeHardforks: hardforksFromCanyon, 14 | parameters: [ 15 | { 16 | name: 'INITIAL_BASE_FEE', 17 | value: 1000000000, 18 | }, 19 | { 20 | name: 'BASE_FEE_MAX_CHANGE_DENOMINATOR', 21 | value: 250, 22 | }, 23 | { 24 | name: 'ELASTICITY_MULTIPLIER', 25 | value: 6, 26 | }, 27 | ], 28 | notes: [ 29 | 'The denominator and elasticity multiplier values of the EIP-1599 formula are modified from their mainnet values.', 30 | ], 31 | references: [ 32 | ...eip1559OnMainnet.references, 33 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/exec-engine.md#1559-parameters', 34 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/superchain-upgrades.md#canyon', 35 | ], 36 | }; 37 | 38 | const eip4399OnOptimism: EIP = { 39 | ...eip1399OnMainnet, 40 | notes: [ 41 | "PREVRANDAO returns the random output of the L1 beacon chain's randomness oracle. This value lags behind the L1 block's prevrandao value by approximately 5 L1 blocks, and is updated when the `L1BlockInfo` predeploy is updated.", 42 | ], 43 | references: [ 44 | ...eip1399OnMainnet.references, 45 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/derivation.md#building-individual-payload-attributes', 46 | 'https://github.com/mds1/evm-diff/issues/21', 47 | ], 48 | }; 49 | 50 | const eip4895OnOptimism: EIP = { 51 | ...eip4895OnMainnet, 52 | notes: [ 53 | 'Optimism has an empty withdrawals list in L2 blocks to be compatible with L1, but since there are no validators the list is always empty.', 54 | ], 55 | references: [ 56 | ...eip4895OnMainnet.references, 57 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/derivation.md#building-individual-payload-attributes', 58 | 'https://github.com/ethereum-optimism/specs/blob/main/specs/superchain-upgrades.md#canyon', 59 | ], 60 | }; 61 | 62 | export const eips: EIP[] = ethereumEIPs 63 | .filter((eip) => { 64 | // Exclude consensus-related EIPs. 65 | return eip.category !== EIPCategory.Consensus; 66 | }) 67 | .map((eip) => { 68 | // EIPs modified by Optimism hard forks. 69 | if (eip.number === 1559) return eip1559OnOptimism; 70 | if (eip.number === 4399) return eip4399OnOptimism; 71 | if (eip.number === 4895) return eip4895OnOptimism; 72 | return eip; 73 | }); 74 | -------------------------------------------------------------------------------- /src/components/diff/DiffPredeploys.tsx: -------------------------------------------------------------------------------- 1 | import { Address, getAddress } from 'viem'; 2 | import { Abi } from '@/components/diff/utils/Abi'; 3 | import { Collapsible } from '@/components/diff/utils/Collapsible'; 4 | import { Markdown } from '@/components/diff/utils/Markdown'; 5 | import { RenderDiff } from '@/components/diff/utils/RenderDiff'; 6 | import { Copyable } from '@/components/ui/Copyable'; 7 | import { Predeploy } from '@/types'; 8 | 9 | type Props = { 10 | base: Predeploy[]; 11 | target: Predeploy[]; 12 | onlyShowDiff: boolean; 13 | }; 14 | 15 | const formatPredeploy = (predeploy: Predeploy | undefined) => { 16 | if (!predeploy) return

Not present

; 17 | return ( 18 | <> 19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 | ); 31 | }; 32 | 33 | const formatAddress = (addr: Address) => { 34 | addr = getAddress(addr); 35 | return {`${addr.slice(0, 6)}...${addr.slice(-4)}`}; 36 | }; 37 | 38 | export const DiffPredeploys = ({ base, target, onlyShowDiff }: Props) => { 39 | // Generate a sorted list of all predeploys from both base and target. 40 | const sortedAddrs = [ 41 | ...base.map((p) => getAddress(p.address)), 42 | ...target.map((p) => getAddress(p.address)), 43 | ].sort((a, b) => a.localeCompare(b)); 44 | const predeployAddrs = [...new Set(sortedAddrs)]; 45 | 46 | const diffContent = ( 47 | <> 48 | {predeployAddrs.map((addr) => { 49 | const basePredeploy = base.find((p) => getAddress(p.address) === addr); 50 | const targetPredeploy = target.find((p) => getAddress(p.address) === addr); 51 | 52 | const isEqual = JSON.stringify(basePredeploy) === JSON.stringify(targetPredeploy); 53 | const showPredeploy = !isEqual || !onlyShowDiff; 54 | 55 | return ( 56 | showPredeploy && ( 57 |
61 |
62 | 63 |
64 |
{formatPredeploy(basePredeploy)}
65 |
{formatPredeploy(targetPredeploy)}
66 |
67 | ) 68 | ); 69 | })} 70 | 71 | ); 72 | 73 | return ; 74 | }; 75 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/stack/dup.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { CURRENT_MAINNET_HARDFORK } from '@/lib/constants'; 3 | import { 4 | OpcodeGroup, 5 | ethSpecsOpcodeSrc, 6 | evmCodesOpcodesLink, 7 | evmCodesPlaygroundLink, 8 | } from '@/lib/opcodes'; 9 | import { Opcode, Variable } from '@/types'; 10 | 11 | const generateIgnoredValues = (n: number): Variable[] => { 12 | const alphabet = 'abcdefghijklmno'; 13 | if (n > alphabet.length) { 14 | throw new Error('Error while generating the ignored values for the dup opcode'); 15 | } 16 | const array: Variable[] = []; 17 | for (let i = 0; i < n; i++) { 18 | const v: Variable = { 19 | name: alphabet[i], 20 | description: 'An ignored value', 21 | }; 22 | array.push(v); 23 | } 24 | return array; 25 | }; 26 | 27 | const dup = (n: number): Opcode => { 28 | if (n < 1 || n > 16) { 29 | throw new Error('Dup number must be between 1 and 16'); 30 | } 31 | const number = 0x7f + n; 32 | const description = `Duplicate ${ 33 | n == 1 ? '1st' : n == 2 ? '2nd' : n == 3 ? '3rd' : `${n}th` 34 | } stack item`; 35 | const ignoredValues: Variable[] = n > 1 ? generateIgnoredValues(n - 1) : []; 36 | const zeros: string[] = n > 1 ? Array(n - 1).fill('0') : []; 37 | return { 38 | number, 39 | name: `dup${n}`, 40 | description, 41 | minGas: 3, 42 | inputs: [ 43 | ...ignoredValues, 44 | { 45 | name: 'value', 46 | description: 'The value to duplicate', 47 | }, 48 | ], 49 | outputs: [ 50 | { 51 | name: 'value', 52 | description: 'The duplicated value', 53 | }, 54 | ...ignoredValues, 55 | { 56 | name: 'value', 57 | description: 'The original value', 58 | }, 59 | ], 60 | examples: [ 61 | { 62 | input: [...zeros, '1'], 63 | output: ['1', ...zeros, '1'], 64 | }, 65 | ], 66 | // TODO: playgroundLink: evmCodesPlaygroundLink(`%27xSet%20state${'FF'.repeat(n)}%5Cn~%27~PUSH${n}%200%01~_`), 67 | playgroundLink: evmCodesPlaygroundLink( 68 | '%27zSet%20styPUSH1%201~~zDuplicyDUP1%27~%5Cnz%2F%2F%20yate~%01yz~_' 69 | ), 70 | errorCases: ['Not enough gas', 'Not enough values on the stack', 'Stack overflow'], 71 | references: [ 72 | evmCodesOpcodesLink(number), 73 | ethSpecsOpcodeSrc(CURRENT_MAINNET_HARDFORK, OpcodeGroup.Stack, 179 + n), 74 | ], 75 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 76 | }; 77 | }; 78 | 79 | const createDupOpcodes = (start: number, end: number): Record => { 80 | const opcodes: Record = {}; 81 | for (let i = start; i <= end; i++) { 82 | const dup_i = dup(i); 83 | opcodes[dup_i.number] = dup_i; 84 | } 85 | return opcodes; 86 | }; 87 | 88 | export const opcodes = createDupOpcodes(1, 16); 89 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/stack/push.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { CURRENT_MAINNET_HARDFORK } from '@/lib/constants'; 3 | import { 4 | OpcodeGroup, 5 | ethSpecsOpcodeSrc, 6 | evmCodesOpcodesLink, 7 | evmCodesPlaygroundLink, 8 | } from '@/lib/opcodes'; 9 | import { Opcode } from '@/types'; 10 | 11 | const push = (n: number): Opcode => { 12 | if (n < 1 || n > 32) { 13 | throw new Error('Push number must be between 1 and 32'); 14 | } 15 | const number = 0x5f + n; 16 | return { 17 | number, 18 | name: `push${n}`, 19 | description: `Place ${n} byte item on stack`, 20 | minGas: 3, 21 | outputs: [ 22 | { 23 | name: 'value', 24 | description: 'The pushed value, aligned to the right (put in the lowest significant bytes)', 25 | }, 26 | ], 27 | examples: [ 28 | { 29 | output: [`0x${'00'.repeat(n)}"`, `0x${'FF'.repeat(n)}"`], 30 | }, 31 | ], 32 | playgroundLink: evmCodesPlaygroundLink(`%27~x${'FF'.repeat(n)}%5Cn~%27~PUSH${n}%200%01~_`), 33 | errorCases: ['Not enough gas', 'Stack overflow'], 34 | notes: [ 35 | 'The new value is put on top of the stack, incrementing all the other value indices. The values for a specific opcode thus have to be pushed in reverse order of the stack. For example, with MSTORE, the first value pushed would have to be value, and then offset.', 36 | ], 37 | references: [ 38 | evmCodesOpcodesLink(number), 39 | ethSpecsOpcodeSrc(CURRENT_MAINNET_HARDFORK, OpcodeGroup.Stack, 146 + n), 40 | ], 41 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 42 | }; 43 | }; 44 | 45 | const createPushOpcodes = (start: number, end: number): Record => { 46 | const opcodes: Record = {}; 47 | for (let i = start; i <= end; i++) { 48 | const push_i = push(i); 49 | opcodes[push_i.number] = push_i; 50 | } 51 | return opcodes; 52 | }; 53 | 54 | export const opcodes = createPushOpcodes(1, 32); 55 | 56 | export const push0: Opcode = { 57 | number: 0x5f, 58 | name: 'push0', 59 | description: 'Place value 0 on stack', 60 | minGas: 2, 61 | outputs: [ 62 | { 63 | name: 'value', 64 | description: 'The pushed value, equal to 0', 65 | }, 66 | ], 67 | examples: [ 68 | { 69 | output: ['0x00'], 70 | }, 71 | ], 72 | errorCases: ['Not enough gas', 'Stack overflow'], 73 | notes: [ 74 | 'The new value is put on top of the stack, incrementing all the other value indices. The values for a specific opcode thus have to be pushed in reverse order of the stack. For example, with MSTORE, the first value pushed would have to be value, and then offset.', 75 | ], 76 | references: [ 77 | evmCodesOpcodesLink(0x5f), 78 | ethSpecsOpcodeSrc(CURRENT_MAINNET_HARDFORK, OpcodeGroup.Stack, 146), 79 | ], 80 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Shanghai), 81 | }; 82 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repo uses [Next.js](https://github.com/vercel/next.js/), [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss), [TypeScript](https://github.com/microsoft/TypeScript), [pnpm](https://github.com/pnpm/pnpm), and [viem](https://github.com/wagmi-dev/viem). To get started: 4 | 5 | ```sh 6 | # Install dependencies. 7 | pnpm install 8 | 9 | # Start the development server. 10 | pnpm dev 11 | 12 | # Format files. 13 | pnpm fmt 14 | ``` 15 | 16 | See the open [issues](https://github.com/mds1/evm-diff/issues) for current needs, and feel free to create new issues for bugs, feature requests, or other ideas. 17 | 18 | ## Bounties 19 | 20 | Most issues are eligible for bounties. Some issues will have something like "(bounty: X ETH 🔴)" at the end of the issue title, where `X ETH` is the number of ETH paid out for this bounty. Others will say nothing, but are still eligible—I just haven't gotten around to assigning a payment amount. 21 | 22 | To apply for and claim a bounty: 23 | 24 | 1. If the issue has no bounty listed, message me on [Twitter](https://twitter.com/msolomon44), [Telegram](https://t.me/msolomon4), or [Discord](https://discordapp.com/users/417428774653657089) to work out an amount. 25 | 2. Leave a brief comment explaining your work plan (may be very brief is issue is well-scoped), and wait to begin work until you are assigned to the issue. If you have any questions about the issue scope, you can ask in the issue or message me. (There is no guarantee that leaving a comment means you will be assigned, if multiple people are interested in the same issue). 26 | 3. I'm not aware of any sufficient github issue bounty platforms, so there is no intermediary managing the bounties. This means you have to trust that I'll pay it out, which I will if the work meets the issue's requirements and is sufficiently high quality to close that issue. 27 | 4. Feel free to open a draft PR before completion if you have any questions. 28 | 5. Include your **OP Mainnet** payout address (all payouts will be on [OP Mainnet](https://docs.optimism.io/chain/networks#op-mainnet)) in the PR description, and once the PR is reviewed and merged I will transfer the ETH. 29 | 30 | ## Architecture 31 | 32 | The specs for each chain live in `src/chains/[chainName]/*.ts`. 33 | Each chain's folder is structured similar to the [ethereum/execution-specs](https://github.com/ethereum/execution-specs) repo. 34 | For example, the `src/ethereum/shanghai` folder in that repo contains the specs for the latest hard fork (Shanghai) on Ethereum mainnet, and information about precompiles lives in the `vm/precompiled_contracts` subfolder. 35 | That folder contains one file for each precompile. 36 | Since EVM Diff doesn't have to actually implement the precompiles, we just use a single file for all precompile data, which lives in this repo at `src/chains/mainnet/vm/precompiles.ts`. 37 | 38 | As more aspects of the execution spec are added, they should be added in such a way to continue this pattern of mirroring the structure of the execution-specs repo. 39 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/stack/swap.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { CURRENT_MAINNET_HARDFORK } from '@/lib/constants'; 3 | import { 4 | OpcodeGroup, 5 | ethSpecsOpcodeSrc, 6 | evmCodesOpcodesLink, 7 | evmCodesPlaygroundLink, 8 | } from '@/lib/opcodes'; 9 | import { Opcode, Variable } from '@/types'; 10 | 11 | const generateIgnoredValues = (n: number): Variable[] => { 12 | const alphabet = 'abcdefghijklmno'; 13 | if (n > alphabet.length) { 14 | throw new Error('Error while generating the ignored values for the dup opcode'); 15 | } 16 | const array: Variable[] = []; 17 | for (let i = 0; i < n; i++) { 18 | const v: Variable = { 19 | name: alphabet[i], 20 | description: 'An ignored value', 21 | }; 22 | array.push(v); 23 | } 24 | return array; 25 | }; 26 | 27 | const swap = (n: number): Opcode => { 28 | if (n < 1 || n > 16) { 29 | throw new Error('Swap number must be between 1 and 16'); 30 | } 31 | const number = 0x8f + n; 32 | const description = `Exchange 1st and ${ 33 | n == 1 ? '2nd' : n == 2 ? '3rd' : `${n + 1}th` 34 | } stack items`; 35 | const ignoredValues: Variable[] = n > 1 ? generateIgnoredValues(n - 1) : []; 36 | const zeros: string[] = n > 1 ? Array(n - 1).fill('0') : []; 37 | return { 38 | number, 39 | name: `swap${n}`, 40 | description, 41 | minGas: 3, 42 | inputs: [ 43 | { 44 | name: 'value_1', 45 | description: 'The value to swap', 46 | }, 47 | ...ignoredValues, 48 | { 49 | name: 'value_2', 50 | description: 'The other value to swap', 51 | }, 52 | ], 53 | outputs: [ 54 | { 55 | name: 'value_2', 56 | description: 'The swapped value', 57 | }, 58 | ...ignoredValues, 59 | { 60 | name: 'value_1', 61 | description: 'The other swapped value', 62 | }, 63 | ], 64 | examples: [ 65 | { 66 | input: ['1', ...zeros, '2'], 67 | output: ['2', ...zeros, '1'], 68 | }, 69 | ], 70 | // TODO: playgroundLink: evmCodesPlaygroundLink(`%27xSet%20state${'FF'.repeat(n)}%5Cn~%27~PUSH${n}%200%01~_`), 71 | playgroundLink: evmCodesPlaygroundLink( 72 | '%27xet%20state~2zzzzzv1yyxwapySWAP14%27~yPUSH1%20v0y%5Cnx%2F%2F%20Svz~0~%01vxyz~_' 73 | ), 74 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 75 | references: [ 76 | evmCodesOpcodesLink(number), 77 | ethSpecsOpcodeSrc(CURRENT_MAINNET_HARDFORK, OpcodeGroup.Stack, 196 + n), 78 | ], 79 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 80 | }; 81 | }; 82 | 83 | const createSwapOpcodes = (start: number, end: number): Record => { 84 | const opcodes: Record = {}; 85 | for (let i = start; i <= end; i++) { 86 | const swap_i = swap(i); 87 | opcodes[swap_i.number] = swap_i; 88 | } 89 | return opcodes; 90 | }; 91 | 92 | export const opcodes = createSwapOpcodes(1, 16); 93 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/memory/mstore8.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const mstore8: Opcode = { 11 | number: 0x53, 12 | name: 'mstore8', 13 | description: 'Save byte to memory', 14 | minGas: 3, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '3', 18 | }, 19 | dynamicGasCost: { 20 | name: 'Memory expansion', 21 | description: 22 | 'During a smart contract execution, memory can be accessed with opcodes. When an offset is first accessed (either read or write), memory may trigger an expansion, which costs gas. Memory expansion may be triggered when the byte offset (modulo 32) accessed is bigger than previous offsets. If a larger offset trigger of memory expansion occurs, the cost of accessing the higher offset is computed and removed from the total gas available at the current call context. Thus, only the additional bytes of memory must be paid for.', 23 | expression: 'memory_expansion_cost = new_memory_cost - last_memory_cost', 24 | variables: [ 25 | { 26 | name: 'memory_cost', 27 | description: 'The memory cost function for a given machine state', 28 | expression: '(memory_size_word ** 2) / 512 + (3 * memory_size_word)', 29 | }, 30 | { 31 | name: 'memory_size_word', 32 | description: 33 | 'Number of (32-byte) words required for memory after the operation in question', 34 | expression: '(memory_byte_size + 31) / 32', 35 | }, 36 | { 37 | name: 'memory_byte_size', 38 | description: 39 | 'The highest referenced memory address after the operation in question (in bytes)', 40 | }, 41 | ], 42 | }, 43 | }, 44 | inputs: [ 45 | { 46 | name: 'offset', 47 | description: 'The offset in the memory in bytes', 48 | }, 49 | { 50 | name: 'value', 51 | description: 52 | 'The 1-byte value to write in the memory (the least significant byte of the 32-byte stack value).', 53 | }, 54 | ], 55 | examples: [ 56 | { 57 | input: ['0', '0xFFFF'], 58 | memory: { 59 | before: '', 60 | after: '0xFF', 61 | }, 62 | }, 63 | { 64 | input: ['1', '0xFF'], 65 | memory: { 66 | before: '0xFF', 67 | after: '0xFFFF', 68 | }, 69 | }, 70 | ], 71 | playgroundLink: evmCodesPlaygroundLink( 72 | '%27z1v2%200xFFFFy0w~z2y0xFFy1w%27~%5Cnz%2F%2F%20Example%20yv1%20w~MSTORE8~v~PUSH%01vwyz~_' 73 | ), 74 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 75 | references: [ 76 | evmCodesOpcodesLink(0x53), 77 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Memory, 58), 78 | ], 79 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 80 | }; 81 | -------------------------------------------------------------------------------- /src/components/diff/DiffAccountTypes.tsx: -------------------------------------------------------------------------------- 1 | import { Collapsible } from '@/components/diff/utils/Collapsible'; 2 | import { Markdown } from '@/components/diff/utils/Markdown'; 3 | import { RenderDiff } from '@/components/diff/utils/RenderDiff'; 4 | import { Copyable } from '@/components/ui/Copyable'; 5 | import { AccountType } from '@/types'; 6 | 7 | type Props = { 8 | base: AccountType[]; 9 | target: AccountType[]; 10 | onlyShowDiff: boolean; 11 | }; 12 | 13 | const formatAccountType = (contents: AccountType | undefined) => { 14 | if (!contents) return
Not present
; 15 | return ( 16 | <> 17 |
18 | 19 |
20 |
21 | {Object.entries(contents.properties).map(([key, value]) => { 22 | // Return a two column table of the property name and description. 23 | return ( 24 |
25 |
26 | {key === 'canBatchTxs' && 'Can batch transactions'} 27 | {key === 'canInitiateTxs' && 'Can initiate transactions'} 28 | {key === 'hasCode' && 'Has code'} 29 | {key === 'hasKeyPair' && 'Has key pair'} 30 | {key === 'hasStorage' && 'Has storage'} 31 |
32 |
{value ? 'Yes' : 'No'}
33 |
34 | ); 35 | })} 36 |
37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | export const DiffAccountTypes = ({ base, target, onlyShowDiff }: Props) => { 44 | // Generate a sorted list of all account types from both base and target. 45 | const sortedAccountTypeNames = [...base.map((a) => a.name), ...target.map((a) => a.name)].sort( 46 | (a, b) => a.localeCompare(b) 47 | ); 48 | const accountTypeNames = [...new Set(sortedAccountTypeNames)]; 49 | 50 | const diffContent = ( 51 | <> 52 | {accountTypeNames.map((name) => { 53 | const baseAccountType = base.find((a) => a.name === name); 54 | const targetAccountType = target.find((a) => a.name === name); 55 | 56 | const isEqual = JSON.stringify(baseAccountType) === JSON.stringify(targetAccountType); 57 | const showAccountType = !isEqual || !onlyShowDiff; 58 | 59 | return ( 60 | showAccountType && ( 61 |
65 |
66 | 67 |
68 |
{formatAccountType(baseAccountType)}
69 |
{formatAccountType(targetAccountType)}
70 |
71 | ) 72 | ); 73 | })} 74 | 75 | ); 76 | 77 | return ; 78 | }; 79 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/memory/mstore.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | export const mstore: Opcode = { 11 | number: 0x52, 12 | name: 'mstore', 13 | description: 'Save word to memory', 14 | minGas: 3, 15 | gasComputation: { 16 | staticGasCost: { 17 | expression: '3', 18 | }, 19 | dynamicGasCost: { 20 | name: 'Memory expansion', 21 | description: 22 | 'During a smart contract execution, memory can be accessed with opcodes. When an offset is first accessed (either read or write), memory may trigger an expansion, which costs gas. Memory expansion may be triggered when the byte offset (modulo 32) accessed is bigger than previous offsets. If a larger offset trigger of memory expansion occurs, the cost of accessing the higher offset is computed and removed from the total gas available at the current call context. Thus, only the additional bytes of memory must be paid for.', 23 | expression: 'memory_expansion_cost = new_memory_cost - last_memory_cost', 24 | variables: [ 25 | { 26 | name: 'memory_cost', 27 | description: 'The memory cost function for a given machine state', 28 | expression: '(memory_size_word ** 2) / 512 + (3 * memory_size_word)', 29 | }, 30 | { 31 | name: 'memory_size_word', 32 | description: 33 | 'Number of (32-byte) words required for memory after the operation in question', 34 | expression: '(memory_byte_size + 31) / 32', 35 | }, 36 | { 37 | name: 'memory_byte_size', 38 | description: 39 | 'The highest referenced memory address after the operation in question (in bytes)', 40 | }, 41 | ], 42 | }, 43 | }, 44 | inputs: [ 45 | { 46 | name: 'offset', 47 | description: 'The offset in the memory in bytes', 48 | }, 49 | { 50 | name: 'value', 51 | description: 'The 32-bytes value to write in the memory', 52 | }, 53 | ], 54 | examples: [ 55 | { 56 | input: ['0', '0xFF'], 57 | memory: { 58 | before: '', 59 | after: '0x00000000000000000000000000000000000000000000000000000000000000FF', 60 | }, 61 | }, 62 | { 63 | input: ['1', '0xFF'], 64 | memory: { 65 | before: '', 66 | after: '0x0000000000000000000000000000000000000000000000000000000000000000FF', 67 | }, 68 | }, 69 | ], 70 | playgroundLink: evmCodesPlaygroundLink( 71 | '%27z1v0wyz2v1w%27~yPUSH1%20z%2F%2F%20Example%20y%5CnwyMSTOREyv~0xFF~%01vwyz~_' 72 | ), 73 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 74 | references: [ 75 | evmCodesOpcodesLink(0x52), 76 | '[evm.codes, Memory Expansion](https://www.evm.codes/about#memoryexpansion)', 77 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.Memory, 27), 78 | ], 79 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 80 | }; 81 | -------------------------------------------------------------------------------- /src/chains/mainnet/signatureTypes.ts: -------------------------------------------------------------------------------- 1 | import { SignatureType } from '@/types'; 2 | 3 | const eip2718 = 'https://eips.ethereum.org/EIPS/eip-2718'; 4 | const sigTypes = 5 | 'https://github.com/ethereum/execution-specs/blob/6f8614566e7117afa327ad054c3f4bfe19694d73/lists/signature-types/README.md'; 6 | 7 | // Some signature prefix bytes are invalid because they collide with the initial byte of valid RLP 8 | // encoded transactions. The range of invalid prefix bytes is 0xc0-0xff, which is a length of 64. 9 | const invalidSigTypes: SignatureType[] = [...Array(64).keys()].map((i) => ({ 10 | prefixByte: i + 0xc0, 11 | description: 'Invalid; collides with the initial byte of valid RLP encoded transactions', 12 | signedData: undefined, 13 | signs: undefined, 14 | references: [sigTypes], 15 | })); 16 | 17 | export const validSigTypes: SignatureType[] = [ 18 | { 19 | prefixByte: 0x0, 20 | description: 'Legacy (untyped) transaction', 21 | signedData: [ 22 | '`keccak256(rlp([nonce, gasPrice, gasLimit, to, value, data]))`', 23 | '`keccak256(rlp([nonce, gasPrice, gasLimit, to, value, data, chainId, 0, 0]))`', 24 | ], 25 | signs: 'transaction', 26 | references: ['https://eips.ethereum.org/EIPS/eip-155', eip2718, sigTypes], 27 | notes: [ 28 | "When signing over chain ID, the signature's v-value is computed as `{0,1} + chainId * 2 + 35`, where 0 and 1 are signature's y-parity.", 29 | ], 30 | }, 31 | { 32 | prefixByte: 0x1, 33 | description: 'EIP-2930 Access list transaction', 34 | signedData: [ 35 | '`keccak256(0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, value, data, accessList]))`', 36 | ], 37 | signs: 'transaction', 38 | references: ['https://eips.ethereum.org/EIPS/eip-2930', eip2718, sigTypes], 39 | }, 40 | { 41 | prefixByte: 0x2, 42 | description: 'EIP-1559 transaction', 43 | signedData: [ 44 | '`keccak256(0x02 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]))`', 45 | ], 46 | signs: 'transaction', 47 | references: ['https://eips.ethereum.org/EIPS/eip-1559', eip2718, sigTypes], 48 | }, 49 | { 50 | prefixByte: 0x3, 51 | description: 'Unused, but tentatively reserved for EIP-3074', 52 | signedData: ['`keccak256(0x03 || chainId || paddedInvokerAddress || commit)`'], 53 | signs: 'transaction', 54 | references: ['https://eips.ethereum.org/EIPS/eip-3074', eip2718, sigTypes], 55 | }, 56 | { 57 | prefixByte: 0x19, 58 | description: 59 | 'Used for signatures of data payloads to prevent collisions between data signatures and transaction signatures', 60 | signedData: ['`keccak256(0x19 || 1-byte-version || versionSpecificData || dataToSign)`'], 61 | signs: 'data', 62 | references: ['https://eips.ethereum.org/EIPS/eip-191', sigTypes], 63 | }, 64 | ...invalidSigTypes, 65 | ]; 66 | 67 | const allSigTypes = [...validSigTypes, ...invalidSigTypes]; 68 | 69 | // Reshape into a map where the key is the prefix byte. 70 | export const signatureTypes: Record = allSigTypes.reduce((acc, sigType) => { 71 | acc[sigType.prefixByte] = sigType; 72 | return acc; 73 | }, {} as Record); 74 | -------------------------------------------------------------------------------- /src/components/layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { ExternalLink } from '@/components/layout/ExternalLink'; 2 | import { ThemeSwitcher } from '@/components/layout/ThemeSwitcher'; 3 | import { COMPANY_NAME, COMPANY_URL, GITHUB_URL, TWITTER_URL } from '@/lib/constants'; 4 | 5 | const navigation = [ 6 | { 7 | name: 'Twitter', 8 | href: TWITTER_URL, 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | icon: (props: any) => ( 11 | 12 | 13 | 14 | ), 15 | }, 16 | { 17 | name: 'GitHub', 18 | href: GITHUB_URL, 19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 20 | icon: (props: any) => ( 21 | 22 | 27 | 28 | ), 29 | }, 30 | ]; 31 | 32 | export const Footer = () => { 33 | const currentYear = new Date().getFullYear(); 34 | return ( 35 |
36 |
37 |
38 |

39 | © {currentYear} . All rights 40 | reserved. 41 |

42 |
43 | 44 |
45 | {navigation.map((item) => ( 46 | 47 | <> 48 | {item.name} 49 | 52 | ))} 53 |
54 | 55 | {} 56 |
57 |
58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /src/chains/mainnet/vm/opcodes/system/return.ts: -------------------------------------------------------------------------------- 1 | import { MainnetHardfork, getMainnetHardforksFrom } from '@/chains/mainnet/hardforks'; 2 | import { 3 | OpcodeGroup, 4 | ethSpecsOpcodeSrc, 5 | evmCodesOpcodesLink, 6 | evmCodesPlaygroundLink, 7 | } from '@/lib/opcodes'; 8 | import { Opcode } from '@/types'; 9 | 10 | // Note: the opcode is named with an underscore to avoid the `return` keyword. 11 | export const _return: Opcode = { 12 | number: 0xf3, 13 | name: 'return', 14 | description: 'Halt execution returning output data', 15 | minGas: 0, 16 | gasComputation: { 17 | staticGasCost: { 18 | expression: '0', 19 | }, 20 | dynamicGasCost: { 21 | expression: 'memory_expansion_cost', 22 | variables: [ 23 | { 24 | name: 'memory_expansion_cost', 25 | description: 26 | 'During a smart contract execution, memory can be accessed with opcodes. When an offset is first accessed (either read or write), memory may trigger an expansion, which costs gas. Memory expansion may be triggered when the byte offset (modulo 32) accessed is bigger than previous offsets. If a larger offset trigger of memory expansion occurs, the cost of accessing the higher offset is computed and removed from the total gas available at the current call context. Thus, only the additional bytes of memory must be paid for.', 27 | expression: 'memory_expansion_cost = new_memory_cost - last_memory_cost', 28 | variables: [ 29 | { 30 | name: 'memory_cost', 31 | description: 'The memory cost function for a given machine state', 32 | expression: '(memory_size_word ** 2) / 512 + (3 * memory_size_word)', 33 | }, 34 | { 35 | name: 'memory_size_word', 36 | description: 37 | 'Number of (32-byte) words required for memory after the operation in question', 38 | expression: '(memory_byte_size + 31) / 32', 39 | }, 40 | { 41 | name: 'memory_byte_size', 42 | description: 43 | 'The highest referenced memory address after the operation in question (in bytes)', 44 | }, 45 | ], 46 | }, 47 | ], 48 | }, 49 | }, 50 | inputs: [ 51 | { 52 | name: 'offset', 53 | description: 54 | 'The byte offset in the memory in bytes, to copy what will be the return data of this context', 55 | }, 56 | { 57 | name: 'size', 58 | description: 'The byte size to copy (size of the return data)', 59 | }, 60 | ], 61 | examples: [ 62 | { 63 | input: ['0', '2'], 64 | memory: { 65 | before: '0xFF01', 66 | after: '0xFF01', 67 | }, 68 | returndata: '0xFF01', // calling context return data 69 | }, 70 | ], 71 | playgroundLink: evmCodesPlaygroundLink( 72 | '%27wSet%20the%20statev32%200xFF01uuuuuz0yMSTOREyywExamplez2z0yRETURN%27~000000zv1%20y%5Cnw%2F%2F%20vyPUSHu~~%01uvwyz~_' 73 | ), 74 | errorCases: ['Not enough gas', 'Not enough values on the stack'], 75 | references: [ 76 | evmCodesOpcodesLink(0xf3), 77 | ethSpecsOpcodeSrc(MainnetHardfork.Shanghai, OpcodeGroup.System, 233), 78 | ], 79 | supportedHardforks: getMainnetHardforksFrom(MainnetHardfork.Frontier), 80 | }; 81 | -------------------------------------------------------------------------------- /src/components/diff/utils/Markdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactMarkdown from 'react-markdown'; 3 | import { ExternalLink } from '@/components/layout/ExternalLink'; 4 | 5 | export const Markdown: React.FC<{ content: string; codeSize?: string; className?: string }> = ({ 6 | content, 7 | codeSize, 8 | className, 9 | }) => { 10 | // --- Transform Content --- 11 | const transformURLs = (content: string): string => { 12 | // Define regex patterns for the different types of URLs we want to convert to named links. 13 | const eipURLPattern = /(https:\/\/eips\.ethereum\.org\/EIPS\/eip-(\d+))/g; 14 | const evmCodesURLPattern = /(https:\/\/www\.evm\.codes\/#([0-9a-fA-F]{2}))/g; 15 | const githubIssueURLPattern = /(https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+))/g; 16 | const githubURLPattern = 17 | /(https:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/([^#]+)(?:#([^ ]+))?)/g; 18 | 19 | // Do the conversions. 20 | let matched = false; 21 | const transformedContent: string = content 22 | .replace(eipURLPattern, (_match, fullUrl, number) => { 23 | matched = true; 24 | return `[EIP-${number}](${fullUrl})`; 25 | }) 26 | .replace(evmCodesURLPattern, (_match, fullUrl, opcode) => { 27 | matched = true; 28 | return `[evm.codes, opcode \`0x${opcode}\`](${fullUrl})`; 29 | }) 30 | .replace(githubIssueURLPattern, (_match, fullUrl, user, repo, issueNumber) => { 31 | matched = true; 32 | return `[${user}/${repo}, Issue #${issueNumber}](${fullUrl})`; 33 | }) 34 | .replace(githubURLPattern, (_match, fullUrl, user, repo, commit, path, _line) => { 35 | matched = true; 36 | return `[${user}/${repo}, \`${path}@${commit.substring(0, 7)}\`](${fullUrl})`; 37 | }); 38 | 39 | if (matched) { 40 | return transformedContent; 41 | } 42 | 43 | // Add a fallback for any URLs that don't match the predefined patterns. 44 | // We take care to avoid matching URLs that are already included in markdown style. 45 | const fallbackPattern = /(? `[${fullUrl}](${fullUrl})` 49 | ); 50 | }; 51 | 52 | const transformedContent = transformURLs(content); 53 | 54 | // --- Render --- 55 | return ( 56 | { 61 | if (!href) return <>; 62 | const isExternal = href.startsWith('http://') || href.startsWith('https://'); 63 | if (isExternal) return {children}; 64 | return ( 65 | 66 | {children} 67 | 68 | ); 69 | }, 70 | // Make sure all code blocks have a smaller font size because otherwise they look big 71 | // relative to the text. 72 | code: ({ node: _node, inline: _inline, ...props }) => ( 73 | 74 | ), 75 | }} 76 | > 77 | {transformedContent} 78 | 79 | ); 80 | }; 81 | --------------------------------------------------------------------------------