ciri 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -3
  3. data/README.md +5 -0
  4. data/Rakefile +24 -2
  5. data/ciri-rlp/.gitignore +11 -0
  6. data/ciri-rlp/.rspec +3 -0
  7. data/ciri-rlp/.travis.yml +5 -0
  8. data/ciri-rlp/CODE_OF_CONDUCT.md +74 -0
  9. data/ciri-rlp/Gemfile +6 -0
  10. data/ciri-rlp/Gemfile.lock +39 -0
  11. data/ciri-rlp/LICENSE.txt +21 -0
  12. data/ciri-rlp/README.md +98 -0
  13. data/ciri-rlp/Rakefile +6 -0
  14. data/ciri-rlp/bin/console +14 -0
  15. data/ciri-rlp/bin/setup +8 -0
  16. data/ciri-rlp/ciri-rlp.gemspec +28 -0
  17. data/{lib → ciri-rlp/lib}/ciri/rlp.rb +2 -2
  18. data/ciri-rlp/lib/ciri/rlp/decode.rb +144 -0
  19. data/ciri-rlp/lib/ciri/rlp/encode.rb +140 -0
  20. data/ciri-rlp/lib/ciri/rlp/serializable.rb +241 -0
  21. data/ciri-rlp/lib/ciri/rlp/version.rb +5 -0
  22. data/ciri-rlp/spec/ciri/fixture_spec.rb +95 -0
  23. data/ciri-rlp/spec/ciri/rlp/decode_spec.rb +68 -0
  24. data/ciri-rlp/spec/ciri/rlp/encode_spec.rb +72 -0
  25. data/ciri-rlp/spec/ciri/rlp/serializable_spec.rb +147 -0
  26. data/ciri-rlp/spec/ciri/rlp_spec.rb +5 -0
  27. data/ciri-rlp/spec/spec_helper.rb +14 -0
  28. data/ciri-utils/.gitignore +11 -0
  29. data/ciri-utils/.rspec +3 -0
  30. data/ciri-utils/.travis.yml +5 -0
  31. data/ciri-utils/CODE_OF_CONDUCT.md +74 -0
  32. data/ciri-utils/Gemfile +6 -0
  33. data/ciri-utils/Gemfile.lock +37 -0
  34. data/ciri-utils/LICENSE.txt +21 -0
  35. data/ciri-utils/README.md +61 -0
  36. data/ciri-utils/Rakefile +6 -0
  37. data/ciri-utils/bin/console +14 -0
  38. data/ciri-utils/bin/setup +8 -0
  39. data/ciri-utils/ciri-utils.gemspec +28 -0
  40. data/{lib → ciri-utils/lib}/ciri/utils.rb +9 -33
  41. data/{lib → ciri-utils/lib}/ciri/utils/logger.rb +0 -0
  42. data/{lib → ciri-utils/lib}/ciri/utils/number.rb +15 -12
  43. data/ciri-utils/lib/ciri/utils/version.rb +5 -0
  44. data/ciri-utils/spec/ciri/utils_spec.rb +5 -0
  45. data/ciri-utils/spec/spec_helper.rb +14 -0
  46. data/ciri.gemspec +6 -3
  47. data/lib/ciri/bloom_filter.rb +83 -0
  48. data/lib/ciri/chain.rb +67 -130
  49. data/lib/ciri/chain/header.rb +2 -2
  50. data/lib/ciri/chain/header_chain.rb +136 -0
  51. data/lib/ciri/chain/transaction.rb +1 -1
  52. data/lib/ciri/crypto.rb +2 -2
  53. data/lib/ciri/db/account_db.rb +145 -0
  54. data/lib/ciri/db/backend/memory.rb +24 -1
  55. data/lib/ciri/devp2p/peer.rb +1 -1
  56. data/lib/ciri/devp2p/rlpx/connection.rb +5 -5
  57. data/lib/ciri/eth/peer.rb +3 -3
  58. data/lib/ciri/eth/protocol_manage.rb +3 -3
  59. data/lib/ciri/eth/protocol_messages.rb +27 -26
  60. data/lib/ciri/ethash.rb +18 -3
  61. data/lib/ciri/evm.rb +101 -56
  62. data/lib/ciri/{utils/lib_c.rb → evm/errors.rb} +28 -18
  63. data/lib/ciri/evm/instruction.rb +3 -1
  64. data/lib/ciri/evm/{serialize.rb → log_entry.rb} +9 -27
  65. data/lib/ciri/evm/machine_state.rb +21 -2
  66. data/lib/ciri/evm/op.rb +19 -16
  67. data/lib/ciri/evm/state.rb +61 -0
  68. data/lib/ciri/evm/vm.rb +78 -102
  69. data/lib/ciri/forks.rb +1 -4
  70. data/lib/ciri/forks/base.rb +55 -0
  71. data/lib/ciri/forks/frontier.rb +37 -10
  72. data/lib/ciri/forks/frontier/cost.rb +186 -0
  73. data/lib/ciri/key.rb +1 -1
  74. data/lib/ciri/pow.rb +1 -1
  75. data/lib/ciri/rlp/decode.rb +6 -3
  76. data/lib/ciri/rlp/encode.rb +10 -10
  77. data/lib/ciri/rlp/serializable.rb +12 -9
  78. data/lib/ciri/serialize.rb +58 -0
  79. data/lib/ciri/trie.rb +362 -0
  80. data/lib/ciri/trie/nibbles.rb +97 -0
  81. data/lib/ciri/trie/nodes.rb +187 -0
  82. data/lib/ciri/{evm → types}/account.rb +20 -13
  83. data/lib/ciri/types/address.rb +16 -11
  84. data/lib/ciri/types/number.rb +73 -0
  85. data/lib/ciri/types/receipt.rb +57 -0
  86. data/lib/ciri/version.rb +1 -1
  87. metadata +82 -19
  88. data/lib/ciri/evm/forks/frontier.rb +0 -183
@@ -21,25 +21,35 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
 
24
- require 'ffi'
25
-
26
24
  module Ciri
27
- module Utils
28
-
29
- module LibC
30
- extend FFI::Library
31
- ffi_lib FFI::Library::LIBC
32
-
33
- # memory allocators
34
- attach_function :malloc, [:size_t], :pointer
35
- attach_function :calloc, [:size_t], :pointer
36
- attach_function :valloc, [:size_t], :pointer
37
- attach_function :realloc, [:pointer, :size_t], :pointer
38
- attach_function :free, [:pointer], :void
39
-
40
- # memory movers
41
- attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
42
- attach_function :bcopy, [:pointer, :pointer, :size_t], :void
25
+ class EVM
26
+
27
+ class Error < StandardError
28
+ end
29
+
30
+ class InvalidTransition < Error
31
+ end
32
+
33
+ class InvalidTransaction < Error
34
+ end
35
+
36
+ # VM errors
37
+ class VMError < Error
38
+ end
39
+
40
+ class InvalidOpCodeError < VMError
41
+ end
42
+
43
+ class GasNotEnoughError < VMError
44
+ end
45
+
46
+ class StackError < VMError
47
+ end
48
+
49
+ class InvalidJumpError < VMError
50
+ end
51
+
52
+ class ReturnError < VMError
43
53
  end
44
54
 
45
55
  end
@@ -37,7 +37,9 @@ module Ciri
37
37
  end
38
38
 
39
39
  def get_op(pos)
40
- return OP::INVALID if pos >= (bytes_code || ''.b).size
40
+ code_size = (bytes_code || ''.b).size
41
+ return OP::STOP if pos == code_size
42
+ return OP::INVALID if pos >= code_size
41
43
  bytes_code[pos].ord
42
44
  end
43
45
 
@@ -21,40 +21,22 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
 
24
+ require 'ciri/rlp'
25
+ require 'ciri/utils'
24
26
  require 'ciri/types/address'
27
+ require 'ciri/types/number'
25
28
 
26
29
  module Ciri
27
30
  class EVM
28
- module Serialize
29
31
 
30
- extend self
32
+ class LogEntry
33
+ include RLP::Serializable
34
+ schema [{address: Types::Address}, {topics: [Types::Int32]}, :data]
31
35
 
32
- def serialize(item)
33
- case item
34
- when Integer
35
- Utils.big_endian_encode(item)
36
- when Types::Address
37
- item.to_s
38
- else
39
- item
40
- end
36
+ def to_blooms
37
+ [address.to_s, *topics.map {|t| Utils.big_endian_encode(t, size: 32)}]
41
38
  end
42
-
43
- def deserialize(type, item)
44
- if type == Integer && !item.is_a?(Integer)
45
- Utils.big_endian_decode(item.to_s)
46
- elsif type == Types::Address && !item.is_a?(Types::Address)
47
- # check if address represent in Integer
48
- item = Utils.big_endian_encode(item) if item.is_a?(Integer)
49
- Types::Address.new(item.size >= 20 ? item[-20..-1] : ''.b)
50
- elsif type.nil?
51
- # get serialized word
52
- serialize(item).rjust(32, "\x00".b)
53
- else
54
- item
55
- end
56
- end
57
-
58
39
  end
40
+
59
41
  end
60
42
  end
@@ -21,13 +21,32 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
 
24
- require_relative 'serialize'
24
+ require 'ciri/serialize'
25
+ require 'ciri/evm/errors'
25
26
 
26
27
  module Ciri
27
28
  class EVM
28
29
 
29
30
  # represent current vm status, include stack, memory..
30
- MachineState = Struct.new(:gas_remain, :pc, :memory, :memory_item, :stack, :output, keyword_init: true) do
31
+ class MachineState
32
+
33
+ attr_reader :remain_gas, :memory, :stack
34
+ attr_accessor :pc, :output, :memory_item
35
+
36
+ def initialize(remain_gas:, pc:, memory:, memory_item:, stack:, output: ''.b)
37
+ raise ArgumentError.new("remain_gas must more than 0") if remain_gas < 0
38
+ @remain_gas = remain_gas
39
+ @pc = pc
40
+ @memory = memory
41
+ @memory_item = memory_item
42
+ @stack = stack
43
+ @output = output
44
+ end
45
+
46
+ def consume_gas(gas)
47
+ raise GasNotEnoughError.new("can't consume gas to negative, remain_gas: #{remain_gas}, consumed: #{gas}") if gas > remain_gas
48
+ @remain_gas -= gas
49
+ end
31
50
 
32
51
  # fetch a list of items from stack
33
52
  def pop_list(count, type = nil)
data/lib/ciri/evm/op.rb CHANGED
@@ -24,7 +24,6 @@
24
24
  require 'ciri/utils'
25
25
  require 'ciri/utils/number'
26
26
  require 'ciri/types/address'
27
- require_relative 'serialize'
28
27
 
29
28
  module Ciri
30
29
  class EVM
@@ -267,7 +266,7 @@ module Ciri
267
266
 
268
267
  def_op :EXTCODESIZE, 0x3b, 0, 1 do |vm|
269
268
  address = vm.pop(Address)
270
- code_size = vm.find_account(address).code&.size || 0
269
+ code_size = vm.get_account_code(address).size
271
270
  vm.push code_size
272
271
  end
273
272
 
@@ -275,8 +274,7 @@ module Ciri
275
274
  address = vm.pop(Address)
276
275
  mem_pos, data_pos, size = vm.pop_list(3, Integer)
277
276
 
278
- account = vm.find_account(address)
279
- code = account.code || ''.b
277
+ code = vm.get_account_code(address)
280
278
  data_end_pos = data_pos + size - 1
281
279
  data = if data_pos >= code.size
282
280
  ''.b
@@ -340,13 +338,13 @@ module Ciri
340
338
  end
341
339
 
342
340
  def_op :SLOAD, 0x54, 1, 1 do |vm|
343
- key = vm.pop
341
+ key = vm.pop(Integer)
344
342
  vm.push vm.fetch(vm.instruction.address, key)
345
343
  end
346
344
 
347
345
  def_op :SSTORE, 0x55, 2, 0 do |vm|
348
- key = vm.pop
349
- value = vm.pop
346
+ key = vm.pop(Integer)
347
+ value = vm.pop(Integer)
350
348
 
351
349
  vm.store(vm.instruction.address, key, value)
352
350
  end
@@ -374,7 +372,7 @@ module Ciri
374
372
  end
375
373
 
376
374
  def_op :GAS, 0x5a, 0, 1 do |vm|
377
- vm.push vm.machine_state.gas_remain
375
+ vm.push vm.machine_state.remain_gas
378
376
  end
379
377
 
380
378
  def_op :JUMPDEST, 0x5b, 0, 0
@@ -421,7 +419,7 @@ module Ciri
421
419
  pos, size = vm.pop_list(2, Integer)
422
420
  log_data = vm.memory_fetch(pos, size)
423
421
  vm.extend_memory(pos, size)
424
- topics = vm.pop_list(i)
422
+ topics = vm.pop_list(i, Integer)
425
423
  vm.add_log_entry(topics, log_data)
426
424
  end
427
425
  end.call(i))
@@ -435,7 +433,7 @@ module Ciri
435
433
  init = vm.memory_fetch(mem_pos, size)
436
434
  vm.extend_memory(mem_pos, size)
437
435
 
438
- contract_address = vm.create_contract(value: value, init: init)
436
+ contract_address, _ = vm.create_contract(value: value, init: init)
439
437
  vm.push contract_address
440
438
  end
441
439
 
@@ -504,7 +502,13 @@ module Ciri
504
502
  end
505
503
 
506
504
  STATICCALL = 0xfa
507
- REVERT = 0xfd
505
+
506
+ # TODO REVERT need to be implement in forks
507
+ def_op :REVERT, -0xfd, 2, 0 do |vm|
508
+ index, size = vm.pop_list(2, Integer)
509
+ vm.output = vm.memory_fetch(index, size)
510
+ vm.extend_memory(index, size)
511
+ end
508
512
 
509
513
  def_op :INVALID, 0xfe, 0, 0 do |vm|
510
514
  raise 'should not invoke INVALID'
@@ -514,7 +518,6 @@ module Ciri
514
518
  refund_address = vm.pop(Address)
515
519
  refund_account = vm.find_account(refund_address)
516
520
 
517
- vm.sub_state.suicide_accounts << vm.instruction.address
518
521
  contract_account = vm.find_account vm.instruction.address
519
522
 
520
523
  if refund_address != vm.instruction.address
@@ -523,12 +526,12 @@ module Ciri
523
526
 
524
527
  contract_account.balance = 0
525
528
 
526
- vm.update_account(refund_account)
527
- vm.update_account(contract_account)
529
+ vm.state.set_balance(refund_address, refund_account.balance)
530
+ vm.state.set_balance(vm.instruction.address, contract_account.balance)
528
531
 
529
532
  # register changed accounts
530
- vm.add_refund_account(refund_account)
531
- vm.add_suicide_account(contract_account)
533
+ vm.add_refund_account(refund_address)
534
+ vm.add_suicide_account(vm.instruction.address)
532
535
  end
533
536
 
534
537
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'forwardable'
25
+ require 'ciri/db/account_db'
26
+
27
+ module Ciri
28
+ class EVM
29
+ class State
30
+
31
+ extend Forwardable
32
+
33
+ def_delegators :@account_db, :set_nonce, :increment_nonce, :set_balance, :add_balance, :touch_account,
34
+ :find_account, :delete_account, :account_dead?, :store, :fetch, :set_account_code, :get_account_code
35
+
36
+ def initialize(db, state_root: nil)
37
+ @db = db
38
+ @account_db = DB::AccountDB.new(db, root_hash: state_root)
39
+ end
40
+
41
+ def snapshot
42
+ [state_root, @db.dup]
43
+ end
44
+
45
+ def revert(snapshot)
46
+ state_root, db = snapshot
47
+ @db = db
48
+ @account_db = DB::AccountDB.new(db, root_hash: state_root)
49
+ end
50
+
51
+ def commit(snapshot)
52
+ true
53
+ end
54
+
55
+ def state_root
56
+ @account_db.root_hash
57
+ end
58
+
59
+ end
60
+ end
61
+ end
data/lib/ciri/evm/vm.rb CHANGED
@@ -21,11 +21,13 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
 
24
+ require 'ciri/utils/logger'
25
+ require_relative 'errors'
24
26
  require_relative 'machine_state'
25
27
  require_relative 'instruction'
26
28
  require_relative 'sub_state'
27
29
  require_relative 'block_info'
28
- require_relative 'serialize'
30
+ require_relative 'log_entry'
29
31
 
30
32
  module Ciri
31
33
  class EVM
@@ -40,25 +42,12 @@ module Ciri
40
42
  # other logic of EVM (include transaction logic) in EVM module.
41
43
  class VM
42
44
 
43
- class VMError < StandardError
44
- end
45
- class InvalidOpCodeError < VMError
46
- end
47
- class GasNotEnoughError < VMError
48
- end
49
- class StackError < VMError
50
- end
51
- class InvalidJumpError < VMError
52
- end
53
- class ReturnError < VMError
54
- end
55
-
56
45
  class << self
57
46
  # this method provide a simpler interface to create VM and execute code
58
47
  # VM.spawn(...) == VM.new(...)
59
48
  # @return VM
60
- def spawn(state:, gas_limit:, header: nil, block_info: nil, instruction:, fork_config:)
61
- ms = MachineState.new(gas_remain: gas_limit, pc: 0, stack: [], memory: "\x00".b * 256, memory_item: 0)
49
+ def spawn(state:, gas_limit:, header: nil, block_info: nil, instruction: EVM::Instruction.new, fork_config:)
50
+ ms = MachineState.new(remain_gas: gas_limit, pc: 0, stack: [], memory: "\x00".b * 256, memory_item: 0)
62
51
 
63
52
  block_info = block_info || header && BlockInfo.new(
64
53
  coinbase: header.beneficiary,
@@ -84,17 +73,18 @@ module Ciri
84
73
 
85
74
  # helper methods
86
75
  include Utils::Logger
87
- include Serialize
88
76
 
89
- def_delegators :@machine_state, :stack, :pc, :pop, :push, :pop_list, :get_stack,
90
- :memory_item, :memory_item=, :memory_store, :memory_fetch, :extend_memory
77
+ def_delegators :@machine_state, :stack, :pc, :pop, :push, :pop_list, :get_stack, :memory_item, :memory_item=,
78
+ :memory_store, :memory_fetch, :extend_memory, :remain_gas, :consume_gas
91
79
  def_delegators :@instruction, :get_op, :get_code, :next_valid_instruction_pos, :get_data, :data, :sender
92
80
  def_delegators :@sub_state, :add_refund_account, :add_touched_account, :add_suicide_account
81
+ def_delegators :@state, :find_account, :account_dead?, :store, :fetch, :set_account_code, :get_account_code
93
82
 
94
- attr_reader :machine_state, :instruction, :sub_state, :block_info, :fork_config
83
+ attr_reader :state, :machine_state, :instruction, :sub_state, :block_info, :fork_config
95
84
  attr_accessor :output, :exception
96
85
 
97
- def initialize(state:, machine_state:, sub_state: nil, instruction:, block_info:, fork_config:)
86
+ def initialize(state:, machine_state:, sub_state: nil, instruction:, block_info:,
87
+ fork_config:, burn_gas_on_exception: true)
98
88
  @state = state
99
89
  @machine_state = machine_state
100
90
  @instruction = instruction
@@ -102,31 +92,7 @@ module Ciri
102
92
  @output = nil
103
93
  @block_info = block_info
104
94
  @fork_config = fork_config
105
- end
106
-
107
- # store data to address
108
- def store(address, key, data)
109
- data_is_blank = Ciri::Utils.blank_binary?(data)
110
- # key_is_blank = Ciri::Utils.blank_binary?(key)
111
-
112
- return unless data && !data_is_blank
113
-
114
- # remove unnecessary null byte from key
115
- key = serialize(key).gsub(/\A\0+/, ''.b)
116
- key = "\x00".b if key.empty?
117
-
118
- account = find_account address
119
- account.storage[key] = serialize(data).rjust(32, "\x00".b)
120
- update_account(account)
121
- end
122
-
123
- # fetch data from address
124
- def fetch(address, key)
125
- # remove unnecessary null byte from key
126
- key = serialize(key).gsub(/\A\0+/, ''.b)
127
- key = "\x00".b if key.empty?
128
-
129
- find_account(address).storage[key] || ''.b
95
+ @burn_gas_on_exception = burn_gas_on_exception
130
96
  end
131
97
 
132
98
  # run vm
@@ -138,21 +104,22 @@ module Ciri
138
104
  # low_level create_contract interface
139
105
  # CREATE_CONTRACT op is based on this method
140
106
  def create_contract(value:, init:)
141
- account = find_account(instruction.address)
107
+ caller_address = instruction.address
108
+ account = find_account(caller_address)
142
109
 
143
110
  # return contract address 0 represent execution failed
144
111
  return 0 unless account.balance >= value || instruction.execute_depth > 1024
145
112
 
146
- account.nonce += 1
113
+ state.increment_nonce(caller_address)
114
+ snapshot = state.snapshot
147
115
 
148
116
  # generate contract_address
149
- material = RLP.encode_simple([instruction.address.to_s, account.nonce - 1])
117
+ material = RLP.encode_simple([caller_address.to_s, account.nonce])
150
118
  contract_address = Utils.sha3(material)[-20..-1]
151
119
 
152
120
  # initialize contract account
153
121
  contract_account = find_account(contract_address)
154
- contract_account.nonce = 1
155
- contract_account.code = Utils::BLANK_SHA3
122
+ # contract_account.nonce = 1
156
123
 
157
124
  # execute initialize code
158
125
  create_contract_instruction = instruction.dup
@@ -160,34 +127,48 @@ module Ciri
160
127
  create_contract_instruction.execute_depth += 1
161
128
  create_contract_instruction.address = contract_address
162
129
 
130
+ # TODO refactoring: Maybe should use call_message to execute data
163
131
  call_instruction(create_contract_instruction) do
164
132
  execute
165
133
 
166
- if exception
167
- update_account(Account.new_empty(contract_address))
134
+ deposit_code_gas = fork_config.calculate_deposit_code_gas(output)
135
+
136
+ if deposit_code_gas > remain_gas
137
+ # deposit_code_gas not enough
138
+ contract_address = 0
139
+ elsif exception
140
+ state.touch_account(contract_address)
168
141
  contract_address = 0
142
+ state.revert(snapshot)
169
143
  else
170
144
  # set contract code
171
- contract_account.code = output || ''.b
145
+ set_account_code(contract_address, output)
146
+ # minus deposit_code_fee
147
+ machine_state.consume_gas deposit_code_gas
172
148
  # transact value
173
149
  account.balance -= value
174
150
  contract_account.balance += value
151
+
152
+ state.set_balance(contract_address, contract_account.balance)
153
+ state.set_balance(caller_address, account.balance)
154
+ state.commit(snapshot)
175
155
  end
156
+ [contract_address, exception]
176
157
  end
177
-
178
- # update account
179
- update_account(contract_account)
180
- update_account(account)
181
-
182
- contract_address
183
158
  end
184
159
 
185
160
  # low level call message interface
186
161
  # CALL, CALLCODE, DELEGATECALL ops is base on this method
187
- def call_message(sender:, value:, receipt:, data:, code_address:)
162
+ def call_message(sender:, value:, receipt:, data:, code_address: receipt)
188
163
  # return status code 0 represent execution failed
189
164
  return [0, ''.b] unless value <= find_account(sender).balance && instruction.execute_depth <= 1024
190
165
 
166
+ state.increment_nonce(sender)
167
+
168
+ snapshot = state.snapshot
169
+
170
+ transact(sender: sender, value: value, to: receipt)
171
+
191
172
  message_call_instruction = instruction.dup
192
173
  message_call_instruction.address = receipt
193
174
  message_call_instruction.sender = sender
@@ -196,54 +177,48 @@ module Ciri
196
177
  message_call_instruction.execute_depth += 1
197
178
 
198
179
  message_call_instruction.data = data
199
- message_call_instruction.bytes_code = find_account(code_address).code || ''.b
180
+ message_call_instruction.bytes_code = get_account_code(code_address)
200
181
 
201
- transact(sender: sender, value: value, to: receipt)
202
182
  call_instruction(message_call_instruction) do
203
183
  execute
204
- status = exception.nil? ? 0 : 1
205
- [status, output || ''.b]
184
+
185
+ if exception
186
+ state.revert(snapshot)
187
+ else
188
+ state.commit(snapshot)
189
+ end
190
+
191
+ [status, output || ''.b, exception]
206
192
  end
207
193
  end
208
194
 
195
+ def status
196
+ exception.nil? ? 0 : 1
197
+ end
198
+
209
199
  # jump to pc
210
200
  # only valid if current op code is allowed to modify pc
211
201
  def jump_to(pc)
212
202
  @jump_to = pc
213
203
  end
214
204
 
215
- def account_dead?(address)
216
- Account.account_dead?(@state, address)
217
- end
218
-
219
- def find_account(address)
220
- Account.find_account(@state, address)
221
- end
222
-
223
- # the only method which touch state
224
- # VM do not consider state revert/commit, we let it to state implementation
225
- def update_account(account)
226
- address = account.address.to_s
227
- @state[address] = account
228
- add_touched_account(account)
229
- end
230
-
231
205
  def add_log_entry(topics, log_data)
232
- sub_state.log_series << [instruction.address, topics, log_data]
206
+ sub_state.log_series << LogEntry.new(address: instruction.address, topics: topics, data: log_data)
233
207
  end
234
208
 
235
209
  # transact value from sender to target address
236
210
  def transact(sender:, value:, to:)
237
- sender = find_account(sender)
238
- to = find_account(to)
211
+ sender_account = find_account(sender)
212
+ to_account = find_account(to)
239
213
 
240
- raise VMError.new("balance not enough") if sender.balance < value
214
+ raise VMError.new("balance not enough") if sender_account.balance < value
241
215
 
242
- sender.balance -= value
243
- to.balance += value
216
+ sender_account.balance -= value
217
+ to_account.balance += value
244
218
 
245
- update_account(sender)
246
- update_account(to)
219
+ state.set_nonce(sender, sender_account.nonce)
220
+ state.set_balance(sender, sender_account.balance)
221
+ state.set_balance(to, to_account.balance)
247
222
  end
248
223
 
249
224
  # call instruction
@@ -268,13 +243,14 @@ module Ciri
268
243
  def execute
269
244
  loop do
270
245
  if (@exception ||= check_exception(@state, machine_state, instruction))
271
- debug("exception: #{@exception}")
246
+ if @burn_gas_on_exception
247
+ debug("exception: #{@exception}, burn gas #{machine_state.remain_gas} to zero")
248
+ machine_state.consume_gas machine_state.remain_gas
249
+ end
272
250
  return [EMPTY_SET, machine_state, SubState::EMPTY, instruction, EMPTY_SET]
273
251
  elsif get_op(machine_state.pc) == OP::REVERT
274
252
  o = halt
275
- gas_cost = fork_config.cost_of_operation[self]
276
- machine_state.gas_remain -= gas_cost
277
- return [EMPTY_SET, machine_state, sub_state, instruction, o]
253
+ return [EMPTY_SET, machine_state, SubState::EMPTY, instruction, o]
278
254
  elsif (o = halt) != EMPTY_SET
279
255
  return [@state, machine_state, sub_state, instruction, o]
280
256
  else
@@ -294,23 +270,23 @@ module Ciri
294
270
 
295
271
  raise "can't find operation #{w}, pc #{ms.pc}" unless operation
296
272
 
297
- op_cost = fork_config.cost_of_operation[self]
298
- old_memory_cost = fork_config.cost_of_memory[ms.memory_item]
299
- ms.gas_remain -= op_cost
273
+ op_cost = fork_config.gas_of_operation(self)
274
+ old_memory_cost = fork_config.gas_of_memory(ms.memory_item)
275
+ ms.consume_gas op_cost
300
276
 
301
277
  prev_sub_state = sub_state.dup
302
278
 
303
279
  # call operation
304
280
  operation.call(self)
305
281
  # calculate gas_cost
306
- new_memory_cost = fork_config.cost_of_memory[ms.memory_item]
282
+ new_memory_cost = fork_config.gas_of_memory(ms.memory_item)
307
283
  memory_gas_cost = new_memory_cost - old_memory_cost
308
284
 
309
- if ms.gas_remain >= memory_gas_cost
310
- ms.gas_remain -= memory_gas_cost
285
+ if ms.remain_gas >= memory_gas_cost
286
+ ms.consume_gas memory_gas_cost
311
287
  else
312
288
  # memory gas_not_enough
313
- @exception = GasNotEnoughError.new "gas not enough: gas remain:#{ms.gas_remain} gas cost: #{memory_gas_cost}"
289
+ @exception = GasNotEnoughError.new "gas not enough: gas remain:#{ms.remain_gas} gas cost: #{memory_gas_cost}"
314
290
  end
315
291
 
316
292
  # revert sub_state and return if exception occur
@@ -356,8 +332,8 @@ module Ciri
356
332
  InvalidOpCodeError.new "can't find op code #{w}"
357
333
  when ms.stack.size < (consume = OP.input_count(w))
358
334
  StackError.new "stack not enough: stack:#{ms.stack.size} next consume: #{consume}"
359
- when ms.gas_remain < (gas_cost = fork_config.cost_of_operation[self])
360
- GasNotEnoughError.new "gas not enough: gas remain:#{ms.gas_remain} gas cost: #{gas_cost}"
335
+ when ms.remain_gas < (gas_cost = fork_config.gas_of_operation(self))
336
+ GasNotEnoughError.new "gas not enough: gas remain:#{ms.remain_gas} gas cost: #{gas_cost}"
361
337
  when w == OP::JUMP && instruction.destinations.include?(ms.get_stack(0, Integer))
362
338
  InvalidJumpError.new "invalid jump dest #{ms.get_stack(0, Integer)}"
363
339
  when w == OP::JUMPI && ms.get_stack(1, Integer) != 0 && instruction.destinations.include?(ms.get_stack(0, Integer))