Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hayesgm committed Aug 2, 2017
0 parents commit 68d60b3
Show file tree
Hide file tree
Showing 43 changed files with 3,574 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .circleci/config.yml
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
20 changes: 20 additions & 0 deletions .gitignore
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
25 changes: 25 additions & 0 deletions README.md
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
30 changes: 30 additions & 0 deletions config/config.exs
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"
20 changes: 20 additions & 0 deletions lib/evm.ex
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
22 changes: 22 additions & 0 deletions lib/evm/application.ex
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
12 changes: 12 additions & 0 deletions lib/evm/builtin.ex
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
58 changes: 58 additions & 0 deletions lib/evm/exec_env.ex
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
123 changes: 123 additions & 0 deletions lib/evm/functions.ex
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
Loading

0 comments on commit 68d60b3

Please sign in to comment.