defmodule EVM.Operation.System do alias EVM.MachineState alias EVM.ExecEnv alias EVM.Interface.AccountInterface alias EVM.Interface.BlockInterface alias EVM.Helpers alias EVM.Address alias EVM.Stack @doc """ Create a new account with associated code. ## Examples iex> block_interface = EVM.Interface.Mock.MockBlockInterface.new(%Block.Header{}) iex> account_map = %{<<100::160>> => %{balance: 5_000, nonce: 5}} iex> account_interface = EVM.Interface.Mock.MockAccountInterface.new(account_map, %{gas: 500, sub_state: nil, output: "output"}) iex> exec_env = %EVM.ExecEnv{stack_depth: 0, address: <<100::160>>, account_interface: account_interface, block_interface: block_interface} iex> machine_state = %EVM.MachineState{gas: 300, stack: [1], memory: "________" <> "input"} iex> %{machine_state: n_machine_state} = ...> EVM.Operation.System.create( ...> [1_000, 5, 5], ...> %{exec_env: exec_env, machine_state: machine_state}) iex> n_machine_state %EVM.MachineState{gas: 800, stack: [0x601bcc2189b7096d8dfaa6f74efeebef20486d0d, 1], active_words: 1, memory: "________input"} """ @spec create(Operation.stack_args, Operation.vm_map) :: Operation.op_result def create([value, in_offset, in_size], %{exec_env: exec_env, machine_state: machine_state}) do {data, machine_state} = EVM.Memory.read(machine_state, in_offset, in_size) account_balance = AccountInterface.get_account_balance(exec_env.account_interface, exec_env.address) block_header = BlockInterface.get_block_header(exec_env.block_interface) is_allowed = value <= account_balance and exec_env.stack_depth < EVM.Functions.max_stack_depth { updated_account_interface, n_gas, _n_sub_state } = if is_allowed do available_gas = Helpers.all_but_one_64th(machine_state.gas) AccountInterface.create_contract( exec_env.account_interface, exec_env.address, # sender exec_env.originator, # originator available_gas, # available_gas exec_env.gas_price, # gas_price value, # endowment data, # init_code exec_env.stack_depth + 1, # stack_depth block_header) # block_header else { exec_env.account_interface, machine_state.gas, nil } end # Add back extra gas machine_state = %{machine_state | gas: machine_state.gas + n_gas} # Note if was exception halt or other failure on stack result = if is_allowed do nonce = exec_env.account_interface |> AccountInterface.get_account_nonce(exec_env.address) EVM.Address.new(exec_env.address, nonce) else 0 end machine_state = %{machine_state | stack: Stack.push(machine_state.stack, result)} exec_env = %{ exec_env | account_interface: updated_account_interface } %{ machine_state: machine_state, exec_env: exec_env # TODO: sub_state } end @doc """ Message-call into an account. Transfer `value` wei from callers account to callees account then run the code in that account. ## Examples iex> account_map = %{ ...> <<0::160>> => %{balance: 100, nonce: 5, code: <<>>}, ...> <<1::160>> => %{balance: 100, nonce: 5, code: <<>>}, ...> <<2::160>> => %{balance: 100, nonce: 5, code: <<>>}, ...> } iex> account_interface = EVM.Interface.Mock.MockAccountInterface.new(account_map) iex> exec_env = %EVM.ExecEnv{ ...> account_interface: account_interface, ...> sender: <<0::160>>, ...> address: <<0::160>> ...> } iex> machine_state = %EVM.MachineState{gas: 1000} iex> %{machine_state: machine_state, exec_env: exec_env} = ...> EVM.Operation.System.call([10, 1, 1, 0, 0, 0, 0], ...> %{exec_env: exec_env, machine_state: machine_state}) iex> EVM.Stack.peek(machine_state.stack) 1 iex> exec_env.account_interface ...> |> EVM.Interface.AccountInterface.get_account_balance(<<0::160>>) 99 iex> exec_env.account_interface ...> |> EVM.Interface.AccountInterface.get_account_balance(<<1::160>>) 101 """ @spec call(Operation.stack_args, Operation.vm_map) :: Operation.op_result def call([call_gas, to, value, in_offset, in_size, out_offset, _out_size], %{exec_env: exec_env, machine_state: machine_state}) do to = Address.new(to) { data, machine_state } = EVM.Memory.read(machine_state, in_offset, in_size) account_balance = AccountInterface.get_account_balance(exec_env.account_interface, exec_env.address) machine_code = AccountInterface.get_account_code(exec_env.account_interface, to) if call_gas <= account_balance && exec_env.stack_depth < EVM.Functions.max_stack_depth do exec_env = ExecEnv.tranfer_wei_to(exec_env, to, value) { n_gas, _n_sub_state, n_exec_env, n_output } = EVM.VM.run( call_gas, Map.merge(exec_env,%{ address: to, # a sender: exec_env.address, # s data: data, # d value_in_wei: value, # v machine_code: machine_code, # b stack_depth: exec_env.stack_depth + 1, # e }) ) exec_env = %{exec_env | account_interface: n_exec_env.account_interface} # TODO: Set n_account_interface machine_state = EVM.Memory.write(machine_state, out_offset, n_output) machine_state = %{machine_state | gas: machine_state.gas + n_gas} # Return 1: 1 = success, 0 = failure # TODO Check if the call was actually successful machine_state = %{machine_state | stack: Stack.push(machine_state.stack, 1)} %{ machine_state: machine_state, exec_env: exec_env, # TODO: sub_state } else %{ machine_state: %{machine_state | stack: Stack.push(machine_state.stack, 0)}, } end end @doc """ Halt execution returning output data, ## Examples iex> EVM.Operation.System.return([5, 33], %{machine_state: %EVM.MachineState{active_words: 0}}) %EVM.MachineState{active_words: 2} iex> EVM.Operation.System.return([5, 33], %{machine_state: %EVM.MachineState{active_words: 5}}) %EVM.MachineState{active_words: 5} """ @spec return(Operation.stack_args, Operation.vm_map) :: Operation.op_result def return([_mem_start, mem_end], %{machine_state: machine_state}) do # We may have to bump up number of active words machine_state |> MachineState.maybe_set_active_words(EVM.Memory.get_active_words(mem_end)) end @doc """ Halt execution and register account for later deletion. ## 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 """ @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)} end end