Skip to content

Commit

Permalink
Add Ethereum Common tests and pass the add0 test
Browse files Browse the repository at this point in the history
  • Loading branch information
masonforest committed Aug 9, 2017
1 parent 91541cc commit 33ee04b
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 50 deletions.
2 changes: 2 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ jobs:
- image: elixir:latest
steps:
- checkout
- run: git submodule sync --recursive
- run: git submodule update --recursive --init
- restore_cache:
key: _build
- run: mix local.hex --force
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "test/support/ethereum_common_tests"]
path = test/support/ethereum_common_tests
url = [email protected]:ethereum/tests.git
10 changes: 5 additions & 5 deletions lib/evm/builtin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ defmodule EVM.Builtin do
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
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
77 changes: 56 additions & 21 deletions lib/evm/gas.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ defmodule EVM.Gas do

alias EVM.MachineState
alias EVM.ExecEnv
alias MerklePatriciaTree.Trie

@type t :: EVM.val
@type gas_price :: EVM.Wei.t
Expand Down Expand Up @@ -48,12 +47,13 @@ defmodule EVM.Gas do

@w_zero_instr [:stop, :return]
@w_base_instr [:address, :origin, :caller, :callvalue, :calldatasize, :codesize, :gasprice, :coinbase, :timestamp, :number, :difficulty, :gaslimit, :pop, :pc, :msize, :gas]
@push_instrs Enum.map(0..32, fn n -> :"push#{n}" end)
@dup_instrs Enum.map(0..16, fn n -> :"dup#{n}" end)
@swap_instrs Enum.map(0..16, fn n -> :"swap#{n}" end)
@w_very_low_instr [
:add, :sub, :not, :lt, :gt, :slt, :sgt, :eq, :iszero, :and, :or, :xor, :byte, :calldataload, :mload, :mstore, :mstore8,
:push0, :push1, :push2, :push3, :push4, :push5, :push6, :push7, :push8, :push9, :push10, :push11, :push12, :push13, :push14, :push15, :push16,
:dup0, :dup1, :dup2, :dup3, :dup4, :dup5, :dup6, :dup7, :dup8, :dup9, :dup10, :dup11, :dup12, :dup13, :dup14, :dup15, :dup16,
:swap0, :swap1, :swap2, :swap3, :swap4, :swap5, :swap6, :swap7, :swap8, :swap9, :swap10, :swap11, :swap12, :swap13, :swap14, :swap15, :swap16,
]
:add, :sub, :not, :lt, :gt, :slt, :sgt, :eq, :iszero, :and, :or, :xor,
:byte, :calldataload, :mload, :mstore, :mstore8 ] ++
@push_instrs ++ @dup_instrs ++ @swap_instrs
@w_low_instr [:mul, :div, :sdiv, :mod, :smod, :signextend]
@w_mid_instr [:addmod, :mulmod, :jump]
@w_high_instr [:jumpi]
Expand Down Expand Up @@ -87,9 +87,8 @@ defmodule EVM.Gas do
## Examples
# TODO: Verify
iex> EVM.Gas.instr_cost(:sstore, nil, nil, nil)
0
iex> EVM.Gas.instr_cost(:sstore, nil, %EVM.MachineState{stack: [0, 0]}, nil)
5000
iex> EVM.Gas.instr_cost(:exp, nil, %EVM.MachineState{stack: [0, 0]}, nil)
10
Expand All @@ -103,6 +102,27 @@ defmodule EVM.Gas do
iex> EVM.Gas.instr_cost(:blockhash, nil, nil, nil)
20
iex> EVM.Gas.instr_cost(:stop, nil, nil, nil)
0
iex> EVM.Gas.instr_cost(:address, nil, nil, nil)
2
iex> EVM.Gas.instr_cost(:push0, nil, nil, nil)
3
iex> EVM.Gas.instr_cost(:mul, nil, nil, nil)
5
iex> EVM.Gas.instr_cost(:addmod, nil, nil, nil)
8
iex> EVM.Gas.instr_cost(:jumpi, nil, nil, nil)
10
iex> EVM.Gas.instr_cost(:extcodesize, nil, nil, nil)
700
"""
@spec instr_cost(atom(), EVM.state, MachineState.t, ExecEnv.t) :: t | nil
def instr_cost(:sstore, state, machine_state, _exec_env), do: cost_sstore(state, machine_state)
Expand All @@ -126,32 +146,47 @@ defmodule EVM.Gas do
def instr_cost(:sha3, _state, _machine_state, _exec_env), do: 0
def instr_cost(:jumpdest, _state, _machine_state, _exec_env), do: @g_jumpdest
def instr_cost(:sload, _state, _machine_state, _exec_env), do: 0
def instr_cost(w_zero_instr, _state, _machine_state, _exec_env) when w_zero_instr in @w_zero_instr, do: 0
def instr_cost(w_base_instr, _state, _machine_state, _exec_env) when w_base_instr in @w_base_instr, do: 0
def instr_cost(w_very_low_instr, _state, _machine_state, _exec_env) when w_very_low_instr in @w_very_low_instr, do: 0
def instr_cost(w_low_instr, _state, _machine_state, _exec_env) when w_low_instr in @w_low_instr, do: 0
def instr_cost(w_mid_instr, _state, _machine_state, _exec_env) when w_mid_instr in @w_mid_instr, do: 0
def instr_cost(w_high_instr, _state, _machine_state, _exec_env) when w_high_instr in @w_high_instr, do: 0
def instr_cost(w_extcode_instr, _state, _machine_state, _exec_env) when w_extcode_instr in @w_extcode_instr, do: 0
def instr_cost(w_zero_instr, _state, _machine_state, _exec_env) when w_zero_instr in @w_zero_instr, do: @g_zero
def instr_cost(w_base_instr, _state, _machine_state, _exec_env) when w_base_instr in @w_base_instr, do: @g_base
def instr_cost(w_very_low_instr, _state, _machine_state, _exec_env) when w_very_low_instr in @w_very_low_instr, do: @g_verylow
def instr_cost(w_low_instr, _state, _machine_state, _exec_env) when w_low_instr in @w_low_instr, do: @g_low
def instr_cost(w_mid_instr, _state, _machine_state, _exec_env) when w_mid_instr in @w_mid_instr, do: @g_mid
def instr_cost(w_high_instr, _state, _machine_state, _exec_env) when w_high_instr in @w_high_instr, do: @g_high
def instr_cost(w_extcode_instr, _state, _machine_state, _exec_env) when w_extcode_instr in @w_extcode_instr, do: @g_extcode
def instr_cost(:balance, _state, _machine_state, _exec_env), do: 0
def instr_cost(:blockhash, _state, _machine_state, _exec_env), do: @g_blockhash
def instr_cost(_unknown_instr, _state, _machine_state, _exec_env), do: nil

# Eq.(222)
def cost_mem(active_words), do: 0
def cost_mem(_active_words), do: 0

@doc """
Returns the cost of a call to `sstore`. This is defined
in Appenfix H.2. of the Yellow Paper under the
definition of SSTORE, referred to as `C_SSTORE`.
# TODO: Implement and add examples
## Examples
iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db(:evm_vm_test))
...> |> MerklePatriciaTree.Trie.update(<<0>>, 1)
iex> EVM.Gas.cost_sstore(state, %EVM.MachineState{stack: [0, 0]})
5000
iex> EVM.Gas.cost_sstore(state, %EVM.MachineState{stack: [0, 2]})
20000
"""
@spec cost_sstore(EVM.state, MachineState.t) :: t
def cost_sstore(state, machine_state), do: 0
def cost_sstore(_state, machine_state) do
{:ok, new_value} = Enum.fetch(machine_state.stack, 1)

if new_value == 0 do
@g_sreset
else
@g_sset
end
end

def cost_call(state, machine_state), do: 0
def cost_suicide(state, machine_state), do: 0
def cost_call(_state, _machine_state), do: 0
def cost_suicide(_state, _machine_state), do: 0

@doc """
Returns the gas cost for G_txdata{zero, nonzero} as defined in
Expand Down
4 changes: 2 additions & 2 deletions lib/evm/instruction/memory.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ defmodule EVM.Instruction.Memory do
"""

@spec active_words_after(EVM.Instruction.instruction, EVM.state, EVM.MachineState.t, EVM.ExecEnv.t) :: integer()
def active_words_after(instruction, state, machine_state, exec_env), do: machine_state.active_words
end
def active_words_after(_instruction, _state, machine_state, _exec_env), do: machine_state.active_words
end
22 changes: 11 additions & 11 deletions lib/evm/vm.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ defmodule EVM.VM do
## Examples
# Full program
iex> 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, %EVM.SubState{}, <<0x08::256>>}
iex> EVM.VM.run(%{}, 21, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])})
{%{}, 0, %EVM.SubState{}, <<0x08::256>>}
# Program with implicit stop
iex> EVM.VM.run(%{}, 5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])})
{%{}, 5, %EVM.SubState{}, ""}
iex> EVM.VM.run(%{}, 9, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])})
{%{}, 0, %EVM.SubState{}, ""}
# Program with explicit stop
iex> EVM.VM.run(%{}, 5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :stop])})
{%{}, 5, %EVM.SubState{}, ""}
{%{}, 2, %EVM.SubState{}, ""}
# Program with exception halt
iex> EVM.VM.run(%{}, 5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])})
Expand All @@ -56,13 +56,13 @@ defmodule EVM.VM do
## Examples
iex> EVM.VM.exec(%{}, %EVM.MachineState{pc: 0, gas: 5, stack: [1, 2]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])})
{%{}, %EVM.MachineState{pc: 2, gas: 5, stack: [3]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])}, <<>>}
{%{}, %EVM.MachineState{pc: 2, gas: 2, stack: [3]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])}, <<>>}
iex> EVM.VM.exec(%{}, %EVM.MachineState{pc: 0, gas: 5, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])})
{%{}, %EVM.MachineState{pc: 6, gas: 5, stack: [8]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])}, ""}
iex> EVM.VM.exec(%{}, %EVM.MachineState{pc: 0, gas: 9, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])})
{%{}, %EVM.MachineState{pc: 6, gas: 0, stack: [8]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])}, ""}
iex> EVM.VM.exec(%{}, %EVM.MachineState{pc: 0, gas: 5, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])})
{%{}, %EVM.MachineState{active_words: 1, memory: <<0x08::256>>, pc: 13, gas: 5, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])}, <<0x08::256>>}
iex> EVM.VM.exec(%{}, %EVM.MachineState{pc: 0, gas: 21, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])})
{%{}, %EVM.MachineState{active_words: 1, memory: <<0x08::256>>, pc: 13, gas: 0, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 0, :push1, 32, :return])}, <<0x08::256>>}
"""
@spec exec(EVM.state, MachineState.t, SubState.t, ExecEnv.t) :: {EVM.state | nil, MachineState.t, SubState.t, ExecEnv.t, output}
def exec(state, machine_state, sub_state, exec_env) do
Expand Down Expand Up @@ -93,7 +93,7 @@ defmodule EVM.VM do
iex> state = MerklePatriciaTree.Trie.new(MerklePatriciaTree.Test.random_ets_db(:evm_vm_test))
iex> EVM.VM.cycle(state, %EVM.MachineState{pc: 0, gas: 5, stack: [1, 2]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])})
{%MerklePatriciaTree.Trie{db: {MerklePatriciaTree.DB.ETS, :evm_vm_test}, root_hash: <<128>>}, %EVM.MachineState{pc: 1, gas: 5, stack: [3]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])}}
{%MerklePatriciaTree.Trie{db: {MerklePatriciaTree.DB.ETS, :evm_vm_test}, root_hash: <<128>>}, %EVM.MachineState{pc: 1, gas: 2, stack: [3]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:add])}}
"""
@spec cycle(EVM.state, MachineState.t, SubState.t, ExecEnv.t) :: {EVM.state, MachineState.t, SubState.t, ExecEnv.t}
def cycle(state, machine_state, sub_state, exec_env) do
Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ defmodule EVM.Mixfile do
defp deps do
[
{:credo, "~> 0.8", only: [:dev, :test], runtime: false},
{:poison, "~> 3.1.0", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.14", only: :dev, runtime: false},
{:merkle_patricia_tree, "~> 0.2.1"},
{:dialyxir, "~> 0.5", only: [:dev], runtime: false},
Expand Down
3 changes: 2 additions & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
"exleveldb": {:hex, :exleveldb, "0.11.1", "37c0414208a50d2419d8246305e6c4a33ed339c25c0bdfa27774795e1e089f7b", [:mix], [{:eleveldb, "~> 2.2.19", [hex: :eleveldb, repo: "hexpm", optional: false]}], "hexpm"},
"hex_prefix": {:hex, :hex_prefix, "0.1.0", "e96b5cbb6ad8493196ce193726240023f5ce0ae0753118a19a5b43e2db0267ca", [:mix], [], "hexpm"},
"keccakf1600": {:hex, :keccakf1600, "2.0.0", "69d02d844a101bf3c75484c9e334fd04b0f57280727e881cac3bd8240432f43a", [:rebar3], [], "hexpm"},
"merkle_patricia_tree": {:hex, :merkle_patricia_tree, "0.2.1", "11d510c73b9e9bbcc6d33a0b48d80325dce8a342568b514d055bbca2658ee09a", [:mix], [{:ex_rlp, "~> 0.2.0", [hex: :ex_rlp, repo: "hexpm", optional: false]}, {:exleveldb, "~> 0.11.1", [hex: :exleveldb, repo: "hexpm", optional: false]}, {:hex_prefix, "~> 0.1.0", [hex: :hex_prefix, repo: "hexpm", optional: false]}, {:keccakf1600, "~> 2.0.0", [hex: :keccakf1600, repo: "hexpm", optional: false]}], "hexpm"}}
"merkle_patricia_tree": {:hex, :merkle_patricia_tree, "0.2.1", "11d510c73b9e9bbcc6d33a0b48d80325dce8a342568b514d055bbca2658ee09a", [:mix], [{:ex_rlp, "~> 0.2.0", [hex: :ex_rlp, repo: "hexpm", optional: false]}, {:exleveldb, "~> 0.11.1", [hex: :exleveldb, repo: "hexpm", optional: false]}, {:hex_prefix, "~> 0.1.0", [hex: :hex_prefix, repo: "hexpm", optional: false]}, {:keccakf1600, "~> 2.0.0", [hex: :keccakf1600, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}}
10 changes: 5 additions & 5 deletions test/evm/vm_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ defmodule EVM.VMTest do
:return
]

result = EVM.VM.run(state, 5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile(instructions)})
result = EVM.VM.run(state, 21, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile(instructions)})

assert result == {state, 5, %EVM.SubState{logs: "", refund: 0, suicide_list: []}, <<0x08::256>>}
assert result == {state, 0, %EVM.SubState{logs: "", refund: 0, suicide_list: []}, <<0x08::256>>}
end

test "simple program with block storage", %{state: state} do
Expand All @@ -42,14 +42,14 @@ defmodule EVM.VMTest do
:stop
]

result = EVM.VM.run(state, 5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile(instructions)})
result = EVM.VM.run(state, 20006, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile(instructions)})

expected_state = %{state|root_hash: <<12, 189, 253, 61, 167, 240, 166, 67, 81, 179, 89, 188, 142, 220, 80, 44, 72, 102, 195, 89, 230, 27, 75, 136, 68, 2, 117, 227, 48, 141, 102, 230>>}

assert result == {expected_state, 5, %EVM.SubState{logs: "", refund: 0, suicide_list: []}, ""}
assert result == {expected_state, 0, %EVM.SubState{logs: "", refund: 0, suicide_list: []}, ""}

{returned_state, _, _, _} = result

assert MerklePatriciaTree.Trie.Inspector.all_values(returned_state) == [{<<5::256>>, <<3::256>>}]
end
end
end
70 changes: 65 additions & 5 deletions test/evm_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,68 @@
defmodule EVMTest do
use ExUnit.Case
doctest EVM
defmodule EvmTest do
use ExUnit.Case, async: true

test "the truth" do
assert 1 + 1 == 2
@passing_tests %{
arithmetic: [
:add0,
:add1,
]
}


test "Ethereum Common Tests" do
for {test_type, test_group} <- @passing_tests do
for {test_name, test} <- read_test_file(test_type),
Enum.member?(test_group, String.to_atom(test_name)) do
db = MerklePatriciaTree.Test.random_ets_db()
state = EVM.VM.run(
MerklePatriciaTree.Trie.new(db),
hex_to_int(test["exec"]["gas"]),
%EVM.ExecEnv{
machine_code: hex_to_binary(test["exec"]["code"]),
}
)

assert_state(test, state)
assert elem(state, 1) == hex_to_int(test["gas"])
end
end
end

def read_test_file(type) do
{:ok, body} = File.read(test_file_name(type))
Poison.decode!(body)
end

def test_file_name(type) do
"test/support/ethereum_common_tests/VMTests/vm#{Macro.camelize(Atom.to_string(type))}Test.json"
end

def hex_to_binary(string) do
string
|> String.slice(2..-1)
|> Base.decode16!(case: :mixed)
end

def hex_to_int(string) do
hex_to_binary(string)
|> :binary.decode_unsigned
end

def assert_state(test, state) do
contract_address = Map.get(Map.get(test, "exec"), "address")
assert (test
|> Map.get("post")
|> Map.get(contract_address)
|> Map.get("storage")
|> Enum.map(fn {k, v} ->
{hex_to_binary(k), hex_to_binary(v)}
end)) == state
|> elem(0)
|> MerklePatriciaTree.Trie.Inspector.all_values()
|> Enum.map(fn {k, v} -> {r_trim(k), r_trim(v)} end)
end

def r_trim(n), do: n
|> :binary.decode_unsigned
|> :binary.encode_unsigned
end
1 change: 1 addition & 0 deletions test/support/ethereum_common_tests
Submodule ethereum_common_tests added at 862589

0 comments on commit 33ee04b

Please sign in to comment.