diff --git a/apps/blockchain/lib/blockchain/account.ex b/apps/blockchain/lib/blockchain/account.ex index 1c66eb570..f54ad50aa 100644 --- a/apps/blockchain/lib/blockchain/account.ex +++ b/apps/blockchain/lib/blockchain/account.ex @@ -177,7 +177,7 @@ defmodule Blockchain.Account do @doc """ Completely removes an account from the world state. - This is used, for instance, after a suicide. + This is used, for instance, after a selfdestruct. This is defined from Eq.(71) and Eq.(80) in the Yellow Paper. ## Examples @@ -354,14 +354,13 @@ defmodule Blockchain.Account do @doc """ Helper function for transferring eth for one account to another. This handles the fact that a new account may be shadow-created if - it receives eth. See Section 8, Eq.(100), Eq.(101), Eq.(102), Eq.(103), - and Eq.(104) of the Yellow Paper. - - The Yellow Paper assumes this function will always succeed (as the checks - occur before this function is called), but we'll check just in case - this function is not properly called. The only case will be if the - sending account is nil or has an insufficient balance, but we add - a few extra checks just in case. + it receives eth. See Section 8, Eq.(100-104) of the Yellow Paper. + + The Yellow Paper assumes this function will always succeed + (as the checks occur before this function is called), + but we'll check just in case this function is not properly called. + The only case will be if the sending account is nil or has an insufficient balance, + but we add a few extra checks just in case. Note: transferring value to an empty account still adds value to said account, even though it's effectively a zombie. diff --git a/apps/blockchain/lib/blockchain/interface/account_interface.ex b/apps/blockchain/lib/blockchain/interface/account_interface.ex index 1debd4605..0c4468f51 100644 --- a/apps/blockchain/lib/blockchain/interface/account_interface.ex +++ b/apps/blockchain/lib/blockchain/interface/account_interface.ex @@ -214,8 +214,8 @@ defimpl EVM.Interface.AccountInterface, for: Blockchain.Interface.AccountInterfa end @doc """ - Suicides an account (err.. SELFDESTRUCT is the new word). This removes any trace - of the account from the system. + Destructs an account (SELFDESTRUCT operation in YP). + This removes any trace of the account from the system. ## Examples @@ -223,7 +223,7 @@ defimpl EVM.Interface.AccountInterface, for: Blockchain.Interface.AccountInterfa ...> |> MerklePatriciaTree.Trie.new() ...> |> Blockchain.Account.add_wei(<<1::160>>, 5) ...> |> Blockchain.Interface.AccountInterface.new() - ...> |> EVM.Interface.AccountInterface.suicide_account(<<1::160>>) + ...> |> EVM.Interface.AccountInterface.destroy_account(<<1::160>>) ...> |> EVM.Interface.AccountInterface.get_account_balance(<<1::160>>) nil @@ -231,13 +231,13 @@ defimpl EVM.Interface.AccountInterface, for: Blockchain.Interface.AccountInterfa ...> |> MerklePatriciaTree.Trie.new() ...> |> Blockchain.Account.add_wei(<<1::160>>, 5) ...> |> Blockchain.Interface.AccountInterface.new() - ...> |> EVM.Interface.AccountInterface.suicide_account(<<2::160>>) + ...> |> EVM.Interface.AccountInterface.destroy_account(<<2::160>>) ...> |> EVM.Interface.AccountInterface.get_account_balance(<<1::160>>) 5 """ - @spec suicide_account(EVM.Interface.AccountInterface.t(), EVM.address()) :: + @spec destroy_account(EVM.Interface.AccountInterface.t(), EVM.address()) :: EVM.Interface.AccountInterface.t() - def suicide_account(account_interface, address) do + def destroy_account(account_interface, address) do updated_state = Account.del_account(account_interface.state, address) %{account_interface | state: updated_state} diff --git a/apps/blockchain/lib/blockchain/transaction.ex b/apps/blockchain/lib/blockchain/transaction.ex index 78171065d..0a638e387 100644 --- a/apps/blockchain/lib/blockchain/transaction.ex +++ b/apps/blockchain/lib/blockchain/transaction.ex @@ -361,15 +361,15 @@ defmodule Blockchain.Transaction do state_after_gas = finalize_transaction_gas(state_p, sender, tx, refund, block_header) - state_after_suicides = - Enum.reduce(sub_state.suicide_list, state_after_gas, fn address, state -> + state_after_selfdestruct = + Enum.reduce(sub_state.selfdestruct_list, state_after_gas, fn address, state -> Account.del_account(state, address) end) expended_gas = tx.gas_limit - remaining_gas # { σ', Υ^g, Υ^l }, as defined in Eq.(79) and Eq.(80) - {state_after_suicides, expended_gas, sub_state.logs} + {state_after_selfdestruct, expended_gas, sub_state.logs} end @doc """ diff --git a/apps/evm/README.md b/apps/evm/README.md index 427d55e96..324caee03 100644 --- a/apps/evm/README.md +++ b/apps/evm/README.md @@ -8,7 +8,7 @@ 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_tree](https://github.com/exthereum/merkle_patricia_tree) 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 Sub State - The sub state tracks the selfdestruct 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 diff --git a/apps/evm/lib/evm/address.ex b/apps/evm/lib/evm/address.ex index 3182e7d14..c260a622f 100644 --- a/apps/evm/lib/evm/address.ex +++ b/apps/evm/lib/evm/address.ex @@ -28,8 +28,11 @@ defmodule EVM.Address do address |> :binary.encode_unsigned() |> EVM.Helpers.left_pad_bytes(@size) + |> EVM.Helpers.take_n_last_bytes(@size) end + def new(address), do: address + @doc """ Returns an address given an address and a nonce. """ diff --git a/apps/evm/lib/evm/debugger/breakpoint.ex b/apps/evm/lib/evm/debugger/breakpoint.ex index 94231e5bd..035ac05c1 100644 --- a/apps/evm/lib/evm/debugger/breakpoint.ex +++ b/apps/evm/lib/evm/debugger/breakpoint.ex @@ -129,9 +129,12 @@ defmodule EVM.Debugger.Breakpoint do """ @spec matches?(t, EVM.MachineState.t(), EVM.SubState.t(), EVM.ExecEnv.t()) :: boolean() def matches?(breakpoint, machine_state, _sub_state, exec_env) do - breakpoint.enabled and break_on_next_pc?(breakpoint, machine_state.program_counter) and + should_break = break_on_next_pc?(breakpoint, machine_state.program_counter) + + breakpoint.enabled and should_break and Enum.all?(breakpoint.conditions, fn {condition, condition_val} -> case condition do + :sender -> exec_env.sender == condition_val :address -> exec_env.address == condition_val end end) @@ -159,6 +162,7 @@ defmodule EVM.Debugger.Breakpoint do conditions = for {k, v} <- breakpoint.conditions do case k do + :sender -> "sender address 0x#{Base.encode16(v, case: :lower)}" :address -> "contract address 0x#{Base.encode16(v, case: :lower)}" end end @@ -295,7 +299,7 @@ defmodule EVM.Debugger.Breakpoint do end end - # Returns true if we should break on the next instruction + # Returns true if we should break on the next instruction. # This is will be true if we're instructed to break on :next or :start, or # when the machine's breakpoint's pc matches the machine's pc. diff --git a/apps/evm/lib/evm/exec_env.ex b/apps/evm/lib/evm/exec_env.ex index bed8e7d03..03d8d7a93 100644 --- a/apps/evm/lib/evm/exec_env.ex +++ b/apps/evm/lib/evm/exec_env.ex @@ -60,10 +60,9 @@ defmodule EVM.ExecEnv do AccountInterface.get_storage(account_interface, address, key) end - @spec suicide_account(t()) :: t() - def suicide_account(exec_env = %{account_interface: account_interface, address: address}) do - account_interface = AccountInterface.suicide_account(account_interface, address) - + @spec destroy_account(t()) :: t() + def destroy_account(exec_env = %{account_interface: account_interface, address: address}) do + account_interface = AccountInterface.destroy_account(account_interface, address) Map.put(exec_env, :account_interface, account_interface) end diff --git a/apps/evm/lib/evm/functions.ex b/apps/evm/lib/evm/functions.ex index 1b8c79581..651c64e54 100644 --- a/apps/evm/lib/evm/functions.ex +++ b/apps/evm/lib/evm/functions.ex @@ -25,7 +25,7 @@ defmodule EVM.Functions do iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{program_counter: 0}, %EVM.ExecEnv{machine_code: <>}) <<>> - iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{program_counter: 0}, %EVM.ExecEnv{machine_code: <>}) + iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{program_counter: 0}, %EVM.ExecEnv{machine_code: <>}) <<>> iex> EVM.Functions.is_normal_halting?(%EVM.MachineState{stack: [0, 1], memory: <<0xabcd::16>>}, %EVM.ExecEnv{machine_code: <>}) @@ -41,7 +41,7 @@ defmodule EVM.Functions do def is_normal_halting?(machine_state, exec_env) do case MachineCode.current_operation(machine_state, exec_env).sym do :return -> h_return(machine_state) - x when x == :stop or x == :suicide -> <<>> + x when x == :stop or x == :selfdestruct -> <<>> _ -> nil end end diff --git a/apps/evm/lib/evm/gas.ex b/apps/evm/lib/evm/gas.ex index f8bbc4a23..ba345af32 100644 --- a/apps/evm/lib/evm/gas.ex +++ b/apps/evm/lib/evm/gas.ex @@ -34,10 +34,10 @@ defmodule EVM.Gas do @g_sreset 5000 # Refund given (added into refund counter) when the storage value is set to zero from non-zero. @g_sclear 15000 - # Refund given (added into refund counter) for suiciding an account. - @g_suicide 24000 - # Amount of gas to pay for a SUICIDE operation. - @g_suicide 5000 + # Refund given (added into refund counter) for selfdestructing an account. + @r_selfdestruct 24000 + # Amount of gas to pay for a SELFDESTRUCT operation. + @g_selfdestruct 5000 # Paid for a CREATE operation. @g_create 32000 # Paid per byte for a CREATE operation to succeed in placing code into state. @@ -48,7 +48,7 @@ defmodule EVM.Gas do @g_callvalue 9000 # A stipend for the called contract subtracted from Gcallvalue for a non-zero value transfer. @g_callstipend 2300 - # Paid for a CALL or SUICIDE operation which creates an account. + # Paid for a CALL or SELFDESTRUCT operation which creates an account. @g_newaccount 25000 # Partial payment for an EXP operation. @g_exp 10 @@ -81,7 +81,7 @@ defmodule EVM.Gas do # Payment for BLOCKHASH operation @g_blockhash 20 - @w_zero_instr [:stop, :return, :suicide] + @w_zero_instr [:stop, :return, :selfdestruct] @w_base_instr [ :address, :origin, diff --git a/apps/evm/lib/evm/interface/account_interface.ex b/apps/evm/lib/evm/interface/account_interface.ex index 5b8b6649b..f2804e226 100644 --- a/apps/evm/lib/evm/interface/account_interface.ex +++ b/apps/evm/lib/evm/interface/account_interface.ex @@ -35,8 +35,8 @@ defprotocol EVM.Interface.AccountInterface do @spec put_storage(t, EVM.address(), integer(), integer()) :: t def put_storage(t, address, key, value) - @spec suicide_account(t, EVM.address()) :: t - def suicide_account(t, address) + @spec destroy_account(t, EVM.address()) :: t + def destroy_account(t, address) @spec dump_storage(t) :: %{EVM.address() => EVM.val()} def dump_storage(t) diff --git a/apps/evm/lib/evm/interface/mock/mock_account_interface.ex b/apps/evm/lib/evm/interface/mock/mock_account_interface.ex index 9df583553..3a6aa07df 100644 --- a/apps/evm/lib/evm/interface/mock/mock_account_interface.ex +++ b/apps/evm/lib/evm/interface/mock/mock_account_interface.ex @@ -103,9 +103,7 @@ defimpl EVM.Interface.AccountInterface, for: EVM.Interface.Mock.MockAccountInter if account do update_storage(account, key, value) else - new_account(%{ - storage: %{key => value} - }) + new_account(%{storage: %{key => value}}) end put_account(mock_account_interface, address, account) @@ -116,26 +114,24 @@ defimpl EVM.Interface.AccountInterface, for: EVM.Interface.Mock.MockAccountInter end defp put_account(mock_account_interface, address, account) do - %{ - mock_account_interface - | account_map: Map.put(mock_account_interface.account_map, address, account) - } + account_map = Map.put(mock_account_interface.account_map, address, account) + %{mock_account_interface | account_map: account_map} end defp new_account(opts \\ %{}) do - Map.merge( - %{ - storage: %{}, - nonce: 0, - balance: 0 - }, - opts - ) + account = %{ + storage: %{}, + nonce: 0, + code: <<>>, + balance: 0 + } + + Map.merge(account, opts) end - @spec suicide_account(EVM.Interface.AccountInterface.t(), EVM.address()) :: + @spec destroy_account(EVM.Interface.AccountInterface.t(), EVM.address()) :: EVM.Interface.AccountInterface.t() - def suicide_account(mock_account_interface, address) do + def destroy_account(mock_account_interface, address) do account_map = mock_account_interface.account_map |> Map.delete(address) diff --git a/apps/evm/lib/evm/machine_code.ex b/apps/evm/lib/evm/machine_code.ex index 2b0f35203..4c6295e43 100644 --- a/apps/evm/lib/evm/machine_code.ex +++ b/apps/evm/lib/evm/machine_code.ex @@ -130,7 +130,7 @@ defmodule EVM.MachineCode do [:push1, 3, :push1, 5, :add, :return] iex> EVM.MachineCode.decompile(<<97, 0, 4, 128, 97, 0, 14, 96, 0, 57, 97, 0, 18, 86, 96, 0, 53, 255, 91, 96, 0, 243>>) - [:push2, 0, 4, :dup1, :push2, 0, 14, :push1, 0, :codecopy, :push2, 0, 18, :jump, :push1, 0, :calldataload, :suicide, :jumpdest, :push1, 0, :return] + [:push2, 0, 4, :dup1, :push2, 0, 14, :push1, 0, :codecopy, :push2, 0, 18, :jump, :push1, 0, :calldataload, :selfdestruct, :jumpdest, :push1, 0, :return] iex> EVM.MachineCode.decompile(<<>>) [] diff --git a/apps/evm/lib/evm/machine_state.ex b/apps/evm/lib/evm/machine_state.ex index 16cec67d6..9a47a0408 100644 --- a/apps/evm/lib/evm/machine_state.ex +++ b/apps/evm/lib/evm/machine_state.ex @@ -73,7 +73,7 @@ defmodule EVM.MachineState do end @doc """ - Pops n values off the stack + Pops n values off the stack. ## Examples diff --git a/apps/evm/lib/evm/operation.ex b/apps/evm/lib/evm/operation.ex index 7cf508d20..d9f5b415b 100644 --- a/apps/evm/lib/evm/operation.ex +++ b/apps/evm/lib/evm/operation.ex @@ -5,11 +5,7 @@ defmodule EVM.Operation do """ alias MathHelper - alias EVM.Helpers - alias EVM.ExecEnv - alias EVM.MachineState - alias EVM.Stack - alias EVM.SubState + alias EVM.{Helpers, ExecEnv, MachineState, Stack, SubState} alias EVM.Operation.Metadata.StopAndArithmetic, as: StopAndArithmeticMetadata alias EVM.Operation.Metadata.ComparisonAndBitwiseLogic, as: ComparisonAndBitwiseLogicMetadata alias EVM.Operation.Metadata.SHA3, as: SHA3Metadata diff --git a/apps/evm/lib/evm/operation/logging.ex b/apps/evm/lib/evm/operation/logging.ex index 141a78e2c..ac16df832 100644 --- a/apps/evm/lib/evm/operation/logging.ex +++ b/apps/evm/lib/evm/operation/logging.ex @@ -21,7 +21,7 @@ defmodule EVM.Operation.Logging do ...> program_counter: 40, ...> stack: [] ...> } - iex> sub_state = %EVM.SubState{logs: [], refund: 0, suicide_list: []} + iex> sub_state = %EVM.SubState{logs: [], refund: 0, selfdestruct_list: []} iex> vm_map = %{sub_state: sub_state, exec_env: env, machine_state: machine_state} iex> EVM.Operation.Logging.log0([0, 32], vm_map) %{ @@ -37,7 +37,7 @@ defmodule EVM.Operation.Logging do } ], refund: 0, - suicide_list: [] + selfdestruct_list: [] } } """ @@ -66,7 +66,7 @@ defmodule EVM.Operation.Logging do ...> program_counter: 40, ...> stack: [] ...> } - iex> sub_state = %EVM.SubState{logs: [], refund: 0, suicide_list: []} + iex> sub_state = %EVM.SubState{logs: [], refund: 0, selfdestruct_list: []} iex> vm_map = %{sub_state: sub_state, exec_env: env, machine_state: machine_state} iex> args = [0, 32, 115792089237316195423570985008687907853269984665640564039457584007913129639935] iex> EVM.Operation.Logging.log1(args, vm_map) @@ -83,7 +83,7 @@ defmodule EVM.Operation.Logging do } ], refund: 0, - suicide_list: [] + selfdestruct_list: [] } } """ @@ -112,7 +112,7 @@ defmodule EVM.Operation.Logging do ...> program_counter: 40, ...> stack: [] ...> } - iex> sub_state = %EVM.SubState{logs: [], refund: 0, suicide_list: []} + iex> sub_state = %EVM.SubState{logs: [], refund: 0, selfdestruct_list: []} iex> vm_map = %{sub_state: sub_state, exec_env: env, machine_state: machine_state} iex> args = [0, 32, ...> 115792089237316195423570985008687907853269984665640564039457584007913129639935, @@ -132,7 +132,7 @@ defmodule EVM.Operation.Logging do } ], refund: 0, - suicide_list: [] + selfdestruct_list: [] } } """ @@ -161,7 +161,7 @@ defmodule EVM.Operation.Logging do ...> program_counter: 40, ...> stack: [] ...> } - iex> sub_state = %EVM.SubState{logs: [], refund: 0, suicide_list: []} + iex> sub_state = %EVM.SubState{logs: [], refund: 0, selfdestruct_list: []} iex> vm_map = %{sub_state: sub_state, exec_env: env, machine_state: machine_state} iex> args = [1, 0, 0, 0, 0] iex> EVM.Operation.Logging.log3(args, vm_map) @@ -176,7 +176,7 @@ defmodule EVM.Operation.Logging do } ], refund: 0, - suicide_list: [] + selfdestruct_list: [] } } """ @@ -208,7 +208,7 @@ defmodule EVM.Operation.Logging do ...> program_counter: 40, ...> stack: [] ...> } - iex> sub_state = %EVM.SubState{logs: [], refund: 0, suicide_list: []} + iex> sub_state = %EVM.SubState{logs: [], refund: 0, selfdestruct_list: []} iex> vm_map = %{sub_state: sub_state, exec_env: env, machine_state: machine_state} iex> args = [115792089237316195423570985008687907853269984665640564039457584007913129639935, ...> 1, 0, 0, 0, 0] @@ -224,7 +224,7 @@ defmodule EVM.Operation.Logging do } ], refund: 0, - suicide_list: [] + selfdestruct_list: [] } } """ diff --git a/apps/evm/lib/evm/operation/metadata/system.ex b/apps/evm/lib/evm/operation/metadata/system.ex index 73953cc92..c41b2ac73 100644 --- a/apps/evm/lib/evm/operation/metadata/system.ex +++ b/apps/evm/lib/evm/operation/metadata/system.ex @@ -54,7 +54,7 @@ defmodule EVM.Operation.Metadata.System do %{ id: 0xFF, description: "Halt execution and register account for later deletion.", - sym: :suicide, + sym: :selfdestruct, input_count: 1, output_count: 0, group: :system diff --git a/apps/evm/lib/evm/operation/push.ex b/apps/evm/lib/evm/operation/push.ex index 498590c8c..f2bc5c7e1 100644 --- a/apps/evm/lib/evm/operation/push.ex +++ b/apps/evm/lib/evm/operation/push.ex @@ -29,7 +29,8 @@ defmodule EVM.Operation.Push do """ @spec push_n(integer(), Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() def push_n(n, _args, %{machine_state: machine_state, exec_env: %{machine_code: machine_code}}) do - EVM.Memory.read_zeroed_memory(machine_code, machine_state.program_counter + 1, n) + machine_code + |> EVM.Memory.read_zeroed_memory(machine_state.program_counter + 1, n) |> :binary.decode_unsigned() end end diff --git a/apps/evm/lib/evm/operation/system.ex b/apps/evm/lib/evm/operation/system.ex index 448a52387..f9a391152 100644 --- a/apps/evm/lib/evm/operation/system.ex +++ b/apps/evm/lib/evm/operation/system.ex @@ -116,7 +116,7 @@ defmodule EVM.Operation.System do exec_env: exec_env, machine_state: machine_state }) do - to = if is_number(to), do: Address.new(to), else: to + to = Address.new(to) {data, machine_state} = EVM.Memory.read(machine_state, in_offset, in_size) account_balance = @@ -215,19 +215,21 @@ defmodule EVM.Operation.System do @doc """ Halt execution and register account for later deletion. + Transfers `value` wei from callers account to the "refund account". + Address of the "refund account" is the first 20 bytes in the stack. - ## Examples - - iex> address = 0x0000000000000000000000000000000000000001 - iex> suicide_address = 0x0000000000000000000000000000000000000001 - iex> account_map = %{address => %{balance: 5_000, nonce: 5}} - iex> account_interface = EVM.Interface.Mock.MockAccountInterface.new(account_map) - iex> account_interface = EVM.Operation.System.suicide([suicide_address], %{stack: [], exec_env: %EVM.ExecEnv{address: address, account_interface: account_interface} })[:exec_env].account_interface - iex> account_interface |> EVM.Interface.AccountInterface.dump_storage |> Map.get(address) - nil + Defined as SELFDESTRUCT in the Yellow Paper. """ - @spec suicide(Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() - def suicide([_suicide_address], %{exec_env: exec_env}) do - %{exec_env: ExecEnv.suicide_account(exec_env)} + @spec selfdestruct(Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() + def selfdestruct([refund_address], %{exec_env: exec_env}) do + to = Address.new(refund_address) + balance = AccountInterface.get_account_balance(exec_env.account_interface, exec_env.address) + + new_exec_env = + exec_env + |> ExecEnv.transfer_wei_to(to, balance) + |> ExecEnv.destroy_account() + + %{exec_env: new_exec_env} end end diff --git a/apps/evm/lib/evm/sub_state.ex b/apps/evm/lib/evm/sub_state.ex index 06b0c2470..1c0e8d63f 100644 --- a/apps/evm/lib/evm/sub_state.ex +++ b/apps/evm/lib/evm/sub_state.ex @@ -6,16 +6,16 @@ defmodule EVM.SubState do alias EVM.{Operation, LogEntry} - defstruct suicide_list: [], + defstruct selfdestruct_list: [], logs: [], refund: 0 - @type suicide_list :: [EVM.address()] + @type selfdestruct_list :: [EVM.address()] @type logs :: [LogEntry.t()] @type refund :: EVM.Wei.t() @type t :: %__MODULE__{ - suicide_list: suicide_list, + selfdestruct_list: selfdestruct_list, logs: logs, refund: refund } @@ -25,7 +25,7 @@ defmodule EVM.SubState do ## Examples - iex> sub_state = %EVM.SubState{suicide_list: [], logs: [], refund: 0} + iex> sub_state = %EVM.SubState{selfdestruct_list: [], logs: [], refund: 0} iex> sub_state |> EVM.SubState.add_log(0, [1, 10, 12], "adsfa") %EVM.SubState{ logs: [ @@ -36,7 +36,7 @@ defmodule EVM.SubState do } ], refund: 0, - suicide_list: [] + selfdestruct_list: [] } """ @spec add_log(t(), EVM.address(), Operation.stack_args(), binary()) :: t() diff --git a/apps/evm/lib/evm/vm.ex b/apps/evm/lib/evm/vm.ex index 01661b642..3dcb8b787 100644 --- a/apps/evm/lib/evm/vm.ex +++ b/apps/evm/lib/evm/vm.ex @@ -9,8 +9,8 @@ defmodule EVM.VM do @type output :: binary() @doc """ - This function computes the Ξ function Eq.(116) of the Section 9.4 of the Yellow Paper. This is the complete - result of running a given program in the VM. + This function computes the Ξ function Eq.(123) of the Section 9.4 of the Yellow Paper. + This is the complete result of running a given program in the VM. Note: We replace returning state with exec env, which in our implementation contains the world state. @@ -21,8 +21,8 @@ defmodule EVM.VM do {0, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 32, :push1, 0, :return])}, <<0x08::256>>} # Program with implicit stop - iex> EVM.VM.run(9, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])}) - {0, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode. compile([:push1, 3, :push1, 5, :add])}, ""} + iex> EVM.VM.run(9, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push2, 0, 3, :push1, 5, :add])}) + {0, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push2, 0, 3, :push1, 5, :add])}, ""} # Program with explicit stop iex> EVM.VM.run(5, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :stop])}) @@ -55,7 +55,7 @@ defmodule EVM.VM do {%EVM.MachineState{program_counter: 6, gas: 0, last_return_data: 8, stack: [8]}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add])}, ""} iex> EVM.VM.exec(%EVM.MachineState{program_counter: 0, gas: 24, stack: []}, %EVM.SubState{}, %EVM.ExecEnv{machine_code: EVM.MachineCode.compile([:push1, 3, :push1, 5, :add, :push1, 0x00, :mstore, :push1, 32, :push1, 0, :return])}) - {%EVM.MachineState{active_words: 1, last_return_data: 0, memory: <<0x08::256>>, gas: 0, program_counter: 13, stack: []}, %EVM.SubState{logs: [], refund: 0, suicide_list: []}, %EVM.ExecEnv{machine_code: <<96, 3, 96, 5, 1, 96, 0, 82, 96, 32, 96, 0, 243>>}, <<8::256>>} + {%EVM.MachineState{active_words: 1, last_return_data: 0, memory: <<0x08::256>>, gas: 0, program_counter: 13, stack: []}, %EVM.SubState{logs: [], refund: 0, selfdestruct_list: []}, %EVM.ExecEnv{machine_code: <<96, 3, 96, 5, 1, 96, 0, 82, 96, 32, 96, 0, 243>>}, <<8::256>>} """ @spec exec(MachineState.t(), SubState.t(), ExecEnv.t()) :: {MachineState.t(), SubState.t(), ExecEnv.t(), output} @@ -102,8 +102,8 @@ defmodule EVM.VM do end @doc """ - Runs a single cycle of our VM returning the new state, defined as `O` - in the Yellow Paper, Eq.(131). + Runs a single cycle of our VM returning the new state, + defined as `O` in the Yellow Paper, Eq.(143). ## Examples @@ -118,11 +118,11 @@ defmodule EVM.VM do machine_state = MachineState.subtract_gas(machine_state, exec_env) - {machine_state, sub_state, exec_env} = + {n_machine_state, n_sub_state, n_exec_env} = Operation.run(operation, machine_state, sub_state, exec_env) - machine_state = MachineState.move_program_counter(machine_state, operation, inputs) + final_machine_state = MachineState.move_program_counter(n_machine_state, operation, inputs) - {machine_state, sub_state, exec_env} + {final_machine_state, n_sub_state, n_exec_env} end end diff --git a/apps/evm/test/evm/operation/system_test.exs b/apps/evm/test/evm/operation/system_test.exs index 46cef2bbc..4d1ca7f70 100644 --- a/apps/evm/test/evm/operation/system_test.exs +++ b/apps/evm/test/evm/operation/system_test.exs @@ -1,4 +1,24 @@ defmodule EVM.Operation.SystemTest do use ExUnit.Case, async: true doctest EVM.Operation.System + + alias EVM.{ExecEnv, Address, Operation} + alias EVM.Interface.Mock.MockAccountInterface + + describe "selfdestruct/2" do + test "transfers wei to refund account" do + selfdestruct_address = 0x0000000000000000000000000000000000000001 + refund_address = 0x0000000000000000000000000000000000000002 + account_map = %{selfdestruct_address => %{balance: 5_000, nonce: 5}} + account_interface = MockAccountInterface.new(account_map) + exec_env = %ExecEnv{address: selfdestruct_address, account_interface: account_interface} + vm_opts = %{stack: [], exec_env: exec_env} + new_exec_env = Operation.System.selfdestruct([refund_address], vm_opts)[:exec_env] + accounts = new_exec_env.account_interface.account_map + + expected_refund_account = %{balance: 5000, code: <<>>, nonce: 0, storage: %{}} + assert Map.get(accounts, Address.new(refund_address)) == expected_refund_account + assert Map.get(accounts, selfdestruct_address) == nil + end + end end diff --git a/apps/evm/test/evm/vm_test.exs b/apps/evm/test/evm/vm_test.exs index b1518a4de..24fb88a07 100644 --- a/apps/evm/test/evm/vm_test.exs +++ b/apps/evm/test/evm/vm_test.exs @@ -2,13 +2,12 @@ defmodule EVM.VMTest do use ExUnit.Case, async: true doctest EVM.VM - setup do - account_interface = EVM.Interface.Mock.MockAccountInterface.new() + alias EVM.{VM, ExecEnv, SubState, MachineCode} + alias EVM.Interface.Mock.MockAccountInterface - {:ok, - %{ - account_interface: account_interface - }} + setup do + account_interface = MockAccountInterface.new() + {:ok, %{account_interface: account_interface}} end test "simple program with return value", %{} do @@ -28,11 +27,13 @@ defmodule EVM.VMTest do :return ] - exec_env = %EVM.ExecEnv{machine_code: EVM.MachineCode.compile(instructions)} - result = EVM.VM.run(24, exec_env) + exec_env = %ExecEnv{machine_code: MachineCode.compile(instructions)} + result = VM.run(24, exec_env) - assert result == - {0, %EVM.SubState{logs: [], refund: 0, suicide_list: []}, exec_env, <<0x08::256>>} + expected_sub_state = %SubState{logs: [], refund: 0, selfdestruct_list: []} + expected = {0, expected_sub_state, exec_env, <<0x08::256>>} + + assert result == expected end test "simple program with block storage", %{account_interface: account_interface} do @@ -47,28 +48,28 @@ defmodule EVM.VMTest do :stop ] - exec_env = %EVM.ExecEnv{ - machine_code: EVM.MachineCode.compile(instructions), + exec_env = %ExecEnv{ + machine_code: MachineCode.compile(instructions), address: address, account_interface: account_interface } - result = EVM.VM.run(20006, exec_env) + result = VM.run(20006, exec_env) expected_account_state = %{ address => %{ balance: 0, nonce: 0, + code: <<>>, storage: %{5 => 3} } } - expected_account_interface = - EVM.Interface.Mock.MockAccountInterface.new(expected_account_state) - + expected_account_interface = MockAccountInterface.new(expected_account_state) expected_exec_env = Map.put(exec_env, :account_interface, expected_account_interface) + expected_sub_state = %SubState{logs: [], refund: 0, selfdestruct_list: []} - assert result == - {0, %EVM.SubState{logs: [], refund: 0, suicide_list: []}, expected_exec_env, ""} + expected = {0, expected_sub_state, expected_exec_env, ""} + assert result == expected end end diff --git a/apps/evm/test/evm_test.exs b/apps/evm/test/evm_test.exs index 4de37337a..d3b10863d 100644 --- a/apps/evm/test/evm_test.exs +++ b/apps/evm/test/evm_test.exs @@ -1,9 +1,7 @@ defmodule EvmTest do import ExthCrypto.Math, only: [hex_to_bin: 1, hex_to_int: 1] - alias MerklePatriciaTree.Trie alias ExthCrypto.Hash.Keccak - alias EVM.Interface.AccountInterface alias EVM.Interface.Mock.{MockAccountInterface, MockBlockInterface} use ExUnit.Case, async: true @@ -47,9 +45,19 @@ defmodule EvmTest do test "Ethereum Common Tests" do for {test_group_name, _test_group} <- @passing_tests_by_group do - for {_test_name, test} <- passing_tests(test_group_name) do + for {test_name, test} <- passing_tests(test_group_name) do {gas, sub_state, exec_env, _} = run_test(test) - assert_state(test, exec_env.account_interface) + + context = %{ + test_name: test_name, + account_interface: exec_env.account_interface, + addresses: %{ + pre: get_addresses(test, "pre"), + post: get_addresses(test, "post") + } + } + + assert_state(test, context) if test["gas"] do assert hex_to_int(test["gas"]) == gas @@ -65,36 +73,29 @@ defmodule EvmTest do defp run_test(test) do exec_env = get_exec_env(test) - EVM.VM.run(hex_to_int(test["exec"]["gas"]), exec_env) + gas = hex_to_int(test["exec"]["gas"]) + EVM.VM.run(gas, exec_env) end defp get_exec_env(test) do %EVM.ExecEnv{ account_interface: account_interface(test), - address: hex_to_int(test["exec"]["address"]), + address: hex_to_bin(test["exec"]["address"]), block_interface: block_interface(test), data: hex_to_bin(test["exec"]["data"]), gas_price: hex_to_bin(test["exec"]["gasPrice"]), machine_code: hex_to_bin(test["exec"]["code"]), originator: hex_to_bin(test["exec"]["origin"]), - sender: hex_to_int(test["exec"]["caller"]), + sender: hex_to_bin(test["exec"]["caller"]), value_in_wei: hex_to_bin(test["exec"]["value"]) } end - def account_storage(storage, db) do - Enum.reduce(storage, Trie.new(db), fn {k, v}, trie -> - key = <> - value = <> - Trie.update(trie, key, value) - end) - end - def account_interface(test) do account_map = %{ - hex_to_int(test["exec"]["caller"]) => %{ + hex_to_bin(test["exec"]["caller"]) => %{ balance: 0, - code: hex_to_bin(test["exec"]["code"]), + code: <<>>, nonce: 0, storage: %{} } @@ -109,7 +110,7 @@ defmodule EvmTest do end) Map.merge(address_map, %{ - hex_to_int(address) => %{ + hex_to_bin(address) => %{ balance: hex_to_int(account["balance"]), code: hex_to_bin(account["code"]), nonce: hex_to_int(account["nonce"]), @@ -154,7 +155,7 @@ defmodule EvmTest do last_block_header = %Block.Header{ number: hex_to_int(test["env"]["currentNumber"]), timestamp: hex_to_int(test["env"]["currentTimestamp"]), - beneficiary: hex_to_int(test["env"]["currentCoinbase"]), + beneficiary: hex_to_bin(test["env"]["currentCoinbase"]), mix_hash: 0, parent_hash: hex_to_int(test["env"]["currentNumber"]) - 1, gas_limit: hex_to_int(test["env"]["currentGasLimit"]), @@ -212,13 +213,15 @@ defmodule EvmTest do "#{@ethereum_common_tests_path}/VMTests/vm#{Macro.camelize(Atom.to_string(group))}/#{name}.json" end - def assert_state(test, mock_account_interface) do + def assert_state(test, context) do if Map.get(test, "post") do - assert expected_state(test) == actual_state(mock_account_interface) + expected = expected_state(test, context) + actual = actual_state(test, context) + assert expected == actual end end - def expected_state(test) do + defp expected_state(test, _context) do post = Map.get(test, "post", %{}) for {address, account_state} <- post, into: %{} do @@ -226,32 +229,54 @@ defmodule EvmTest do storage = for {key, value} <- storage, into: %{} do - {hex_to_bin(key), hex_to_bin(value)} + {hex_to_bin(key), hex_to_int(value)} end - {hex_to_int(address), storage} + account = %{ + storage: storage, + balance: hex_to_int(account_state["balance"]), + code: hex_to_bin(account_state["code"]), + nonce: hex_to_int(account_state["nonce"]) + } + + {hex_to_bin(address), account} end - |> Enum.reject(fn {_key, value} -> value == %{} end) |> Enum.into(%{}) end - def actual_state(mock_account_interface) do - mock_account_interface - |> AccountInterface.dump_storage() - |> Enum.reject(fn {_key, value} -> value == %{} end) - |> Enum.into(%{}, fn {address, storage} -> + defp actual_state(test, context) do + caller = hex_to_bin(test["exec"]["caller"]) + # exclude caller's account from the actual state + # if it isn't in the "pre" or "post" addresses + context.account_interface.account_map + |> Enum.reject(fn {address, _} -> + address == caller && !Enum.member?(context.addresses.pre, address) && + !Enum.member?(context.addresses.post, address) + end) + |> Enum.into(%{}, fn {address, account_state} -> storage = - Enum.into(storage, %{}, fn {key, value} -> - {:binary.encode_unsigned(key), :binary.encode_unsigned(value)} + account_state[:storage] + |> Enum.reject(fn {_key, value} -> value == 0 end) + |> Enum.into(%{}, fn {key, value} -> + {:binary.encode_unsigned(key), value} end) - {address, storage} + account = %{account_state | storage: storage} + + {address, account} end) end - def logs_hash(logs) do + defp logs_hash(logs) do logs |> ExRLP.encode() |> Keccak.kec() end + + defp get_addresses(test, state_key) do + test + |> Map.get(state_key, %{}) + |> Map.keys() + |> Enum.map(&hex_to_bin/1) + end end diff --git a/apps/exth_crypto/lib/math.ex b/apps/exth_crypto/lib/math.ex index cecfc4eeb..0d7baddaa 100644 --- a/apps/exth_crypto/lib/math.ex +++ b/apps/exth_crypto/lib/math.ex @@ -31,10 +31,13 @@ defmodule ExthCrypto.Math do iex> ExthCrypto.Math.hex_to_bin("01020a0d") <<0x01, 0x02, 0x0a, 0x0d>> + iex> ExthCrypto.Math.hex_to_bin("01020a0D") <<0x01, 0x02, 0x0a, 0x0d>> + iex> ExthCrypto.Math.hex_to_bin("0x01020a0d") <<0x01, 0x02, 0x0a, 0x0d>> + iex> ExthCrypto.Math.hex_to_bin("0x01020A0d") <<0x01, 0x02, 0x0a, 0x0d>> """ @@ -49,10 +52,13 @@ defmodule ExthCrypto.Math do ## Examples iex> ExthCrypto.Math.hex_to_int("01020a0d") 16910861 + iex> ExthCrypto.Math.hex_to_int("01020a0D") 16910861 + iex> ExthCrypto.Math.hex_to_int("0x01020a0d") 16910861 + iex> ExthCrypto.Math.hex_to_int("0x01020A0d") 16910861 """