ruby-ethereum 0.9.3 → 0.9.4

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.
@@ -28,14 +28,14 @@ module Ethereum
28
28
  size = sub.to_i
29
29
  raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
30
30
  raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
31
- when 'ureal', 'real', 'fixed', 'ufixed'
32
- raise ParseError, "Real type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
31
+ when 'fixed', 'ufixed'
32
+ raise ParseError, "Fixed type must have suffix of form <high>x<low>, e.g. 128x128" unless sub =~ /\A[0-9]+x[0-9]+\z/
33
33
 
34
34
  high, low = sub.split('x').map(&:to_i)
35
35
  total = high + low
36
36
 
37
- raise ParseError, "Real size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
38
- raise ParseError, "Real high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
37
+ raise ParseError, "Fixed size out of bounds (max 32 bytes)" unless total >= 8 && total <= 256
38
+ raise ParseError, "Fixed high/low sizes must be multiples of 8" unless high % 8 == 0 && low % 8 == 0
39
39
  when 'hash'
40
40
  raise ParseError, "Hash type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
41
41
  when 'address'
@@ -31,6 +31,8 @@ module Ethereum
31
31
  private
32
32
 
33
33
  def parse(s)
34
+ return Utils.int_to_addr(s) if s.is_a?(Integer)
35
+
34
36
  case s.size
35
37
  when 0
36
38
  s
@@ -104,7 +104,9 @@ module Ethereum
104
104
  config = parent.config
105
105
  offset = parent.difficulty / config[:block_diff_factor]
106
106
 
107
- if parent.number >= (config[:homestead_fork_blknum] - 1)
107
+ if parent.number >= (config[:metropolis_fork_blknum] - 1)
108
+ sign = [parent.uncles.size - ((ts - parent.timestamp) / config[:metropolis_diff_adjustment_cutoff]), -99].max
109
+ elsif parent.number >= (config[:homestead_fork_blknum] - 1)
108
110
  sign = [1 - ((ts - parent.timestamp) / config[:homestead_diff_adjustment_cutoff]), -99].max
109
111
  else
110
112
  sign = (ts - parent.timestamp) < config[:diff_adjustment_cutoff] ? 1 : -1
@@ -368,6 +370,8 @@ module Ethereum
368
370
  uncles.each do |uncle|
369
371
  parent = Block.find env, uncle.prevhash
370
372
  return false if uncle.difficulty != Block.calc_difficulty(parent, uncle.timestamp)
373
+ return false if uncle.number != parent.number + 1
374
+ return false if uncle.timestamp < parent.timestamp
371
375
  return false unless uncle.check_pow
372
376
 
373
377
  unless eligible_ancestor_hashes.include?(uncle.prevhash)
@@ -1116,7 +1120,13 @@ module Ethereum
1116
1120
  alias :inspect :to_s
1117
1121
 
1118
1122
  def validate_transaction(tx)
1119
- raise UnsignedTransactionError.new(tx) unless tx.sender
1123
+ unless tx.sender
1124
+ if number >= config[:metropolis_fork_blknum]
1125
+ tx.sender = Utils.normalize_address(config[:metropolis_entry_point])
1126
+ else
1127
+ raise UnsignedTransactionError.new(tx)
1128
+ end
1129
+ end
1120
1130
 
1121
1131
  acct_nonce = get_nonce tx.sender
1122
1132
  raise InvalidNonce, "#{tx}: nonce actual: #{tx.nonce} target: #{acct_nonce}" if acct_nonce != tx.nonce
@@ -1158,6 +1168,8 @@ module Ethereum
1158
1168
  @transaction_count = 0 # TODO - should always equal @transactions.size
1159
1169
  self.gas_used = 0
1160
1170
 
1171
+ hard_fork_initialize(parent)
1172
+
1161
1173
  transaction_list.each {|tx| apply_transaction tx }
1162
1174
 
1163
1175
  finalize
@@ -1174,6 +1186,24 @@ module Ethereum
1174
1186
  end
1175
1187
  end
1176
1188
 
1189
+ def hard_fork_initialize(parent)
1190
+ if number == @config[:metropolis_fork_blknum]
1191
+ set_code Utils.normalize_address(@config[:metropolis_stateroot_store]), @config[:metropolis_getter_code]
1192
+ set_code Utils.normalize_address(@config[:metropolis_blockhash_store]), @config[:metropolis_getter_code]
1193
+ end
1194
+
1195
+ if number >= @config[:metropolis_fork_blknum]
1196
+ set_storage_data Utils.normalize_address(@config[:metropolis_stateroot_store]), (number % @config[:metropolis_wrapround]), parent.state_root
1197
+ set_storage_data Utils.normalize_address(@config[:metropolis_blockhash_store]), (number % @config[:metropolis_wrapround]), prevhash
1198
+ end
1199
+
1200
+ if number == @config[:dao_fork_blknum]
1201
+ @config[:child_dao_list].each do |acct|
1202
+ transfer_value acct, @config[:dao_withdrawer], get_balance(acct)
1203
+ end
1204
+ end
1205
+ end
1206
+
1177
1207
  ##
1178
1208
  # Validate block (header) against previous block.
1179
1209
  #
@@ -1184,6 +1214,7 @@ module Ethereum
1184
1214
  raise ValidationError, "Block's gaslimit is inconsistent with its parent's gaslimit" unless Block.check_gaslimit(parent, gas_limit)
1185
1215
  raise ValidationError, "Block's difficulty is inconsistent with its parent's difficulty" if difficulty != Block.calc_difficulty(parent, timestamp)
1186
1216
  raise ValidationError, "Gas used exceeds gas limit" if gas_used > gas_limit
1217
+ raise ValidationError, "Block's gaslimit went too high!" if gas_limit > @config[:max_gas_limit]
1187
1218
  raise ValidationError, "Timestamp equal to or before parent" if timestamp <= parent.timestamp
1188
1219
  raise ValidationError, "Timestamp way too large" if timestamp > Constant::UINT_MAX
1189
1220
  end
@@ -1314,7 +1345,11 @@ module Ethereum
1314
1345
  end
1315
1346
 
1316
1347
  def mk_transaction_receipt(tx)
1317
- Receipt.new state_root, gas_used, logs
1348
+ if number >= @config[:metropolis_fork_blknum]
1349
+ Receipt.new Constant::HASH_ZERO, gas_used, logs
1350
+ else
1351
+ Receipt.new state_root, gas_used, logs
1352
+ end
1318
1353
  end
1319
1354
 
1320
1355
  end
@@ -4,17 +4,19 @@ module Ethereum
4
4
  module Constant
5
5
 
6
6
  BYTE_EMPTY = "".freeze
7
- BYTE_ZERO = "\x00".freeze
8
- BYTE_ONE = "\x01".freeze
7
+ BYTE_ZERO = "\x00".freeze
8
+ BYTE_ONE = "\x01".freeze
9
9
 
10
- TT32 = 2**32
11
- TT256 = 2**256
10
+ TT32 = 2**32
11
+ TT40 = 2**40
12
+ TT160 = 2**160
13
+ TT256 = 2**256
12
14
  TT64M1 = 2**64 - 1
13
15
 
14
16
  UINT_MAX = 2**256 - 1
15
17
  UINT_MIN = 0
16
- INT_MAX = 2**255 - 1
17
- INT_MIN = -2**255
18
+ INT_MAX = 2**255 - 1
19
+ INT_MIN = -2**255
18
20
 
19
21
  HASH_ZERO = ("\x00"*32).freeze
20
22
 
data/lib/ethereum/env.rb CHANGED
@@ -20,6 +20,7 @@ module Ethereum
20
20
  # block.gas_limit = block.parent.gas_limit * 1023/1024 +
21
21
  # (block.gas.used * 6/5) / 1024
22
22
  min_gas_limit: 5000,
23
+ max_gas_limit: 2**63 - 1,
23
24
  gaslimit_ema_factor: 1024,
24
25
  gaslimit_adjmax_factor: 1024,
25
26
  blklim_factor_nom: 3,
@@ -46,8 +47,21 @@ module Ethereum
46
47
 
47
48
  account_initial_nonce: 0,
48
49
 
50
+ # Milestones
49
51
  homestead_fork_blknum: 1150000,
50
- homestead_diff_adjustment_cutoff: 10
52
+ homestead_diff_adjustment_cutoff: 10,
53
+
54
+ metropolis_fork_blknum: 2**100,
55
+ metropolis_entry_point: 2**160-1,
56
+ metropolis_stateroot_store: 0x10,
57
+ metropolis_blockhash_store: 0x20,
58
+ metropolis_wrapround: 65536,
59
+ metropolis_getter_code: Utils.decode_hex('6000355460205260206020f3'),
60
+ metropolis_diff_adjustment_cutoff: 9,
61
+
62
+ dao_fork_blknum: 1920000,
63
+ child_dao_list: Utils.child_dao_list.map {|addr| Utils.normalize_address addr },
64
+ dao_withdrawer: Utils.normalize_address('0xbf4ed7b27f1d666546e30d74d50d173d20bca754')
51
65
  }.freeze
52
66
 
53
67
  attr :db, :config, :global_config
@@ -12,7 +12,10 @@ module Ethereum
12
12
  class ExternalCall
13
13
 
14
14
  extend Forwardable
15
- def_delegators :@block, :get_code, :get_balance, :set_balance, :get_storage_data, :set_storage_data, :add_refund, :account_exists
15
+ def_delegators :@block, :get_code, :set_code, :get_balance, :set_balance,
16
+ :delta_balance, :get_nonce, :set_nonce, :increment_nonce,
17
+ :get_storage_data, :set_storage_data, :get_storage_bytes, :reset_storage,
18
+ :add_refund, :account_exists, :snapshot, :revert, :transfer_value
16
19
 
17
20
  def initialize(block, tx)
18
21
  @block = block
@@ -28,11 +31,16 @@ module Ethereum
28
31
  end
29
32
 
30
33
  def block_hash(x)
31
- d = @block.number - x
32
- if d > 0 && d <= 256
33
- @block.get_ancestor_hash d
34
+ if post_metropolis_hardfork
35
+ get_storage_data @block.config[:metropolis_blockhash_store], x
34
36
  else
35
- Constant::BYTE_EMPTY
37
+ d = @block.number - x
38
+ hash = if d > 0 && d <= 256
39
+ @block.get_ancestor_hash d
40
+ else
41
+ Constant::BYTE_EMPTY
42
+ end
43
+ Utils.big_endian_to_int hash
36
44
  end
37
45
  end
38
46
 
@@ -72,30 +80,45 @@ module Ethereum
72
80
  @block.number >= @block.config[:homestead_fork_blknum]
73
81
  end
74
82
 
83
+ def post_metropolis_hardfork
84
+ @block.number >= @block.config[:metropolis_fork_blknum]
85
+ end
86
+
75
87
  def create(msg)
76
88
  log_msg.debug 'CONTRACT CREATION'
77
89
 
78
90
  sender = Utils.normalize_address(msg.sender, allow_blank: true)
79
91
 
80
- @block.increment_nonce msg.sender if tx_origin != msg.sender
92
+ code = msg.data.extract_all
93
+ if post_metropolis_hardfork
94
+ msg.to = Utils.mk_metropolis_contract_address msg.sender, code
95
+ if get_code(msg.to)
96
+ n1 = get_nonce msg.to
97
+ n2 = n1 >= Constant::TT40 ?
98
+ (n + 1) :
99
+ (Utils.big_endian_to_int(msg.to) + 2)
100
+ set_nonce msg.to, (n2 % Constant::TT160)
101
+ msg.to = Utils.normalize_address((get_nonce(msg.to) - 1) % Constant::TT160)
102
+ end
103
+ else
104
+ increment_nonce msg.sender if tx_origin != msg.sender
81
105
 
82
- nonce = Utils.encode_int(@block.get_nonce(msg.sender) - 1)
83
- msg.to = Utils.mk_contract_address sender, nonce
106
+ nonce = Utils.encode_int(get_nonce(msg.sender) - 1)
107
+ msg.to = Utils.mk_contract_address sender, nonce
108
+ end
84
109
 
85
110
  balance = get_balance(msg.to)
86
111
  if balance > 0
87
112
  set_balance msg.to, balance
88
- @block.set_nonce msg.to, 0
89
- @block.set_code msg.to, Constant::BYTE_EMPTY
90
- @block.reset_storage msg.to
113
+ set_nonce msg.to, 0
114
+ set_code msg.to, Constant::BYTE_EMPTY
115
+ reset_storage msg.to
91
116
  end
92
117
 
93
118
  msg.is_create = true
94
-
95
- code = msg.data.extract_all
96
119
  msg.data = VM::CallData.new [], 0, 0
97
- snapshot = @block.snapshot
98
120
 
121
+ snapshot = self.snapshot
99
122
  res, gas, dat = apply_msg msg, code
100
123
 
101
124
  if res.true?
@@ -108,13 +131,13 @@ module Ethereum
108
131
  dat = []
109
132
  log_msg.debug "CONTRACT CREATION OOG", have: gas, want: gcost, block_number: @block.number
110
133
 
111
- if @block.number >= @block.config[:homestead_fork_blknum]
112
- @block.revert snapshot
134
+ if post_homestead_hardfork
135
+ revert snapshot
113
136
  return 0, 0, Constant::BYTE_EMPTY
114
137
  end
115
138
  end
116
139
 
117
- @block.set_code msg.to, Utils.int_array_to_bytes(dat)
140
+ set_code msg.to, Utils.int_array_to_bytes(dat)
118
141
  return 1, gas, msg.to
119
142
  else
120
143
  return 0, gas, Constant::BYTE_EMPTY
@@ -129,11 +152,11 @@ module Ethereum
129
152
  log_state.trace "MSG PRE STATE RECIPIENT", account: Utils.encode_hex(msg.to), balance: get_balance(msg.to), state: log_storage(msg.to)
130
153
 
131
154
  # snapshot before execution
132
- snapshot = @block.snapshot
155
+ snapshot = self.snapshot
133
156
 
134
157
  # transfer value
135
158
  if msg.transfers_value
136
- unless @block.transfer_value(msg.sender, msg.to, msg.value)
159
+ unless transfer_value(msg.sender, msg.to, msg.value)
137
160
  log_msg.debug "MSG TRANSFER FAILED", have: get_balance(msg.to), want: msg.value
138
161
  return [1, msg.gas, []]
139
162
  end
@@ -152,7 +175,7 @@ module Ethereum
152
175
 
153
176
  if res == 0
154
177
  log_msg.debug 'REVERTING'
155
- @block.revert snapshot
178
+ revert snapshot
156
179
  end
157
180
 
158
181
  return res, gas, dat
@@ -6,26 +6,23 @@ module Ethereum
6
6
 
7
7
  attr :address, :abi
8
8
 
9
- def initialize(state, abi, address, listen: true, log_listener: nil)
9
+ def initialize(state, abi, address, listen: true, log_listener: nil, default_key: nil)
10
10
  @state = state
11
- @abi = abi
12
11
  @address = address
12
+ @default_key = default_key || Fixture.keys.first
13
13
 
14
- @translator = ABI::ContractTranslator.new abi
14
+ @translator = abi.instance_of?(ABI::ContractTranslator) ? abi : ABI::ContractTranslator.new(abi)
15
15
 
16
16
  if listen
17
- if log_listener
18
- listener = lambda do |log|
19
- r = @translator.listen(log, noprint: true)
20
- log_listener.call r if r.true?
21
- end
22
- else
23
- listener = ->(log) { @translator.listen(log, noprint: false) }
24
- end
17
+ listener = ->(log) {
18
+ result = @translator.listen log, noprint: false
19
+ log_listener(result) if result && log_listener
20
+ }
21
+ @state.block.log_listeners.push listener
25
22
  end
26
23
 
27
- @translator.function_data.each do |f, _|
28
- generate_function f
24
+ @translator.function_data.each do |fn, _|
25
+ generate_function fn
29
26
  end
30
27
  end
31
28
 
@@ -38,7 +35,7 @@ module Ethereum
38
35
  def generate_function(f)
39
36
  singleton_class.class_eval <<-EOF
40
37
  def #{f}(*args, **kwargs)
41
- sender = kwargs.delete(:sender) || Fixture.keys[0]
38
+ sender = kwargs.delete(:sender) || @default_key
42
39
  to = @address
43
40
  value = kwargs.delete(:value) || 0
44
41
  evmdata = @translator.encode('#{f}', args)
@@ -55,7 +52,7 @@ module Ethereum
55
52
  outdata = outdata.size == 1 ? outdata[0] : outdata
56
53
  end
57
54
 
58
- kwargs[:profiling].true? ? o.merge(outdata: outdata) : outdata
55
+ kwargs[:profiling].true? ? o.merge(output: outdata) : outdata
59
56
  end
60
57
  EOF
61
58
  end
@@ -8,125 +8,293 @@ module Ethereum
8
8
  class CompileError < StandardError; end
9
9
 
10
10
  class <<self
11
- def split_contracts(code)
12
- contracts = []
13
- contract = nil
14
-
15
- code.split("\n").each do |line|
16
- if line =~ /\Acontract /
17
- contracts.push(contract.join("\n")) if contract
18
- contract = [line]
19
- elsif contract
20
- contract.push line
21
- end
22
- end
23
11
 
24
- contracts.push(contract.join("\n")) if contract
12
+ def compile_code_or_path(code, path, contract_name, libraries, combined)
13
+ raise ValueError, 'code and path are mutually exclusive' if code && path
14
+
15
+ return compile_contract(path, contract_name, libraries: libraries, combined: combined) if path && contract_name.true?
16
+ return compile_last_contract(path, libraries: libraries, combined: combined) if path
17
+
18
+ all_names = solidity_names code
19
+ all_contract_names = all_names.map(&:last)
25
20
 
26
- contracts
21
+ result = compile_code(code, libraries: libraries, combined: combined)
22
+ result[all_contract_names.last]
27
23
  end
28
24
 
29
- def contract_names(code)
30
- names = []
25
+ ##
26
+ # Returns binary of last contract in code.
27
+ #
28
+ def compile(code, contract_name: '', libraries: nil, path: nil)
29
+ result = compile_code_or_path code, path, contract_name, libraries, 'bin'
30
+ result['bin']
31
+ end
32
+
33
+ ##
34
+ # Returns signature of last contract in code.
35
+ #
36
+ def mk_full_signature(code, contract_name: '', libraries: nil, path: nil)
37
+ result = compile_code_or_path code, path, contract_name, libraries, 'abi'
38
+ result['abi']
39
+ end
40
+
41
+ ##
42
+ # Compile combined-json with abi,bin,devdoc,userdoc.
43
+ #
44
+ def combined(code, path: nil)
45
+ raise ValueError, "code and path are mutually exclusive." if code && path
31
46
 
32
- split_contracts(code).each do |contract|
33
- keyword, name, _ = contract.split(/\s+/, 3)
34
- raise AssertError, 'keyword must be contract' unless keyword == 'contract' && !name.empty?
35
- names.push name
47
+ if path
48
+ contracts = compile_file path
49
+ code = File.read(path)
50
+ elsif code
51
+ contracts = compile_code code
52
+ else
53
+ raise ValueError, 'either code or path needs to be supplied.'
36
54
  end
37
55
 
38
- names
56
+ solidity_names(code).map do |(kind, name)|
57
+ [name, contracts[name]]
58
+ end
39
59
  end
40
60
 
41
- def combined(code, format='bin')
42
- out = Tempfile.new 'solc_output_'
61
+ ##
62
+ # Full format as returned by jsonrpc.
63
+ #
64
+ def compile_rich(code, path: nil)
65
+ combined(code, path: path).map do |(name, contract)|
66
+ [
67
+ name,
68
+ {
69
+ 'code' => "0x#{contract['bin_hex']}",
70
+ 'info' => {
71
+ 'abiDefinition' => contract['abi'],
72
+ 'compilerVersion' => compiler_version,
73
+ 'developerDoc' => contract['devdoc'],
74
+ 'language' => 'Solidity',
75
+ 'languageVersion' => '0',
76
+ 'source' => code,
77
+ 'userDoc' => contract['userdoc']
78
+ }
79
+ }
80
+ ]
81
+ end.to_h
82
+ end
43
83
 
44
- pipe = IO.popen([solc_path, '--add-std', '--optimize', '--combined-json', "abi,#{format},devdoc,userdoc"], 'w', [:out, :err] => out)
84
+ def compile_code(code, libraries: nil, combined:'bin,abi', optimize: true)
85
+ args = solc_arguments libraries: libraries, combined: combined, optimize: optimize
86
+ args.unshift solc_path
87
+
88
+ out = Tempfile.new 'solc_output_'
89
+ pipe = IO.popen(args, 'w', [:out, :err] => out)
45
90
  pipe.write code
46
91
  pipe.close_write
47
92
  raise CompileError, 'compilation failed' unless $?.success?
48
93
 
49
94
  out.rewind
50
- contracts = JSON.parse(out.read)['contracts']
51
-
52
- contracts.each do |name, data|
53
- data['abi'] = JSON.parse data['abi']
54
- data['devdoc'] = JSON.parse data['devdoc']
55
- data['userdoc'] = JSON.parse data['userdoc']
56
- end
95
+ solc_parse_output out.read
96
+ end
57
97
 
58
- names = contract_names code
59
- raise AssertError unless names.size <= contracts.size
98
+ def compile_last_contract(path, libraries: nil, combined: 'bin,abi', optimize: true)
99
+ all_names = solidity_names File.read(path)
100
+ all_contract_names = all_names.map(&:last) # don't filter libraries
101
+ compile_contract path, all_contract_names.last, libraries: libraries, combined: combined, optimize: optimize
102
+ end
60
103
 
61
- names.map {|n| [n, contracts[n]] }
62
- ensure
63
- out.close
104
+ def compile_contract(path, contract_name, libraries: nil, combined: 'bin,abi', optimize: true)
105
+ all_contracts = compile_file path, libraries: libraries, combined: combined, optimize: optimize
106
+ all_contracts[contract_name]
64
107
  end
65
108
 
66
109
  ##
67
- # Returns binary of last contract in code.
110
+ # Return the compiled contract code.
68
111
  #
69
- def compile(code, contract_name='', format='bin')
70
- sorted_contracts = combined code, format
71
- if contract_name.true?
72
- idx = sorted_contracts.map(&:first).index(contract_name)
73
- else
74
- idx = -1
112
+ # @param path [String] Path to the contract source code.
113
+ # @param libraries [Hash] A hash mapping library name to its address.
114
+ # @param combined [Array[String]] The argument for solc's --combined-json.
115
+ # @param optimize [Bool] Enable/disables compiler optimization.
116
+ #
117
+ # @return [Hash] A mapping from the contract name to it's bytecode.
118
+ #
119
+ def compile_file(path, libraries: nil, combined: 'bin,abi', optimize: true)
120
+ workdir = File.dirname path
121
+ filename = File.basename path
122
+
123
+ args = solc_arguments libraries: libraries, combined: combined, optimize: optimize
124
+ args.unshift solc_path
125
+ args.push filename
126
+
127
+ out = Tempfile.new 'solc_output_'
128
+ Dir.chdir(workdir) do
129
+ pipe = IO.popen(args, 'w', [:out, :err] => out)
130
+ pipe.close_write
75
131
  end
76
132
 
77
- output = sorted_contracts[idx][1][format]
78
- case format
79
- when 'bin'
80
- Utils.decode_hex output
81
- when 'asm'
82
- output['.code']
83
- when 'opcodes'
84
- output
85
- else
86
- raise ArgumentError
133
+ out.rewind
134
+ solc_parse_output out.read
135
+ end
136
+
137
+ ##
138
+ # Return the library and contract names in order of appearence.
139
+ #
140
+ def solidity_names(code)
141
+ names = []
142
+ in_string = nil
143
+ backslash = false
144
+ comment = nil
145
+
146
+ # "parse" the code by hand to handle the corner cases:
147
+ #
148
+ # - the contract or library can be inside a comment or string
149
+ # - multiline comments
150
+ # - the contract and library keywords could not be at the start of the line
151
+ code.each_char.with_index do |char, pos|
152
+ if in_string
153
+ if !backslash && in_string == char
154
+ in_string = nil
155
+ backslash = false
156
+ end
157
+
158
+ backslash = char == "\\"
159
+ elsif comment == "//"
160
+ comment = nil if ["\n", "\r"].include?(char)
161
+ elsif comment == "/*"
162
+ comment = nil if char == "*" && code[pos + 1] == "/"
163
+ else
164
+ in_string = char if %w(' ").include?(char)
165
+
166
+ if char == "/"
167
+ char2 = code[pos + 1]
168
+ comment = char + char2 if %w(/ *).include?(char2)
169
+ end
170
+
171
+ if char == 'c' && code[pos, 8] == 'contract'
172
+ result = code[pos..-1] =~ /^contract[^_$a-zA-Z]+([_$a-zA-Z][_$a-zA-Z0-9]*)/
173
+ names.push ['contract', $1] if result
174
+ end
175
+
176
+ if char == 'l' && code[pos, 7] == 'library'
177
+ result = code[pos..-1] =~ /^library[^_$a-zA-Z]+([_$a-zA-Z][_$a-zA-Z0-9]*)/
178
+ names.push ['library', $1] if result
179
+ end
180
+ end
87
181
  end
182
+
183
+ names
88
184
  end
89
185
 
90
186
  ##
91
- # Returns signature of last contract in code.
187
+ # Return the symbol used in the bytecode to represent the
188
+ # `library_name`.
92
189
  #
93
- def mk_full_signature(code, contract_name='')
94
- sorted_contracts = combined code
95
- if contract_name.true?
96
- idx = sorted_contracts.map(&:first).index(contract_name)
97
- else
98
- idx = -1
190
+ # The symbol is always 40 characters in length with the minimum of two
191
+ # leading and trailing underscores.
192
+ #
193
+ def solidity_library_symbol(library_name)
194
+ len = [library_name.size, 36].min
195
+ lib_piece = library_name[0,len]
196
+ hold_piece = '_' * (36 - len)
197
+ "__#{lib_piece}#{hold_piece}__"
198
+ end
199
+
200
+ ##
201
+ # Change the bytecode to use the given library address.
202
+ #
203
+ # @param hex_code [String] The bytecode encoded in hex.
204
+ # @param library_name [String] The library that will be resolved.
205
+ # @param library_address [String] The address of the library.
206
+ #
207
+ # @return [String] The bytecode encoded in hex with the library
208
+ # references.
209
+ #
210
+ def solidity_resolve_address(hex_code, library_symbol, library_address)
211
+ raise ValueError, "Address should not contain the 0x prefix" if library_address =~ /\A0x/
212
+ raise ValueError, "Address with wrong length" if library_symbol.size != 40 || library_address.size != 40
213
+
214
+ begin
215
+ Utils.decode_hex library_address
216
+ rescue TypeError
217
+ raise ValueError, "library_address contains invalid characters, it must be hex encoded."
218
+ end
219
+
220
+ hex_code.gsub library_symbol, library_address
221
+ end
222
+
223
+ def solidity_resolve_symbols(hex_code, libraries)
224
+ symbol_address = libraries
225
+ .map {|name, addr| [solidity_library_symbol(name), addr] }
226
+ .to_h
227
+
228
+ solidity_unresolved_symbols(hex_code).each do |unresolved|
229
+ address = symbol_address[unresolved]
230
+ hex_code = solidity_resolve_address(hex_code, unresolved, address)
99
231
  end
100
232
 
101
- sorted_contracts[idx][1]['abi']
233
+ hex_code
234
+ end
235
+
236
+ ##
237
+ # Return the unresolved symbols contained in the `hex_code`.
238
+ #
239
+ # Note: the binary representation should not be provided since this
240
+ # function relies on the fact that the '_' is invalid in hex encoding.
241
+ #
242
+ # @param hex_code [String] The bytecode encoded as hex.
243
+ #
244
+ def solidity_unresolved_symbols(hex_code)
245
+ hex_code.scan(/_.{39}/).uniq
102
246
  end
103
247
 
104
248
  def compiler_version
105
249
  output = `#{solc_path} --version`.strip
106
- output =~ /^Version: ([0-9a-z.-]+)\// ? $1 : nil
250
+ output =~ /^Version: ([0-9a-z.-]+)\///m ? $1 : nil
107
251
  end
108
252
 
109
253
  ##
110
- # Full format as returned by jsonrpc.
254
+ # Parse compiler output.
111
255
  #
112
- def compile_rich(code)
113
- combined(code).map do |(name, contract)|
114
- [
115
- name,
116
- {
117
- 'code' => "0x#{contract['bin']}",
118
- 'info' => {
119
- 'abiDefinition' => contract['abi'],
120
- 'compilerVersion' => compiler_version,
121
- 'developerDoc' => contract['devdoc'],
122
- 'language' => 'Solidity',
123
- 'languageVersion' => '0',
124
- 'source' => code,
125
- 'userDoc' => contract['userdoc']
126
- }
127
- }
128
- ]
129
- end.to_h
256
+ def solc_parse_output(compiler_output)
257
+ result = JSON.parse(compiler_output)['contracts']
258
+
259
+ if result.values.first.has_key?('bin')
260
+ result.each_value do |v|
261
+ v['bin_hex'] = v['bin']
262
+
263
+ # decoding can fail if the compiled contract has unresolved symbols
264
+ begin
265
+ v['bin'] = Utils.decode_hex v['bin']
266
+ rescue TypeError
267
+ # do nothing
268
+ end
269
+ end
270
+ end
271
+
272
+ %w(abi devdoc userdoc).each do |json_data|
273
+ next unless result.values.first.has_key?(json_data)
274
+
275
+ result.each_value do |v|
276
+ v[json_data] = JSON.parse v[json_data]
277
+ end
278
+ end
279
+
280
+ result
281
+ end
282
+
283
+ def solc_arguments(libraries: nil, combined: 'bin,abi', optimize: true)
284
+ args = [
285
+ '--combined-json', combined,
286
+ '--add-std'
287
+ ]
288
+
289
+ args.push '--optimize' if optimize
290
+
291
+ if libraries && !libraries.empty?
292
+ addresses = libraries.map {|name, addr| "#{name}:#{addr}" }
293
+ args.push '--libraries'
294
+ args.push addresses.join(',')
295
+ end
296
+
297
+ args
130
298
  end
131
299
 
132
300
  def solc_path