-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 68d60b3
Showing
43 changed files
with
3,574 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
jobs: | ||
build: | ||
working_directory: ~/evm | ||
docker: | ||
- image: elixir:latest | ||
steps: | ||
- checkout | ||
- restore_cache: | ||
key: _build | ||
- run: mix local.hex --force | ||
- run: mix local.rebar --force | ||
- run: mix deps.get | ||
- run: mix test | ||
- run: mix dialyzer | ||
- save_cache: | ||
key: _build | ||
paths: | ||
- _build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# The directory Mix will write compiled artifacts to. | ||
/_build | ||
|
||
# If you run "mix test --cover", coverage assets end up here. | ||
/cover | ||
|
||
# The directory Mix downloads your dependencies sources to. | ||
/deps | ||
|
||
# Where 3rd-party dependencies like ExDoc output generated docs. | ||
/doc | ||
|
||
# Ignore .fetch files in case you like to edit your project deps locally. | ||
/.fetch | ||
|
||
# If the VM crashes, it generates a dump, let's ignore it too. | ||
erl_crash.dump | ||
|
||
# Also ignore archive artifacts (built via "mix archive.build"). | ||
*.ez |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# EVM | ||
|
||
The EVM is a fully working Ethereum Virtual Machine. This machine effective encodes Section 9 "Execution Model" of the [Yellow Paper](http://gavwood.com/Paper.pdf). | ||
|
||
# Basics | ||
|
||
As discussed in the paper, we define a few data structures. | ||
|
||
* State - The world state of Ethereum, defined as the root hash of a Merkle Patricia Trie containing all account data. See Section 4.1 of the Yellow Paper, or explore the [merkle_patricia_trie](https://github.com/hayesgm/exthereum/tree/master/apps/merkle_patricia_trie) umbrella project in this repo. | ||
* The Machine State - This structure effectively encodes the current context of a running VM (e.g. the program counter, the current memory data, etc). This structure is simply used during execution of the program, and thrown away after it completes. Before we finish, we extract the gas used and return value from this object. | ||
* The Sub State - The sub state tracks the suicide list (contracts to destroy), the logs and the refund (for cleaning up storage) for a contract execution. | ||
* The Execution Environment - This tracks information about the call into a contract, such as the machine code itself and the value passed to the contract or message call. Other than stack depth, this is generally not mutated during execution of the machine. | ||
|
||
# Examples | ||
|
||
Here is an example of a simple program running on the VM: | ||
|
||
```elixir | ||
EVM.VM.run(%{}, 5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])}) | ||
{%{}, 5, [], [], 0, <<0x08::256>>} | ||
``` | ||
|
||
Let's walk through this step-by-step. First, we take some pseudo-machine code and compile it. Thus, the machine code above becomes `<<96, 3, 96, 5, 1, 96, 0, 82, 96, 0, 96, 32, 243>>` before it's passed to the virtual machine. The code itself says to push two values (hard-coded) on to the stack (3 and 5). Then we ask the machine to take the top two values off of the stack, add them, and place them back on to the stack. This leaves the stack as [8]. Then we ask the machine to push zero on to the stack and run store. This stores the value "8" at memory offset 0. Finally, we push two more values 0 and 32 to tell the machine that we'll be returning the first 32 bytes of memory as our return result (since we count words and gas in blocks of 32, we might as well return the full value). We then return and get the correct result of `<<0x08::256>>` as a erlang binary. | ||
|
||
TODO: Add diagram or debug output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# This file is responsible for configuring your application | ||
# and its dependencies with the aid of the Mix.Config module. | ||
use Mix.Config | ||
|
||
# This configuration is loaded before any dependency and is restricted | ||
# to this project. If another project depends on this project, this | ||
# file won't be loaded nor affect the parent project. For this reason, | ||
# if you want to provide default values for your application for | ||
# 3rd-party users, it should be done in your "mix.exs" file. | ||
|
||
# You can configure for your application as: | ||
# | ||
# config :evm, key: :value | ||
# | ||
# And access this configuration in your application as: | ||
# | ||
# Application.get_env(:evm, :key) | ||
# | ||
# Or configure a 3rd-party app: | ||
# | ||
# config :logger, level: :info | ||
# | ||
|
||
# It is also possible to import configuration files, relative to this | ||
# directory. For example, you can emulate configuration per environment | ||
# by uncommenting the line below and defining dev.exs, test.exs and such. | ||
# Configuration from the imported file will override the ones defined | ||
# here (which is why it is important to import them last). | ||
# | ||
# import_config "#{Mix.env}.exs" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
defmodule EVM do | ||
@moduledoc """ | ||
Documentation for EVM. | ||
""" | ||
|
||
@type state :: Trie.t | ||
@type trie_root :: <<_::256>> | ||
@type val :: integer() | ||
@type address :: <<_::160>> | ||
@type hash :: <<_::256>> | ||
@type timestamp :: integer() | ||
|
||
@max_int round(:math.pow(2, 256)) | ||
|
||
@doc """ | ||
Returns maximum allowed integer size. | ||
""" | ||
def max_int(), do: @max_int | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
defmodule EVM.Application do | ||
# See http://elixir-lang.org/docs/stable/elixir/Application.html | ||
# for more information on OTP Applications | ||
@moduledoc false | ||
|
||
use Application | ||
|
||
def start(_type, _args) do | ||
import Supervisor.Spec, warn: false | ||
|
||
# Define workers and child supervisors to be supervised | ||
children = [ | ||
# Starts a worker by calling: EVM.Worker.start_link(arg1, arg2, arg3) | ||
# worker(EVM.Worker, [arg1, arg2, arg3]), | ||
] | ||
|
||
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html | ||
# for other strategies and supported options | ||
opts = [strategy: :one_for_one, name: EVM.Supervisor] | ||
Supervisor.start_link(children, opts) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
defmodule EVM.Builtin do | ||
@moduledoc """ | ||
Implements the built-in functions as defined in Appendix E | ||
of the Yellow Paper. These are contract functions that | ||
natively exist in Ethereum. | ||
""" | ||
|
||
def run_ecrec(state, gas, exec_env), do: nil | ||
def run_sha256(state, gas, exec_env), do: nil | ||
def run_rip160(state, gas, exec_env), do: nil | ||
def run_id(state, gas, exec_env), do: nil | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
defmodule EVM.ExecEnv do | ||
@moduledoc """ | ||
Stores information about the execution environment which led | ||
to this EVM being called. This is, for instance, the sender of | ||
a payment or message to a contract, or a sub-contract call. | ||
This generally relates to `I` in the Yellow Paper, defined in | ||
Section 9.3. | ||
""" | ||
|
||
defstruct [ | ||
address: nil, # a | ||
originator: nil, # o | ||
gas_price: nil, # p | ||
data: nil, # d | ||
sender: nil, # s | ||
value_in_wei: nil, # v | ||
machine_code: <<>>, # b | ||
block_header: nil, # h | ||
stack_depth: nil] # e | ||
|
||
@type t :: %{ | ||
address: EVM.address, | ||
originator: EVM.address, | ||
gas_price: EVM.Gas.gas_price, | ||
data: binary(), | ||
sender: EVM.address, | ||
value_in_wei: EVM.Wei.t, | ||
machine_code: EVM.MachineCode.t, | ||
block_header: binary(), | ||
stack_depth: integer() | ||
} | ||
|
||
@doc """ | ||
Returns the base execution environment for a message call. | ||
This is generally defined as equations 107-114 in the Yellow Paper. | ||
TODO: Machine code may be passed in as a hash | ||
TODO: How is block header passed in? | ||
# TODO: Examples | ||
""" | ||
@spec exec_env_for_message_call(EVM.address, EVM.address, EVM.Gas.gas_price, binary(), EVM.address, EVM.Wei.t, integer(), Blockchain.Block.Header.t, EVM.MachineCode.t) :: t | ||
def exec_env_for_message_call(recipient, originator, gas_price, data, sender, value_in_wei, stack_depth, block_header, machine_code) do | ||
%__MODULE__{ | ||
address: recipient, | ||
originator: originator, | ||
gas_price: gas_price, | ||
data: data, | ||
sender: sender, | ||
value_in_wei: value_in_wei, | ||
stack_depth: stack_depth, | ||
block_header: block_header, | ||
machine_code: machine_code, | ||
} | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
defmodule EVM.Functions do | ||
@moduledoc """ | ||
Set of functions defined in the Yellow Paper that do not logically | ||
fit in other modules. | ||
""" | ||
|
||
alias EVM.ExecEnv | ||
alias EVM.MachineCode | ||
alias EVM.MachineState | ||
alias EVM.Gas | ||
alias EVM.Instruction | ||
alias EVM.Stack | ||
|
||
@max_stack 1024 | ||
|
||
@doc """ | ||
Returns whether or not the current program is halting due to a `return` or terminal statement. | ||
# Examples | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{pc: 0}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:add)>>}) | ||
nil | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{pc: 0}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:mul)>>}) | ||
nil | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{pc: 0}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:stop)>>}) | ||
<<>> | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{pc: 0}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:suicide)>>}) | ||
<<>> | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{stack: [0, 1], memory: <<0xabcd::16>>}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:return)>>}) | ||
<<0xab>> | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{stack: [0, 2], memory: <<0xabcd::16>>}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:return)>>}) | ||
<<0xab, 0xcd>> | ||
iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{stack: [1, 2], memory: <<0xabcd::16>>}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:return)>>}) | ||
<<0xcd>> | ||
""" | ||
@spec is_normal_halting?(MachineState.t, ExecEnv.t) :: nil | binary() | ||
def is_normal_halting?(machine_state, exec_env) do | ||
case MachineCode.current_instruction(machine_state, exec_env) |> Instruction.decode do | ||
:return -> h_return(machine_state) | ||
x when x == :stop or x == :suicide -> <<>> | ||
_ -> nil | ||
end | ||
end | ||
|
||
# Defined in Appendix H of the Yellow Paper | ||
defp h_return(machine_state) do | ||
{[mem_start, mem_end], _} = EVM.Stack.pop_n(machine_state.stack, 2) | ||
|
||
{result, _} = EVM.Memory.read(machine_state, mem_start, mem_end - mem_start) | ||
|
||
result | ||
end | ||
|
||
@doc """ | ||
Returns whether or not the current program is in an exceptional halting state. | ||
This may be due to running out of gas, having an invalid instruction, having | ||
a stack underflow, having an invalid jump destination or having a stack overflow. | ||
This is defined as `Z` in Eq.(126) of the Yellow Paper. | ||
## Examples | ||
# TODO: Once we add gas cost, make this more reasonable | ||
# TODO: How do we pass in state? | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: -1}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:add)>>}) | ||
{:halt, :insufficient_gas} | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff}, %EVM.ExecEnv{machine_code: <<0xfe>>}) | ||
{:halt, :undefined_instruction} | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: []}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:add)>>}) | ||
{:halt, :stack_underflow} | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: [5]}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:jump)>>}) | ||
{:halt, :invalid_jump_destination} | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: [1]}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:jump), EVM.Instruction.encode(:jumpdest)>>}) | ||
:continue | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: [1, 5]}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:jumpi)>>}) | ||
{:halt, :invalid_jump_destination} | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: [1, 5]}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:jumpi), EVM.Instruction.encode(:jumpdest)>>}) | ||
:continue | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: (for _ <- 1..1024, do: 0x0)}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:stop)>>}) | ||
:continue | ||
iex> EVM.Functions.is_exception_halt?(%{}, %EVM.MachineState{pc: 0, gas: 0xffff, stack: (for _ <- 1..1024, do: 0x0)}, %EVM.ExecEnv{machine_code: <<EVM.Instruction.encode(:push1)>>}) | ||
{:halt, :stack_overflow} | ||
""" | ||
@spec is_exception_halt?(EVM.state, MachineState.t, ExecEnv.t) :: :continue | {:halt, String.t} | ||
def is_exception_halt?(state, machine_state, exec_env) do | ||
instruction = MachineCode.current_instruction(machine_state, exec_env) |> Instruction.decode | ||
metadata = Instruction.metadata(instruction) | ||
dw = if metadata, do: Map.get(metadata, :d), else: nil | ||
aw = if metadata, do: Map.get(metadata, :a), else: nil | ||
s0 = Stack.peek(machine_state.stack) | ||
|
||
cond do | ||
metadata == nil || dw == nil -> | ||
{:halt, :undefined_instruction} | ||
machine_state.gas < Gas.cost(state, machine_state, exec_env) -> | ||
{:halt, :insufficient_gas} | ||
length(machine_state.stack) < dw -> | ||
{:halt, :stack_underflow} | ||
Enum.member?([:jump, :jumpi], instruction) and | ||
not MachineCode.valid_jump_dest?(s0, exec_env.machine_code) -> | ||
{:halt, :invalid_jump_destination} | ||
Stack.length(machine_state.stack) - dw + aw > @max_stack -> | ||
{:halt, :stack_overflow} | ||
true -> | ||
:continue | ||
end | ||
end | ||
|
||
end |
Oops, something went wrong.