Class: Ethereum::VM

Inherits:
Object show all
Includes:
Constant
Defined in:
lib/ethereum/vm.rb,
lib/ethereum/vm/state.rb,
lib/ethereum/vm/message.rb,
lib/ethereum/vm/call_data.rb

Defined Under Namespace

Classes: CallData, Message, State

Constant Summary

Constants included from Constant

Constant::BYTE_EMPTY, Constant::BYTE_ONE, Constant::BYTE_ZERO, Constant::HASH_ZERO, Constant::INT_MAX, Constant::INT_MIN, Constant::PRIVKEY_ZERO, Constant::PRIVKEY_ZERO_HEX, Constant::PUBKEY_ZERO, Constant::TT256, Constant::TT32, Constant::TT64M1, Constant::UINT_MAX, Constant::UINT_MIN

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.code_cacheObject



11
12
13
# File 'lib/ethereum/vm.rb', line 11

def code_cache
  @code_cache ||= {}
end

.execute(*args) ⇒ Object



15
16
17
# File 'lib/ethereum/vm.rb', line 15

def execute(*args)
  new.execute(*args)
end

Instance Method Details

#execute(ext, msg, code) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/ethereum/vm.rb', line 22

def execute(ext, msg, code)
  s = State.new gas: msg.gas

  if VM.code_cache.has_key?(code)
    processed_code = VM.code_cache[code]
  else
    processed_code = preprocess_code code
    VM.code_cache[code] = processed_code
  end

  # for trace only
  steps = 0
  _prevop = nil

  timestamp = Time.now
  loop do
    return peaceful_exit('CODE OUT OF RANGE', s.gas, []) if s.pc >= processed_code.size

    op, in_args, out_args, fee, opcode, pushval = processed_code[s.pc]

    return vm_exception('OUT OF GAS') if fee > s.gas
    return vm_exception('INSUFFICIENT STACK', op: op, needed: in_args, available: s.stack.size) if in_args > s.stack.size
    return vm_exception('STACK SIZE LIMIT EXCEEDED', op: op, pre_height: s.stack.size) if (s.stack.size - in_args + out_args) > 1024

    s.gas -= fee
    s.pc += 1

    # This diverges from normal logging, as we use the logging namespace
    # only to decide which features get logged in 'eth.vm.op', i.e.
    # tracing can not be activated by activating a sub like
    # 'eth.vm.op.stack'.
    if Logger.trace?(log_vm_exit.name)
      trace_data = {
        stack: s.stack.map(&:to_s),
        gas: s.gas + fee,
        inst: opcode,
        pc: s.pc-1,
        op: op,
        steps: steps
      }

      if %i(MLOAD MSTORE MSTORE8 SHA3 CALL CALLCODE CREATE CALLDATACOPY CODECOPY EXTCODECOPY).include?(_prevop)
        if s.memory.size < 1024
          trace_data[:memory] = Utils.encode_hex(Utils.int_array_to_bytes(s.memory))
        else
          trace_data[:sha3memory] = Utils.encode_hex(Utils.keccak256(Utils.int_array_to_bytes(s.memory)))
        end
      end

      if %i(SSTORE SLOAD).include?(_prevop) || steps == 0
        trace_data[:storage] = ext.log_storage(msg.to)
      end

      if steps == 0
        trace_data[:depth] = msg.depth
        trace_data[:address] = msg.to
      end

      if op[0,4] == 'PUSH'
        trace_data[:pushvalue] = pushval
      end

      log_vm_op.trace('vm', **trace_data)

      steps += 1
      _prevop = op
    end

    # Invalid operation
    return vm_exception('INVALID OP', opcode: opcode) if op == :INVALID

    # Valid operations
    stk = s.stack
    mem = s.memory
    if opcode < 0x10 # Stop & Arithmetic Operations
      case op
      when :STOP
        return peaceful_exit('STOP', s.gas, [])
      when :ADD
        r = (stk.pop + stk.pop) & UINT_MAX
        stk.push r
      when :SUB
        r = (stk.pop - stk.pop) & UINT_MAX
        stk.push r
      when :MUL
        r = (stk.pop * stk.pop) & UINT_MAX
        stk.push r
      when :DIV
        s0, s1 = stk.pop, stk.pop
        stk.push(s1 == 0 ? 0 : s0 / s1)
      when :MOD
        s0, s1 = stk.pop, stk.pop
        stk.push(s1 == 0 ? 0 : s0 % s1)
      when :SDIV
        s0, s1 = Utils.to_signed(stk.pop), Utils.to_signed(stk.pop)
        r = s1 == 0 ? 0 : ((s0.abs / s1.abs * (s0*s1 < 0 ? -1 : 1)) & UINT_MAX)
        stk.push r
      when :SMOD
        s0, s1 = Utils.to_signed(stk.pop), Utils.to_signed(stk.pop)
        r = s1 == 0 ? 0 : ((s0.abs % s1.abs * (s0 < 0 ? -1 : 1)) & UINT_MAX)
        stk.push r
      when :ADDMOD
        s0, s1, s2 = stk.pop, stk.pop, stk.pop
        r = s2 == 0 ? 0 : (s0+s1) % s2
        stk.push r
      when :MULMOD
        s0, s1, s2 = stk.pop, stk.pop, stk.pop
        r = s2 == 0 ? 0 : Utils.mod_mul(s0, s1, s2)
        stk.push r
      when :EXP
        base, exponent = stk.pop, stk.pop

        # fee for exponent is dependent on its bytes
        # calc n bytes to represent exponent
        nbytes = Utils.encode_int(exponent).size
        expfee = nbytes * Opcodes::GEXPONENTBYTE
        if s.gas < expfee
          s.gas = 0
          return vm_exception('OOG EXPONENT')
        end

        s.gas -= expfee
        stk.push Utils.mod_exp(base, exponent, TT256)
      when :SIGNEXTEND # extend sign from bytes at s0 to left
        s0, s1 = stk.pop, stk.pop
        if s0 < 32
          testbit = s0*8 + 7
          mask = 1 << testbit
          if s1 & mask == 0 # extend 0s
            stk.push(s1 & (mask - 1))
          else # extend 1s
            stk.push(s1 | (TT256 - mask))
          end
        else
          stk.push s1
        end
      end
    elsif opcode < 0x20 # Comparison & Bitwise Logic Operations
      case op
      when :LT
        s0, s1 = stk.pop, stk.pop
        stk.push(s0 < s1 ? 1 : 0)
      when :GT
        s0, s1 = stk.pop, stk.pop
        stk.push(s0 > s1 ? 1 : 0)
      when :SLT
        s0, s1 = Utils.to_signed(stk.pop), Utils.to_signed(stk.pop)
        stk.push(s0 < s1 ? 1 : 0)
      when :SGT
        s0, s1 = Utils.to_signed(stk.pop), Utils.to_signed(stk.pop)
        stk.push(s0 > s1 ? 1 : 0)
      when :EQ
        s0, s1 = stk.pop, stk.pop
        stk.push(s0 == s1 ? 1 : 0)
      when :ISZERO
        s0 = stk.pop
        stk.push(s0 == 0 ? 1 : 0)
      when :AND
        s0, s1 = stk.pop, stk.pop
        stk.push(s0 & s1)
      when :OR
        s0, s1 = stk.pop, stk.pop
        stk.push(s0 | s1)
      when :XOR
        s0, s1 = stk.pop, stk.pop
        stk.push(s0 ^ s1)
      when :NOT
        s0 = stk.pop
        stk.push(UINT_MAX - s0)
      when :BYTE
        s0, s1 = stk.pop, stk.pop
        if s0 < 32
          stk.push((s1 / 256**(31-s0)) % 256)
        else
          stk.push(0)
        end
      end
    elsif opcode < 0x40 # SHA3 & Environmental Information
      case op
      when :SHA3
        s0, s1 = stk.pop, stk.pop

        s.gas -= Opcodes::GSHA3WORD * (Utils.ceil32(s1) / 32)
        return vm_exception('OOG PAYING FOR SHA3') if s.gas < 0

        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, s0, s1)

        data = Utils.int_array_to_bytes mem.safe_slice(s0,s1)
        stk.push Utils.big_endian_to_int(Utils.keccak256(data))
      when :ADDRESS
        stk.push Utils.coerce_to_int(msg.to)
      when :BALANCE
        s0 = stk.pop
        addr = Utils.coerce_addr_to_hex(s0 % 2**160)
        stk.push ext.get_balance(addr)
      when :ORIGIN
        stk.push Utils.coerce_to_int(ext.tx_origin)
      when :CALLER
        stk.push Utils.coerce_to_int(msg.sender)
      when :CALLVALUE
        stk.push msg.value
      when :CALLDATALOAD
        stk.push msg.data.extract32(stk.pop)
      when :CALLDATASIZE
        stk.push msg.data.size
      when :CALLDATACOPY
        mstart, dstart, size = stk.pop, stk.pop, stk.pop

        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, mstart, size)
        return vm_exception('OOG COPY DATA') unless data_copy(s, size)

        msg.data.extract_copy(mem, mstart, dstart, size)
      when :CODESIZE
        stk.push processed_code.size
      when :CODECOPY
        mstart, cstart, size = stk.pop, stk.pop, stk.pop

        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, mstart, size)
        return vm_exception('OOG COPY CODE') unless data_copy(s, size)

        size.times do |i|
          if cstart + i < processed_code.size
            mem[mstart+i] = processed_code[cstart+i][4] # copy opcode
          else
            mem[mstart+i] = 0
          end
        end
      when :GASPRICE
        stk.push ext.tx_gasprice
      when :EXTCODESIZE
        addr = stk.pop
        addr = Utils.coerce_addr_to_hex(addr % 2**160)
        stk.push (ext.get_code(addr) || Constant::BYTE_EMPTY).size
      when :EXTCODECOPY
        addr, mstart, cstart, size = stk.pop, stk.pop, stk.pop, stk.pop
        addr = Utils.coerce_addr_to_hex(addr % 2**160)
        extcode = ext.get_code(addr) || Constant::BYTE_EMPTY
        raise ValueError, "extcode must be string" unless extcode.is_a?(String)

        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, mstart, size)
        return vm_exception('OOG COPY CODE') unless data_copy(s, size)

        size.times do |i|
          if cstart + i < extcode.size
            mem[mstart+i] = extcode[cstart+i].ord
          else
            mem[mstart+i] = 0
          end
        end
      end
    elsif opcode < 0x50 # Block Information
      case op
      when :BLOCKHASH
        s0 = stk.pop
        stk.push Utils.big_endian_to_int(ext.block_hash(s0))
      when :COINBASE
        stk.push Utils.big_endian_to_int(ext.block_coinbase)
      when :TIMESTAMP
        stk.push ext.block_timestamp
      when :NUMBER
        stk.push ext.block_number
      when :DIFFICULTY
        stk.push ext.block_difficulty
      when :GASLIMIT
        stk.push ext.block_gas_limit
      end
    elsif opcode < 0x60 # Stack, Memory, Storage and Flow Operations
      case op
      when :POP
        stk.pop
      when :MLOAD
        s0 = stk.pop
        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, s0, 32)

        data = Utils.int_array_to_bytes mem.safe_slice(s0, 32)
        stk.push Utils.big_endian_to_int(data)
      when :MSTORE
        s0, s1 = stk.pop, stk.pop
        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, s0, 32)

        32.times.to_a.reverse.each do |i|
          mem[s0+i] = s1 % 256
          s1 /= 256
        end
      when :MSTORE8
        s0, s1 = stk.pop, stk.pop
        return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, s0, 1)
        mem[s0] = s1 % 256
      when :SLOAD
        s0 = stk.pop
        stk.push ext.get_storage_data(msg.to, s0)
      when :SSTORE
        s0, s1 = stk.pop, stk.pop

        if ext.get_storage_data(msg.to, s0) != 0
          gascost = s1 == 0 ? Opcodes::GSTORAGEKILL : Opcodes::GSTORAGEMOD
          refund = s1 == 0 ? Opcodes::GSTORAGEREFUND : 0
        else
          gascost = s1 == 0 ? Opcodes::GSTORAGEMOD : Opcodes::GSTORAGEADD
          refund = 0
        end

        return vm_exception('OUT OF GAS') if s.gas < gascost

        s.gas -= gascost
        ext.add_refund refund
        ext.set_storage_data msg.to, s0, s1
      when :JUMP
        s0 = stk.pop
        s.pc = s0

        op_new = s.pc < processed_code.size ? processed_code[s.pc][0] : :STOP
        return vm_exception('BAD JUMPDEST') if op_new != :JUMPDEST
      when :JUMPI
        s0, s1 = stk.pop, stk.pop
        if s1 != 0
          s.pc = s0
          op_new = s.pc < processed_code.size ? processed_code[s.pc][0] : :STOP
          return vm_exception('BAD JUMPDEST') if op_new != :JUMPDEST
        end
      when :PC
        stk.push(s.pc - 1)
      when :MSIZE
        stk.push mem.size
      when :GAS
        stk.push s.gas # AFTER subtracting cost 1
      end
    elsif op[0,Opcodes::PREFIX_PUSH.size] == Opcodes::PREFIX_PUSH
      pushnum = op[Opcodes::PREFIX_PUSH.size..-1].to_i
      s.pc += pushnum
      stk.push pushval
    elsif op[0,Opcodes::PREFIX_DUP.size] == Opcodes::PREFIX_DUP
      depth = op[Opcodes::PREFIX_DUP.size..-1].to_i
      stk.push stk[-depth]
    elsif op[0,Opcodes::PREFIX_SWAP.size] == Opcodes::PREFIX_SWAP
      depth = op[Opcodes::PREFIX_SWAP.size..-1].to_i
      temp = stk[-depth - 1]
      stk[-depth - 1] = stk[-1]
      stk[-1] = temp
    elsif op[0,Opcodes::PREFIX_LOG.size] == Opcodes::PREFIX_LOG
      # 0xa0 ... 0xa4, 32/64/96/128/160 + data.size gas
      #
      # a. Opcodes LOG0...LOG4 are added, takes 2-6 stake arguments:
      #      MEMSTART MEMSZ (TOPIC1) (TOPIC2) (TOPIC3) (TOPIC4)
      #
      # b. Logs are kept track of during tx execution exactly the same way
      #    as suicides (except as an ordered list, not a set).
      #
      #    Each log is in the form [address, [topic1, ... ], data] where:
      #    * address is what the ADDRESS opcode would output
      #    * data is mem[MEMSTART, MEMSZ]
      #    * topics are as provided by the opcode
      #
      # c. The ordered list of logs in the transation are expreseed as
      #    [log0, log1, ..., logN].
      #
      depth = op[Opcodes::PREFIX_LOG.size..-1].to_i
      mstart, msz = stk.pop, stk.pop
      topics = depth.times.map {|i| stk.pop }

      s.gas -= msz * Opcodes::GLOGBYTE

      return vm_exception("OOG EXTENDING MEMORY") unless mem_extend(mem, s, mstart, msz)

      data = mem.safe_slice(mstart, msz)
      ext.log(msg.to, topics, Utils.int_array_to_bytes(data))
      log_log.trace('LOG', to: msg.to, topics: topics, data: data)
    elsif op == :CREATE
      value, mstart, msz = stk.pop, stk.pop, stk.pop

      return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, mstart, msz)

      if ext.get_balance(msg.to) >= value && msg.depth < 1024
        cd = CallData.new mem, mstart, msz
        create_msg = Message.new(msg.to, Constant::BYTE_EMPTY, value, s.gas, cd, depth: msg.depth+1)

        o, gas, addr = ext.create create_msg
        if o.true?
          stk.push Utils.coerce_to_int(addr)
          s.gas = gas
        else
          stk.push 0
          s.gas = 0
        end
      else
        stk.push(0)
      end
    elsif op == :CALL
      gas, to, value, memin_start, memin_sz, memout_start, memout_sz = \
        stk.pop, stk.pop, stk.pop, stk.pop, stk.pop, stk.pop, stk.pop

      return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, memin_start, memin_sz)
      return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, memout_start, memout_sz)

      to = Utils.zpad_int(to)[12..-1] # last 20 bytes
      extra_gas = (ext.(to) ? 0 : 1) * Opcodes::GCALLNEWACCOUNT +
        (value > 0 ? 1 : 0) * Opcodes::GCALLVALUETRANSFER
      submsg_gas = gas + Opcodes::GSTIPEND * (value > 0 ? 1 : 0)
      total_gas = gas + extra_gas

      return vm_exception('OUT OF GAS', needed: total_gas) if s.gas < total_gas

      if ext.get_balance(msg.to) >= value && msg.depth < 1024
        s.gas -= total_gas

        cd = CallData.new mem, memin_start, memin_sz
        call_msg = Message.new(msg.to, to, value, submsg_gas, cd, depth: msg.depth+1, code_address: to)

        result, gas, data = ext.apply_msg call_msg
        if result == 0
          stk.push 0
        else
          stk.push 1
          s.gas += gas
          [data.size, memout_sz].min.times do |i|
            mem[memout_start+i] = data[i]
          end
        end
      else
        s.gas -= (total_gas - submsg_gas)
        stk.push(0)
      end
    elsif op == :CALLCODE || op == :DELEGATECALL
      if op == :CALLCODE
        gas, to, value, memin_start, memin_sz, memout_start, memout_sz = \
          stk.pop, stk.pop, stk.pop, stk.pop, stk.pop, stk.pop, stk.pop
      else
        gas, to, memin_start, memin_sz, memout_start, memout_sz = \
          stk.pop, stk.pop, stk.pop, stk.pop, stk.pop, stk.pop
        value = 0
      end

      return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, memin_start, memin_sz)
      return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, memout_start, memout_sz)

      extra_gas = (value > 0 ? 1 : 0) * Opcodes::GCALLVALUETRANSFER
      submsg_gas = gas + Opcodes::GSTIPEND * (value > 0 ? 1 : 0)
      total_gas = gas + extra_gas

      return vm_exception('OUT OF GAS', needed: total_gas) if s.gas < total_gas

      if ext.get_balance(msg.to) >= value && msg.depth < 1024
        s.gas -= total_gas

        to = Utils.zpad_int(to)[12..-1] # last 20 bytes
        cd = CallData.new mem, memin_start, memin_sz

        if ext.post_homestead_hardfork && op == :DELEGATECALL
          call_msg = Message.new(msg.sender, msg.to, msg.value, submsg_gas, cd, depth: msg.depth+1, code_address: to, transfers_value: false)
        elsif op == :DELEGATECALL
          return vm_exception('OPCODE INACTIVE')
        else
          call_msg = Message.new(msg.to, msg.to, value, submsg_gas, cd, depth: msg.depth+1, code_address: to)
        end

        result, gas, data = ext.apply_msg call_msg
        if result == 0
          stk.push 0
        else
          stk.push 1
          s.gas += gas
          [data.size, memout_sz].min.times do |i|
            mem[memout_start+i] = data[i]
          end
        end
      else
        s.gas -= (total_gas - submsg_gas)
        stk.push(0)
      end
    elsif op == :RETURN
      s0, s1 = stk.pop, stk.pop
      return vm_exception('OOG EXTENDING MEMORY') unless mem_extend(mem, s, s0, s1)
      return peaceful_exit('RETURN', s.gas, mem.safe_slice(s0, s1))
    elsif op == :SUICIDE
      s0 = stk.pop
      to = Utils.zpad_int(s0)[12..-1] # last 20 bytes

      xfer = ext.get_balance(msg.to)
      ext.set_balance(to, ext.get_balance(to)+xfer)
      ext.set_balance(msg.to, 0)
      ext.add_suicide(msg.to)

      return 1, s.gas, []
    end
  end
end

#preprocess_code(code) ⇒ Object

Preprocesses code, and determines which locations are in the middle of pushdata and thus invalid



511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/ethereum/vm.rb', line 511

def preprocess_code(code)
  code = Utils.bytes_to_int_array code
  ops = []

  i = 0
  while i < code.size
    o = Opcodes::TABLE.fetch(code[i], [:INVALID, 0, 0, 0]) + [code[i], 0]
    ops.push o

    if o[0][0,Opcodes::PREFIX_PUSH.size] == Opcodes::PREFIX_PUSH
      n = o[0][Opcodes::PREFIX_PUSH.size..-1].to_i
      n.times do |j|
        i += 1
        byte = i < code.size ? code[i] : 0
        o[-1] = (o[-1] << 8) + byte

        # polyfill, these INVALID ops will be skipped in execution
        ops.push [:INVALID, 0, 0, 0, byte, 0] if i < code.size
      end
    end

    i += 1
  end

  ops
end