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.
- checksums.yaml +4 -4
- data/README.md +1 -9
- data/lib/ethereum/abi.rb +79 -44
- data/lib/ethereum/abi/contract_translator.rb +142 -97
- data/lib/ethereum/abi/type.rb +4 -4
- data/lib/ethereum/address.rb +2 -0
- data/lib/ethereum/block.rb +38 -3
- data/lib/ethereum/constant.rb +8 -6
- data/lib/ethereum/env.rb +15 -1
- data/lib/ethereum/external_call.rb +43 -20
- data/lib/ethereum/tester/abi_contract.rb +12 -15
- data/lib/ethereum/tester/solidity_wrapper.rb +249 -81
- data/lib/ethereum/tester/state.rb +50 -36
- data/lib/ethereum/utils.rb +23 -0
- data/lib/ethereum/version.rb +1 -1
- data/lib/ethereum/vm.rb +1 -1
- metadata +2 -2
data/lib/ethereum/abi/type.rb
CHANGED
@@ -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 '
|
32
|
-
raise ParseError, "
|
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, "
|
38
|
-
raise ParseError, "
|
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'
|
data/lib/ethereum/address.rb
CHANGED
data/lib/ethereum/block.rb
CHANGED
@@ -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[:
|
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
|
-
|
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
|
-
|
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
|
data/lib/ethereum/constant.rb
CHANGED
@@ -4,17 +4,19 @@ module Ethereum
|
|
4
4
|
module Constant
|
5
5
|
|
6
6
|
BYTE_EMPTY = "".freeze
|
7
|
-
BYTE_ZERO
|
8
|
-
BYTE_ONE
|
7
|
+
BYTE_ZERO = "\x00".freeze
|
8
|
+
BYTE_ONE = "\x01".freeze
|
9
9
|
|
10
|
-
TT32
|
11
|
-
|
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
|
17
|
-
INT_MIN
|
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, :
|
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
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
83
|
-
|
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
|
-
|
89
|
-
|
90
|
-
|
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
|
112
|
-
|
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
|
-
|
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 =
|
155
|
+
snapshot = self.snapshot
|
133
156
|
|
134
157
|
# transfer value
|
135
158
|
if msg.transfers_value
|
136
|
-
unless
|
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
|
-
|
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
|
14
|
+
@translator = abi.instance_of?(ABI::ContractTranslator) ? abi : ABI::ContractTranslator.new(abi)
|
15
15
|
|
16
16
|
if listen
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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 |
|
28
|
-
generate_function
|
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) ||
|
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(
|
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
|
-
|
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
|
-
|
21
|
+
result = compile_code(code, libraries: libraries, combined: combined)
|
22
|
+
result[all_contract_names.last]
|
27
23
|
end
|
28
24
|
|
29
|
-
|
30
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
56
|
+
solidity_names(code).map do |(kind, name)|
|
57
|
+
[name, contracts[name]]
|
58
|
+
end
|
39
59
|
end
|
40
60
|
|
41
|
-
|
42
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
-
#
|
110
|
+
# Return the compiled contract code.
|
68
111
|
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
#
|
187
|
+
# Return the symbol used in the bytecode to represent the
|
188
|
+
# `library_name`.
|
92
189
|
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
-
|
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.-]+)
|
250
|
+
output =~ /^Version: ([0-9a-z.-]+)\///m ? $1 : nil
|
107
251
|
end
|
108
252
|
|
109
253
|
##
|
110
|
-
#
|
254
|
+
# Parse compiler output.
|
111
255
|
#
|
112
|
-
def
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|