ruby-ethereum 0.9.3 → 0.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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