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.
- 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
|