├── .gitignore ├── README.md ├── daily-progress ├── day00.md ├── day01.md ├── day02.md ├── day03.md ├── day04.md ├── day05.md ├── day06.md ├── day07.md ├── day08.md ├── day09.md ├── day10.md ├── day11.md ├── day12.md ├── day13.md ├── day14.md ├── day15.md ├── day16.md ├── day17.md └── day18.md └── evm-from-scratch-challenge ├── .gitignore ├── .prettierrc ├── babel.config.js ├── evm.json ├── index.test.ts ├── index.ts ├── input.json ├── jest-environment-fail-fast.js ├── package.json ├── src ├── constants.ts ├── errors.ts ├── evm.ts ├── globalState.ts ├── logger.ts ├── machine-state │ ├── memory.ts │ ├── stack.ts │ ├── storage.ts │ ├── types.ts │ └── utils.ts ├── opcodes │ ├── runners.ts │ ├── runners │ │ ├── arithmetic.ts │ │ ├── bitwise.ts │ │ ├── block.ts │ │ ├── comparison.ts │ │ ├── control-flow.ts │ │ ├── environmental.ts │ │ ├── keccak.ts │ │ ├── logging.ts │ │ ├── memory.ts │ │ ├── stack.ts │ │ ├── storage.ts │ │ └── system.ts │ ├── types.ts │ └── utils.ts ├── types.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .obsidian 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EVM From Scratch Challenge 2 | 3 | > I implemented the Ethereum Virtual Machine from scratch in Typescript. The only external dependency is the `ethereum-cryptography` package used for the keccak256 hash function. 4 | 5 | ## Introduction 6 | 7 | The EVM is the core of the Ethereum protocol. It is a stack-based virtual machine that executes bytecode and updates the glogbal state according to the rules described in the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). The EVM is responsible for executing smart contracts and is what makes Ethereum a "World Computer". 8 | 9 | ## What is this challenge about? 10 | 11 | [W1nt3r.eth](https://twitter.com/w1nt3r_eth) is the creator of the [EVM From Scratch](https://github.com/w1nt3r-eth/evm-from-scratch) challenge, which consists in a series of 116 tests that need to be passed sequentially in order to have a working EVM implementation. The challenge is a great way to learn about the EVM and how it works. I highly recommend it if you want to _really understand_ what is going on under the hood of Ethereum smart contracts. 12 | 13 | ## Progress Log 14 | 15 | - [x] [Day 0](./daily-progress/day00.md): Research of relevant learning material & tools to get started 16 | - [x] [Day 1](./daily-progress/day01.md): Gathering more resources & reading Mastering Ethereum chapter 13 17 | - [x] [Day 2](./daily-progress/day02.md): Setting up the EVM-from-scratch challenge & EVM class 18 | - [x] [Day 3](./daily-progress/day03.md): Reading the yellow paper & EVM inception (EVM inside EVM) 19 | - [x] [Day 4](./daily-progress/day04.md): Stack & memory implementation & first Opcodes 20 | - [x] [Day 5](./daily-progress/day05.md): PUSH, POP, SUB Opcodes, MachineState context struct 21 | - [x] [Day 6](./daily-progress/day06.md): Most Arithmetic, Comparison, Bitwise operations & JUMP Opcodes 22 | - [x] [Day 7](./daily-progress/day07.md): Memory structure & related Opcodes 23 | - [x] [Day 8](./daily-progress/day08.md): TxData, globalState, Block data & related Opcodes 24 | - [x] [Day 9](./daily-progress/day09.md): More Environmental Opcodes & CALLDATALOAD. "Officially" started the challenge! 25 | - [x] [Day 10](./daily-progress/day10.md): CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY Opcodes 26 | - [x] [Day 11](./daily-progress/day11.md): EXTCODESIZE, EXTCODECOPY, SELFBALANCE Opcodes 27 | - [x] [Day 12](./daily-progress/day12.md): Research & study on the Storage / data layer of the Ethereum protocol 28 | - [x] [Day 13](./daily-progress/day13.md): Simple Storage implementation, SSTORE, SLOAD, RETURN, REVERT Opcodes 29 | - [x] [Day 14](./daily-progress/day14.md): Upgraded test file & refactored code, added GAS, LOG Opcodes 30 | - [x] [Day 15](./daily-progress/day15.md): Major EVM class refactoring & started CALL Opcode 31 | - [x] [Day 16](./daily-progress/day16.md): Final CALL implementation & RETURNDATASIZE, RETURNDATACOPY Opcodes 32 | - [x] [Day 17](./daily-progress/day17.md): Opcode runners refactoring, DELEGATECALL, STATICCALL Opcodes 33 | - [x] [Day 18](./daily-progress/day18.md): CREATE, SELFDESTRUCT Opcodes. Challenge completed! 34 | 35 | ## Running the EVM locally 36 | 37 | Get started by cloning the repo and installing the node dependencies: 38 | 39 | ```bash 40 | git clone https://github.com/nicolas-racchi/evm-from-scratch.git 41 | cd evm-from-scratch/evm-from-scratch-challenge 42 | yarn 43 | ``` 44 | 45 | I added a command-line script to allow running the EVM with a simple command. If you want to run simple bytecode input (without passing any transaction data or block data), you can use the following command: 46 | 47 | ```bash 48 | yarn start:bytecode 49 | 50 | # example: 51 | yarn start:bytecode 604260005260206000F3 52 | ``` 53 | 54 | If you want to provide additional runtime data, you can use the [input.json](./evm-from-scratch-challenge/input.json) file in the root directory as a template for the expected input format. You can then run the file content with the following command: 55 | 56 | ```bash 57 | yarn start:file input.json 58 | ``` 59 | 60 | The program will output the step-by-step execution in a file under the `evm-from-scratch-challenge/logs` directory. 61 | 62 | ## Running tests 63 | 64 | This command will run all challenge-related tests: 65 | 66 | ```bash 67 | yarn test 68 | ``` 69 | 70 | ## Credits 71 | 72 | - [W1nt3r.eth](https://twitter.com/w1nt3r_eth) for creating the [EVM From Scratch](https://github.com/w1nt3r-eth/evm-from-scratch) challenge. 73 | -------------------------------------------------------------------------------- /daily-progress/day00.md: -------------------------------------------------------------------------------- 1 | # Day 0 2 | 3 | Ok here we go! Today I gathered all the resources I had saved in the past months by way of online research and tweets by people who are smarter than me. 4 | 5 | ## EVM-specific material that I've gathered over time 6 | 7 | - [ethereum.org](https://ethereum.org/en/developers/docs/evm/): a good first intro to the topic at hand. 8 | - [ethereum book chapter 13](https://github.com/ethereumbook/ethereumbook/blob/develop/13evm.asciidoc): reference explanation of the EVM. Solid resource. 9 | - [evm.codes](https://evm.codes): interactive reference for each EVM opcode. 10 | - their [about section](https://evm.codes/about) has a general EVM overview which I found amazing. 11 | - their [EVM playground tool](https://www.evm.codes/playground) is an incredibly useful interactive debugger. 12 | - [ethereum yellow paper](https://ethereum.github.io/yellowpaper/paper.pdf): this is the EVM bible, pretty much. 13 | - [ethereum beige paper](https://github.com/chronaeon/beigepaper/blob/master/beigepaper.pdf): A more friendly version of the yellow paper with easier syntax. 14 | - [evm from scratch series](https://karmacoma.notion.site/Building-an-EVM-from-scratch-series-90ee3c827b314e0599e705a1152eecf9): a Python implementation of the yellow paper. 15 | - [crypto dev hub EVM tutorials](https://cryptodevhub.io/ethereum-virtual-machine-tutorials): this page might have useful resources, although most of these seem more production-oriented rather than low-level EVM. 16 | 17 | ## General Virtual Machine building material & courses 18 | 19 | - [building a virtual machine youtube series](https://www.youtube.com/watch?v=7pLCpN811tQ): graduate-level course on VMs. 20 | - [a virtual machine from Crafting Interpreters](https://craftinginterpreters.com/a-virtual-machine.html): intricate low-level guide on stack-based virtual machines in C. 21 | - [wikipedia stack machine article](https://en.wikipedia.org/wiki/Stack_machine): because the evm is not the first stack-based virtual machine (and not the last either). 22 | 23 | ## EVM Challenges 24 | 25 | - [evm-from-scratch](https://github.com/w1nt3r-eth/evm-from-scratch): practical course that requires one to build their own EVM. 26 | - [evm-puzzles](https://github.com/fvictorio/evm-puzzles): a collection of EVM challenges with [accompanying writeup](https://stermi.xyz/blog/lets-play-evm-puzzles) 27 | - [more-evm-puzzles](https://github.com/daltyboy11/more-evm-puzzles): 10 more EVM challenges. 28 | 29 | --- 30 | 31 | That's basically all the time I have for today. 32 | 33 | My short-term roadmap is to explore a few of these resources until I find the one I like most, and then try to follow it to create a basic vm in typescript that passes the first few challenges of the evm-from-scratch course. 34 | -------------------------------------------------------------------------------- /daily-progress/day01.md: -------------------------------------------------------------------------------- 1 | # Day 1 2 | 3 | Today I collected a few resources that will be useful along the way, mainly focusing on existing reference EVM implementations in different languages as well as some generic (non ethereum-specific) stack-based VMs. I also started reading chapter 13 of Mastering Ethereum in detail. 4 | 5 | ## Official EVM implementations from Eth clients 6 | 7 | - [go-ethereum](https://github.com/ethereum/go-ethereum/blob/master/core/vm/evm.go): Go 8 | - [py-evm](https://github.com/ethereum/py-evm): Python 9 | - [evmone](https://github.com/ethereum/evmone): C++ 10 | - [ethereumjs-evm](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/evm): Typescript 11 | - [akula-evm](https://github.com/akula-bft/akula/tree/master/src/execution/evm): Rust (WIP!) 12 | 13 | ## Generalized stack-based VM implementations in different languages (found on Github) 14 | 15 | - [rooster](https://github.com/BranislavLazic/rooster): Go 16 | - [stack-vm-tutorials](https://github.com/pbohun/stack-vm-tutorials): C++ 17 | - [stackVM](https://github.com/JLWalsh/StackVM): C 18 | - [microvm](https://github.com/Prishvin/microvm): C 19 | - [tinyvm](https://github.com/mkhan45/tinyvm): Rust 20 | - [stack-VM](https://github.com/NishanthSpShetty/Stack-VM): Rust 21 | - [crianza](https://github.com/cslarsen/crianza): Python 22 | 23 | ## Mastering Ethereum chapter 13: The EVM (reading notes) 24 | 25 | The EVM is a computation engine similar to interpreters of other bytecode-compiled languages such as Java. When you want to run a Java program, you have to compile it first. Then you can run the compiled bytecode on the Java Virtual Machine (JVM). The term **virtual machine** here refers to the fact that the evm software is independent of the underlying hardware. It is a software abstraction that allows you to run the same program on different hardware architectures. 26 | 27 | Moreover, the domain in which the EVM operates is much more limited than what is usually meant by the word virtual machine. For instance, there is no scheduling capability because transaction ordering is offloaded to the Ethereum clients. The same can be said for other parts such as system interface handling, external hardware support, etc. 28 | 29 | This is important because Ethereum is a decentralized network of computers all over the world. The EVM is the software that allows these computers to run the same program and obtain the same output deterministically, even though they might have different hardware architectures. 30 | 31 | The EVM is also a quasi-Turing-complete state machine. The keyword "quasi" aims to the fact that all execution processes are limited to a finite number of computational steps by the amount of gas "budget" they have. This is a security measure to prevent infinite loops from consuming all the available resources of the network. 32 | 33 | Other than the **stack**, each transaction execution context has different data components: the volatile **memory** which is zero-initialized on each transaction, the **code** where the smart contract bytecode is stored upon deployment and is immutable, the permanent **storage** that is part of the global Ethereum state, as well as some **environmental** data such as the **blockchain context** (block number, timestamp, difficulty, gas limit, etc) and the **transaction context** (sender, recipient, value, gas price, etc). 34 | 35 | The execution context isn't the only slice of state that is managed by the EVM. The job of the EVM is to ultimately update the global Ethereum state one transaction at a time. Here's how the Ethereum state works: 36 | 37 | The top level is the **world state** which is a key-value store that maps addresses to accounts. Each 160-bit address is associated with an **account** comprised of an ether balance, a nonce, the storage (only used by smart contract addresses), and the program code (also only used by smart contract addresses). An EOA (externally-owned account) will always have no code and an empty storage. 38 | 39 | If a user transfers ETH to another user, there is no need to involve the EVM at all. The transaction will simply update the balance of the sender and the recipient in the world state. However, if a user wants to interact with a smart contract, the EVM is instantiated with all the information required in relation to the specific transaction being created. The transaction will also update the storage and the code of the smart contract account. 40 | 41 | If a transaction fails during the EVM execution, all the state changes are reverted and the transaction is not included in the blockchain. It's easier to think of Ethereum clients running the EVM in a sandbox environment where the state changes are only committed if the transaction is successful. 42 | -------------------------------------------------------------------------------- /daily-progress/day02.md: -------------------------------------------------------------------------------- 1 | # Day 2 2 | 3 | Today I finally started implementing the EVM in Typescript. I started with the basics, such as the `EVM` class, starting to layout what the main components are going to be. To keep it simple, I have chosen to implement the interpreter directly in the EVM class, bypassing everything including the global state and any external data. For now, I just want some simple code that can read from a bytecode input and can start executing the instructions sequentially. Then I will focus on implementing the actual functions on the opcodes themselves, but this will require an initial implementation of the memory, storage and stack footprints. I will also need to start thinking about gas implications, and exactly when and how to include every component in the mix. Very exciting stuff ahead! 4 | 5 | I am also debating if I should have a separate `Opcode` class, or keeping the opcodes as atomic as possible, with each one having a function that only takes the current state as input, and can change it accordingly. 6 | 7 | --- 8 | 9 | That's all the time I have for today! 10 | -------------------------------------------------------------------------------- /daily-progress/day03.md: -------------------------------------------------------------------------------- 1 | # Day 3 2 | 3 | Today I have read a portion of the yellow paper. I am interested the official names for specific functions, aswell as implementation guidelines. Before reading this document, I somehow thought that it would be a lot longer than what it actually is. It's only 41 pages, most of which are part of the appendix. There are way more implementation choices left to the developers than I initially anticipated, which is probably a good thing for the Ethereum protocol in terms of client diversity, flexibility and innovation. 4 | 5 | Other than that, I tried to proceed with my EVM by writing some extremely simple classes. Most of the work is going to be actually understanding how to represent the global state and the machine state correctly in the interpreter. 6 | 7 | ## EVM Inception 8 | 9 | Apparently, since the EVM is turing-complete if you remove the gas constraints, it's possible to build an EVM inside the EVM... There are already a few implementations of this I found online: 10 | 11 | - [ricmoo/lurch](https://github.com/ricmoo/lurch) 12 | - [ohalo-ltd/solevm](https://github.com/Ohalo-Ltd/solevm) 13 | - [brockelmore/solvm](https://github.com/brockelmore/solvm) 14 | 15 | Maybe something like this would be fun for a future challenge! I imagine it would be a lot easier once I have implemented it in at least one other language before. 16 | -------------------------------------------------------------------------------- /daily-progress/day04.md: -------------------------------------------------------------------------------- 1 | # Day 4 2 | 3 | Today I kept working on the initial setup, and I got the basic structure of the execution flow going. So far, I implemented the stack and a first version of the memory, and then implemented the first OPCODEs, namely STOP and ADD. I started working towards PUSH1, which requires a bit more setup than what I have going for now. 4 | 5 | For the stack, I initially decided to use a `Buffer` array, but then I started writing the opcode runner functions, and I immediately noticed how inconvenient it would be because of how I need to manipulate the stack items. So I decided to rewrite the stack with a simple js `bigint` array, which will hold any value pushed into the stack just fine. 6 | 7 | For the memory, I am using a single Buffer, allocating, reading and writing data directly onto it. I am not sure if this is the best approach but it makes sense for now. As I said, the implementation is still really simple so changing it later will be easy anyway. 8 | 9 | I also added some utilities like an `ERRORS` enum. 10 | 11 | That's all for today :D 12 | -------------------------------------------------------------------------------- /daily-progress/day05.md: -------------------------------------------------------------------------------- 1 | # Day 5 2 | 3 | Wow, things are taking shape faster than I anticipated! I finalized the `PUSH` opcode, which required providing the opcode runner with a reference to the execution code and program counter, which needs to be incremented for the number of bytes corresponding to the push instruction. For now I am using a simple `MachineState` context struct for this. 4 | 5 | I also implemented a few more simple opcodes such as `SUB` and `POP`. For each instruction I am also making sure that the overflow/underflow cases are handled correctly. The challenge tests help a lot with this. 6 | 7 | ## Resources 8 | 9 | An incredibly useful resource I forgot to mention is the [EVM illustrated](https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf) which does a great job of explaining the state transitions of the EVM and exactly what data dependencies are involved in each different operation. 10 | 11 | From reading these slides, I already know that the two major challenges will be implementing the `CREATE` and `CALL` operations, but these will surely be the subject of future posts. 12 | 13 | ## Other thoughts 14 | 15 | I am noticing that many opcodes have repeated functionality, such as the `PUSH` operation which has 32 different sizes, or the `POP` operation which has 16. I think it's worth to generalize the implementation of these runners to avoid repeated code, with a helper function that chooses the correct runner based on the opcode range. 16 | 17 | Update: I did this with a simple util called `buildOpcodeRangeObjects` and I love how elegant & clear the result looks. 18 | 19 | ## Unit tests 20 | 21 | I am following the evm-from-scratch challenge which offers a test suite of the evm functionality, but I am thinking of starting to write proper unit tests to isolate the more complex parts of the project. Perhaps I will postpone this until I have to deal with the more difficult parts like `CREATE` and `CALL`. 22 | -------------------------------------------------------------------------------- /daily-progress/day06.md: -------------------------------------------------------------------------------- 1 | # Day 6 2 | 3 | Today I will try to finalize the implmentation of the arithmetic opcodes and pass all the related tests. 4 | 5 | EDIT: I overdid it a bit, and also implemented: `SDIV`, `MOD`, `SMOD`, `LT`, `GT`, `SLT`, `SGT`, `EQ`, `ISZERO`, `AND`, `OR`, `XOR`, `NOT`, `BYTE`, `JUMP`, `JUMPI` and `JUMPDEST`. I managed to do so many of them together because they don't require any new data structures, and they all are pretty basic. Nonetheless I learned a lot trying to debug the differences between `DIV` and `SDIV`, which then made the others easier to implement once I understood the modulo-256-bit pattern. 6 | 7 | I am noticing that the `src/opcodes/runner.ts` file is getting a touch crowded. I might refactor the runners into subfolders and just import them into a general runners file. 8 | 9 | While reading through the "EVM Illustrated" slides, I came across the debug view functionality available in geth. It's a great way to visualize all the state changes that occur during an execution, so I want to implement something similar in my EVM in the future. 10 | 11 | I also found and fixed a bug in the stack `popN` method, in which I returned the items in the wrong order. 12 | 13 | ## Resources 14 | 15 | - [Ethereum Specification](https://ethereum.github.io/execution-specs/autoapi/ethereum/index.html): A collection of documents that describe the Ethereum protocol, maintained by the Ethereum Foundation. Great insights about specific opcodes implementation in there as well. 16 | - [EVM Rust crate](https://docs.rs/evm/latest/evm/index.html): A Rust crate that implements the EVM. Will be useful in the future maybe! 17 | -------------------------------------------------------------------------------- /daily-progress/day07.md: -------------------------------------------------------------------------------- 1 | # Day 7 2 | 3 | The first week is up! 4 | 5 | I can say that I'm very happy about the results so far. I've learned a ton about the internal workings of the Ethereum protocol in general – not only the EVM. I've had a chance to look at client software, to read the yellow paper, and to brainstorm my way through some of the more important parts of the execution model. 6 | 7 | --- 8 | 9 | Enough talk, let's get to what I did today: it was finally time to implement the **Memory** structure! I had a draft model of this which I did a few days ago (on Day 4 to be precise). It turns out what I did was way too simple and bugged, so I had to basically rewrite it completely. 10 | 11 | I learned how to manipulate Buffers in javascript - and I'm surprised as to how flexible and reliable they are. I also learned how MSTORE and MLOAD work. In particular I created a generic `write` function in the memory, which takes a `size` argument that can be either 1 or 32 depending on how many bytes I want to write into memory. 12 | 13 | I didn't know how the memory layout actually works, and how it expands dynamically based on where someone is trying to access it. Now I know why accessing memory which is "far away" is more expensive: you pay for each 32-byte word expansion that happens to get there. 14 | 15 | MLOAD also gave me some trouble because I couldn't figure out how to read a single byte on the tail (with something like a PUSH1 31 hex and then MLOAD). Nonetheless, solving it has been rewarding as always, thanks to the tests provided by this challenge. 16 | -------------------------------------------------------------------------------- /daily-progress/day08.md: -------------------------------------------------------------------------------- 1 | # Day 8 2 | 3 | Today I started with the `SHA3` Opcode. The Evm-from-scratch challenge I'm following says that you are allowed to use external libraries for hash functions, so I installed the `ethereum-crypto` package as it seems like a well-maintained library and with not too many dependencies and overall bloat. It implements KECCAK256 out of the box so plugging it in has been pretty effortless. 4 | 5 | Now comes the interesting part: the tests at this point start having more information regarding the execution context: transaction data, block data, and so on. So I had to make changes to the EVM class to accomodate for these new items. 6 | 7 | In particular, I started with the `tx` object, which can contain info such as the address of the origin EOA, the caller account, and the current execution address. I learned how these fields interact with each other when some of them are not specified, for instance the `tx.origin` is set to the `tx.from` if not specified to the EVM context, and will fallback to the Zero address. 8 | 9 | I also had to implement pieces of code relative to the test suite, because in the real client integration you would have a database constisting of the global state, whereas in this challenge the tests can contain specific global state slices. I can say that as of now, the part of how the client stores and loads global state info is obscure to me, but maybe I will have time to dig into it in the later stages of this challenge. 10 | 11 | After setting up the general infrastructure, I implemented some `Txdata` and `Block`-related Opcodes. 12 | 13 | Finally, I arrived to a test of the `DIFFICULTY` Opcode. This one has been deprecated with the Merge hardfork, which was the one I was using as reference on today, which had me confused at first because I didn't find it. I had to switch to the previous hardfork to find it. 14 | 15 | Hardforks are another aspect that I haven't spent any time thinking about during this challenge. I know that many opcodes have been added with EIPs and subsequent hardforks, so a good EVM implementation (that isn't done for learning purposes like mine) should have a way to handle these recurring updates with ease. Nonetheless, I will try to think about them while writing my code, so that maybe one day I can revisit it to expand the support for this functionality. 16 | -------------------------------------------------------------------------------- /daily-progress/day09.md: -------------------------------------------------------------------------------- 1 | # Day 9 2 | 3 | Big day today. [W1nt3r](https://twitter.com/w1nt3r_eth) (the creator of the challenge I am doing here) tweeted about the possibility to join his private cohort of the "evm from scratch" challenge. As I am a big fan of his work, and I'm already involved in the challenge by myself, I decided to message him to join the small cohort of students who will be attending the challenge. 4 | 5 | So I am happy to say that this challenge has **officially started** for me today! Even though I am more than 65% done already, I will still try to follow the activity and schedule, and most importantly I am looking forward to chatting with the other students – I actually am very thankful for the opportunity because I now have a place to learn with like-minded engineers and people with whom I share a strong passion. I am also looking forward to the feedback I will get from the other students, and I hope to be able to give some feedback as well. 6 | 7 | --- 8 | 9 | Regarding the challenge itself, yesterday I left off at the `DIFFICULTY` Opcode, and today I started with some simple ones, like `GASLIMIT`, `GASPRICE`, `CHAINID` & `CALLVALUE`. All of these are pretty straightforward as no new data structures or big modifications are required, it was just a matter of creating the interfaces and the correct adapters to accept the new Context-related fields to the EVM class (for instance, the Block.gaslimit field). 10 | 11 | Then I finally arrived to a more tricky one: `CALLDATALOAD`. This one is not so easy to work with, mainly because of how I am currently parsing the transaction calldata. I figured the most practical way to implement calldata would be with a bytecode string, which I then parsed as Buffer for ease of use. For this reason, I had to change the implementation of the Test interface to accomodate for this double possibility. Nothing too complex, but _I hate removing simplicity from my code_. 12 | 13 | Here is the implementation of `CALLDATALOAD` that I finally came up with: 14 | 15 | ```typescript 16 | function CALLDATALOAD(ms: MachineState) { 17 | // get the byte offset from the stack 18 | const offset = Number(ms.stack.pop()); 19 | 20 | // get the calldata as a buffer starting from the offset position, 21 | // with max size of 32 bytes (or less if the calldata is smaller) 22 | const calldataWord = ms.txData.data.subarray(offset, offset + 32); 23 | 24 | // add padding to the calldata word if it is smaller than 32 bytes 25 | // by copying it to the start of a new 32-bytes buffer 26 | const calldataWordPadded = Buffer.alloc(32); 27 | calldataWord.copy(calldataWordPadded, 0, 0); 28 | 29 | // convert the calldata word to a bigint (safe because it is capped at 32 bytes) 30 | const res = parseBytesIntoBigInt(calldataWordPadded); 31 | 32 | // push the result to the stack 33 | ms.stack.push(res); 34 | } 35 | ``` 36 | 37 | I am realizing just now that I should have put way more commented code blocks in these daily progress reports. I promise I'll try to do that more from now on. 38 | 39 | This was the last Opcode for today! 40 | 41 | P.S. Here is the point I'm at right now: _79 passed, 101 total_. So about 78% done! 42 | -------------------------------------------------------------------------------- /daily-progress/day10.md: -------------------------------------------------------------------------------- 1 | # Day 10 2 | 3 | Today I haven't got much time because I'm running late on my plans for the night, but I still managed to get a few things done. I started with the `CALLDATASIZE` Opcode, which was pretty straightforward. Then I moved on to the `CALLDATACOPY` Opcode, which was a bit more tricky but nothing too fancy. Here's how I did it: 4 | 5 | ```typescript 6 | function CALLDATACOPY(ms: MachineState) { 7 | // copy a portion of the calldata to memory 8 | // taking 3 arguments from the stack: 9 | 10 | // dataOffset = where to start copying from in calldata 11 | const memOffset = Number(ms.stack.pop()); 12 | 13 | // memOffset = where to start pasting in memory 14 | const dataOffset = Number(ms.stack.pop()); 15 | 16 | // size = how many bytes to copy in memory 17 | const size = Number(ms.stack.pop()); 18 | 19 | // then I can index the exact portion of calldata I want to copy 20 | const data = ms.txData.data.subarray(dataOffset, dataOffset + size); 21 | 22 | // and finally load it into memory 23 | ms.memory.write(memOffset, data, size); 24 | } 25 | ``` 26 | 27 | Then I also implemented `CODESIZE` and `CODECOPY`. Both are very similar to their memory equivalent, with the only difference that I am parsing the code as an `Uint8Array` instead of a `Buffer`. 28 | 29 | --- 30 | 31 | One unrelated thing which I haven't spent enough time on yet is the calculation of the gas used by the execution. I know that there are some pre-determined values that are subtracted from the initial gas limit, but there are other dynamic prices such as the memory expansion or storage slot access which depend based on some variables. 32 | 33 | This is not part of the current EVM challenge, but maybe I could implement it as an add-on challenge in the future. 34 | -------------------------------------------------------------------------------- /daily-progress/day11.md: -------------------------------------------------------------------------------- 1 | # Day 11 2 | 3 | I am approaching the end of the challenge, and in my opinion this is the most fun part. I will need to redo most of my main EVM class to allow for the `CALL` and `CREATE` operations, but I am looking forward to it. 4 | 5 | Today I implemented `EXTCODESIZE` which was pretty easy, I just had to modify my `buildState` function to also include the code of any external account provided in the test. Then it was just a matter of returning the size of the code of the account pulled from the stack. 6 | 7 | After that, I tried my hand at `EXTCODECOPY`. This one was definitely one of the longest ones in terms of lines of code, but after having dealt with many Opcodes handling memory slices and manipulating them, I was able to complete it quite quickly. 8 | 9 | Here's a rundown of what `EXTCODECOPY` does: fun-fact, it's the first Opcode I encountered that takes 4 arguments from the stack! 10 | 11 | ```typescript 12 | function EXTCODECOPY(ms: MachineState) { 13 | // We want to copy a portion of code of the account at the `address` on the stack 14 | // into the memory slice starting at the `offset` and of length `size` bytes 15 | 16 | // the first argument is the address of the account to copy the code from 17 | const address = ms.stack.pop(); 18 | const addressHex = parsers.BigintIntoHexString(address); 19 | const extAccount = ms.globalState?.getAccount(addressHex); 20 | 21 | // then we load the other 3 arguments 22 | const memOffset = Number(ms.stack.pop()); 23 | const codeOffset = Number(ms.stack.pop()); 24 | const size = Number(ms.stack.pop()); 25 | 26 | // we get the code portion of the external account (it can be undefined) 27 | // I parsed this as Uint8Array to stay consistent with the main account code 28 | const codeBytesPortion = extAccount?.code?.subarray( 29 | codeOffset, 30 | codeOffset + size 31 | ); 32 | 33 | // we prepare a buffer of the right size from the Uint8Array slice created above 34 | // also making sure to account for the undefined case 35 | const codeBuffer = Buffer.from(codeBytesPortion ?? Buffer.alloc(0)); 36 | 37 | // we use our usual trick to copy the buffer into the memory slice 38 | const code = Buffer.alloc(size); 39 | codeBuffer.copy(code, 0, 0); 40 | 41 | // and finally we write it into memory 42 | ms.memory.write(memOffset, code, size); 43 | } 44 | ``` 45 | 46 | I then also implemented `SELFBALANCE` which was very simply pushing `ms.globalState.getBalance(ms.txData.to)` onto the stack. 47 | 48 | That's it for today! 49 | 50 | Progress so far: _91 passed, 101 total_. 51 | -------------------------------------------------------------------------------- /daily-progress/day12.md: -------------------------------------------------------------------------------- 1 | # Day 12 2 | 3 | Time has finally come. Today is `Storage` day. 4 | 5 | This is perhaps the most critical and complex part of the EVM execution itself. 6 | 7 | - Where is the account storage coming from? 8 | - How is data stored permanently on the blockchain? 9 | - What exactly are storage checkpoints and how do they relate to gas costs? 10 | - What are the abstractions from the data layer to the execution layer? 11 | 12 | These are the questions I will try to answer today and in the coming days. 13 | 14 | ## Storage 15 | 16 | For this challenge, we don't actually need to recreate anything remotely as complex as the real Ethereum storage (luckily) but I'd like to understand how it works since I'm already here. 17 | 18 | The storage is a key-value store holding data about each account indexed by address. The data is stored in the form of 256-bit words. The storage is a part of the state of the blockchain and is updated by the transactions. 19 | 20 | Now for the EVM-related part. The storage can be accessed in read and write operations. One does not actually need to load storage information about all addresses in the blockchain to execute a transaction, because only a small slice of storage might be accessed if at all. 21 | 22 | [Here is a good article explaining how the storage works](https://medium.com/hackernoon/getting-deep-into-ethereum-how-data-is-stored-in-ethereum-e3f669d96033). 10/10 article, really amazing explanations. 0/10 for the background color though. I had to open it on Safari reader mode to read it. Whoever chose that color needs to be jailed ASAP. 23 | 24 | ## TL;DR and reading notes 25 | 26 | The Ethereum protocol main data structure is a [Merkle Patricia Tree](https://ethereum.org/en/developers/docs/data-structures-and-encoding/patricia-merkle-trie/) (also read more [here](https://github.com/ethereum/wiki/wiki/Patricia-Tree)). Importantly, data inside this state trie are not stored directly in the blocks of the blockchain. What is stored on each block is simply the hash of the root of the state trie, transaction trie and receipts trie. This is a very important point to understand. 27 | 28 | Here's a simple bird's eye view of what happens: 29 | 30 | 1. The storage trie keeps info about smart contract data storage slots and their values. 31 | 2. The root of that tree (storageRoot) is then kept in the State Trie which also tracks all the accounts state. 32 | 3. The root of the State Trie (stateRoot) is set in the block header. 33 | 4. The same goes for the receipts trie (receiptsRoot) and the transaction trie (transactionsRoot). 34 | 35 | ### A closer look at the trie structure 36 | 37 | There is one, and one only, global state trie in Ethereum. This global state trie is constantly updated. 38 | **The state trie contains a key and value pair for every account which exists on the Ethereum network.** 39 | 40 | - The "key" is a single 160 bit identifier (the address of an Ethereum account). 41 | - The "value" in the global state trie is created by encoding the following account details of an Ethereum account (using the Recursive-Length Prefix encoding (RLP) method): `nonce`, `balance`, `storageRoot`, `codeHash`. 42 | 43 | The state trie’s root node ( a hash of the entire state trie at a given point in time) is used as a secure and unique identifier for the state trie. 44 | 45 | ### Storage Trie: smart contract data storage 46 | 47 | Each Ethereum account has its own Storage Trie. A hash of its root node is stored as the `storageRoot` in the global state trie explained above. 48 | 49 | ### Storage Tries at a software/hardware client level 50 | 51 | Most Ethereum clients store the different tries in a key-value store called [LevelDB](https://github.com/google/leveldb). 52 | 53 | LevelDB offers useful features such as automatic compression using `Snappy` (a Google-developed compression library) and a simple key-value interface. 54 | 55 | LevelDB is a dependency of the most popular Eth clients such as go-ethereum, cpp-ethereum and pyethereum. 56 | 57 | Interestingly, accounts in Ethereum are only added to the state trie once a transaction has taken place (in relation to that specific account). For example, just creating a new account using `geth account new` will not include that account in the state trie; even after many blocks have been mined. However, if a successful transaction (one which costs gas and is included in a mined block) is recorded against that account, then and only then will that account appear in the state trie. 58 | 59 | --- 60 | 61 | This is all the time I have for today, and even if I didn't make any progress in the challenge, I've learned plenty of new concepts that I will try to simplify and implement in the coming days. 62 | -------------------------------------------------------------------------------- /daily-progress/day13.md: -------------------------------------------------------------------------------- 1 | # Day 13 2 | 3 | Ok, it's finally time to put in practice what we've learned yesterday. 4 | 5 | Here is the basic Storage I've come up with so far: 6 | 7 | ```typescript 8 | // in machine-state/storage.ts you will find more explanation as to why I've chosen this format 9 | // but basically it's a key-value store for each address. 10 | type StorageLayout = Map>; 11 | 12 | export default class Storage { 13 | private _storage: StorageLayout; 14 | 15 | constructor() { 16 | this._storage = new Map(); 17 | } 18 | 19 | public get(address: Address, key: string): Buffer { 20 | // simply access the map by address and get the value from the key 21 | return this._storage.get(address)?.get(key) ?? Buffer.alloc(32); 22 | } 23 | 24 | public set(address: Address, key: string, value: Buffer): void { 25 | if (value.length > 32) throw new Error(ERRORS.INVALID_STORAGE_VALUE_SIZE); 26 | 27 | // check if an old value exists for this key 28 | // if it doesn't, create a new map for this address 29 | const oldStorageValue = this._storage.get(address)?.get(key); 30 | if (!oldStorageValue) this._storage.set(address, new Map()); 31 | 32 | // if the old value is the same as the new one, do nothing 33 | if (oldStorageValue?.equals(value)) return; 34 | 35 | // finally update the key with the new value 36 | this._storage.get(address)!.set(key, value); 37 | } 38 | } 39 | ``` 40 | 41 | This passes all `SSTORE` and `SLOAD` tests for now, but I will need to implement the logger and persistent changes log later. 42 | 43 | In the real EVM, storage changes aren't persisted until the transaction is finalized, and they are reverted if the transaction itself reverts. This simple storage doesn't account for this kind of logic yet. 44 | 45 | Ok, now that the storage part seems to be working, let's move on to some more simple Opcodes! 46 | 47 | `RETURN` is a good one. It halts the execution of the current frame and returns a portion of data read from memory. It's the first interaction with the `returnData` property, which I defined as a Buffer in the `MachineState` object. 48 | 49 | `REVERT` is very similar to `RETURN`, but instead of throwing the `STOP` exception, it throws `REVERT` which does not indicate a special case for the run function. 50 | -------------------------------------------------------------------------------- /daily-progress/day14.md: -------------------------------------------------------------------------------- 1 | # Day 14 2 | 3 | Today I upgraded my EVM setup by pulling the newest version of the evm-from-scratch repo. Some things have been refactored and some have been added aswell. So I spent a few minutes adjusting the new tests to my old code. During the process, I had to implement a couple Opcodes that were added in order to get back at my last result, namely `GAS` and `LOG`. 4 | 5 | `GAS` is simple, it pushes the remaining available gas to the stack, but I haven't implemented a full gas-tracking system yet, so this is still a work in progress I would say. 6 | 7 | `LOG` required some extra work: I had to add the `logs` field to the MachineState and return it to the test result. Here's how I implemented the actual LOG runners (in a single function for all LOG Opcodes): 8 | 9 | ```typescript 10 | function LOG(ms: MachineState) { 11 | // take the number of arguments from the opcode 12 | const n = ms.code[ms.pc] - 0xa0; 13 | 14 | // get the memory offset and size to read 15 | const [memOffset, size] = ms.stack.popN(2); 16 | 17 | // get the actual log topics from stack 18 | const topics = ms.stack.popN(n); 19 | 20 | // read the memory and parse the topics 21 | const data = ms.memory.read(Number(memOffset), Number(size)); 22 | const topicsHex = topics.map(parsers.BigintIntoHexString); 23 | 24 | // push the log with the appropriate data and topics 25 | ms.logs.push({ 26 | address: ms.txData.to, 27 | data: data.toString("hex"), 28 | topics: topicsHex, 29 | }); 30 | } 31 | ``` 32 | 33 | Also since today I had more time to dedicate, I implemented a big refactoring of the EVM class introducing a Logger, which is a way to visualize exactly what is happening on every execution step: stack, memory and storage dump. 34 | 35 | I also added a way to start the EVM execution from a specified bytecode or an input file instead of only relying on provided tests, so I can potentially use it for more than just testing in the future... :D 36 | -------------------------------------------------------------------------------- /daily-progress/day15.md: -------------------------------------------------------------------------------- 1 | # Day 15 2 | 3 | Today unfortunately was not a very fruitful day. I ended up spending way too much time on a code refactoring process in order to make the `CALL` instruction work. It was somewhat frustrating, because I had to change so much that worked perfectly up until now. Nonetheless, I will continue tomorrow and hopefully get the `CALL` Opcode working. 4 | 5 | Among other things, the main refactoring was done to the `EVM` class itself: I now have a separate constructor and a `start()` function, which calls the `run()` handler with an instance of `MachineState`. So now I can create special opcode handlers for `CALL` and `CREATE` that will have the EVM class itself as parameter instead of only having access to the machine state. This is useful because during a `CALL` you are basically creating a new interpreter execution with the code of another contract and then return to the original execution. 6 | -------------------------------------------------------------------------------- /daily-progress/day16.md: -------------------------------------------------------------------------------- 1 | # Day 16 2 | 3 | Okay so yesterday was a bit harsh, I was stuck on a bug where I couldn't get the right returnData to show after a sub-call finished executing. I'm happy to report that I've since solved the bug in 5 minutes after having it viewed with a fresh mind today. Here's what I was missing: `Buffer.from(value, "hex")` but I had forgotten to specify the hex encoding, and the default is UTF-8. 4 | 5 | Anyway, now that `CALL` works, I can try and explain quickly how I implemented it. The `CALL` opcode is a bit tricky because it requires a lot of state to be saved and restored. The way I did it was to create a new `MachineState` instance, which is basically a copy of the current state (with a new program counter, etc) and then execute the code of the called contract. After the execution is finished, the return data is copied back to the original state and the execution continues. This makes use of recursion, and there is a `depth` variable that tracks how deep the current call stack is. 6 | 7 | Another difference from other Opcodes seen so far, as explained in yesterday's report, is that the `CALL` runner also takes in the EVM class other than the MachineState. This is to be able to call `evm.run()` to execute a sub-call. 8 | 9 | ```typescript 10 | async function CALL(ms: MachineState, evm: EVM) { 11 | // read required call arguments from the stack 12 | const [gas, address, value, argsOffset, argsSize, retOffset, retSize] = 13 | ms.stack.popN(7); 14 | 15 | // parse them into calldata and target address 16 | const data = ms.memory.read(Number(argsOffset), Number(argsSize)); 17 | const to = parsers.BigintIntoHexString(address); 18 | 19 | // get the code of the contract to be called (return success if empty) 20 | const codeToCall = ms.globalState.getAccount(to).code; 21 | if (!codeToCall) return ms.stack.push(CALL_RESULT.SUCCESS); 22 | 23 | // generate a new machine state for the sub-call 24 | const callMachineState: MachineState = { 25 | // start by copying the current state 26 | ...ms, 27 | // but with a new program counter, memory and stack 28 | ...freshExecutionContext(), 29 | // also with custom gas limit (should be below a threshold, todo!) 30 | gasAvailable: gas, 31 | 32 | // the most important part: the new txData and code to execute 33 | txData: { ...ms.txData, from: ms.txData.to, to, value, data }, 34 | code: codeToCall, 35 | }; 36 | 37 | // execute the sub-call invoking the evm method (recursion!) 38 | const callResult = await evm.run(callMachineState, true); 39 | 40 | // save the return data to the memory if needed 41 | if (callResult.return) { 42 | // here's the bug I wasted 1 hour on yesterday: "hex" encoding! 43 | const callReturnData = Buffer.from(callResult.return, "hex"); 44 | const callReturnOffset = Number(retOffset); 45 | const callReturnSize = Number(retSize); 46 | 47 | ms.returnData = callReturnData; 48 | 49 | if (callReturnSize > 0) 50 | ms.memory.write(callReturnOffset, callReturnData, callReturnSize); 51 | } 52 | 53 | if (callResult.success) ms.stack.push(CALL_RESULT.SUCCESS); 54 | else ms.stack.push(CALL_RESULT.REVERT); 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /daily-progress/day17.md: -------------------------------------------------------------------------------- 1 | # Day 17 2 | 3 | Today I want to make some refactoring to the garbage that has become the `src/opcodes/runners.ts` file. It's currently 586 lines. I would like to split the runners by Opcode category, and I noticed that both evm.codes and the ethereum execution specs have a nice categorization of each opcode. I'm going to use that to split the runners into separate files. 4 | 5 | Here are the instruction categories [as defined here](https://ethereum.github.io/execution-specs/autoapi/ethereum/gray_glacier/vm/instructions/index.html): 6 | 7 | - Arithmetic 8 | - Bitwise Logic 9 | - Block 10 | - Comparison 11 | - Control Flow 12 | - Environmental 13 | - Keccak 14 | - Logging 15 | - Memory 16 | - Stack 17 | - Storage 18 | - System 19 | 20 | --- 21 | 22 | After this small refactoring, I proceeded with the next instructions: `DELEGATECALL` and `STATICCALL`. These are very similar to their `CALL` sibling, so most of the runner code was reused. There are some changes that make a huge difference when it comes to actual execution, and learning them by having to implement them manually was a great learning experience. 23 | 24 | For instance, the `STATICCALL` Opcode required a new flag inside the MachineState struct: "static" now identifies if the current execution context is running in static mode. In this particular mode, the opcodes that change the global state of Ethereum are disallowed, for instance `SSTORE` and `CREATE`. What I didn't realize is that a simple `CALL` can be executed in static mode, but only if its context txData.value sent is equal to 0. 25 | 26 | This course is making me appreciate the smart implementations baked in the EVM and the Ethereum protocol. I know I've said it already, but this has been a learning experience like no others for me. I'm really enjoying it. 27 | -------------------------------------------------------------------------------- /daily-progress/day18.md: -------------------------------------------------------------------------------- 1 | # Day 18 2 | 3 | Today is a huge day. I have only 2 Opcodes left, `CREATE` and `SELFDESTRUCT`. Let's push to the end!! 4 | 5 | ## CREATE Instruction 6 | 7 | ```typescript 8 | export async function CREATE(ms: MachineState, evm: EVM) { 9 | const [value, offset, length] = ms.stack.popN(3); 10 | 11 | // todo: generate real address: keccak256(rlp([sender_address,sender_nonce]))[12:] 12 | const sender = parsers.hexStringToUint8Array(ms.txData.to); 13 | const keccak = parsers.BufferToHexString(Buffer.from(keccak256(sender))); 14 | const addressCreated = keccak.substring(0, 42); 15 | 16 | // set the balance of the new address to the value sent to create 17 | ms.globalState.setAccount(addressCreated, { balance: value }); 18 | 19 | // get the init code of the contract created 20 | const initCode = ms.memory.read(Number(offset), Number(length)); 21 | 22 | // create a new machine state for the sub-context execution 23 | const createMachineState: MachineState = { 24 | ...ms, 25 | ...freshExecutionContext(), 26 | txData: { 27 | ...ms.txData, 28 | value: 0n, 29 | from: ZERO_ADDRESS, 30 | to: addressCreated, 31 | }, 32 | code: initCode, 33 | }; 34 | 35 | // run the sub-call 36 | const createResult = await evm.run(createMachineState, true); 37 | 38 | // if the sub-call failed, return 0. 39 | // otherwise, push the newly created address to the stack 40 | // and update the contract code to the return value of the sub-call 41 | if (createResult.success) { 42 | ms.globalState.setAccount(addressCreated, { 43 | ...ms.globalState.getAccount(addressCreated), 44 | code: parsers.hexStringToUint8Array(createResult.return), 45 | }); 46 | 47 | ms.stack.push(parsers.HexStringIntoBigInt(addressCreated)); 48 | } else ms.stack.push(CALL_RESULT.REVERT); 49 | } 50 | ``` 51 | 52 | I learned a bunch of things here that I didn't know. First, the way the new address is generated was obscure to me. Then, I thought that the new contract code would be taken directly from the calldata, but it's actually the return value of the create sub-call. This is very interesting. I wonder how this works at the Solidity compiler level. Maybe I will have to look into that in the future. I also learned that the sub-call can fail, but the transaction can succeed because only the child context will return a failure but the parent is not affected. 53 | 54 | ## SELFDESTRUCT Instruction 55 | 56 | ```typescript 57 | export function SELFDESTRUCT(ms: MachineState) { 58 | const [address] = ms.stack.popN(1); 59 | const addressToPay = parsers.BigintIntoHexString(address); 60 | const accountToPay = ms.globalState.getAccount(addressToPay); 61 | const accountToDestroy = ms.globalState.getAccount(ms.txData.to); 62 | 63 | // transfer the balance of the contract to the specified address 64 | if (accountToDestroy?.balance) { 65 | ms.globalState.setAccount(addressToPay, { 66 | ...accountToPay, 67 | balance: accountToDestroy.balance + (accountToPay?.balance || 0n), 68 | }); 69 | } 70 | 71 | // delete the current contract and return 72 | ms.globalState.setAccount(ms.txData.to, {}); 73 | ms.pc = ms.code.length; 74 | 75 | throw new Error(ERRORS.STOP); 76 | } 77 | ``` 78 | 79 | For `SELFDESTRUCT`, I know for sure that this implementation is very incomplete, but it gets the basics right for passing the required tests. I think this challenge has the right amount of details to make it still fun to proceed without getting too bogged down in the single edge cases. So that was it! 80 | 81 | --- 82 | 83 | ## Conclusion 84 | 85 | Yeah, that was the last Opcode of the challenge! I feel like I've talked about my feedback too many times already, so I will only say here that **it was awesome and I'm grateful to Winter for putting this together**. 86 | 87 | I absolutely recommend undertaking this experience if you are curious about smart contracts and the Ethereum protocol in general. You will learn a ton of low-level details that will be immensely useful for your future smart contract projects. Shoutout to W1nt3r for putting this together! 88 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | logs -------------------------------------------------------------------------------- /evm-from-scratch-challenge/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "printWidth": 90, 5 | "semi": false 6 | } 7 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/evm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "STOP", 4 | "code": { 5 | "asm": "STOP", 6 | "bin": "00" 7 | }, 8 | "expect": { 9 | "success": true, 10 | "stack": [] 11 | } 12 | }, 13 | { 14 | "name": "PUSH", 15 | "code": { 16 | "asm": "PUSH1 1", 17 | "bin": "6001" 18 | }, 19 | "expect": { 20 | "stack": ["0x1"] 21 | } 22 | }, 23 | { 24 | "name": "PUSH (twice)", 25 | "code": { 26 | "asm": "PUSH1 1\nPUSH1 2", 27 | "bin": "60016002" 28 | }, 29 | "expect": { 30 | "stack": ["0x2", "0x1"] 31 | } 32 | }, 33 | { 34 | "name": "POP", 35 | "code": { 36 | "asm": "PUSH1 1\nPUSH1 2\nPOP", 37 | "bin": "6001600250" 38 | }, 39 | "expect": { 40 | "stack": ["0x1"] 41 | } 42 | }, 43 | { 44 | "name": "STOP (midway)", 45 | "code": { 46 | "asm": "PUSH1 1\nSTOP\nPUSH1 2", 47 | "bin": "6001006002" 48 | }, 49 | "expect": { 50 | "stack": ["0x1"] 51 | } 52 | }, 53 | { 54 | "name": "ADD", 55 | "code": { 56 | "asm": "PUSH1 0x01\nPUSH1 0x02\nADD", 57 | "bin": "6001600201" 58 | }, 59 | "expect": { 60 | "stack": ["0x3"] 61 | } 62 | }, 63 | { 64 | "name": "ADD (overflow)", 65 | "code": { 66 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPUSH1 0x02\nADD", 67 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600201" 68 | }, 69 | "expect": { 70 | "stack": ["0x1"] 71 | } 72 | }, 73 | { 74 | "name": "MUL", 75 | "code": { 76 | "asm": "PUSH1 0x02\nPUSH1 0x03\nMUL", 77 | "bin": "6002600302" 78 | }, 79 | "expect": { 80 | "stack": ["0x6"] 81 | } 82 | }, 83 | { 84 | "name": "MUL (overflow)", 85 | "code": { 86 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPUSH1 0x02\nMUL", 87 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600202" 88 | }, 89 | "expect": { 90 | "stack": ["0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"] 91 | } 92 | }, 93 | { 94 | "name": "SUB", 95 | "code": { 96 | "asm": "PUSH1 0x02\nPUSH1 0x03\nSUB", 97 | "bin": "6002600303" 98 | }, 99 | "expect": { 100 | "stack": ["0x1"] 101 | } 102 | }, 103 | { 104 | "name": "SUB (underflow)", 105 | "code": { 106 | "asm": "PUSH1 0x03\nPUSH1 0x02\nSUB", 107 | "bin": "6003600203" 108 | }, 109 | "expect": { 110 | "stack": ["0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"] 111 | } 112 | }, 113 | { 114 | "name": "DIV", 115 | "code": { 116 | "asm": "PUSH1 0x02\nPUSH1 0x06\nDIV", 117 | "bin": "6002600604" 118 | }, 119 | "expect": { 120 | "stack": ["0x3"] 121 | } 122 | }, 123 | { 124 | "name": "DIV (whole)", 125 | "code": { 126 | "asm": "PUSH1 0x06\nPUSH1 0x02\nDIV", 127 | "bin": "6006600204" 128 | }, 129 | "expect": { 130 | "stack": ["0x0"] 131 | } 132 | }, 133 | { 134 | "name": "DIV (by zero)", 135 | "code": { 136 | "asm": "PUSH1 0x00\nPUSH1 0x02\nDIV", 137 | "bin": "6000600204" 138 | }, 139 | "expect": { 140 | "stack": ["0x0"] 141 | } 142 | }, 143 | { 144 | "name": "SDIV", 145 | "code": { 146 | "asm": "PUSH1 10\nPUSH1 10\nSDIV", 147 | "bin": "600a600a05" 148 | }, 149 | "expect": { 150 | "stack": ["0x1"] 151 | } 152 | }, 153 | { 154 | "name": "SDIV (negative)", 155 | "code": { 156 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPUSH32 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\nSDIV", 157 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe05" 158 | }, 159 | "expect": { 160 | "stack": ["0x2"] 161 | } 162 | }, 163 | { 164 | "name": "SDIV (mix of negative and positive)", 165 | "code": { 166 | "asm": "PUSH32 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\nPUSH1 10\nSDIV", 167 | "bin": "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe600a05" 168 | }, 169 | "expect": { 170 | "stack": ["0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"] 171 | } 172 | }, 173 | { 174 | "name": "MOD", 175 | "code": { 176 | "asm": "PUSH1 3\nPUSH1 10\nMOD", 177 | "bin": "6003600a06" 178 | }, 179 | "expect": { 180 | "stack": ["0x1"] 181 | } 182 | }, 183 | { 184 | "name": "MOD (by larger number)", 185 | "code": { 186 | "asm": "PUSH1 17\nPUSH1 5\nMOD", 187 | "bin": "6011600506" 188 | }, 189 | "expect": { 190 | "stack": ["0x5"] 191 | } 192 | }, 193 | { 194 | "name": "MOD (by zero)", 195 | "code": { 196 | "asm": "PUSH1 0\nPUSH1 2\nMOD", 197 | "bin": "6000600206" 198 | }, 199 | "expect": { 200 | "stack": ["0x0"] 201 | } 202 | }, 203 | { 204 | "name": "SMOD", 205 | "code": { 206 | "asm": "PUSH1 3\nPUSH1 10\nSMOD", 207 | "bin": "6003600a07" 208 | }, 209 | "expect": { 210 | "stack": ["0x1"] 211 | } 212 | }, 213 | { 214 | "name": "SMOD (negative)", 215 | "code": { 216 | "asm": "PUSH32 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd\nPUSH32 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8\nSMOD", 217 | "bin": "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff807" 218 | }, 219 | "expect": { 220 | "stack": ["0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"] 221 | } 222 | }, 223 | { 224 | "name": "SMOD (by zero)", 225 | "code": { 226 | "asm": "PUSH1 0x00\nPUSH1 0x02\nSMOD", 227 | "bin": "6000600207" 228 | }, 229 | "expect": { 230 | "stack": ["0x0"] 231 | } 232 | }, 233 | { 234 | "name": "LT", 235 | "code": { 236 | "asm": "PUSH1 10\nPUSH1 9\nLT", 237 | "bin": "600a600910" 238 | }, 239 | "expect": { 240 | "stack": ["0x1"] 241 | } 242 | }, 243 | { 244 | "name": "LT (equal)", 245 | "code": { 246 | "asm": "PUSH1 10\nPUSH1 10\nLT", 247 | "bin": "600a600a10" 248 | }, 249 | "expect": { 250 | "stack": ["0x0"] 251 | } 252 | }, 253 | { 254 | "name": "LT (greater)", 255 | "code": { 256 | "asm": "PUSH1 10\nPUSH1 11\nLT", 257 | "bin": "600a600b10" 258 | }, 259 | "expect": { 260 | "stack": ["0x0"] 261 | } 262 | }, 263 | { 264 | "name": "GT", 265 | "code": { 266 | "asm": "PUSH1 9\nPUSH1 10\nGT", 267 | "bin": "6009600a11" 268 | }, 269 | "expect": { 270 | "stack": ["0x1"] 271 | } 272 | }, 273 | { 274 | "name": "GT (equal)", 275 | "code": { 276 | "asm": "PUSH1 10\nPUSH1 10\nGT", 277 | "bin": "600a600a11" 278 | }, 279 | "expect": { 280 | "stack": ["0x0"] 281 | } 282 | }, 283 | { 284 | "name": "GT (less)", 285 | "code": { 286 | "asm": "PUSH1 11\nPUSH1 10\nGT", 287 | "bin": "600b600a11" 288 | }, 289 | "expect": { 290 | "stack": ["0x0"] 291 | } 292 | }, 293 | { 294 | "name": "SLT", 295 | "code": { 296 | "asm": "PUSH1 0\nPUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nSLT", 297 | "bin": "60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff12" 298 | }, 299 | "expect": { 300 | "stack": ["0x1"] 301 | } 302 | }, 303 | { 304 | "name": "SLT (equal)", 305 | "code": { 306 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nSLT", 307 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff12" 308 | }, 309 | "expect": { 310 | "stack": ["0x0"] 311 | } 312 | }, 313 | { 314 | "name": "SLT (greater)", 315 | "code": { 316 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPUSH1 0\nSLT", 317 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600012" 318 | }, 319 | "expect": { 320 | "stack": ["0x0"] 321 | } 322 | }, 323 | { 324 | "name": "SGT", 325 | "code": { 326 | "asm": "PUSH1 9\nPUSH1 10\nSGT", 327 | "bin": "6009600a13" 328 | }, 329 | "expect": { 330 | "stack": ["0x1"] 331 | } 332 | }, 333 | { 334 | "name": "SGT (equal)", 335 | "code": { 336 | "asm": "PUSH32 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\nPUSH32 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe\nSGT", 337 | "bin": "7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe13" 338 | }, 339 | "expect": { 340 | "stack": ["0x0"] 341 | } 342 | }, 343 | { 344 | "name": "SGT (less)", 345 | "code": { 346 | "asm": "PUSH1 10\nPUSH1 11\nSGT", 347 | "bin": "600a600b13" 348 | }, 349 | "expect": { 350 | "stack": ["0x1"] 351 | } 352 | }, 353 | { 354 | "name": "EQ", 355 | "code": { 356 | "asm": "PUSH1 10\nPUSH1 10\nEQ", 357 | "bin": "600a600a14" 358 | }, 359 | "expect": { 360 | "stack": ["0x1"] 361 | } 362 | }, 363 | { 364 | "name": "EQ (not equal)", 365 | "code": { 366 | "asm": "PUSH1 9\nPUSH1 10\nEQ", 367 | "bin": "6009600a14" 368 | }, 369 | "expect": { 370 | "stack": ["0x0"] 371 | } 372 | }, 373 | { 374 | "name": "ISZERO (not zero)", 375 | "code": { 376 | "asm": "PUSH1 9\nISZERO", 377 | "bin": "600915" 378 | }, 379 | "expect": { 380 | "stack": ["0x0"] 381 | } 382 | }, 383 | { 384 | "name": "ISZERO (zero)", 385 | "code": { 386 | "asm": "PUSH1 0\nISZERO", 387 | "bin": "600015" 388 | }, 389 | "expect": { 390 | "stack": ["0x1"] 391 | } 392 | }, 393 | { 394 | "name": "AND", 395 | "code": { 396 | "asm": "PUSH1 0xe\nPUSH1 0x3\nAND", 397 | "bin": "600e600316" 398 | }, 399 | "expect": { 400 | "stack": ["0x2"] 401 | } 402 | }, 403 | { 404 | "name": "OR", 405 | "code": { 406 | "asm": "PUSH1 0xe\nPUSH1 0x3\nOR", 407 | "bin": "600e600317" 408 | }, 409 | "expect": { 410 | "stack": ["0xf"] 411 | } 412 | }, 413 | { 414 | "name": "XOR", 415 | "code": { 416 | "asm": "PUSH1 0xf0\nPUSH1 0x0f\nXOR", 417 | "bin": "60f0600f18" 418 | }, 419 | "expect": { 420 | "stack": ["0xff"] 421 | } 422 | }, 423 | { 424 | "name": "NOT", 425 | "code": { 426 | "asm": "PUSH1 0x0f\nNOT", 427 | "bin": "600f19" 428 | }, 429 | "expect": { 430 | "stack": ["0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0"] 431 | } 432 | }, 433 | { 434 | "name": "BYTE", 435 | "code": { 436 | "asm": "PUSH1 0xff\nPUSH1 31\nBYTE", 437 | "bin": "60ff601f1a" 438 | }, 439 | "expect": { 440 | "stack": ["0xff"] 441 | } 442 | }, 443 | { 444 | "name": "BYTE (30th)", 445 | "code": { 446 | "asm": "PUSH2 0xff00\nPUSH1 30\nBYTE", 447 | "bin": "61ff00601e1a" 448 | }, 449 | "expect": { 450 | "stack": ["0xff"] 451 | } 452 | }, 453 | { 454 | "name": "BYTE (29th)", 455 | "code": { 456 | "asm": "PUSH3 0xff0000\nPUSH1 29\nBYTE", 457 | "bin": "62ff0000601d1a" 458 | }, 459 | "expect": { 460 | "stack": ["0xff"] 461 | } 462 | }, 463 | { 464 | "name": "BYTE (out of range)", 465 | "code": { 466 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPUSH1 42\nBYTE", 467 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff602a1a" 468 | }, 469 | "expect": { 470 | "stack": ["0x0"] 471 | } 472 | }, 473 | { 474 | "name": "DUP1", 475 | "code": { 476 | "asm": "PUSH1 1\nDUP1\nADD", 477 | "bin": "60018001" 478 | }, 479 | "expect": { 480 | "stack": ["0x2"] 481 | } 482 | }, 483 | { 484 | "name": "DUP3", 485 | "code": { 486 | "asm": "PUSH1 1\nPUSH1 2\nPUSH1 3\nDUP3", 487 | "bin": "60016002600382" 488 | }, 489 | "expect": { 490 | "stack": ["0x1", "0x3", "0x2", "0x1"] 491 | } 492 | }, 493 | { 494 | "name": "SWAP", 495 | "code": { 496 | "asm": "PUSH1 1\nPUSH1 2\nSWAP1", 497 | "bin": "6001600290" 498 | }, 499 | "expect": { 500 | "stack": ["0x1", "0x2"] 501 | } 502 | }, 503 | { 504 | "name": "SWAP3", 505 | "code": { 506 | "asm": "PUSH1 1\nPUSH1 2\nPUSH1 3\nPUSH1 4\nSWAP3", 507 | "bin": "600160026003600492" 508 | }, 509 | "expect": { 510 | "stack": ["0x1", "0x3", "0x2", "0x4"] 511 | } 512 | }, 513 | { 514 | "name": "INVALID", 515 | "code": { 516 | "asm": "INVALID", 517 | "bin": "fe" 518 | }, 519 | "expect": { 520 | "success": false, 521 | "stack": [] 522 | } 523 | }, 524 | { 525 | "name": "PC", 526 | "code": { 527 | "asm": "PC", 528 | "bin": "58" 529 | }, 530 | "expect": { 531 | "stack": ["0x0"] 532 | } 533 | }, 534 | { 535 | "name": "PC (more code)", 536 | "code": { 537 | "asm": "PUSH1 0\nPOP\nPC", 538 | "bin": "60005058" 539 | }, 540 | "expect": { 541 | "stack": ["0x3"] 542 | } 543 | }, 544 | { 545 | "name": "GAS", 546 | "code": { 547 | "asm": "GAS", 548 | "bin": "5a" 549 | }, 550 | "expect": { 551 | "stack": ["0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"] 552 | } 553 | }, 554 | { 555 | "name": "JUMP", 556 | "code": { 557 | "asm": "PUSH1 5\nJUMP\nPUSH1 1\nJUMPDEST\nPUSH1 2", 558 | "bin": "60055660015b6002" 559 | }, 560 | "expect": { 561 | "stack": ["0x2"] 562 | } 563 | }, 564 | { 565 | "name": "JUMPI (no jump)", 566 | "code": { 567 | "asm": "PUSH1 0\nPUSH1 7\nJUMPI\nPUSH1 1\nJUMPDEST\nPUSH1 2\nPOP", 568 | "bin": "600060075760015b600250" 569 | }, 570 | "expect": { 571 | "stack": ["0x1"] 572 | } 573 | }, 574 | { 575 | "name": "JUMPI (jump)", 576 | "code": { 577 | "asm": "PUSH1 1\nPUSH1 7\nJUMPI\nPUSH1 1\nJUMPDEST\nPUSH1 2", 578 | "bin": "600160075760015b6002" 579 | }, 580 | "expect": { 581 | "stack": ["0x2"] 582 | } 583 | }, 584 | { 585 | "name": "MSTORE", 586 | "code": { 587 | "asm": "PUSH32 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\nPUSH1 0\nMSTORE\nPUSH1 0\nMLOAD", 588 | "bin": "7f0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20600052600051" 589 | }, 590 | "expect": { 591 | "stack": ["0x102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"] 592 | } 593 | }, 594 | { 595 | "name": "MSTORE (tail)", 596 | "code": { 597 | "asm": "PUSH32 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20\nPUSH1 0\nMSTORE\nPUSH1 31\nMLOAD", 598 | "bin": "7f0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20600052601f51" 599 | }, 600 | "expect": { 601 | "stack": ["0x2000000000000000000000000000000000000000000000000000000000000000"] 602 | } 603 | }, 604 | { 605 | "name": "MSTORE8", 606 | "code": { 607 | "asm": "PUSH1 0xff\nPUSH1 31\nMSTORE8\nPUSH1 0\nMLOAD", 608 | "bin": "60ff601f53600051" 609 | }, 610 | "expect": { 611 | "stack": ["0xff"] 612 | } 613 | }, 614 | { 615 | "name": "MSIZE", 616 | "code": { 617 | "asm": "MSIZE", 618 | "bin": "59" 619 | }, 620 | "expect": { 621 | "stack": ["0x0"] 622 | } 623 | }, 624 | { 625 | "name": "MSIZE (0x20)", 626 | "code": { 627 | "asm": "PUSH1 0\nMLOAD\nPOP\nMSIZE", 628 | "bin": "6000515059" 629 | }, 630 | "expect": { 631 | "stack": ["0x20"] 632 | } 633 | }, 634 | { 635 | "name": "MSIZE (0x60)", 636 | "code": { 637 | "asm": "PUSH1 0x39\nMLOAD\nPOP\nMSIZE", 638 | "bin": "6039515059" 639 | }, 640 | "expect": { 641 | "stack": ["0x60"] 642 | } 643 | }, 644 | { 645 | "name": "MSIZE (after MSTORE)", 646 | "code": { 647 | "asm": "PUSH1 0xff\nPUSH1 0xff\nMSTORE8\nMSIZE", 648 | "bin": "60ff60ff5359" 649 | }, 650 | "expect": { 651 | "stack": ["0x100"] 652 | } 653 | }, 654 | { 655 | "name": "SHA3", 656 | "code": { 657 | "asm": "PUSH32 0xffffffff00000000000000000000000000000000000000000000000000000000\nPUSH1 0\nMSTORE\nPUSH1 4\nPUSH1 0\nSHA3", 658 | "bin": "7fffffffff000000000000000000000000000000000000000000000000000000006000526004600020" 659 | }, 660 | "expect": { 661 | "stack": ["0x29045a592007d0c246ef02c2223570da9522d0cf0f73282c79a1bc8f0bb2c238"] 662 | } 663 | }, 664 | { 665 | "name": "ADDRESS", 666 | "tx": { 667 | "to": "0x1000000000000000000000000000000000000aaa" 668 | }, 669 | "code": { 670 | "asm": "ADDRESS", 671 | "bin": "30" 672 | }, 673 | "expect": { 674 | "stack": ["0x1000000000000000000000000000000000000aaa"] 675 | } 676 | }, 677 | { 678 | "name": "CALLER", 679 | "tx": { 680 | "from": "0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d" 681 | }, 682 | "code": { 683 | "asm": "CALLER", 684 | "bin": "33" 685 | }, 686 | "expect": { 687 | "stack": ["0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d"] 688 | } 689 | }, 690 | { 691 | "name": "BALANCE", 692 | "state": { 693 | "0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d": { 694 | "balance": "0x100" 695 | } 696 | }, 697 | "code": { 698 | "asm": "PUSH20 0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d\nBALANCE", 699 | "bin": "731e79b045dc29eae9fdc69673c9dcd7c53e5e159d31" 700 | }, 701 | "expect": { 702 | "stack": ["0x100"] 703 | } 704 | }, 705 | { 706 | "name": "BALANCE (empty)", 707 | "code": { 708 | "asm": "PUSH20 0xaf69610ea9ddc95883f97a6a3171d52165b69b03\nBALANCE", 709 | "bin": "73af69610ea9ddc95883f97a6a3171d52165b69b0331" 710 | }, 711 | "expect": { 712 | "stack": ["0x0"] 713 | } 714 | }, 715 | { 716 | "name": "ORIGIN", 717 | "tx": { 718 | "origin": "0x1337" 719 | }, 720 | "code": { 721 | "asm": "ORIGIN", 722 | "bin": "32" 723 | }, 724 | "expect": { 725 | "stack": ["0x1337"] 726 | } 727 | }, 728 | { 729 | "name": "COINBASE", 730 | "block": { 731 | "coinbase": "0x777" 732 | }, 733 | "code": { 734 | "asm": "COINBASE", 735 | "bin": "41" 736 | }, 737 | "expect": { 738 | "stack": ["0x777"] 739 | } 740 | }, 741 | { 742 | "name": "TIMESTAMP", 743 | "block": { 744 | "timestamp": "0xe4e1c1" 745 | }, 746 | "code": { 747 | "asm": "TIMESTAMP", 748 | "bin": "42" 749 | }, 750 | "expect": { 751 | "stack": ["0xe4e1c1"] 752 | } 753 | }, 754 | { 755 | "name": "NUMBER", 756 | "block": { 757 | "number": "0x1000001" 758 | }, 759 | "code": { 760 | "asm": "NUMBER", 761 | "bin": "43" 762 | }, 763 | "expect": { 764 | "stack": ["0x1000001"] 765 | } 766 | }, 767 | { 768 | "name": "DIFFICULTY", 769 | "block": { 770 | "difficulty": "0x20000" 771 | }, 772 | "code": { 773 | "asm": "DIFFICULTY", 774 | "bin": "44" 775 | }, 776 | "expect": { 777 | "stack": ["0x20000"] 778 | } 779 | }, 780 | { 781 | "name": "GASLIMIT", 782 | "block": { 783 | "gaslimit": "0xffffffffffff" 784 | }, 785 | "code": { 786 | "asm": "GASLIMIT", 787 | "bin": "45" 788 | }, 789 | "expect": { 790 | "stack": ["0xffffffffffff"] 791 | } 792 | }, 793 | { 794 | "name": "GASPRICE", 795 | "tx": { 796 | "gasprice": "0x99" 797 | }, 798 | "code": { 799 | "asm": "GASPRICE", 800 | "bin": "3a" 801 | }, 802 | "expect": { 803 | "stack": ["0x99"] 804 | } 805 | }, 806 | { 807 | "name": "CHAINID", 808 | "block": { 809 | "chainid": "0x1" 810 | }, 811 | "code": { 812 | "asm": "CHAINID", 813 | "bin": "46" 814 | }, 815 | "expect": { 816 | "stack": ["0x1"] 817 | } 818 | }, 819 | { 820 | "name": "CALLVALUE", 821 | "tx": { 822 | "value": "0x1000" 823 | }, 824 | "code": { 825 | "asm": "CALLVALUE", 826 | "bin": "34" 827 | }, 828 | "expect": { 829 | "stack": ["0x1000"] 830 | } 831 | }, 832 | { 833 | "name": "CALLDATALOAD", 834 | "tx": { 835 | "data": "000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff" 836 | }, 837 | "code": { 838 | "asm": "PUSH1 0\nCALLDATALOAD", 839 | "bin": "600035" 840 | }, 841 | "expect": { 842 | "stack": ["0x102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff"] 843 | } 844 | }, 845 | { 846 | "name": "CALLDATALOAD (tail)", 847 | "tx": { 848 | "data": "000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff" 849 | }, 850 | "code": { 851 | "asm": "PUSH1 31\nCALLDATALOAD", 852 | "bin": "601f35" 853 | }, 854 | "expect": { 855 | "stack": ["0xff00000000000000000000000000000000000000000000000000000000000000"] 856 | } 857 | }, 858 | { 859 | "name": "CALLDATASIZE", 860 | "tx": { 861 | "data": "000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff" 862 | }, 863 | "code": { 864 | "asm": "CALLDATASIZE", 865 | "bin": "36" 866 | }, 867 | "expect": { 868 | "stack": ["0x20"] 869 | } 870 | }, 871 | { 872 | "name": "CALLDATASIZE (no data)", 873 | "code": { 874 | "asm": "CALLDATASIZE", 875 | "bin": "36" 876 | }, 877 | "expect": { 878 | "stack": ["0x0"] 879 | } 880 | }, 881 | { 882 | "name": "CALLDATACOPY", 883 | "tx": { 884 | "data": "000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff" 885 | }, 886 | "code": { 887 | "asm": "PUSH1 32\nPUSH1 0\nPUSH1 0\nCALLDATACOPY\nPUSH1 0\nMLOAD", 888 | "bin": "60206000600037600051" 889 | }, 890 | "expect": { 891 | "stack": ["0x102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff"] 892 | } 893 | }, 894 | { 895 | "name": "CALLDATACOPY (tail)", 896 | "tx": { 897 | "data": "000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeff" 898 | }, 899 | "code": { 900 | "asm": "PUSH1 1\nPUSH1 31\nPUSH1 0\nCALLDATACOPY\nPUSH1 0\nMLOAD", 901 | "bin": "6001601f600037600051" 902 | }, 903 | "expect": { 904 | "stack": ["0xff00000000000000000000000000000000000000000000000000000000000000"] 905 | } 906 | }, 907 | { 908 | "name": "CODESIZE (small)", 909 | "code": { 910 | "asm": "CODESIZE", 911 | "bin": "38" 912 | }, 913 | "expect": { 914 | "stack": ["0x1"] 915 | } 916 | }, 917 | { 918 | "name": "CODESIZE", 919 | "code": { 920 | "asm": "PUSH20 0\nPOP\nCODESIZE", 921 | "bin": "7300000000000000000000000000000000000000005038" 922 | }, 923 | "expect": { 924 | "stack": ["0x17"] 925 | } 926 | }, 927 | { 928 | "name": "CODECOPY", 929 | "code": { 930 | "asm": "PUSH1 32\nPUSH1 0\nPUSH1 0\nCODECOPY\nPUSH1 0\nMLOAD", 931 | "bin": "60206000600039600051" 932 | }, 933 | "expect": { 934 | "stack": ["0x6020600060003960005100000000000000000000000000000000000000000000"] 935 | } 936 | }, 937 | { 938 | "name": "CODECOPY (tail)", 939 | "code": { 940 | "asm": "PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\nPOP\nPUSH1 2\nPUSH1 32\nPUSH1 0\nCODECOPY\nPUSH1 0\nMLOAD", 941 | "bin": "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5060026020600039600051" 942 | }, 943 | "expect": { 944 | "stack": ["0xff50000000000000000000000000000000000000000000000000000000000000"] 945 | } 946 | }, 947 | { 948 | "name": "EXTCODESIZE (empty)", 949 | "code": { 950 | "asm": "PUSH20 0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d\nEXTCODESIZE", 951 | "bin": "731e79b045dc29eae9fdc69673c9dcd7c53e5e159d3b" 952 | }, 953 | "expect": { 954 | "stack": ["0x0"] 955 | } 956 | }, 957 | { 958 | "name": "EXTCODESIZE", 959 | "state": { 960 | "0x1000000000000000000000000000000000000aaa": { 961 | "code": { 962 | "asm": "PUSH1 1", 963 | "bin": "6001" 964 | } 965 | } 966 | }, 967 | "code": { 968 | "asm": "PUSH20 0x1000000000000000000000000000000000000aaa\nEXTCODESIZE", 969 | "bin": "731000000000000000000000000000000000000aaa3b" 970 | }, 971 | "expect": { 972 | "stack": ["0x2"] 973 | } 974 | }, 975 | { 976 | "name": "EXTCODECOPY", 977 | "state": { 978 | "0x1000000000000000000000000000000000000aaa": { 979 | "code": { 980 | "asm": null, 981 | "bin": "6001" 982 | } 983 | } 984 | }, 985 | "code": { 986 | "asm": "PUSH1 32\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000aaa\nEXTCODECOPY\nPUSH1 0\nMLOAD", 987 | "bin": "602060006000731000000000000000000000000000000000000aaa3c600051" 988 | }, 989 | "expect": { 990 | "stack": ["0x6001000000000000000000000000000000000000000000000000000000000000"] 991 | } 992 | }, 993 | { 994 | "name": "SELFBALANCE", 995 | "tx": { 996 | "to": "0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d" 997 | }, 998 | "state": { 999 | "0x1e79b045dc29eae9fdc69673c9dcd7c53e5e159d": { 1000 | "balance": "0x200" 1001 | } 1002 | }, 1003 | "code": { 1004 | "asm": "SELFBALANCE", 1005 | "bin": "47" 1006 | }, 1007 | "expect": { 1008 | "stack": ["0x200"] 1009 | } 1010 | }, 1011 | { 1012 | "name": "SSTORE", 1013 | "code": { 1014 | "asm": "PUSH1 1\nPUSH1 0\nSSTORE\nPUSH1 0\nSLOAD", 1015 | "bin": "6001600055600054" 1016 | }, 1017 | "expect": { 1018 | "stack": ["0x1"] 1019 | } 1020 | }, 1021 | { 1022 | "name": "SSTORE (non-zero location)", 1023 | "code": { 1024 | "asm": "PUSH1 2\nPUSH4 0x98fe5c2c\nSSTORE\nPUSH4 0x98fe5c2c\nSLOAD", 1025 | "bin": "60026398fe5c2c556398fe5c2c54" 1026 | }, 1027 | "expect": { 1028 | "stack": ["0x2"] 1029 | } 1030 | }, 1031 | { 1032 | "name": "SLOAD (empty)", 1033 | "code": { 1034 | "asm": "PUSH1 0xff\nSLOAD", 1035 | "bin": "60ff54" 1036 | }, 1037 | "expect": { 1038 | "stack": ["0x0"] 1039 | } 1040 | }, 1041 | { 1042 | "name": "LOG0", 1043 | "tx": { 1044 | "to": "0x1000000000000000000000000000000000000001" 1045 | }, 1046 | "code": { 1047 | "asm": "PUSH1 0xaa\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nLOG0", 1048 | "bin": "60aa6000526001601fa0" 1049 | }, 1050 | "expect": { 1051 | "logs": [ 1052 | { 1053 | "address": "0x1000000000000000000000000000000000000001", 1054 | "data": "aa", 1055 | "topics": [] 1056 | } 1057 | ] 1058 | } 1059 | }, 1060 | { 1061 | "name": "LOG1", 1062 | "tx": { 1063 | "to": "0x1000000000000000000000000000000000000001" 1064 | }, 1065 | "code": { 1066 | "asm": "PUSH1 0xbb\nPUSH1 0\nMSTORE\nPUSH32 0x1111111111111111111111111111111111111111111111111111111111111111\nPUSH1 1\nPUSH1 31\nLOG1", 1067 | "bin": "60bb6000527f11111111111111111111111111111111111111111111111111111111111111116001601fa1" 1068 | }, 1069 | "expect": { 1070 | "logs": [ 1071 | { 1072 | "address": "0x1000000000000000000000000000000000000001", 1073 | "data": "bb", 1074 | "topics": ["0x1111111111111111111111111111111111111111111111111111111111111111"] 1075 | } 1076 | ] 1077 | } 1078 | }, 1079 | { 1080 | "name": "LOG2", 1081 | "tx": { 1082 | "to": "0x1000000000000000000000000000000000000001" 1083 | }, 1084 | "code": { 1085 | "asm": "PUSH1 0xcc\nPUSH1 0\nMSTORE\nPUSH32 0x1111111111111111111111111111111111111111111111111111111111111111\nPUSH32 0x2222222222222222222222222222222222222222222222222222222222222222\nPUSH1 1\nPUSH1 31\nLOG2", 1086 | "bin": "60cc6000527f11111111111111111111111111111111111111111111111111111111111111117f22222222222222222222222222222222222222222222222222222222222222226001601fa2" 1087 | }, 1088 | "expect": { 1089 | "logs": [ 1090 | { 1091 | "address": "0x1000000000000000000000000000000000000001", 1092 | "data": "cc", 1093 | "topics": [ 1094 | "0x2222222222222222222222222222222222222222222222222222222222222222", 1095 | "0x1111111111111111111111111111111111111111111111111111111111111111" 1096 | ] 1097 | } 1098 | ] 1099 | } 1100 | }, 1101 | { 1102 | "name": "LOG3", 1103 | "tx": { 1104 | "to": "0x1000000000000000000000000000000000000001" 1105 | }, 1106 | "code": { 1107 | "asm": "PUSH1 0xdd\nPUSH1 0\nMSTORE\nPUSH32 0x1111111111111111111111111111111111111111111111111111111111111111\nPUSH32 0x2222222222222222222222222222222222222222222222222222222222222222\nPUSH32 0x3333333333333333333333333333333333333333333333333333333333333333\nPUSH1 1\nPUSH1 31\nLOG3", 1108 | "bin": "60dd6000527f11111111111111111111111111111111111111111111111111111111111111117f22222222222222222222222222222222222222222222222222222222222222227f33333333333333333333333333333333333333333333333333333333333333336001601fa3" 1109 | }, 1110 | "expect": { 1111 | "logs": [ 1112 | { 1113 | "address": "0x1000000000000000000000000000000000000001", 1114 | "data": "dd", 1115 | "topics": [ 1116 | "0x3333333333333333333333333333333333333333333333333333333333333333", 1117 | "0x2222222222222222222222222222222222222222222222222222222222222222", 1118 | "0x1111111111111111111111111111111111111111111111111111111111111111" 1119 | ] 1120 | } 1121 | ] 1122 | } 1123 | }, 1124 | { 1125 | "name": "LOG4", 1126 | "tx": { 1127 | "to": "0x1000000000000000000000000000000000000001" 1128 | }, 1129 | "code": { 1130 | "asm": "PUSH1 0xee\nPUSH1 0\nMSTORE\nPUSH32 0x1111111111111111111111111111111111111111111111111111111111111111\nPUSH32 0x2222222222222222222222222222222222222222222222222222222222222222\nPUSH32 0x3333333333333333333333333333333333333333333333333333333333333333\nPUSH32 0x4444444444444444444444444444444444444444444444444444444444444444\nPUSH1 1\nPUSH1 31\nLOG4", 1131 | "bin": "60ee6000527f11111111111111111111111111111111111111111111111111111111111111117f22222222222222222222222222222222222222222222222222222222222222227f33333333333333333333333333333333333333333333333333333333333333337f44444444444444444444444444444444444444444444444444444444444444446001601fa4" 1132 | }, 1133 | "expect": { 1134 | "logs": [ 1135 | { 1136 | "address": "0x1000000000000000000000000000000000000001", 1137 | "data": "ee", 1138 | "topics": [ 1139 | "0x4444444444444444444444444444444444444444444444444444444444444444", 1140 | "0x3333333333333333333333333333333333333333333333333333333333333333", 1141 | "0x2222222222222222222222222222222222222222222222222222222222222222", 1142 | "0x1111111111111111111111111111111111111111111111111111111111111111" 1143 | ] 1144 | } 1145 | ] 1146 | } 1147 | }, 1148 | { 1149 | "name": "RETURN", 1150 | "code": { 1151 | "asm": "PUSH1 0xA2\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nRETURN", 1152 | "bin": "60a26000526001601ff3" 1153 | }, 1154 | "expect": { 1155 | "success": true, 1156 | "return": "a2" 1157 | } 1158 | }, 1159 | { 1160 | "name": "REVERT", 1161 | "code": { 1162 | "asm": "PUSH1 0xF1\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nREVERT", 1163 | "bin": "60f16000526001601ffd" 1164 | }, 1165 | "expect": { 1166 | "success": false, 1167 | "return": "f1" 1168 | } 1169 | }, 1170 | { 1171 | "name": "CALL", 1172 | "state": { 1173 | "0x1000000000000000000000000000000000000c42": { 1174 | "code": { 1175 | "asm": "PUSH1 0x42\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nRETURN", 1176 | "bin": "60426000526001601ff3" 1177 | } 1178 | } 1179 | }, 1180 | "code": { 1181 | "asm": "PUSH1 1\nPUSH1 31\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nCALL\nPUSH1 0\nMLOAD", 1182 | "bin": "6001601f600060006000731000000000000000000000000000000000000c426000f1600051" 1183 | }, 1184 | "expect": { 1185 | "stack": ["0x42", "0x1"] 1186 | } 1187 | }, 1188 | { 1189 | "name": "CALL (returns address)", 1190 | "tx": { 1191 | "to": "0x1000000000000000000000000000000000000aaa" 1192 | }, 1193 | "state": { 1194 | "0x1000000000000000000000000000000000000c42": { 1195 | "code": { 1196 | "asm": "CALLER\nPUSH1 0\nMSTORE\nPUSH1 32\nPUSH1 0\nRETURN", 1197 | "bin": "3360005260206000f3" 1198 | } 1199 | } 1200 | }, 1201 | "code": { 1202 | "asm": "PUSH1 32\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nCALL\nPUSH1 0\nMLOAD", 1203 | "bin": "60206000600060006000731000000000000000000000000000000000000c426000f1600051" 1204 | }, 1205 | "expect": { 1206 | "stack": ["0x1000000000000000000000000000000000000aaa", "0x1"] 1207 | } 1208 | }, 1209 | { 1210 | "name": "CALL (reverts)", 1211 | "state": { 1212 | "0x1000000000000000000000000000000000000c42": { 1213 | "code": { 1214 | "asm": "PUSH1 0x42\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nREVERT", 1215 | "bin": "60426000526001601ffd" 1216 | } 1217 | } 1218 | }, 1219 | "code": { 1220 | "asm": "PUSH1 1\nPUSH1 31\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nCALL\nPUSH1 0\nMLOAD", 1221 | "bin": "6001601f600060006000731000000000000000000000000000000000000c426000f1600051" 1222 | }, 1223 | "expect": { 1224 | "stack": ["0x42", "0x0"] 1225 | } 1226 | }, 1227 | { 1228 | "name": "RETURNDATASIZE (empty)", 1229 | "code": { 1230 | "asm": "RETURNDATASIZE", 1231 | "bin": "3d" 1232 | }, 1233 | "expect": { 1234 | "stack": ["0x0"] 1235 | } 1236 | }, 1237 | { 1238 | "name": "RETURNDATASIZE", 1239 | "state": { 1240 | "0x1000000000000000000000000000000000000c42": { 1241 | "code": { 1242 | "asm": "PUSH1 0x42\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nRETURN", 1243 | "bin": "60426000526001601ff3" 1244 | } 1245 | } 1246 | }, 1247 | "code": { 1248 | "asm": "PUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nCALL\nPOP\nRETURNDATASIZE", 1249 | "bin": "60006000600060006000731000000000000000000000000000000000000c426000f1503d" 1250 | }, 1251 | "expect": { 1252 | "stack": ["0x1"] 1253 | } 1254 | }, 1255 | { 1256 | "name": "RETURNDATACOPY", 1257 | "state": { 1258 | "0x1000000000000000000000000000000000000c42": { 1259 | "code": { 1260 | "asm": "PUSH1 0x42\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nRETURN", 1261 | "bin": "60426000526001601ff3" 1262 | } 1263 | } 1264 | }, 1265 | "code": { 1266 | "asm": "PUSH1 1\nPUSH1 31\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nCALL\nPOP\nPUSH1 1\nPUSH1 0\nPUSH1 0xff\nRETURNDATACOPY\nPUSH1 0xff\nMLOAD", 1267 | "bin": "6001601f600060006000731000000000000000000000000000000000000c426000f1506001600060ff3e60ff51" 1268 | }, 1269 | "expect": { 1270 | "stack": ["0x4200000000000000000000000000000000000000000000000000000000000000"] 1271 | } 1272 | }, 1273 | { 1274 | "name": "DELEGATECALL", 1275 | "tx": { 1276 | "to": "0x1000000000000000000000000000000000000aaa" 1277 | }, 1278 | "state": { 1279 | "0xdddddddddddddddddddddddddddddddddddddddd": { 1280 | "code": { 1281 | "asm": "ADDRESS\nPUSH1 0\nSSTORE", 1282 | "bin": "30600055" 1283 | } 1284 | } 1285 | }, 1286 | "code": { 1287 | "asm": "PUSH1 0\nDUP1\nDUP1\nDUP1\nPUSH20 0xdddddddddddddddddddddddddddddddddddddddd\nGAS\nDELEGATECALL\nPUSH1 0\nSLOAD", 1288 | "bin": "600080808073dddddddddddddddddddddddddddddddddddddddd5af4600054" 1289 | }, 1290 | "expect": { 1291 | "stack": ["0x1000000000000000000000000000000000000aaa", "0x1"] 1292 | } 1293 | }, 1294 | { 1295 | "name": "STATICCALL", 1296 | "state": { 1297 | "0x1000000000000000000000000000000000000c42": { 1298 | "code": { 1299 | "asm": "PUSH1 0x42\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nRETURN", 1300 | "bin": "60426000526001601ff3" 1301 | } 1302 | } 1303 | }, 1304 | "code": { 1305 | "asm": "PUSH1 1\nPUSH1 31\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nSTATICCALL\nPUSH1 0\nMLOAD", 1306 | "bin": "6001601f60006000731000000000000000000000000000000000000c426000fa600051" 1307 | }, 1308 | "expect": { 1309 | "stack": ["0x42", "0x1"] 1310 | } 1311 | }, 1312 | { 1313 | "name": "STATICCALL (reverts on write)", 1314 | "state": { 1315 | "0x1000000000000000000000000000000000000c42": { 1316 | "code": { 1317 | "asm": "PUSH1 0x42\nPUSH1 0\nSSTORE", 1318 | "bin": "6042600055" 1319 | } 1320 | } 1321 | }, 1322 | "code": { 1323 | "asm": "PUSH1 1\nPUSH1 31\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nSTATICCALL", 1324 | "bin": "6001601f60006000731000000000000000000000000000000000000c426000fa" 1325 | }, 1326 | "expect": { 1327 | "stack": ["0x0"] 1328 | } 1329 | }, 1330 | { 1331 | "name": "CREATE (empty)", 1332 | "tx": { 1333 | "to": "0x9bbfed6889322e016e0a02ee459d306fc19545d8" 1334 | }, 1335 | "code": { 1336 | "asm": "PUSH1 0\nPUSH1 0\nPUSH1 9\nCREATE\nBALANCE", 1337 | "bin": "600060006009f031" 1338 | }, 1339 | "expect": { 1340 | "stack": ["0x9"] 1341 | } 1342 | }, 1343 | { 1344 | "name": "CREATE (with 4x FF)", 1345 | "tx": { 1346 | "to": "0x9bbfed6889322e016e0a02ee459d306fc19545d8" 1347 | }, 1348 | "code": { 1349 | "asm": "PUSH1 32\nPUSH1 0\nPUSH1 0\nPUSH13 0x63FFFFFFFF6000526004601CF3\nPUSH1 0\nMSTORE\nPUSH1 13\nPUSH1 19\nPUSH1 0\nCREATE\nEXTCODECOPY\nPUSH1 0\nMLOAD", 1350 | "bin": "6020600060006c63ffffffff6000526004601cf3600052600d60136000f03c600051" 1351 | }, 1352 | "expect": { 1353 | "stack": ["0xffffffff00000000000000000000000000000000000000000000000000000000"] 1354 | } 1355 | }, 1356 | { 1357 | "name": "CREATE (reverts)", 1358 | "tx": { 1359 | "to": "0x9bbfed6889322e016e0a02ee459d306fc19545d8" 1360 | }, 1361 | "code": { 1362 | "asm": "PUSH13 0x63FFFFFFFF6000526004601CFD\nPUSH1 0\nMSTORE\nPUSH1 13\nPUSH1 19\nPUSH1 0\nCREATE", 1363 | "bin": "6c63ffffffff6000526004601cfd600052600d60136000f0" 1364 | }, 1365 | "expect": { 1366 | "stack": ["0x0"] 1367 | } 1368 | }, 1369 | { 1370 | "name": "SELFDESTRUCT", 1371 | "state": { 1372 | "0xdead00000000000000000000000000000000dead": { 1373 | "balance": "0x7", 1374 | "code": { 1375 | "asm": "PUSH20 0xa1c300000000000000000000000000000000a1c3\nSELFDESTRUCT", 1376 | "bin": "73a1c300000000000000000000000000000000a1c3ff" 1377 | } 1378 | } 1379 | }, 1380 | "code": { 1381 | "asm": "PUSH1 0\nDUP1\nDUP1\nDUP1\nDUP1\nPUSH20 0xdead00000000000000000000000000000000dead\nGAS\nCALL\nPOP\nPUSH20 0xa1c300000000000000000000000000000000a1c3\nBALANCE\nPUSH20 0xdead00000000000000000000000000000000dead\nEXTCODESIZE", 1382 | "bin": "60008080808073dead00000000000000000000000000000000dead5af15073a1c300000000000000000000000000000000a1c33173dead00000000000000000000000000000000dead3b" 1383 | }, 1384 | "expect": { 1385 | "stack": ["0x0", "0x7"] 1386 | } 1387 | } 1388 | ] 1389 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/index.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "@jest/globals" 2 | 3 | import EVM from "./src/evm" 4 | import tests from "./evm.json" 5 | import { parsers } from "./src/opcodes/utils" 6 | import { buildBlock, buildState, buildTxData } from "./src/utils" 7 | 8 | import type { EvmRuntimeParams, Test } from "./src/types" 9 | 10 | for (const t of tests as Test[]) { 11 | test(t.name, async () => { 12 | const evm = new EVM({ debug: false, saveLogs: false }) 13 | 14 | const EvmRuntimeParams: EvmRuntimeParams = { 15 | _code: parsers.hexStringToUint8Array(t.code.bin), 16 | _asm: t.code.asm, 17 | _txData: buildTxData(t), 18 | _globalState: buildState(t), 19 | _block: buildBlock(t), 20 | } 21 | 22 | const result = await evm.start(EvmRuntimeParams) 23 | 24 | if (typeof t.expect.stack !== "undefined") 25 | expect(result.stack).toEqual( 26 | t.expect.stack.map((item) => parsers.HexStringIntoBigInt(item)) 27 | ) 28 | 29 | if (typeof t.expect.success !== "undefined") 30 | expect(result.success).toEqual(t.expect.success) 31 | 32 | if (typeof t.expect.return !== "undefined") 33 | expect(result.return).toEqual(t.expect.return) 34 | 35 | if (typeof t.expect.logs !== "undefined") expect(result.logs).toEqual(t.expect.logs) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from "fs" 4 | import yargs from "yargs/yargs" 5 | 6 | import EVM from "./src/evm" 7 | import { parsers } from "./src/opcodes/utils" 8 | import { buildBlock, buildState, buildTxData, validateInputFile } from "./src/utils" 9 | 10 | async function main() { 11 | const argv = await yargs(process.argv.slice(2)).options({ 12 | bytecode: { type: "string", demandOption: false }, 13 | file: { type: "string", demandOption: false }, 14 | debug: { type: "boolean", default: false }, 15 | saveLogs: { type: "boolean", default: true }, 16 | }).argv 17 | 18 | if (!argv.bytecode && !argv.file) 19 | throw new Error("Please provide either a bytecode or file flag") 20 | 21 | if (argv.bytecode && argv.file) 22 | throw new Error("Please specify either bytecode or file input, not both") 23 | 24 | if (argv.bytecode) { 25 | console.log("Running specified bytecode...") 26 | 27 | const evm = new EVM({ 28 | debug: argv.debug, 29 | saveLogs: argv.saveLogs, 30 | }) 31 | 32 | const result = await evm.start({ 33 | _code: parsers.hexStringToUint8Array(argv.bytecode), 34 | }) 35 | 36 | console.log("Execution complete") 37 | console.log("Result:\n", result) 38 | } 39 | 40 | if (argv.file) { 41 | console.log("Running from file input...") 42 | 43 | const file = fs.readFileSync(argv.file, "utf8") 44 | const options = JSON.parse(file) 45 | 46 | if (!validateInputFile(options)) 47 | throw new Error("Invalid EVM input JSON. Please see README for more information.") 48 | 49 | const _code = parsers.hexStringToUint8Array(options.code.bin) 50 | const _asm = options.code.asm 51 | const _txData = buildTxData(options.txData) 52 | const _globalState = buildState(options.globalState) 53 | const _block = buildBlock(options.block) 54 | 55 | const evm = new EVM({ 56 | debug: argv.debug, 57 | saveLogs: argv.saveLogs, 58 | }) 59 | 60 | const result = await evm.start({ _code, _asm, _txData, _globalState, _block }) 61 | 62 | console.log("Execution complete") 63 | if (argv.saveLogs) console.log("Logs saved to file.") 64 | 65 | console.log("Result:\n", result) 66 | } 67 | } 68 | 69 | main() 70 | .then(() => process.exit(0)) 71 | .catch((err) => { 72 | console.error(err) 73 | process.exit(1) 74 | }) 75 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RETURNDATASIZE", 3 | "state": { 4 | "0x1000000000000000000000000000000000000c42": { 5 | "code": { 6 | "asm": "PUSH1 0x42\nPUSH1 0\nMSTORE\nPUSH1 1\nPUSH1 31\nRETURN", 7 | "bin": "60426000526001601ff3" 8 | } 9 | } 10 | }, 11 | "code": { 12 | "asm": "PUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH1 0\nPUSH20 0x1000000000000000000000000000000000000c42\nPUSH1 0\nCALL\nPOP\nRETURNDATASIZE", 13 | "bin": "60006000600060006000731000000000000000000000000000000000000c426000f1503d" 14 | }, 15 | "expect": { 16 | "stack": ["0x1"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/jest-environment-fail-fast.js: -------------------------------------------------------------------------------- 1 | const ParentEnvironment = require("jest-environment-node").default 2 | 3 | // This is a custom Jest environment that skips the remaining tests when a test fails. 4 | // It's used to focus on a single test at a time 5 | 6 | class JestEnvironmentFailFast extends ParentEnvironment { 7 | failedTest = false 8 | 9 | async handleTestEvent(event, state) { 10 | if (event.name === "hook_failure" || event.name === "test_fn_failure") { 11 | this.failedTest = true 12 | showFailedTestOnExit(event) 13 | } else if (this.failedTest && event.name === "test_start") { 14 | event.test.mode = "skip" 15 | } 16 | 17 | if (super.handleTestEvent) { 18 | await super.handleTestEvent(event, state) 19 | } 20 | } 21 | } 22 | 23 | function showFailedTestOnExit(event) { 24 | // setTimeout is used to output text after Jest's default reporter has finished 25 | // printing the test failure message. Otherwise, Jest could print over our text. 26 | setTimeout(() => { 27 | const t = require("./evm.json").find((t) => t.name === event.test.name) 28 | process.stdout.write( 29 | `\n\nFailing test case (${event.test.name}):\n\n` + 30 | (t.code.asm 31 | ? t.code.asm 32 | .split("\n") 33 | .map((s) => " " + s) 34 | .join("\n") 35 | : t.code.bin) + 36 | "\n\n" 37 | ) 38 | }) 39 | } 40 | 41 | module.exports = JestEnvironmentFailFast 42 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evm-from-scratch-typescript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest --env ./jest-environment-fail-fast.js .", 8 | "test:watch": "jest --watch --env ./jest-environment-fail-fast.js .", 9 | "start:bytecode": "ts-node ./index.ts --saveLogs --bytecode ", 10 | "start:file": "ts-node ./index.ts --saveLogs --file " 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@babel/core": "^7.19.3", 17 | "@babel/preset-env": "^7.19.4", 18 | "@babel/preset-typescript": "^7.18.6", 19 | "babel-jest": "^29.2.0", 20 | "dotenv": "^16.0.3", 21 | "ethereum-cryptography": "^1.1.2", 22 | "jest": "^29.2.0", 23 | "ts-jest": "^29.0.3", 24 | "ts-node": "^10.9.1", 25 | "typescript": "^4.8.4", 26 | "yargs": "^17.6.0" 27 | }, 28 | "devDependencies": { 29 | "@types/yargs": "^17.0.13" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const MAX_256_BITS = 2n ** 256n 2 | export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 3 | 4 | export const CALL = 0xf1 5 | export const CALL_OR_CREATE = [0xf0, 0xf1, 0xf2, 0xf4, 0xf5, 0xfa] 6 | export const STATIC_DISALLOWED = [0xf0, 0xf5, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0x55, 0xff] 7 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/errors.ts: -------------------------------------------------------------------------------- 1 | enum ERRORS { 2 | STACK_OVERFLOW = "stack overflow", 3 | STACK_UNDERFLOW = "stack underflow", 4 | STACK_VALUE_TOO_BIG = "stack value too big", 5 | STACK_VALUE_TOO_SMALL = "stack value too small", 6 | INVALID_OPCODE = "invalid opcode", 7 | PC_OUT_OF_BOUNDS = "pc out of bounds", 8 | JUMP_OUT_OF_BOUNDS = "jump out of bounds", 9 | JUMP_TO_INVALID_DESTINATION = "jump to invalid destination", 10 | INVALID_MEMORY_OFFSET = "invalid memory offset", 11 | INVALID_MEMORY_VALUE_SIZE = "invalid memory value size", 12 | INVALID_STORAGE_KEY_SIZE = "invalid storage key size", 13 | INVALID_STORAGE_VALUE_SIZE = "invalid storage value size", 14 | 15 | STOP = "STOP", 16 | REVERT = "REVERT", 17 | 18 | NO_CODE_PROVIDED = "no code provided", 19 | OPCODE_NOT_IMPLEMENTED = "opcode not implemented", 20 | INVALID_CALL_DEPTH = "invalid call depth", 21 | 22 | STATIC_DISALLOWED = "static disallowed", 23 | STATIC_DISALLOWED_OPCODE = "static disallowed opcode", 24 | STATIC_DISALLOWED_CALL_VALUE = "static disallowed call value", 25 | } 26 | 27 | export default ERRORS 28 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/evm.ts: -------------------------------------------------------------------------------- 1 | import { buildBlock, buildState, buildTxData } from "./utils" 2 | import { CALL, CALL_OR_CREATE, MAX_256_BITS, STATIC_DISALLOWED } from "./constants" 3 | import runners from "./opcodes/runners" 4 | import ERRORS from "./errors" 5 | 6 | import GlobalState from "./globalState" 7 | import Logger from "./logger" 8 | 9 | import Stack from "./machine-state/stack" 10 | import Memory from "./machine-state/memory" 11 | import Storage from "./machine-state/storage" 12 | 13 | import type { EVMOpts, EvmRuntimeParams } from "./types" 14 | import type { MachineState } from "./machine-state/types" 15 | import { CallOrCreateRunner, SimpleRunner } from "./opcodes/types" 16 | 17 | export default class EVM { 18 | private readonly debug: boolean 19 | private readonly saveLogs: boolean 20 | private readonly logger: Logger 21 | private _depth: number 22 | 23 | constructor(options: EVMOpts) { 24 | this.debug = options.debug ?? false 25 | this.saveLogs = options.saveLogs ?? false 26 | this.logger = new Logger() 27 | this._depth = 0 28 | } 29 | 30 | public async start(params: Partial) { 31 | if (!params._code) throw new Error(ERRORS.NO_CODE_PROVIDED) 32 | 33 | // build default state objects if not provided in params 34 | if (!params._globalState) params._globalState = buildState() 35 | if (!params._txData) params._txData = buildTxData() 36 | if (!params._block) params._block = buildBlock() 37 | 38 | const ms: MachineState = { 39 | globalState: new GlobalState(params._globalState), 40 | storage: new Storage(), 41 | memory: new Memory(), 42 | stack: new Stack(), 43 | returnData: Buffer.alloc(0), 44 | gasAvailable: MAX_256_BITS - 1n, // todo 45 | txData: params._txData, 46 | block: params._block, 47 | code: params._code, 48 | static: false, 49 | logs: [], 50 | pc: 0, 51 | } 52 | 53 | this.logger.start(params._code, params._asm) 54 | 55 | return await this.run(ms) 56 | } 57 | 58 | public async run(ms: MachineState, isSubCall = false) { 59 | let success = false 60 | let reverted = false 61 | 62 | if (isSubCall) { 63 | this._depth++ 64 | this.logger.notify(`Starting subcall. Depth: ${this._depth}`) 65 | } 66 | 67 | // execute opcodes sequentially 68 | while (ms.pc < ms.code.length) { 69 | try { 70 | this.logger.step(ms) 71 | const opcode = ms.code[ms.pc] 72 | 73 | if (ms.static) { 74 | // throw if opcode is not allowed in static context 75 | if (STATIC_DISALLOWED.includes(opcode)) throw new Error(ERRORS.REVERT) 76 | if (opcode === CALL && ms.txData.value !== 0n) throw new Error(ERRORS.REVERT) 77 | } 78 | 79 | await this.execute(ms) 80 | } catch (err: any) { 81 | this.logger.error(err) 82 | 83 | if (err.message === ERRORS.REVERT) reverted = true 84 | if (err.message === ERRORS.STOP) success = true 85 | 86 | break 87 | } 88 | } 89 | 90 | if (isSubCall) { 91 | if (this._depth === 0) throw new Error(ERRORS.INVALID_CALL_DEPTH) 92 | this._depth-- 93 | 94 | if (!reverted) { 95 | this.logger.notify(`Subcall completed without REVERT. Depth: ${this._depth}`) 96 | success = true 97 | } 98 | } 99 | 100 | if (this.debug) console.log(this.logger.output) 101 | if (this.saveLogs) this.logger.saveToFile() 102 | 103 | const result = { 104 | success, 105 | stack: ms.stack.dump, 106 | return: ms.returnData.toString("hex"), 107 | logs: ms.logs, 108 | } 109 | 110 | return result 111 | } 112 | 113 | // Execute a single opcode and update the machine state 114 | private async execute(ms: MachineState): Promise { 115 | const opcode = ms.code[ms.pc] 116 | const runner = runners[opcode]?.runner 117 | if (!runner) throw new Error(ERRORS.OPCODE_NOT_IMPLEMENTED) 118 | 119 | // Handle special cases for CALL and CREATE instructions 120 | if (CALL_OR_CREATE.includes(opcode)) await (runner as CallOrCreateRunner)(ms, this) 121 | else await (runner as SimpleRunner)(ms) 122 | 123 | ms.pc++ 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/globalState.ts: -------------------------------------------------------------------------------- 1 | import { Account, Address, State } from "./types" 2 | 3 | // For the sake of this test-based challenge, 4 | // the global state is just a map of addresses to account info, 5 | // which is passed directly from the test file. 6 | 7 | export default class GlobalState { 8 | private _state: State 9 | 10 | constructor(_state: State) { 11 | this._state = _state 12 | } 13 | 14 | getAccount(address: Address): Account { 15 | return this._state[address] ?? {} 16 | } 17 | 18 | setAccount(address: Address, account: Account) { 19 | this._state[address] = account 20 | } 21 | 22 | getBalance(address: Address): bigint { 23 | return this.getAccount(address)?.balance ?? 0n 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/logger.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs" 2 | import path from "path" 3 | 4 | import runners from "./opcodes/runners" 5 | 6 | import type { MachineState } from "./machine-state/types" 7 | import { parsers } from "./opcodes/utils" 8 | 9 | export default class Logger { 10 | private _output: string[] 11 | private _steps: number 12 | 13 | constructor() { 14 | this._steps = 0 15 | this._output = [] 16 | } 17 | 18 | start(bin: Uint8Array, asm?: string) { 19 | this._output.push(`******************** Starting Execution ********************`) 20 | this._output.push(``) 21 | this._output.push(`Execution Bytecode:`) 22 | this._output.push(`${Buffer.from(bin).toString("hex")}`) 23 | this._output.push(``) 24 | 25 | if (asm) { 26 | this._output.push(`Execution ASM:`) 27 | this._output.push(asm) 28 | this._output.push(``) 29 | } 30 | 31 | this._output.push(`Starting execution...`) 32 | this._output.push(``) 33 | } 34 | 35 | step(ms: MachineState) { 36 | this._output.push(`******************** Step ${this._steps++} ********************`) 37 | this._output.push(`Opcode: ${runners[ms.code[ms.pc]].name}`) 38 | this._output.push(`Program Counter: ${ms.pc}`) 39 | this._output.push(``) 40 | this._output.push(`Stack:`) 41 | this._output.push(`${ms.stack.dump.map(parsers.BigintIntoHexString).join("\n")}`) 42 | this._output.push(``) 43 | this._output.push(`Memory:`) 44 | this._output.push(`${ms.memory.dump || "Empty"}`) 45 | this._output.push(``) 46 | this._output.push(`Storage:`) 47 | this._output.push(`${ms.storage.dump || "Empty"}`) 48 | this._output.push(``) 49 | this._output.push(`Return data:`) 50 | this._output.push(`${ms.returnData.toString("hex") || "Empty"}`) 51 | this._output.push(``) 52 | this._output.push(`Logs:`) 53 | this._output.push(`${ms.logs || "Empty"}`) 54 | this._output.push(``) 55 | } 56 | 57 | error(err: string) { 58 | this._output.push(`******************** ERROR ********************`) 59 | this._output.push(``) 60 | this._output.push(`Runtime Error encountered: ${err}`) 61 | this._output.push(``) 62 | } 63 | 64 | notify(message: string) { 65 | this._output.push(`******************** NOTIFICATION ********************`) 66 | this._output.push(``) 67 | this._output.push(`${message}`) 68 | this._output.push(``) 69 | } 70 | 71 | get output() { 72 | return this._output.join("\n") 73 | } 74 | 75 | saveToFile(filename?: string): string { 76 | try { 77 | if (!filename) filename = `execution-${Date.now()}` 78 | 79 | if (!fs.existsSync(path.join(__dirname, "../logs"))) 80 | fs.mkdirSync(path.join(__dirname, "../logs")) 81 | 82 | const filepath = path.join(__dirname, `../logs/${filename}.log`) 83 | fs.writeFileSync(filepath, this.output) 84 | return filepath 85 | } catch (err) { 86 | console.error("Error while saving logs to file: ", err) 87 | return "" 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/machine-state/memory.ts: -------------------------------------------------------------------------------- 1 | import ERRORS from "../errors" 2 | 3 | // From the yellow paper: 4 | // The memory model is a simple word-addressed byte array. 5 | // The word size is 256 bits (32 bytes). 6 | // The memory is expanded by a word (32 bytes) at a time. Memory expansion costs gas. 7 | // The memory size is always a multiple of 32 bytes. 8 | // The memory starts empty at the beginning of every instance execution. 9 | 10 | export default class Memory { 11 | protected _memory: Buffer 12 | 13 | constructor() { 14 | this._memory = Buffer.alloc(0) 15 | } 16 | 17 | write(offset: number, value: Buffer, size: 1 | 32 | number) { 18 | if (offset < 0) throw new Error(ERRORS.INVALID_MEMORY_OFFSET) 19 | if (value.length !== size) throw new Error(ERRORS.INVALID_MEMORY_VALUE_SIZE) 20 | 21 | const overflow = Math.ceil((offset + size) / 32) * 32 - this.size 22 | if (overflow) this._memory = Buffer.concat([this._memory, Buffer.alloc(overflow)]) 23 | 24 | for (const byte of value) this._memory[offset++] = byte 25 | } 26 | 27 | read(offset: number, size: number): Buffer { 28 | if (offset < 0) throw new Error(ERRORS.INVALID_MEMORY_OFFSET) 29 | if (size === 0) return Buffer.alloc(0) 30 | 31 | const overflow = Math.ceil((offset + size) / 32) * 32 - this.size 32 | if (!overflow) return this._memory.subarray(offset, offset + size) 33 | 34 | this._memory = Buffer.concat([this._memory, Buffer.alloc(overflow)]) 35 | 36 | const output = Buffer.alloc(size) 37 | this._memory.copy(output, 0, offset) 38 | return output 39 | } 40 | 41 | get size(): number { 42 | return this._memory.length 43 | } 44 | 45 | get activeWordsCount(): number { 46 | return this.size / 32 47 | } 48 | 49 | get dump(): string { 50 | let dump = "" 51 | for (let i = 0; i < this._memory.length; i += 32) 52 | dump += this._memory.subarray(i, i + 32).toString("hex") + "\n" 53 | 54 | return dump 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/machine-state/stack.ts: -------------------------------------------------------------------------------- 1 | import { MAX_256_BITS } from "../constants" 2 | import ERRORS from "../errors" 3 | 4 | // From the yellow paper: 5 | // The EVM is a simple stack-based architecture. 6 | // The word size of the machine (and thus size of stack items) is 256-bit. 7 | // This was chosen to facilitate the Keccak-256 hash scheme and elliptic-curve computations. 8 | // The stack has a maximum size of 1024 elements. 9 | 10 | // Put simply, it is a 1024-long array of 256-bit words. 11 | // Initially I implemented this with a bigint array, 12 | // but I realized it's way simpler to use a js bigint array instead 13 | // because I save a lot of time converting between hex strings, numbers and bigints 14 | // and I don't have to worry about endianness. 15 | 16 | const EMPTY_STACK = 0 17 | const FULL_STACK = 1024 18 | 19 | export default class Stack { 20 | protected _stack: bigint[] 21 | 22 | constructor() { 23 | this._stack = [] 24 | } 25 | 26 | push(value: bigint) { 27 | if (value < 0n) throw new Error(ERRORS.STACK_VALUE_TOO_SMALL) 28 | if (value > MAX_256_BITS) throw new Error(ERRORS.STACK_VALUE_TOO_BIG) 29 | if (this._stack.length === FULL_STACK) throw new Error(ERRORS.STACK_OVERFLOW) 30 | 31 | this._stack.push(value) 32 | } 33 | 34 | pop(): bigint { 35 | if (this._stack.length === EMPTY_STACK) throw new Error(ERRORS.STACK_UNDERFLOW) 36 | 37 | return this._stack.pop()! 38 | } 39 | 40 | popN(n: number): bigint[] { 41 | if (this._stack.length < n) throw new Error(ERRORS.STACK_UNDERFLOW) 42 | 43 | return this._stack.splice(this._stack.length - n, n).reverse() 44 | } 45 | 46 | peek(n?: number): bigint { 47 | if (this._stack.length === EMPTY_STACK) throw new Error(ERRORS.STACK_UNDERFLOW) 48 | 49 | return this._stack[this._stack.length - (n || 1)] 50 | } 51 | 52 | swap(n: number) { 53 | if (this._stack.length < n) throw new Error(ERRORS.STACK_UNDERFLOW) 54 | 55 | const top = this._stack.pop()! 56 | const bottom = this._stack[this._stack.length - n] 57 | this._stack[this._stack.length - n] = top 58 | this._stack.push(bottom) 59 | } 60 | 61 | get length(): number { 62 | return this._stack.length 63 | } 64 | 65 | get dump(): bigint[] { 66 | return this._stack.slice().reverse() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/machine-state/storage.ts: -------------------------------------------------------------------------------- 1 | import ERRORS from "../errors" 2 | import type { Address } from "../types" 3 | 4 | // From the ethereum yellow paper: 5 | // the storage is a mapping from 256-bit words to 256-bit words 6 | // for each address. The storage is initially empty on account creation 7 | // and can be modified by the execution of EVM code. 8 | // 9 | // Refer to `day12.md` for more information and theory on the storage. 10 | 11 | // I basically need a map over ethereum addresses to maps over strings to 256-bit words 12 | // interface StorageLayout { 13 | // [address: string]: { 14 | // [key: string]: Buffer 15 | // } 16 | // } 17 | // 18 | // this is more efficiently implemented with maps: 19 | 20 | type StorageLayout = Map> 21 | 22 | export default class Storage { 23 | private _storage: StorageLayout 24 | 25 | constructor() { 26 | this._storage = new Map() 27 | } 28 | 29 | public get(address: Address, key: string): Buffer { 30 | return this._storage.get(address)?.get(key) ?? Buffer.alloc(32) 31 | } 32 | 33 | public set(address: Address, key: string, value: Buffer): void { 34 | // todo: add validation for key size after switching to buffers 35 | 36 | if (value.length > 32) throw new Error(ERRORS.INVALID_STORAGE_VALUE_SIZE) 37 | 38 | const oldStorageValue = this._storage.get(address)?.get(key) 39 | if (!oldStorageValue) this._storage.set(address, new Map()) 40 | if (oldStorageValue?.equals(value)) return 41 | 42 | // todo: implement logger & persistent changes log 43 | 44 | this._storage.get(address)!.set(key, value) 45 | } 46 | 47 | public getAsBigInt(address: Address, key: string): bigint { 48 | return BigInt("0x" + this.get(address, key).toString("hex")) 49 | } 50 | 51 | public setAsBigInt(address: Address, key: string, value: bigint): void { 52 | this.set(address, key, Buffer.from(value.toString(16).padStart(64, "0"), "hex")) 53 | } 54 | 55 | get dump(): string { 56 | return JSON.stringify(this._storage, null, 2) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/machine-state/types.ts: -------------------------------------------------------------------------------- 1 | import type Stack from "./stack" 2 | import type Memory from "./memory" 3 | import type Storage from "./storage" 4 | import type GlobalState from "../globalState" 5 | import type { Block, Gas, Log, ProgramCounter, TxData } from "../types" 6 | 7 | // From the yellow paper: 8 | // Machine State: The machine state μ is defined as the tuple (g, pc, m, i, s) 9 | // which are the gas available, the program counter pc ∈ N256 , the memory contents, 10 | // the active number of words in memory (counting continuously from position 0), 11 | // and the stack contents. The memory contents μm are a series of zeroes of size 2^256. 12 | 13 | // The machine state is a tuple defined of: 14 | // - gas available 15 | // - program counter 16 | // - memory contents 17 | // - active number of words in memory 18 | // - stack contents 19 | 20 | // I decided to implement the activeWordsCount method directly in the Memory class 21 | // instead of having it stand-alone in the MachineState interface. 22 | 23 | // I also added items that make state manipulation in opcodes complete with a single object: 24 | // - code (bin): the full code of the current execution context 25 | // - txData: the transaction data of the current execution context 26 | // - storage: the transient account state map of the current execution context 27 | // - globalState: the global state of the current execution context 28 | // - block: the block data of the current execution context 29 | // - returnData: the return data of the current execution context 30 | // - logs: the logs output by the LOG operations 31 | // - static: a flag that indicates whether the current execution context is static or not 32 | 33 | export interface MachineState { 34 | gasAvailable: Gas 35 | pc: ProgramCounter 36 | memory: Memory 37 | stack: Stack 38 | 39 | code: Uint8Array 40 | txData: TxData 41 | storage: Storage 42 | globalState: GlobalState 43 | block: Block 44 | returnData: Buffer 45 | logs: Log[] 46 | 47 | static: boolean 48 | } 49 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/machine-state/utils.ts: -------------------------------------------------------------------------------- 1 | import Memory from "./memory" 2 | import Stack from "./stack" 3 | 4 | import type { MachineState } from "./types" 5 | 6 | export function freshExecutionContext(): Partial { 7 | return { 8 | stack: new Stack(), 9 | memory: new Memory(), 10 | pc: 0, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners.ts: -------------------------------------------------------------------------------- 1 | import { buildOpcodeRangeObjects } from "./utils" 2 | import type { Runners } from "./types" 3 | 4 | // Opcode Runners categorization based on the EVM Execution Spec: 5 | // https://ethereum.github.io/execution-specs/autoapi/ethereum/gray_glacier/vm/instructions/index.html 6 | 7 | import * as arithmetic from "./runners/arithmetic" 8 | import * as bitwise from "./runners/bitwise" 9 | import * as block from "./runners/block" 10 | import * as comparison from "./runners/comparison" 11 | import * as controlFlow from "./runners/control-flow" 12 | import * as environmental from "./runners/environmental" 13 | import * as keccak from "./runners/keccak" 14 | import * as logging from "./runners/logging" 15 | import * as memory from "./runners/memory" 16 | import * as stack from "./runners/stack" 17 | import * as storage from "./runners/storage" 18 | import * as system from "./runners/system" 19 | 20 | const runners: Runners = { 21 | 0x00: { name: "STOP", runner: controlFlow.STOP }, 22 | 0x01: { name: "ADD", runner: arithmetic.ADD }, 23 | 0x02: { name: "MUL", runner: arithmetic.MUL }, 24 | 0x03: { name: "SUB", runner: arithmetic.SUB }, 25 | 0x04: { name: "DIV", runner: arithmetic.DIV }, 26 | 0x05: { name: "SDIV", runner: arithmetic.SDIV }, 27 | 0x06: { name: "MOD", runner: arithmetic.MOD }, 28 | 0x07: { name: "SMOD", runner: arithmetic.SMOD }, 29 | 30 | 0x10: { name: "LT", runner: comparison.LT }, 31 | 0x11: { name: "GT", runner: comparison.GT }, 32 | 0x12: { name: "SLT", runner: comparison.SLT }, 33 | 0x13: { name: "SGT", runner: comparison.SGT }, 34 | 0x14: { name: "EQ", runner: comparison.EQ }, 35 | 0x15: { name: "ISZERO", runner: comparison.ISZERO }, 36 | 0x16: { name: "AND", runner: bitwise.AND }, 37 | 0x17: { name: "OR", runner: bitwise.OR }, 38 | 0x18: { name: "XOR", runner: bitwise.XOR }, 39 | 0x19: { name: "NOT", runner: bitwise.NOT }, 40 | 0x1a: { name: "BYTE", runner: bitwise.BYTE }, 41 | 42 | 0x20: { name: "SHA3", runner: keccak.SHA3 }, 43 | 44 | 0x30: { name: "ADDRESS", runner: environmental.ADDRESS }, 45 | 0x31: { name: "BALANCE", runner: environmental.BALANCE }, 46 | 0x32: { name: "ORIGIN", runner: environmental.ORIGIN }, 47 | 0x33: { name: "CALLER", runner: environmental.CALLER }, 48 | 0x34: { name: "CALLVALUE", runner: environmental.CALLVALUE }, 49 | 0x35: { name: "CALLDATALOAD", runner: environmental.CALLDATALOAD }, 50 | 0x36: { name: "CALLDATASIZE", runner: environmental.CALLDATASIZE }, 51 | 0x37: { name: "CALLDATACOPY", runner: environmental.CALLDATACOPY }, 52 | 0x38: { name: "CODESIZE", runner: environmental.CODESIZE }, 53 | 0x39: { name: "CODECOPY", runner: environmental.CODECOPY }, 54 | 0x3a: { name: "GASPRICE", runner: environmental.GASPRICE }, 55 | 0x3b: { name: "EXTCODESIZE", runner: environmental.EXTCODESIZE }, 56 | 0x3c: { name: "EXTCODECOPY", runner: environmental.EXTCODECOPY }, 57 | 0x3d: { name: "RETURNDATASIZE", runner: environmental.RETURNDATASIZE }, 58 | 0x3e: { name: "RETURNDATACOPY", runner: environmental.RETURNDATACOPY }, 59 | 60 | 0x41: { name: "COINBASE", runner: block.COINBASE }, 61 | 0x42: { name: "TIMESTAMP", runner: block.TIMESTAMP }, 62 | 0x43: { name: "NUMBER", runner: block.NUMBER }, 63 | 0x44: { name: "DIFFICULTY", runner: block.DIFFICULTY }, 64 | 0x45: { name: "GASLIMIT", runner: block.GASLIMIT }, 65 | 0x46: { name: "CHAINID", runner: block.CHAINID }, 66 | 0x47: { name: "SELFBALANCE", runner: environmental.SELFBALANCE }, 67 | 68 | 0x50: { name: "POP", runner: stack.POP }, 69 | 0x51: { name: "MLOAD", runner: memory.MLOAD }, 70 | 0x52: { name: "MSTORE", runner: memory.MSTORE }, 71 | 0x53: { name: "MSTORE8", runner: memory.MSTORE8 }, 72 | 0x54: { name: "SLOAD", runner: storage.SLOAD }, 73 | 0x55: { name: "SSTORE", runner: storage.SSTORE }, 74 | 0x56: { name: "JUMP", runner: controlFlow.JUMP }, 75 | 0x57: { name: "JUMPI", runner: controlFlow.JUMPI }, 76 | 0x58: { name: "PC", runner: controlFlow.PC }, 77 | 0x59: { name: "MSIZE", runner: memory.MSIZE }, 78 | 0x5a: { name: "GAS", runner: controlFlow.GAS }, 79 | 0x5b: { name: "JUMPDEST", runner: controlFlow.JUMPDEST }, 80 | 81 | ...buildOpcodeRangeObjects(0x60, 0x7f, "PUSH", stack.PUSH), 82 | ...buildOpcodeRangeObjects(0x80, 0x8f, "DUP", stack.DUP), 83 | ...buildOpcodeRangeObjects(0x90, 0x9f, "SWAP", stack.SWAP), 84 | ...buildOpcodeRangeObjects(0xa0, 0xa4, "LOG", logging.LOG), 85 | 86 | 0xf0: { name: "CREATE", runner: system.CREATE }, 87 | 0xf1: { name: "CALL", runner: system.CALL }, 88 | 0xf3: { name: "RETURN", runner: system.RETURN }, 89 | 0xf4: { name: "DELEGATECALL", runner: system.DELEGATECALL }, 90 | 91 | 0xfa: { name: "STATICCALL", runner: system.STATICCALL }, 92 | 93 | 0xfd: { name: "REVERT", runner: system.REVERT }, 94 | 95 | 0xff: { name: "SELFDESTRUCT", runner: system.SELFDESTRUCT }, 96 | } 97 | 98 | export default runners 99 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/arithmetic.ts: -------------------------------------------------------------------------------- 1 | import { bigMath } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x01 6 | export function ADD(ms: MachineState) { 7 | const [a, b] = ms.stack.popN(2) 8 | const res = bigMath.mod256(a + b) 9 | ms.stack.push(res) 10 | } 11 | 12 | // 0x02 13 | export function MUL(ms: MachineState) { 14 | const [a, b] = ms.stack.popN(2) 15 | const res = bigMath.mod256(a * b) 16 | ms.stack.push(res) 17 | } 18 | 19 | // 0x03 20 | export function SUB(ms: MachineState) { 21 | const [a, b] = ms.stack.popN(2) 22 | const res = bigMath.mod256(a - b) 23 | ms.stack.push(res) 24 | } 25 | 26 | // 0x04 27 | export function DIV(ms: MachineState) { 28 | const [a, b] = ms.stack.popN(2) 29 | const res = b === 0n ? 0n : bigMath.mod256(a / b) 30 | ms.stack.push(res) 31 | } 32 | 33 | // 0x05 34 | export function SDIV(ms: MachineState) { 35 | const [a, b] = ms.stack.popN(2) 36 | const div = b === 0n ? 0n : bigMath.toSigned256(a) / bigMath.toSigned256(b) 37 | const res = bigMath.toUnsigned256(div) 38 | ms.stack.push(res) 39 | } 40 | 41 | // 0x06 42 | export function MOD(ms: MachineState) { 43 | const [a, b] = ms.stack.popN(2) 44 | const res = b === 0n ? 0n : bigMath.mod256(a % b) 45 | ms.stack.push(res) 46 | } 47 | 48 | // 0x07 49 | export function SMOD(ms: MachineState) { 50 | const [a, b] = ms.stack.popN(2) 51 | const mod = b === 0n ? 0n : bigMath.toSigned256(a) % bigMath.toSigned256(b) 52 | const res = bigMath.toUnsigned256(mod) 53 | ms.stack.push(res) 54 | } 55 | 56 | // TODO: addmod, mulmod, exp, signextend 57 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/bitwise.ts: -------------------------------------------------------------------------------- 1 | import { bigMath } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x16 6 | export function AND(ms: MachineState) { 7 | const [a, b] = ms.stack.popN(2) 8 | const res = a & b 9 | ms.stack.push(res) 10 | } 11 | 12 | // 0x17 13 | export function OR(ms: MachineState) { 14 | const [a, b] = ms.stack.popN(2) 15 | const res = a | b 16 | ms.stack.push(res) 17 | } 18 | 19 | // 0x18 20 | export function XOR(ms: MachineState) { 21 | const [a, b] = ms.stack.popN(2) 22 | const res = a ^ b 23 | ms.stack.push(res) 24 | } 25 | 26 | // 0x19 27 | export function NOT(ms: MachineState) { 28 | const a = ms.stack.pop() 29 | const res = bigMath.mod256(~a) 30 | ms.stack.push(res) 31 | } 32 | 33 | // 0x1a 34 | export function BYTE(ms: MachineState) { 35 | const [pos, val] = ms.stack.popN(2) 36 | const res = pos > 31n ? 0n : (val >> (8n * (31n - pos))) & 0xffn 37 | ms.stack.push(res) 38 | } 39 | 40 | // todo: 1b, 1c, 1d 41 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/block.ts: -------------------------------------------------------------------------------- 1 | import { parsers } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // todo: 0x40 6 | 7 | // 0x41 8 | export function COINBASE(ms: MachineState) { 9 | const res = ms.block.coinbase 10 | ms.stack.push(parsers.HexStringIntoBigInt(res)) 11 | } 12 | 13 | // 0x42 14 | export function TIMESTAMP(ms: MachineState) { 15 | const res = ms.block.timestamp 16 | ms.stack.push(res) 17 | } 18 | 19 | // 0x43 20 | export function NUMBER(ms: MachineState) { 21 | const res = ms.block.number 22 | ms.stack.push(BigInt(res)) 23 | } 24 | 25 | // 0x44 26 | export function DIFFICULTY(ms: MachineState) { 27 | const res = ms.block.difficulty 28 | ms.stack.push(BigInt(res)) 29 | } 30 | 31 | // 0x45 32 | export function GASLIMIT(ms: MachineState) { 33 | const res = ms.block.gaslimit 34 | ms.stack.push(parsers.HexStringIntoBigInt(res)) 35 | } 36 | 37 | // 0x46 38 | export function CHAINID(ms: MachineState) { 39 | const res = ms.block.chainid 40 | ms.stack.push(BigInt(res)) 41 | } 42 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/comparison.ts: -------------------------------------------------------------------------------- 1 | import { bigMath } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x10 6 | export function LT(ms: MachineState) { 7 | const [a, b] = ms.stack.popN(2) 8 | const res = a < b ? 1n : 0n 9 | ms.stack.push(res) 10 | } 11 | 12 | // 0x11 13 | export function GT(ms: MachineState) { 14 | const [a, b] = ms.stack.popN(2) 15 | const res = a > b ? 1n : 0n 16 | ms.stack.push(res) 17 | } 18 | 19 | // 0x12 20 | export function SLT(ms: MachineState) { 21 | const [a, b] = ms.stack.popN(2) 22 | const res = bigMath.toSigned256(a) < bigMath.toSigned256(b) ? 1n : 0n 23 | ms.stack.push(res) 24 | } 25 | 26 | // 0x13 27 | export function SGT(ms: MachineState) { 28 | const [a, b] = ms.stack.popN(2) 29 | const res = bigMath.toSigned256(a) > bigMath.toSigned256(b) ? 1n : 0n 30 | ms.stack.push(res) 31 | } 32 | 33 | // 0x14 34 | export function EQ(ms: MachineState) { 35 | const [a, b] = ms.stack.popN(2) 36 | const res = a === b ? 1n : 0n 37 | ms.stack.push(res) 38 | } 39 | 40 | // 0x15 41 | export function ISZERO(ms: MachineState) { 42 | const a = ms.stack.pop() 43 | const res = a === 0n ? 1n : 0n 44 | ms.stack.push(res) 45 | } 46 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/control-flow.ts: -------------------------------------------------------------------------------- 1 | import ERRORS from "../../errors" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x00 6 | export function STOP() { 7 | throw new Error(ERRORS.STOP) 8 | } 9 | 10 | // 0x56 11 | export function JUMP(ms: MachineState) { 12 | const dest = ms.stack.pop() 13 | if (dest > ms.code.length) throw new Error(ERRORS.JUMP_OUT_OF_BOUNDS) 14 | if (ms.code[Number(dest)] !== 0x5b) throw new Error(ERRORS.JUMP_TO_INVALID_DESTINATION) 15 | ms.pc = Number(dest) 16 | } 17 | 18 | // 0x57 19 | export function JUMPI(ms: MachineState) { 20 | const [dest, cond] = ms.stack.popN(2) 21 | if (cond === 0n) return 22 | if (dest > ms.code.length) throw new Error(ERRORS.JUMP_OUT_OF_BOUNDS) 23 | if (ms.code[Number(dest)] !== 0x5b) throw new Error(ERRORS.JUMP_TO_INVALID_DESTINATION) 24 | ms.pc = Number(dest) 25 | } 26 | 27 | // 0x58 28 | export function PC(ms: MachineState) { 29 | ms.stack.push(BigInt(ms.pc)) 30 | } 31 | 32 | // 0x5a 33 | export function GAS(ms: MachineState) { 34 | ms.stack.push(ms.gasAvailable) 35 | } 36 | 37 | // 0x5b 38 | export function JUMPDEST(ms: MachineState) { 39 | // do nothing 40 | } 41 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/environmental.ts: -------------------------------------------------------------------------------- 1 | import { parsers } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x30 6 | export function ADDRESS(ms: MachineState) { 7 | const res = ms.txData.to 8 | ms.stack.push(parsers.HexStringIntoBigInt(res)) 9 | } 10 | 11 | // 0x31 12 | export function BALANCE(ms: MachineState) { 13 | const address = ms.stack.pop() 14 | const addressHex = parsers.BigintIntoHexString(address) 15 | const res = ms.globalState.getBalance(addressHex) 16 | ms.stack.push(res) 17 | } 18 | 19 | // 0x32 20 | export function ORIGIN(ms: MachineState) { 21 | const res = ms.txData.origin 22 | ms.stack.push(parsers.HexStringIntoBigInt(res)) 23 | } 24 | 25 | // 0x33 26 | export function CALLER(ms: MachineState) { 27 | const res = ms.txData.from 28 | ms.stack.push(parsers.HexStringIntoBigInt(res)) 29 | } 30 | 31 | // 0x34 32 | export function CALLVALUE(ms: MachineState) { 33 | const res = ms.txData.value 34 | ms.stack.push(res) 35 | } 36 | 37 | // 0x35 38 | export function CALLDATALOAD(ms: MachineState) { 39 | const offset = Number(ms.stack.pop()) 40 | const calldataWord = ms.txData.data.subarray(offset, offset + 32) 41 | 42 | const calldataWordPadded = Buffer.alloc(32) 43 | calldataWord.copy(calldataWordPadded, 0, 0) 44 | 45 | const res = parsers.BytesIntoBigInt(calldataWordPadded) 46 | ms.stack.push(res) 47 | } 48 | 49 | // 0x36 50 | export function CALLDATASIZE(ms: MachineState) { 51 | const res = ms.txData.data.length 52 | ms.stack.push(BigInt(res)) 53 | } 54 | 55 | // 0x37 56 | export function CALLDATACOPY(ms: MachineState) { 57 | const memOffset = Number(ms.stack.pop()) 58 | const dataOffset = Number(ms.stack.pop()) 59 | const size = Number(ms.stack.pop()) 60 | 61 | const data = ms.txData.data.subarray(dataOffset, dataOffset + size) 62 | ms.memory.write(memOffset, data, size) 63 | } 64 | 65 | // 0x38 66 | export function CODESIZE(ms: MachineState) { 67 | const res = ms.code.length 68 | ms.stack.push(BigInt(res)) 69 | } 70 | 71 | // 0x39 72 | export function CODECOPY(ms: MachineState) { 73 | const memOffset = Number(ms.stack.pop()) 74 | const codeOffset = Number(ms.stack.pop()) 75 | const size = Number(ms.stack.pop()) 76 | 77 | const codeBytesPortion = ms.code.subarray(codeOffset, codeOffset + size) 78 | const codeBuffer = Buffer.from(codeBytesPortion) 79 | 80 | const code = Buffer.alloc(size) 81 | codeBuffer.copy(code, 0, 0) 82 | 83 | ms.memory.write(memOffset, code, size) 84 | } 85 | 86 | // 0x3a 87 | export function GASPRICE(ms: MachineState) { 88 | const res = ms.txData.gasprice 89 | ms.stack.push(res) 90 | } 91 | 92 | // 0x3b 93 | export function EXTCODESIZE(ms: MachineState) { 94 | const address = ms.stack.pop() 95 | const addressHex = parsers.BigintIntoHexString(address) 96 | const extAccount = ms.globalState?.getAccount(addressHex) 97 | const res = extAccount?.code?.length ?? 0 98 | ms.stack.push(BigInt(res)) 99 | } 100 | 101 | // 0x3c 102 | export function EXTCODECOPY(ms: MachineState) { 103 | const address = ms.stack.pop() 104 | const addressHex = parsers.BigintIntoHexString(address) 105 | const extAccount = ms.globalState?.getAccount(addressHex) 106 | 107 | const memOffset = Number(ms.stack.pop()) 108 | const codeOffset = Number(ms.stack.pop()) 109 | const size = Number(ms.stack.pop()) 110 | 111 | const codeBytesPortion = extAccount?.code?.subarray(codeOffset, codeOffset + size) 112 | const codeBuffer = Buffer.from(codeBytesPortion ?? Buffer.alloc(0)) 113 | 114 | const code = Buffer.alloc(size) 115 | codeBuffer.copy(code, 0, 0) 116 | 117 | ms.memory.write(memOffset, code, size) 118 | } 119 | 120 | // 0x3d 121 | export function RETURNDATASIZE(ms: MachineState) { 122 | const res = BigInt(ms.returnData.length) 123 | ms.stack.push(res) 124 | } 125 | 126 | // 0x3e 127 | export function RETURNDATACOPY(ms: MachineState) { 128 | const memOffset = Number(ms.stack.pop()) 129 | const returnDataOffset = Number(ms.stack.pop()) 130 | const size = Number(ms.stack.pop()) 131 | 132 | const returnData = ms.returnData.subarray(returnDataOffset, returnDataOffset + size) 133 | ms.memory.write(memOffset, returnData, size) 134 | } 135 | 136 | // todo: 0x3f 137 | 138 | // 0x47 139 | export function SELFBALANCE(ms: MachineState) { 140 | const res = ms.globalState.getBalance(ms.txData.to) 141 | ms.stack.push(res) 142 | } 143 | 144 | // todo: 0x48 145 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/keccak.ts: -------------------------------------------------------------------------------- 1 | import { keccak256 } from "ethereum-cryptography/keccak" 2 | import { parsers } from "../utils" 3 | 4 | import type { MachineState } from "../../machine-state/types" 5 | 6 | // 0x20 7 | export function SHA3(ms: MachineState) { 8 | const [offset, size] = ms.stack.popN(2) 9 | const data = ms.memory.read(Number(offset), 32).subarray(0, Number(size)) 10 | const hash = keccak256(data) 11 | const res = parsers.BytesIntoBigInt(hash) 12 | ms.stack.push(res) 13 | } 14 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/logging.ts: -------------------------------------------------------------------------------- 1 | import { parsers } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0xa0 - 0xa4 6 | export function LOG(ms: MachineState) { 7 | const n = ms.code[ms.pc] - 0xa0 8 | const [memOffset, size] = ms.stack.popN(2) 9 | const topics = ms.stack.popN(n) 10 | 11 | const data = ms.memory.read(Number(memOffset), Number(size)) 12 | const topicsHex = topics.map(parsers.BigintIntoHexString) 13 | 14 | ms.logs.push({ 15 | address: ms.txData.to, 16 | data: data.toString("hex"), 17 | topics: topicsHex, 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/memory.ts: -------------------------------------------------------------------------------- 1 | import { parsers } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x51 6 | export function MLOAD(ms: MachineState) { 7 | const offset = Number(ms.stack.pop()) 8 | const val = parsers.BytesIntoBigInt(ms.memory.read(offset, 32)) 9 | ms.stack.push(val) 10 | } 11 | 12 | // 0x52 13 | export function MSTORE(ms: MachineState) { 14 | const [offset, val] = ms.stack.popN(2) 15 | const word = parsers.BigIntIntoBytes(val, 32) 16 | ms.memory.write(Number(offset), word, 32) 17 | } 18 | 19 | // 0x53 20 | export function MSTORE8(ms: MachineState) { 21 | const [offset, val] = ms.stack.popN(2) 22 | const byte = parsers.BigIntIntoBytes(val, 1) 23 | ms.memory.write(Number(offset), byte, 1) 24 | } 25 | 26 | // 0x59 27 | export function MSIZE(ms: MachineState) { 28 | ms.stack.push(BigInt(ms.memory.size)) 29 | } 30 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/stack.ts: -------------------------------------------------------------------------------- 1 | import ERRORS from "../../errors" 2 | import { parsers } from "../utils" 3 | 4 | import type { MachineState } from "../../machine-state/types" 5 | 6 | // 0x50 7 | export function POP(ms: MachineState) { 8 | ms.stack.pop() 9 | } 10 | 11 | // 0x60 - 0x7f 12 | export function PUSH(ms: MachineState) { 13 | const size = ms.code[ms.pc] - 0x5f 14 | if (ms.pc + size >= ms.code.length) throw new Error(ERRORS.PC_OUT_OF_BOUNDS) 15 | 16 | const value = ms.code.slice(ms.pc + 1, ms.pc + size + 1) 17 | const valueAsBigInt = parsers.BytesIntoBigInt(value) 18 | 19 | ms.pc += size 20 | ms.stack.push(valueAsBigInt) 21 | } 22 | 23 | // 0x80 - 0x8f 24 | export function DUP(ms: MachineState) { 25 | const pos = ms.code[ms.pc] - 0x7f 26 | const value = ms.stack.peek(pos) 27 | ms.stack.push(value) 28 | } 29 | 30 | // 0x90 - 0x9f 31 | export function SWAP(ms: MachineState) { 32 | const pos = ms.code[ms.pc] - 0x8f 33 | ms.stack.swap(pos) 34 | } 35 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/storage.ts: -------------------------------------------------------------------------------- 1 | import { parsers } from "../utils" 2 | 3 | import type { MachineState } from "../../machine-state/types" 4 | 5 | // 0x54 6 | export function SLOAD(ms: MachineState) { 7 | const key = ms.stack.pop() 8 | const keyHex = parsers.BigintIntoHexString(key) 9 | const val = ms.storage.getAsBigInt(ms.txData.to, keyHex) 10 | ms.stack.push(val) 11 | } 12 | 13 | // 0x55 14 | export function SSTORE(ms: MachineState) { 15 | const [key, val] = ms.stack.popN(2) 16 | const keyHex = parsers.BigintIntoHexString(key) 17 | ms.storage.setAsBigInt(ms.txData.to, keyHex, val) 18 | } 19 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/runners/system.ts: -------------------------------------------------------------------------------- 1 | import { keccak256 } from "ethereum-cryptography/keccak" 2 | 3 | import ERRORS from "../../errors" 4 | import { freshExecutionContext } from "../../machine-state/utils" 5 | import { parsers, CALL_RESULT } from "../utils" 6 | import { ZERO_ADDRESS } from "../../constants" 7 | 8 | import type EVM from "../../evm" 9 | import type { MachineState } from "../../machine-state/types" 10 | 11 | // 0xf0 12 | export async function CREATE(ms: MachineState, evm: EVM) { 13 | const [value, offset, length] = ms.stack.popN(3) 14 | 15 | // todo: generate real address: keccak256(rlp([sender_address,sender_nonce]))[12:] 16 | const sender = parsers.hexStringToUint8Array(ms.txData.to) 17 | const keccak = parsers.BufferToHexString(Buffer.from(keccak256(sender))) 18 | const addressCreated = keccak.substring(0, 42) 19 | 20 | ms.globalState.setAccount(addressCreated, { balance: value }) 21 | 22 | const initCode = ms.memory.read(Number(offset), Number(length)) 23 | 24 | const createMachineState: MachineState = { 25 | ...ms, 26 | ...freshExecutionContext(), 27 | txData: { 28 | ...ms.txData, 29 | value: 0n, 30 | from: ZERO_ADDRESS, 31 | to: addressCreated, 32 | }, 33 | code: initCode, 34 | } 35 | 36 | const createResult = await evm.run(createMachineState, true) 37 | 38 | if (createResult.success) { 39 | ms.globalState.setAccount(addressCreated, { 40 | ...ms.globalState.getAccount(addressCreated), 41 | code: parsers.hexStringToUint8Array(createResult.return), 42 | }) 43 | 44 | ms.stack.push(parsers.HexStringIntoBigInt(addressCreated)) 45 | } else ms.stack.push(CALL_RESULT.REVERT) 46 | } 47 | 48 | // 0xf1 49 | export async function CALL(ms: MachineState, evm: EVM) { 50 | const [gas, address, value, argsOffset, argsSize, retOffset, retSize] = ms.stack.popN(7) 51 | 52 | const data = ms.memory.read(Number(argsOffset), Number(argsSize)) 53 | const to = parsers.BigintIntoHexString(address) 54 | const codeToCall = ms.globalState.getAccount(to).code 55 | 56 | if (!codeToCall) return ms.stack.push(CALL_RESULT.SUCCESS) 57 | 58 | const callMachineState: MachineState = { 59 | ...ms, 60 | ...freshExecutionContext(), 61 | gasAvailable: gas, 62 | txData: { ...ms.txData, from: ms.txData.to, to, value, data }, 63 | code: codeToCall, 64 | } 65 | 66 | const callResult = await evm.run(callMachineState, true) 67 | 68 | if (callResult.return) { 69 | const callReturnData = Buffer.from(callResult.return, "hex") 70 | const callReturnOffset = Number(retOffset) 71 | const callReturnSize = Number(retSize) 72 | 73 | ms.returnData = callReturnData 74 | 75 | if (callReturnSize > 0) 76 | ms.memory.write(callReturnOffset, callReturnData, callReturnSize) 77 | } 78 | 79 | if (callResult.success) ms.stack.push(CALL_RESULT.SUCCESS) 80 | else ms.stack.push(CALL_RESULT.REVERT) 81 | } 82 | 83 | // 0xf3 84 | export function RETURN(ms: MachineState) { 85 | const [offset, size] = ms.stack.popN(2) 86 | const ret = ms.memory.read(Number(offset), Number(size)) 87 | ms.returnData = ret 88 | ms.pc = ms.code.length 89 | 90 | throw new Error(ERRORS.STOP) 91 | } 92 | 93 | // 0xf4 94 | export async function DELEGATECALL(ms: MachineState, evm: EVM) { 95 | const [gas, address, argsOffset, argsSize, retOffset, retSize] = ms.stack.popN(6) 96 | 97 | const data = ms.memory.read(Number(argsOffset), Number(argsSize)) 98 | const to = parsers.BigintIntoHexString(address) 99 | const codeToCall = ms.globalState.getAccount(to).code 100 | 101 | if (!codeToCall) return ms.stack.push(CALL_RESULT.SUCCESS) 102 | 103 | const callMachineState: MachineState = { 104 | ...ms, 105 | ...freshExecutionContext(), 106 | gasAvailable: gas, 107 | 108 | // The caller and value are the same as the current context 109 | txData: { ...ms.txData, data }, 110 | code: codeToCall, 111 | } 112 | 113 | const callResult = await evm.run(callMachineState, true) 114 | 115 | console.log(callResult) 116 | 117 | if (callResult.return) { 118 | const callReturnData = Buffer.from(callResult.return, "hex") 119 | const callReturnOffset = Number(retOffset) 120 | const callReturnSize = Number(retSize) 121 | 122 | ms.returnData = callReturnData 123 | 124 | if (callReturnSize > 0) 125 | ms.memory.write(callReturnOffset, callReturnData, callReturnSize) 126 | } 127 | 128 | if (callResult.success) ms.stack.push(CALL_RESULT.SUCCESS) 129 | else ms.stack.push(CALL_RESULT.REVERT) 130 | } 131 | 132 | // 0xfa 133 | export async function STATICCALL(ms: MachineState, evm: EVM) { 134 | const [gas, address, argsOffset, argsSize, retOffset, retSize] = ms.stack.popN(6) 135 | 136 | const data = ms.memory.read(Number(argsOffset), Number(argsSize)) 137 | const to = parsers.BigintIntoHexString(address) 138 | const codeToCall = ms.globalState.getAccount(to).code 139 | 140 | if (!codeToCall) return ms.stack.push(CALL_RESULT.SUCCESS) 141 | 142 | const callMachineState: MachineState = { 143 | ...ms, 144 | ...freshExecutionContext(), 145 | gasAvailable: gas, 146 | txData: { ...ms.txData, data }, 147 | code: codeToCall, 148 | static: true, 149 | } 150 | 151 | const callResult = await evm.run(callMachineState, true) 152 | 153 | if (callResult.return) { 154 | const callReturnData = Buffer.from(callResult.return, "hex") 155 | const callReturnOffset = Number(retOffset) 156 | const callReturnSize = Number(retSize) 157 | 158 | ms.returnData = callReturnData 159 | 160 | if (callReturnSize > 0) 161 | ms.memory.write(callReturnOffset, callReturnData, callReturnSize) 162 | } 163 | 164 | if (callResult.success) ms.stack.push(CALL_RESULT.SUCCESS) 165 | else ms.stack.push(CALL_RESULT.REVERT) 166 | } 167 | 168 | // 0xfd 169 | export function REVERT(ms: MachineState) { 170 | const [offset, size] = ms.stack.popN(2) 171 | const ret = ms.memory.read(Number(offset), Number(size)) 172 | ms.returnData = ret 173 | ms.pc = ms.code.length 174 | 175 | throw new Error(ERRORS.REVERT) 176 | } 177 | 178 | // 0xff 179 | export function SELFDESTRUCT(ms: MachineState) { 180 | const [address] = ms.stack.popN(1) 181 | const addressToPay = parsers.BigintIntoHexString(address) 182 | const accountToPay = ms.globalState.getAccount(addressToPay) 183 | const accountToDestroy = ms.globalState.getAccount(ms.txData.to) 184 | 185 | if (accountToDestroy?.balance) { 186 | ms.globalState.setAccount(addressToPay, { 187 | ...accountToPay, 188 | balance: accountToDestroy.balance + (accountToPay?.balance || 0n), 189 | }) 190 | } 191 | 192 | ms.globalState.setAccount(ms.txData.to, {}) 193 | ms.pc = ms.code.length 194 | 195 | throw new Error(ERRORS.STOP) 196 | } 197 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/types.ts: -------------------------------------------------------------------------------- 1 | import type EVM from "../evm" 2 | import type { MachineState } from "../machine-state/types" 3 | 4 | export type Opcode = number 5 | export type OpcodeInfo = { name: string } 6 | export type SimpleRunner = (ms: MachineState) => void | Promise 7 | export type CallOrCreateRunner = (ms: MachineState, evm: EVM) => void | Promise 8 | export type OpcodeRunner = SimpleRunner | CallOrCreateRunner 9 | export type Runners = Record 10 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/opcodes/utils.ts: -------------------------------------------------------------------------------- 1 | import { MAX_256_BITS } from "../constants" 2 | 3 | import type { OpcodeRunner, Runners } from "./types" 4 | 5 | // enums don't support bigints 6 | export const CALL_RESULT = { 7 | REVERT: 0n, 8 | SUCCESS: 1n, 9 | } 10 | 11 | export function buildOpcodeRangeObjects( 12 | start: number, 13 | end: number, 14 | name: string, 15 | runner: OpcodeRunner 16 | ): Runners { 17 | const rangeRunners: Runners = {} 18 | for (let i = start; i <= end; i++) rangeRunners[i] = { name, runner } 19 | return rangeRunners 20 | } 21 | 22 | export const parsers = { 23 | BytesIntoBigInt(bytes: Uint8Array): bigint { 24 | let array: string[] = [] 25 | for (const byte of bytes) array.push(byte.toString(16).padStart(2, "0")) 26 | return BigInt("0x" + array.join("")) 27 | }, 28 | BigIntIntoBytes(bigint: bigint, length: number): Buffer { 29 | const hex = bigint.toString(16).padStart(2 * length, "0") 30 | return Buffer.from(hex, "hex") 31 | }, 32 | HexStringIntoBigInt(hex: string): bigint { 33 | if (!hex.startsWith("0x")) hex = hex.padStart(2 * hex.length + 2, "0x") 34 | return BigInt(hex) 35 | }, 36 | BigintIntoHexString(bigint: bigint): string { 37 | return "0x" + bigint.toString(16) 38 | }, 39 | hexStringToUint8Array(hexString: string): Uint8Array { 40 | return new Uint8Array( 41 | (hexString?.match(/../g) || []).map((byte) => parseInt(byte, 16)) 42 | ) 43 | }, 44 | BufferToHexString(buffer: Buffer): string { 45 | return "0x" + buffer.toString("hex") 46 | }, 47 | } 48 | 49 | // https://stackoverflow.com/questions/51867270 50 | export const bigMath = { 51 | abs(x: bigint): bigint { 52 | return x < 0n ? -x : x 53 | }, 54 | sign(x: bigint): bigint { 55 | if (x === 0n) return 0n 56 | return x < 0n ? -1n : 1n 57 | }, 58 | pow(base: bigint, exponent: bigint): bigint { 59 | return base ** exponent 60 | }, 61 | min(value: bigint, ...values: bigint[]): bigint { 62 | for (const v of values) if (v < value) value = v 63 | return value 64 | }, 65 | max(value: bigint, ...values: bigint[]): bigint { 66 | for (const v of values) if (v > value) value = v 67 | return value 68 | }, 69 | toSigned256(x: bigint): bigint { 70 | return BigInt.asIntN(256, x) 71 | }, 72 | toUnsigned256(x: bigint): bigint { 73 | return BigInt.asUintN(256, x) 74 | }, 75 | mod256(x: bigint): bigint { 76 | const mod = x % MAX_256_BITS 77 | return mod < 0n ? mod + MAX_256_BITS : mod 78 | }, 79 | ceil(x: bigint, ceil: bigint): bigint { 80 | const mod = x % ceil 81 | return mod === 0n ? x : x + ceil - mod 82 | }, 83 | } 84 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Gas = bigint 2 | export type Value = bigint 3 | export type Address = string 4 | export type ProgramCounter = number 5 | export type Calldata = Buffer 6 | 7 | export interface EVMOpts { 8 | debug?: boolean 9 | saveLogs?: boolean 10 | } 11 | 12 | export interface EvmRuntimeParams { 13 | _code: Uint8Array 14 | _asm: string 15 | _txData: TxData 16 | _globalState: State 17 | _block: Block 18 | } 19 | 20 | export interface Code { 21 | asm: string 22 | bin: string 23 | } 24 | 25 | interface TestOutput { 26 | success: boolean 27 | stack: string[] 28 | return: string 29 | logs: Log[] 30 | } 31 | 32 | export interface Test { 33 | name: string 34 | code: Code 35 | tx?: Partial 36 | block?: Partial 37 | state?: TestState 38 | expect: Partial 39 | } 40 | 41 | type TestTxData = Omit & { data: string } 42 | type TestAccount = Omit & { code: Code } 43 | type TestState = Record 44 | 45 | export interface TxData { 46 | to: Address 47 | from: Address 48 | value: Value 49 | origin: Address 50 | gasprice: Gas 51 | data: Calldata 52 | } 53 | 54 | export interface State { 55 | [key: Address]: Account 56 | } 57 | 58 | export interface Account { 59 | balance?: Value 60 | code?: Uint8Array 61 | } 62 | 63 | export interface Block { 64 | number: number 65 | timestamp: bigint 66 | coinbase: Address 67 | difficulty: bigint 68 | gaslimit: string 69 | chainid: number 70 | } 71 | 72 | export interface Log { 73 | address: Address 74 | topics: string[] 75 | data: string 76 | } 77 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { parsers } from "./opcodes/utils" 2 | import { ZERO_ADDRESS } from "./constants" 3 | 4 | import type { Test, TxData, State, Block } from "./types" 5 | 6 | export function buildTxData(t?: Test): TxData { 7 | return { 8 | to: t?.tx?.to ?? ZERO_ADDRESS, 9 | from: t?.tx?.from ?? ZERO_ADDRESS, 10 | value: BigInt(t?.tx?.value ?? 0), 11 | origin: t?.tx?.origin ?? t?.tx?.from ?? ZERO_ADDRESS, 12 | gasprice: BigInt(t?.tx?.gasprice ?? 0), 13 | data: Buffer.from(t?.tx?.data ?? "", "hex"), 14 | } 15 | } 16 | 17 | export function buildState(t?: Test): State { 18 | const state = {} 19 | if (!t?.state) return state 20 | 21 | for (const address in t.state) 22 | state[address] = { 23 | balance: BigInt(t.state[address].balance || 0), 24 | code: parsers.hexStringToUint8Array(t.state[address].code?.bin || "0x00"), 25 | } 26 | 27 | return state 28 | } 29 | 30 | export function buildBlock(t?: Test): Block { 31 | return { 32 | number: Number(t?.block?.number || 0), 33 | timestamp: BigInt(t?.block?.timestamp || 0n), 34 | coinbase: t?.block?.coinbase || ZERO_ADDRESS, 35 | difficulty: BigInt(t?.block?.difficulty || 0n), 36 | gaslimit: t?.block?.gaslimit || "0x0", 37 | chainid: Number(t?.block?.chainid || 0), 38 | } 39 | } 40 | 41 | export function validateInputFile(options: any) { 42 | if (!options.code) return false 43 | if (!options.code.bin && !options.code.asm) return false 44 | if (options.code.bin && typeof options.code.bin !== "string") return false 45 | if (options.code.asm && typeof options.code.asm !== "string") return false 46 | 47 | return true 48 | } 49 | -------------------------------------------------------------------------------- /evm-from-scratch-challenge/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "commonjs", 5 | "resolveJsonModule": true, 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "noEmit": true, 9 | "noImplicitAny": false, 10 | "declaration": true, 11 | "outDir": "dist" 12 | } 13 | } 14 | --------------------------------------------------------------------------------