tezos_client 1.1.2 → 1.2.3
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/Gemfile.lock +31 -31
- data/lib/tezos_client.rb +2 -0
- data/lib/tezos_client/commands.rb +4 -1
- data/lib/tezos_client/crypto.rb +20 -0
- data/lib/tezos_client/exceptions.rb +3 -0
- data/lib/tezos_client/operation_mgr.rb +30 -26
- data/lib/tezos_client/rpc_interface/contracts.rb +11 -3
- data/lib/tezos_client/tools/annots_to_type.rb +48 -0
- data/lib/tezos_client/tools/convert_to_hash.rb +10 -91
- data/lib/tezos_client/tools/convert_to_hash/address.rb +29 -0
- data/lib/tezos_client/tools/convert_to_hash/base.rb +50 -0
- data/lib/tezos_client/tools/convert_to_hash/big_map.rb +20 -0
- data/lib/tezos_client/tools/convert_to_hash/bytes.rb +13 -0
- data/lib/tezos_client/tools/convert_to_hash/int.rb +13 -0
- data/lib/tezos_client/tools/convert_to_hash/key.rb +27 -0
- data/lib/tezos_client/tools/convert_to_hash/list.rb +22 -0
- data/lib/tezos_client/tools/convert_to_hash/map.rb +31 -0
- data/lib/tezos_client/tools/convert_to_hash/nat.rb +13 -0
- data/lib/tezos_client/tools/convert_to_hash/pair.rb +21 -0
- data/lib/tezos_client/tools/convert_to_hash/signature.rb +19 -0
- data/lib/tezos_client/tools/convert_to_hash/string.rb +13 -0
- data/lib/tezos_client/tools/convert_to_hash/timestamp.rb +19 -0
- data/lib/tezos_client/tools/find_big_maps_in_storage.rb +9 -40
- data/lib/tezos_client/tools/hash_to_micheline.rb +78 -0
- data/lib/tezos_client/version.rb +1 -1
- data/tezos_client.gemspec +1 -1
- metadata +23 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1edfdfe5f7332ebbe4ecafd4baa3f03998e02204e33f854d9f394ba84cbf98c3
|
|
4
|
+
data.tar.gz: 7544eba6b1a6d602b24025a5c2831c1e803f53f92ae5f0925daa05a0b2ef3167
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a4e3ddacd06478d21821ce1e38ac5d731f2602d6cbca55e2440828de46c5933aaa8b77df00d3d989295ee325318ff53284725dc91d165ed32918cd2b3de9d8a
|
|
7
|
+
data.tar.gz: 980264f44b9761e4f7e716675a5775d1b2f3cbfd6de5d09024154c53000a1a95cc3c168efe259c1fd7b78d9a877d23a6c0fa4feea8a8bda942b753122cf73bc7
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
tezos_client (1.
|
|
4
|
+
tezos_client (1.2.3)
|
|
5
5
|
active_interaction (~> 3.7)
|
|
6
6
|
activesupport (~> 6.0.0)
|
|
7
7
|
base58 (~> 0.2.3)
|
|
@@ -14,68 +14,68 @@ PATH
|
|
|
14
14
|
GEM
|
|
15
15
|
remote: https://rubygems.org/
|
|
16
16
|
specs:
|
|
17
|
-
actionpack (6.0.
|
|
18
|
-
actionview (= 6.0.
|
|
19
|
-
activesupport (= 6.0.
|
|
20
|
-
rack (~> 2.0)
|
|
17
|
+
actionpack (6.0.3.2)
|
|
18
|
+
actionview (= 6.0.3.2)
|
|
19
|
+
activesupport (= 6.0.3.2)
|
|
20
|
+
rack (~> 2.0, >= 2.0.8)
|
|
21
21
|
rack-test (>= 0.6.3)
|
|
22
22
|
rails-dom-testing (~> 2.0)
|
|
23
23
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
|
24
|
-
actionview (6.0.
|
|
25
|
-
activesupport (= 6.0.
|
|
24
|
+
actionview (6.0.3.2)
|
|
25
|
+
activesupport (= 6.0.3.2)
|
|
26
26
|
builder (~> 3.1)
|
|
27
27
|
erubi (~> 1.4)
|
|
28
28
|
rails-dom-testing (~> 2.0)
|
|
29
29
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
30
|
-
active_interaction (3.
|
|
30
|
+
active_interaction (3.8.2)
|
|
31
31
|
activemodel (>= 4, < 7)
|
|
32
|
-
activemodel (6.0.
|
|
33
|
-
activesupport (= 6.0.
|
|
34
|
-
activesupport (6.0.
|
|
32
|
+
activemodel (6.0.3.2)
|
|
33
|
+
activesupport (= 6.0.3.2)
|
|
34
|
+
activesupport (6.0.3.2)
|
|
35
35
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
36
36
|
i18n (>= 0.7, < 2)
|
|
37
37
|
minitest (~> 5.1)
|
|
38
38
|
tzinfo (~> 1.1)
|
|
39
|
-
zeitwerk (~> 2.
|
|
39
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
|
40
40
|
addressable (2.7.0)
|
|
41
41
|
public_suffix (>= 2.0.2, < 5.0)
|
|
42
42
|
ast (2.4.0)
|
|
43
43
|
base58 (0.2.3)
|
|
44
44
|
bip_mnemonic (0.0.4)
|
|
45
|
-
builder (3.2.
|
|
45
|
+
builder (3.2.4)
|
|
46
46
|
coderay (1.1.2)
|
|
47
|
-
concurrent-ruby (1.1.
|
|
47
|
+
concurrent-ruby (1.1.6)
|
|
48
48
|
crack (0.4.3)
|
|
49
49
|
safe_yaml (~> 1.0.0)
|
|
50
|
-
crass (1.0.
|
|
50
|
+
crass (1.0.6)
|
|
51
51
|
diff-lcs (1.3)
|
|
52
52
|
domain_name (0.5.20190701)
|
|
53
53
|
unf (>= 0.0.5, < 1.0.0)
|
|
54
54
|
erubi (1.9.0)
|
|
55
|
-
ffi (1.
|
|
55
|
+
ffi (1.13.1)
|
|
56
56
|
hashdiff (1.0.0)
|
|
57
57
|
http-cookie (1.0.3)
|
|
58
58
|
domain_name (~> 0.5)
|
|
59
59
|
httparty (0.17.3)
|
|
60
60
|
mime-types (~> 3.0)
|
|
61
61
|
multi_xml (>= 0.5.2)
|
|
62
|
-
i18n (1.
|
|
62
|
+
i18n (1.8.3)
|
|
63
63
|
concurrent-ruby (~> 1.0)
|
|
64
64
|
jaro_winkler (1.5.3)
|
|
65
|
-
loofah (2.
|
|
65
|
+
loofah (2.6.0)
|
|
66
66
|
crass (~> 1.0.2)
|
|
67
67
|
nokogiri (>= 1.5.9)
|
|
68
68
|
method_source (0.9.2)
|
|
69
69
|
mime-types (3.3.1)
|
|
70
70
|
mime-types-data (~> 3.2015)
|
|
71
|
-
mime-types-data (3.
|
|
71
|
+
mime-types-data (3.2020.0512)
|
|
72
72
|
mini_portile2 (2.4.0)
|
|
73
|
-
minitest (5.
|
|
73
|
+
minitest (5.14.1)
|
|
74
74
|
money-tree (0.10.0)
|
|
75
75
|
ffi
|
|
76
76
|
multi_xml (0.6.0)
|
|
77
77
|
netrc (0.11.0)
|
|
78
|
-
nokogiri (1.10.
|
|
78
|
+
nokogiri (1.10.9)
|
|
79
79
|
mini_portile2 (~> 2.4.0)
|
|
80
80
|
parallel (1.18.0)
|
|
81
81
|
parser (2.6.5.0)
|
|
@@ -84,7 +84,7 @@ GEM
|
|
|
84
84
|
coderay (~> 1.1.0)
|
|
85
85
|
method_source (~> 0.9.0)
|
|
86
86
|
public_suffix (4.0.1)
|
|
87
|
-
rack (2.
|
|
87
|
+
rack (2.2.3)
|
|
88
88
|
rack-test (1.1.0)
|
|
89
89
|
rack (>= 1.0, < 3)
|
|
90
90
|
rails-dom-testing (2.0.3)
|
|
@@ -92,14 +92,14 @@ GEM
|
|
|
92
92
|
nokogiri (>= 1.6)
|
|
93
93
|
rails-html-sanitizer (1.3.0)
|
|
94
94
|
loofah (~> 2.3)
|
|
95
|
-
railties (6.0.
|
|
96
|
-
actionpack (= 6.0.
|
|
97
|
-
activesupport (= 6.0.
|
|
95
|
+
railties (6.0.3.2)
|
|
96
|
+
actionpack (= 6.0.3.2)
|
|
97
|
+
activesupport (= 6.0.3.2)
|
|
98
98
|
method_source
|
|
99
99
|
rake (>= 0.8.7)
|
|
100
100
|
thor (>= 0.20.3, < 2.0)
|
|
101
101
|
rainbow (3.0.0)
|
|
102
|
-
rake (
|
|
102
|
+
rake (13.0.1)
|
|
103
103
|
rbnacl (5.0.0)
|
|
104
104
|
ffi
|
|
105
105
|
rest-client (2.0.2)
|
|
@@ -138,20 +138,20 @@ GEM
|
|
|
138
138
|
rubocop-rails (~> 2.0)
|
|
139
139
|
ruby-progressbar (1.10.1)
|
|
140
140
|
safe_yaml (1.0.5)
|
|
141
|
-
thor (0.
|
|
141
|
+
thor (1.0.1)
|
|
142
142
|
thread_safe (0.3.6)
|
|
143
|
-
tzinfo (1.2.
|
|
143
|
+
tzinfo (1.2.7)
|
|
144
144
|
thread_safe (~> 0.1)
|
|
145
145
|
unf (0.1.4)
|
|
146
146
|
unf_ext
|
|
147
|
-
unf_ext (0.0.7.
|
|
147
|
+
unf_ext (0.0.7.7)
|
|
148
148
|
unicode-display_width (1.6.0)
|
|
149
149
|
vcr (4.0.0)
|
|
150
150
|
webmock (3.7.6)
|
|
151
151
|
addressable (>= 2.3.6)
|
|
152
152
|
crack (>= 0.3.2)
|
|
153
153
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
154
|
-
zeitwerk (2.
|
|
154
|
+
zeitwerk (2.3.0)
|
|
155
155
|
|
|
156
156
|
PLATFORMS
|
|
157
157
|
ruby
|
|
@@ -159,7 +159,7 @@ PLATFORMS
|
|
|
159
159
|
DEPENDENCIES
|
|
160
160
|
bundler (~> 1.16)
|
|
161
161
|
pry
|
|
162
|
-
rake (~>
|
|
162
|
+
rake (~> 13.0)
|
|
163
163
|
rspec (~> 3.0)
|
|
164
164
|
rubocop-rails_config
|
|
165
165
|
tezos_client!
|
data/lib/tezos_client.rb
CHANGED
|
@@ -36,6 +36,8 @@ require "tezos_client/smartpy_interface"
|
|
|
36
36
|
|
|
37
37
|
require "tezos_client/tools/convert_to_hash"
|
|
38
38
|
require "tezos_client/tools/find_big_maps_in_storage"
|
|
39
|
+
require "tezos_client/tools/hash_to_micheline"
|
|
40
|
+
require "tezos_client/tools/annots_to_type"
|
|
39
41
|
|
|
40
42
|
class TezosClient
|
|
41
43
|
using CurrencyUtils
|
data/lib/tezos_client/crypto.rb
CHANGED
|
@@ -165,6 +165,22 @@ class TezosClient
|
|
|
165
165
|
end
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
# payload must be bytes
|
|
169
|
+
def check_signature(public_key:, signature:, payload:)
|
|
170
|
+
verify_key = RbNaCl::VerifyKey.new(decode_tz(public_key).to_bin)
|
|
171
|
+
|
|
172
|
+
bin_sig = decode_tz(signature).to_bin
|
|
173
|
+
payload_hash = RbNaCl::Hash::Blake2b.digest(ignore_0x(payload).to_bin, digest_size: 32)
|
|
174
|
+
|
|
175
|
+
verify_key.verify(bin_sig, payload_hash)
|
|
176
|
+
rescue RbNaCl::BadSignatureError
|
|
177
|
+
false
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def check_signature!(public_key:, signature:, payload:)
|
|
181
|
+
check_signature(public_key: public_key, signature: signature, payload: payload) || raise(BadSignatureError)
|
|
182
|
+
end
|
|
183
|
+
|
|
168
184
|
def operation_id(signed_operation_hex)
|
|
169
185
|
hash = RbNaCl::Hash::Blake2b.digest(
|
|
170
186
|
signed_operation_hex.to_bin,
|
|
@@ -220,5 +236,9 @@ class TezosClient
|
|
|
220
236
|
RbNaCl::SigningKey.generate
|
|
221
237
|
end
|
|
222
238
|
end
|
|
239
|
+
|
|
240
|
+
def ignore_0x(payload)
|
|
241
|
+
payload.starts_with?("0x") ? payload[2..-1] : payload
|
|
242
|
+
end
|
|
223
243
|
end
|
|
224
244
|
end
|
|
@@ -184,7 +184,8 @@ class TezosClient
|
|
|
184
184
|
operations: rpc_operation_args,
|
|
185
185
|
signature: base_58_signature,
|
|
186
186
|
protocol: protocol,
|
|
187
|
-
branch: branch
|
|
187
|
+
branch: branch
|
|
188
|
+
)
|
|
188
189
|
|
|
189
190
|
ensure_applied!(rpc_responses)
|
|
190
191
|
|
|
@@ -201,37 +202,40 @@ class TezosClient
|
|
|
201
202
|
end
|
|
202
203
|
|
|
203
204
|
private
|
|
204
|
-
def ensure_applied!(rpc_responses)
|
|
205
|
-
metadatas = rpc_responses.map { |response| response[:metadata] }
|
|
206
|
-
operation_results = metadatas.map { |metadata| metadata[:operation_result] }
|
|
207
205
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
206
|
+
def ensure_applied!(rpc_responses)
|
|
207
|
+
metadatas = rpc_responses.map { |response| response[:metadata] }
|
|
208
|
+
operation_results = metadatas.map { |metadata| metadata[:operation_result] }
|
|
209
|
+
internal_operations = metadatas.map { |metadata| metadata[:internal_operation_results] }.flatten.compact
|
|
210
|
+
operation_results.concat(internal_operations.map { |internal_operation| internal_operation[:result] })
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
failed = operation_results.detect do |operation_result|
|
|
213
|
+
operation_result != nil && operation_result[:status] != "applied"
|
|
214
|
+
end
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
operation_result[:status] == "failed"
|
|
216
|
-
end
|
|
216
|
+
return metadatas if failed.nil?
|
|
217
217
|
|
|
218
|
-
|
|
218
|
+
failed_operation_result = operation_results.detect do |operation_result|
|
|
219
|
+
operation_result[:status] == "failed"
|
|
219
220
|
end
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
case error[:id]
|
|
224
|
-
when TezBalanceTooLow::FIRST_ERROR_REGEXP
|
|
225
|
-
TezBalanceTooLow
|
|
226
|
-
when ScriptRuntimeError::FIRST_ERROR_REGEXP
|
|
227
|
-
ScriptRuntimeError
|
|
228
|
-
else
|
|
229
|
-
OperationFailure
|
|
230
|
-
end
|
|
231
|
-
end
|
|
222
|
+
failed!("failed", failed_operation_result[:errors], operation_results)
|
|
223
|
+
end
|
|
232
224
|
|
|
233
|
-
|
|
234
|
-
|
|
225
|
+
def exception_klass(errors)
|
|
226
|
+
error = errors[0]
|
|
227
|
+
case error[:id]
|
|
228
|
+
when TezBalanceTooLow::FIRST_ERROR_REGEXP
|
|
229
|
+
TezBalanceTooLow
|
|
230
|
+
when ScriptRuntimeError::FIRST_ERROR_REGEXP
|
|
231
|
+
ScriptRuntimeError
|
|
232
|
+
else
|
|
233
|
+
OperationFailure
|
|
235
234
|
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def failed!(status, errors, metadata)
|
|
238
|
+
raise exception_klass(errors).new(metadata: metadata, errors: errors, status: status)
|
|
239
|
+
end
|
|
236
240
|
end
|
|
237
|
-
end
|
|
241
|
+
end
|
|
@@ -28,20 +28,28 @@ class TezosClient
|
|
|
28
28
|
get "#{contract_link(contract_id)}/manager_key"
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def contract_storage_type(contract_id)
|
|
32
|
+
contract = contract_detail(contract_id)
|
|
33
|
+
contract[:script][:code].find { |elem| elem[:prim] == "storage" }[:args].first
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
def contract_storage(contract_id)
|
|
32
37
|
get "#{contract_link(contract_id)}/storage"
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
def entrypoint(contract_id, entrypoint)
|
|
41
|
+
get("#{contract_link(contract_id)}/entrypoints/#{entrypoint}")
|
|
42
|
+
end
|
|
43
|
+
|
|
35
44
|
def big_map_value(big_map_id:, key:, key_type:)
|
|
36
45
|
expr_key = encode_script_expr(data: key, type: key_type)
|
|
37
46
|
|
|
38
47
|
get "/chains/main/blocks/head/context/big_maps/#{big_map_id}/#{expr_key}"
|
|
39
48
|
end
|
|
40
49
|
|
|
41
|
-
def
|
|
50
|
+
def contract_big_maps(contract_address)
|
|
42
51
|
contract_storage = contract_storage(contract_address)
|
|
43
|
-
|
|
44
|
-
storage_type = contract[:script][:code].find { |elem| elem[:prim] == "storage" }[:args].first
|
|
52
|
+
storage_type = contract_storage_type(contract_address)
|
|
45
53
|
|
|
46
54
|
TezosClient::Tools::FindBigMapsInStorage.run!(
|
|
47
55
|
storage: contract_storage,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient::Tools::AnnotsToType < ActiveInteraction::Base
|
|
4
|
+
# example of typed_annots :
|
|
5
|
+
# {
|
|
6
|
+
# spending_ref: "string",
|
|
7
|
+
# remainder_amount: "nat",
|
|
8
|
+
# expires_at: "timestamp"
|
|
9
|
+
# }
|
|
10
|
+
hash :typed_annots, strip: false
|
|
11
|
+
|
|
12
|
+
validate :validate_types
|
|
13
|
+
|
|
14
|
+
def execute
|
|
15
|
+
return { "prim" => typed_annots.values.first } if typed_annots.size == 1
|
|
16
|
+
|
|
17
|
+
{ "prim" => "pair", "args" => generate_type_args(ordered_annots) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
def generate_type_args(annots)
|
|
22
|
+
annot = annots.pop
|
|
23
|
+
annot_type = typed_annots[annot]
|
|
24
|
+
|
|
25
|
+
unless annots.size == 1
|
|
26
|
+
return [{ "prim" => "pair", "args" => generate_type_args(annots) },
|
|
27
|
+
{ "prim" => annot_type, "annots" => ["%#{annot}"] }]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
generated_args = [{ "prim" => annot_type, "annots" => ["%#{annot}"] }]
|
|
31
|
+
annot = annots.pop
|
|
32
|
+
annot_type = typed_annots[annot]
|
|
33
|
+
generated_args.unshift({ "prim" => annot_type, "annots" => ["%#{annot}"] })
|
|
34
|
+
|
|
35
|
+
generated_args
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ordered_annots
|
|
39
|
+
@ordered_annots ||= typed_annots.keys.sort
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def validate_types
|
|
43
|
+
allowed_types = TezosClient::Tools::HashToMicheline::TYPES_MAPPING.keys
|
|
44
|
+
return if typed_annots.values.map(&:to_sym).all? { |type| type.in? allowed_types }
|
|
45
|
+
|
|
46
|
+
errors.add(:base, "The allowed types are: #{allowed_types.join(', ')}")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -1,99 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
interface :data
|
|
5
|
-
interface :type
|
|
3
|
+
require_relative "convert_to_hash/base"
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
decorated_value
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
private
|
|
12
|
-
def value
|
|
13
|
-
case type[:prim]
|
|
14
|
-
when "pair"
|
|
15
|
-
pair_type
|
|
16
|
-
when "list"
|
|
17
|
-
list_type
|
|
18
|
-
when "int"
|
|
19
|
-
int_type
|
|
20
|
-
when "nat"
|
|
21
|
-
int_type
|
|
22
|
-
when "key"
|
|
23
|
-
key_type
|
|
24
|
-
when "timestamp"
|
|
25
|
-
timestamp_type
|
|
26
|
-
when "string"
|
|
27
|
-
string_type
|
|
28
|
-
when "address"
|
|
29
|
-
address_type
|
|
30
|
-
else
|
|
31
|
-
raise "type '#{type[:prim]}' not implemented"
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def pair_type
|
|
36
|
-
raise "Not a 'Pair' type" unless data[:prim] == "Pair"
|
|
37
|
-
raise "Difference detected between data and type \nDATA: #{data} \nTYPE:#{type} " unless data[:args].size == type[:args].size
|
|
38
|
-
|
|
39
|
-
(data[:args]).zip(type[:args]).map do |data_n, type_n|
|
|
40
|
-
compose(
|
|
41
|
-
TezosClient::Tools::ConvertToHash,
|
|
42
|
-
data: data_n,
|
|
43
|
-
type: type_n
|
|
44
|
-
)
|
|
45
|
-
end.reduce({}, &:merge)
|
|
46
|
-
end
|
|
5
|
+
Dir[File.join(__dir__, "convert_to_hash", "*.rb")].each { |file| require file }
|
|
47
6
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
7
|
+
class TezosClient
|
|
8
|
+
module Tools
|
|
9
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
10
|
+
interface :data
|
|
11
|
+
interface :type
|
|
51
12
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
TezosClient::Tools::ConvertToHash,
|
|
56
|
-
data: elem,
|
|
57
|
-
type: element_type
|
|
58
|
-
)
|
|
13
|
+
def execute
|
|
14
|
+
TezosClient::Tools::ConvertToHash::Base.new(data: data, type: type).value
|
|
15
|
+
end
|
|
59
16
|
end
|
|
60
17
|
end
|
|
61
|
-
|
|
62
|
-
def int_type
|
|
63
|
-
data[:int].to_i
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def key_type
|
|
67
|
-
data[:bytes] || data[:string]
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def timestamp_type
|
|
71
|
-
Time.zone.at(data[:int].to_i)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def string_type
|
|
75
|
-
data[:string]
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def address_type
|
|
79
|
-
data[:bytes] || data[:string]
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def decorated_value
|
|
83
|
-
anonymous? ? value : { var_name => value }
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def anonymous?
|
|
87
|
-
!(type.key?(:annots) && type[:annots].any?)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def var_name_annot
|
|
91
|
-
type[:annots].first
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def var_name
|
|
95
|
-
return nil if anonymous?
|
|
96
|
-
|
|
97
|
-
"#{var_name_annot[1..-1]}".to_sym
|
|
98
|
-
end
|
|
99
18
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Address < Base
|
|
7
|
+
include TezosClient::Crypto
|
|
8
|
+
|
|
9
|
+
def decode
|
|
10
|
+
if data.key?(:bytes)
|
|
11
|
+
if data[:bytes].start_with?("0000")
|
|
12
|
+
encode_tz(:tz1, data[:bytes][4..-1])
|
|
13
|
+
elsif data[:bytes].start_with?("0001")
|
|
14
|
+
encode_tz(:tz2, data[:bytes][4..-1])
|
|
15
|
+
elsif data[:bytes].start_with?("0002")
|
|
16
|
+
encode_tz(:tz3, data[:bytes][4..-1])
|
|
17
|
+
elsif data[:bytes].start_with?("01")
|
|
18
|
+
encode_tz(:KT, data[:bytes][2..-3])
|
|
19
|
+
else
|
|
20
|
+
data[:bytes]
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
data[:string]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Base
|
|
7
|
+
def initialize(data:, type:)
|
|
8
|
+
@data = data
|
|
9
|
+
@type = type
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
attr_accessor :data, :type
|
|
13
|
+
|
|
14
|
+
def value
|
|
15
|
+
anonymous? ? decode : { var_name => decode }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
def decode
|
|
20
|
+
klass.new(
|
|
21
|
+
data: data,
|
|
22
|
+
type: type
|
|
23
|
+
).decode
|
|
24
|
+
|
|
25
|
+
rescue NameError => e
|
|
26
|
+
raise NotImplementedError, "type '#{type[:prim]}' not implemented"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def anonymous?
|
|
30
|
+
!(type.key?(:annots) && type[:annots].any?)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def var_name_annot
|
|
34
|
+
type[:annots].first
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def var_name
|
|
38
|
+
return nil if anonymous?
|
|
39
|
+
|
|
40
|
+
"#{var_name_annot[1..-1]}".to_sym
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def klass
|
|
45
|
+
"TezosClient::Tools::ConvertToHash::#{type[:prim].camelize}".constantize
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
class BigMap < Struct.new(:name, :id, :value_type, :key_type); end
|
|
5
|
+
|
|
6
|
+
module Tools
|
|
7
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
8
|
+
class BigMap < Base
|
|
9
|
+
def decode
|
|
10
|
+
::TezosClient::BigMap.new(
|
|
11
|
+
var_name,
|
|
12
|
+
data[:int],
|
|
13
|
+
type[:args].second,
|
|
14
|
+
type[:args].first
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Key < Base
|
|
7
|
+
include TezosClient::Crypto
|
|
8
|
+
|
|
9
|
+
def decode
|
|
10
|
+
if data.key?(:bytes)
|
|
11
|
+
if data[:bytes].start_with?("00")
|
|
12
|
+
encode_tz(:edpk, data[:bytes][2..-1])
|
|
13
|
+
elsif data[:bytes].start_with?("01")
|
|
14
|
+
encode_tz(:sppk, data[:bytes][2..-1])
|
|
15
|
+
elsif data[:bytes].start_with?("02")
|
|
16
|
+
encode_tz(:p2pk, data[:bytes][2..-1])
|
|
17
|
+
else
|
|
18
|
+
data[:bytes]
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
data[:string]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class List < Base
|
|
7
|
+
def decode
|
|
8
|
+
data.map do |elem|
|
|
9
|
+
TezosClient::Tools::ConvertToHash::Base.new(
|
|
10
|
+
data: elem,
|
|
11
|
+
type: elem_type
|
|
12
|
+
).value
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def elem_type
|
|
17
|
+
type[:args].first
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Map < Base
|
|
7
|
+
def decode
|
|
8
|
+
new_map = {}
|
|
9
|
+
|
|
10
|
+
data.each do |elem|
|
|
11
|
+
raise "Not a 'Map' type" unless elem[:prim] == "Elt"
|
|
12
|
+
|
|
13
|
+
key = TezosClient::Tools::ConvertToHash::Base.new(
|
|
14
|
+
data: elem[:args].first,
|
|
15
|
+
type: type[:args].first
|
|
16
|
+
).value
|
|
17
|
+
|
|
18
|
+
value = TezosClient::Tools::ConvertToHash::Base.new(
|
|
19
|
+
data: elem[:args].second,
|
|
20
|
+
type: type[:args].second
|
|
21
|
+
).value
|
|
22
|
+
|
|
23
|
+
new_map[key] = value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
new_map
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Pair < Base
|
|
7
|
+
def decode
|
|
8
|
+
raise "Not a 'Pair' type" unless data[:prim] == "Pair"
|
|
9
|
+
raise "Difference detected between data and type \nDATA: #{data} \nTYPE:#{type} " unless data[:args].size == type[:args].size
|
|
10
|
+
|
|
11
|
+
(data[:args]).zip(type[:args]).map do |data_n, type_n|
|
|
12
|
+
TezosClient::Tools::ConvertToHash::Base.new(
|
|
13
|
+
data: data_n,
|
|
14
|
+
type: type_n
|
|
15
|
+
).value
|
|
16
|
+
end.reduce({}, &:merge)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Signature < Base
|
|
7
|
+
include TezosClient::Crypto
|
|
8
|
+
|
|
9
|
+
def decode
|
|
10
|
+
if data.key?(:bytes)
|
|
11
|
+
encode_tz(:edsig, data[:bytes])
|
|
12
|
+
else
|
|
13
|
+
data[:string]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient
|
|
4
|
+
module Tools
|
|
5
|
+
class ConvertToHash < ActiveInteraction::Base
|
|
6
|
+
class Timestamp < Base
|
|
7
|
+
def decode
|
|
8
|
+
if data.key? :int
|
|
9
|
+
Time.zone.at(data[:int].to_i)
|
|
10
|
+
elsif data.key? :string
|
|
11
|
+
Time.zone.parse(data[:string])
|
|
12
|
+
else
|
|
13
|
+
raise "Can not convert timestamp: #{data}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -7,50 +7,19 @@ class TezosClient::Tools::FindBigMapsInStorage < ActiveInteraction::Base
|
|
|
7
7
|
strip: false
|
|
8
8
|
|
|
9
9
|
def execute
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
when "big_map"
|
|
16
|
-
big_map_type(data: storage, type: storage_type)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def pair_type(data:, type:)
|
|
21
|
-
raise "Not a 'Pair' type" unless data[:prim] == "Pair"
|
|
22
|
-
raise "Difference detected between data and type \nDATA: #{data} \nTYPE:#{type} " unless data[:args].size == type[:args].size
|
|
23
|
-
|
|
24
|
-
(0 .. data[:args].size - 1).map do |iter|
|
|
25
|
-
compose(
|
|
26
|
-
TezosClient::Tools::FindBigMapsInStorage,
|
|
27
|
-
storage: data[:args][iter],
|
|
28
|
-
storage_type: type[:args][iter]
|
|
29
|
-
)
|
|
30
|
-
end.compact.flatten
|
|
10
|
+
hash_storage
|
|
11
|
+
.map(&:last)
|
|
12
|
+
.select { |value| value.is_a? TezosClient::BigMap }
|
|
13
|
+
.map(&:to_h)
|
|
14
|
+
.map(&:with_indifferent_access)
|
|
31
15
|
end
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
data.map do |elem|
|
|
17
|
+
private
|
|
18
|
+
def hash_storage
|
|
36
19
|
compose(
|
|
37
20
|
TezosClient::Tools::ConvertToHash,
|
|
38
|
-
data:
|
|
39
|
-
type:
|
|
21
|
+
data: storage,
|
|
22
|
+
type: storage_type
|
|
40
23
|
)
|
|
41
24
|
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def big_map_type(data:, type:)
|
|
45
|
-
{
|
|
46
|
-
name: var_name(type),
|
|
47
|
-
id: data[:int],
|
|
48
|
-
value_type: type[:args].second,
|
|
49
|
-
key_type: type[:args].first
|
|
50
|
-
}.with_indifferent_access
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def var_name(type)
|
|
54
|
-
"#{type[:annots].first[1..-1]}".to_sym
|
|
55
|
-
end
|
|
56
25
|
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class TezosClient::Tools::HashToMicheline < ActiveInteraction::Base
|
|
4
|
+
# TODO: handle Arrays and Maps
|
|
5
|
+
TYPES_MAPPING = {
|
|
6
|
+
int: :int,
|
|
7
|
+
nat: :int,
|
|
8
|
+
string: :string,
|
|
9
|
+
signature: :string,
|
|
10
|
+
bytes: :bytes,
|
|
11
|
+
timestamp: :int,
|
|
12
|
+
key: :string,
|
|
13
|
+
address: :string
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
string :contract_address, default: nil
|
|
17
|
+
string :entrypoint, default: nil
|
|
18
|
+
# example of params:
|
|
19
|
+
# {
|
|
20
|
+
# spending_ref: "toto",
|
|
21
|
+
# expires_at: Time.now
|
|
22
|
+
# }
|
|
23
|
+
hash :params, strip: false
|
|
24
|
+
hash :storage_type, strip: false, default: {}
|
|
25
|
+
interface :blockchain_client, methods: [:entrypoint], default: -> { TezosClient.new }
|
|
26
|
+
|
|
27
|
+
# if storage_type is not received, it is fetched from the blockchain using
|
|
28
|
+
# contract_address and entrypoint (that are mandatory in this case)
|
|
29
|
+
validate :storage_type_or_contract_address_presence
|
|
30
|
+
|
|
31
|
+
def execute
|
|
32
|
+
return hash_type_to_hash_data(_storage_type.fetch(:prim), params.values.first) if params.size == 1
|
|
33
|
+
|
|
34
|
+
{ prim: "Pair", args: generate_micheline(_storage_type[:args]) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
def generate_micheline(remaining_storage_type)
|
|
39
|
+
remaining_storage_type.each_with_object([]) do |h, acc|
|
|
40
|
+
next acc << { prim: "Pair", args: generate_micheline(h[:args]) } if h[:prim] == "pair"
|
|
41
|
+
|
|
42
|
+
annot = h[:annots].first.slice(1..-1).to_sym # remove '%'
|
|
43
|
+
acc << hash_type_to_hash_data(h[:prim], params.fetch(annot))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def convert_type(michelson_type)
|
|
48
|
+
TYPES_MAPPING.fetch(michelson_type.to_sym)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def hash_type_to_hash_data(michelson_type, value)
|
|
52
|
+
type = convert_type(michelson_type)
|
|
53
|
+
|
|
54
|
+
converted_value = case michelson_type.to_sym
|
|
55
|
+
when :nat, :int
|
|
56
|
+
value.to_s
|
|
57
|
+
when :timestamp
|
|
58
|
+
errors.add(:base, "timestamp input must be an instance of Time") unless value.is_a? Time
|
|
59
|
+
|
|
60
|
+
value.to_i.to_s
|
|
61
|
+
else
|
|
62
|
+
value
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
{ type => converted_value }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def _storage_type
|
|
69
|
+
(storage_type.presence || blockchain_client.entrypoint(contract_address, entrypoint)).deep_symbolize_keys
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def storage_type_or_contract_address_presence
|
|
73
|
+
return if storage_type.present? ^ (contract_address.present? && entrypoint.present?)
|
|
74
|
+
|
|
75
|
+
errors.add(:base,
|
|
76
|
+
"You should provide the contract_address and the entrypoint only if storage_type is not provided")
|
|
77
|
+
end
|
|
78
|
+
end
|
data/lib/tezos_client/version.rb
CHANGED
data/tezos_client.gemspec
CHANGED
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
|
34
34
|
spec.require_paths = ["lib"]
|
|
35
35
|
|
|
36
36
|
spec.add_development_dependency "bundler", "~> 1.16"
|
|
37
|
-
spec.add_development_dependency "rake", "~>
|
|
37
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
38
38
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
39
39
|
spec.add_development_dependency "rubocop-rails_config"
|
|
40
40
|
spec.add_development_dependency "webmock"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tezos_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pierre Michard
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-07-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '13.0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
40
|
+
version: '13.0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rspec
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -274,8 +274,23 @@ files:
|
|
|
274
274
|
- lib/tezos_client/smartpy_inteface/smartpy_wrapper.rb
|
|
275
275
|
- lib/tezos_client/smartpy_interface.rb
|
|
276
276
|
- lib/tezos_client/string_utils.rb
|
|
277
|
+
- lib/tezos_client/tools/annots_to_type.rb
|
|
277
278
|
- lib/tezos_client/tools/convert_to_hash.rb
|
|
279
|
+
- lib/tezos_client/tools/convert_to_hash/address.rb
|
|
280
|
+
- lib/tezos_client/tools/convert_to_hash/base.rb
|
|
281
|
+
- lib/tezos_client/tools/convert_to_hash/big_map.rb
|
|
282
|
+
- lib/tezos_client/tools/convert_to_hash/bytes.rb
|
|
283
|
+
- lib/tezos_client/tools/convert_to_hash/int.rb
|
|
284
|
+
- lib/tezos_client/tools/convert_to_hash/key.rb
|
|
285
|
+
- lib/tezos_client/tools/convert_to_hash/list.rb
|
|
286
|
+
- lib/tezos_client/tools/convert_to_hash/map.rb
|
|
287
|
+
- lib/tezos_client/tools/convert_to_hash/nat.rb
|
|
288
|
+
- lib/tezos_client/tools/convert_to_hash/pair.rb
|
|
289
|
+
- lib/tezos_client/tools/convert_to_hash/signature.rb
|
|
290
|
+
- lib/tezos_client/tools/convert_to_hash/string.rb
|
|
291
|
+
- lib/tezos_client/tools/convert_to_hash/timestamp.rb
|
|
278
292
|
- lib/tezos_client/tools/find_big_maps_in_storage.rb
|
|
293
|
+
- lib/tezos_client/tools/hash_to_micheline.rb
|
|
279
294
|
- lib/tezos_client/tools/system_call.rb
|
|
280
295
|
- lib/tezos_client/tools/temporary_file.rb
|
|
281
296
|
- lib/tezos_client/version.rb
|
|
@@ -286,7 +301,7 @@ licenses:
|
|
|
286
301
|
- MIT
|
|
287
302
|
metadata:
|
|
288
303
|
allowed_push_host: https://rubygems.org
|
|
289
|
-
post_install_message:
|
|
304
|
+
post_install_message:
|
|
290
305
|
rdoc_options: []
|
|
291
306
|
require_paths:
|
|
292
307
|
- lib
|
|
@@ -301,9 +316,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
301
316
|
- !ruby/object:Gem::Version
|
|
302
317
|
version: '0'
|
|
303
318
|
requirements: []
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
signing_key:
|
|
319
|
+
rubygems_version: 3.0.4
|
|
320
|
+
signing_key:
|
|
307
321
|
specification_version: 4
|
|
308
322
|
summary: Wrapper to the tezos client.
|
|
309
323
|
test_files: []
|