ton-sdk-ruby 0.0.1
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 +7 -0
- data/bin/ton-sdk-ruby +7 -0
- data/lib/ton-sdk-ruby/bit_array/bit_array.rb +169 -0
- data/lib/ton-sdk-ruby/boc/builder.rb +261 -0
- data/lib/ton-sdk-ruby/boc/cell.rb +362 -0
- data/lib/ton-sdk-ruby/boc/hashmap.rb +398 -0
- data/lib/ton-sdk-ruby/boc/mask.rb +46 -0
- data/lib/ton-sdk-ruby/boc/serializer.rb +428 -0
- data/lib/ton-sdk-ruby/boc/slice.rb +335 -0
- data/lib/ton-sdk-ruby/johnny_mnemonic/ton_mnemonic.rb +144 -0
- data/lib/ton-sdk-ruby/johnny_mnemonic/utils.rb +40 -0
- data/lib/ton-sdk-ruby/johnny_mnemonic/words/english.json +2050 -0
- data/lib/ton-sdk-ruby/providers/provider.rb +41 -0
- data/lib/ton-sdk-ruby/providers/toncenter.rb +71 -0
- data/lib/ton-sdk-ruby/types/address.rb +203 -0
- data/lib/ton-sdk-ruby/types/block.rb +388 -0
- data/lib/ton-sdk-ruby/types/coins.rb +188 -0
- data/lib/ton-sdk-ruby/utils/bits.rb +25 -0
- data/lib/ton-sdk-ruby/utils/checksum.rb +46 -0
- data/lib/ton-sdk-ruby/utils/hash.rb +15 -0
- data/lib/ton-sdk-ruby/utils/helpers.rb +161 -0
- data/lib/ton-sdk-ruby/utils/numbers.rb +42 -0
- data/lib/ton-sdk-ruby/version.rb +4 -0
- data/lib/ton-sdk-ruby.rb +29 -0
- metadata +137 -0
@@ -0,0 +1,428 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module TonSdkRuby
|
4
|
+
extend TonSdkRuby
|
5
|
+
|
6
|
+
REACH_BOC_MAGIC_PREFIX = hex_to_bytes('B5EE9C72')
|
7
|
+
LEAN_BOC_MAGIC_PREFIX = hex_to_bytes('68FF65F3')
|
8
|
+
LEAN_BOC_MAGIC_PREFIX_CRC = hex_to_bytes('ACC3A728')
|
9
|
+
|
10
|
+
class BOCOptions
|
11
|
+
attr_accessor :has_index, :hash_crc32, :has_cache_bits, :topological_order, :flags
|
12
|
+
end
|
13
|
+
|
14
|
+
class BocHeader
|
15
|
+
attr_accessor :has_index, :hash_crc32, :has_cache_bits, :flags, :size_bytes,
|
16
|
+
:offset_bytes, :cells_num, :roots_num, :absent_num,
|
17
|
+
:tot_cells_size, :root_list, :cells_data
|
18
|
+
end
|
19
|
+
|
20
|
+
class CellNode
|
21
|
+
attr_accessor :cell, :children, :scanned
|
22
|
+
end
|
23
|
+
|
24
|
+
class BuilderNode
|
25
|
+
attr_accessor :builder, :indent
|
26
|
+
end
|
27
|
+
|
28
|
+
class CellPointer
|
29
|
+
attr_accessor :cell, :type, :builder, :refs
|
30
|
+
end
|
31
|
+
|
32
|
+
class CellData
|
33
|
+
attr_accessor :pointer, :remainder
|
34
|
+
end
|
35
|
+
|
36
|
+
def deserialize_fift(data)
|
37
|
+
raise 'Can\'t deserialize. Empty fift hex.' if data.nil? || data.empty?
|
38
|
+
|
39
|
+
re = /((\s*)x{([0-9a-zA-Z_]+)}\n?)/mi
|
40
|
+
matches = data.scan(re) || []
|
41
|
+
|
42
|
+
raise 'Can\'t deserialize. Bad fift hex.' if matches.empty?
|
43
|
+
|
44
|
+
parse_fift_hex = lambda do |fift|
|
45
|
+
return [] if fift == '_'
|
46
|
+
|
47
|
+
bits = fift.split('')
|
48
|
+
.map { |el| el == '_' ? el : hex_to_bits(el).join('') }
|
49
|
+
.join('')
|
50
|
+
.sub(/1[0]*_$/, '')
|
51
|
+
.split('')
|
52
|
+
.map(&:to_i)
|
53
|
+
|
54
|
+
bits
|
55
|
+
end
|
56
|
+
|
57
|
+
if matches.length == 1
|
58
|
+
return [Cell.new(bits: parse_fift_hex.call(matches[0][2]))]
|
59
|
+
end
|
60
|
+
|
61
|
+
is_last_nested = lambda do |stack, indent|
|
62
|
+
last_stack_indent = stack[-1][:indent]
|
63
|
+
last_stack_indent != 0 && last_stack_indent >= indent
|
64
|
+
end
|
65
|
+
|
66
|
+
stack = matches.each_with_object([]).with_index do |(el, acc), i|
|
67
|
+
_, spaces, fift = el
|
68
|
+
is_last = i == matches.length - 1
|
69
|
+
indent = spaces.length
|
70
|
+
bits = parse_fift_hex.call(fift)
|
71
|
+
builder = Builder.new.store_bits(bits)
|
72
|
+
|
73
|
+
while !acc.empty? && is_last_nested.call(acc, indent)
|
74
|
+
b = acc.pop[:builder]
|
75
|
+
acc[-1][:builder].store_ref(b.cell)
|
76
|
+
end
|
77
|
+
|
78
|
+
if is_last
|
79
|
+
acc[-1][:builder].store_ref(builder.cell)
|
80
|
+
else
|
81
|
+
acc.push(indent: indent, builder: builder)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
stack.map { |el| el[:builder].cell }
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def deserialize_header(bytes)
|
90
|
+
raise 'Not enough bytes for magic prefix' if bytes.length < 4 + 1
|
91
|
+
|
92
|
+
crcbytes = bytes[0, bytes.length - 4]
|
93
|
+
prefix = bytes.shift(4)
|
94
|
+
flags_byte = bytes.shift
|
95
|
+
header = {
|
96
|
+
has_index: true,
|
97
|
+
hash_crc32: nil,
|
98
|
+
has_cache_bits: false,
|
99
|
+
flags: 0,
|
100
|
+
size_bytes: flags_byte,
|
101
|
+
offset_bytes: nil,
|
102
|
+
cells_num: nil,
|
103
|
+
roots_num: nil,
|
104
|
+
absent_num: nil,
|
105
|
+
tot_cells_size: nil,
|
106
|
+
root_list: nil,
|
107
|
+
cells_data: nil
|
108
|
+
}
|
109
|
+
|
110
|
+
if bytes_compare(prefix, REACH_BOC_MAGIC_PREFIX)
|
111
|
+
header[:has_index] = (flags_byte & 128) != 0
|
112
|
+
header[:has_cache_bits] = (flags_byte & 32) != 0
|
113
|
+
header[:flags] = (flags_byte & 16) * 2 + (flags_byte & 8)
|
114
|
+
header[:size_bytes] = flags_byte % 8
|
115
|
+
header[:hash_crc32] = flags_byte & 64
|
116
|
+
elsif bytes_compare(prefix, LEAN_BOC_MAGIC_PREFIX)
|
117
|
+
header[:hash_crc32] = 0
|
118
|
+
elsif bytes_compare(prefix, LEAN_BOC_MAGIC_PREFIX_CRC)
|
119
|
+
header[:hash_crc32] = 1
|
120
|
+
else
|
121
|
+
raise 'Bad magic prefix'
|
122
|
+
end
|
123
|
+
|
124
|
+
raise 'Not enough bytes for encoding cells counters' if bytes.length < 1 + 5 * header[:size_bytes]
|
125
|
+
|
126
|
+
offset_bytes = bytes.shift
|
127
|
+
header[:offset_bytes] = offset_bytes
|
128
|
+
header[:cells_num] = bytes_to_uint(bytes.shift(header[:size_bytes]))
|
129
|
+
header[:roots_num] = bytes_to_uint(bytes.shift(header[:size_bytes]))
|
130
|
+
header[:absent_num] = bytes_to_uint(bytes.shift(header[:size_bytes]))
|
131
|
+
header[:tot_cells_size] = bytes_to_uint(bytes.shift(offset_bytes))
|
132
|
+
|
133
|
+
raise 'Not enough bytes for encoding root cells hashes' if bytes.length < header[:roots_num] * header[:size_bytes]
|
134
|
+
|
135
|
+
header[:root_list] = Array.new(header[:roots_num]) do
|
136
|
+
ref_index = bytes_to_uint(bytes.shift(header[:size_bytes]))
|
137
|
+
ref_index
|
138
|
+
end
|
139
|
+
|
140
|
+
if header[:has_index]
|
141
|
+
raise 'Not enough bytes for index encoding' if bytes.length < header[:offset_bytes] * header[:cells_num]
|
142
|
+
Array.new(header[:cells_num]) { bytes.shift(header[:offset_bytes]) }
|
143
|
+
end
|
144
|
+
|
145
|
+
raise 'Not enough bytes for cells data' if bytes.length < header[:tot_cells_size]
|
146
|
+
# byebug
|
147
|
+
header[:cells_data] = bytes.shift(header[:tot_cells_size])
|
148
|
+
|
149
|
+
if header[:hash_crc32]
|
150
|
+
raise 'Not enough bytes for crc32c hashsum' if bytes.length < 4
|
151
|
+
|
152
|
+
result = crc32c_bytes_le(crcbytes)
|
153
|
+
|
154
|
+
raise 'Crc32c hashsum mismatch' unless bytes_compare(result, bytes.shift(4))
|
155
|
+
end
|
156
|
+
|
157
|
+
raise 'Too much bytes in BoC serialization' unless bytes.empty?
|
158
|
+
|
159
|
+
header
|
160
|
+
end
|
161
|
+
|
162
|
+
def deserialize_cell(remainder, ref_index_size)
|
163
|
+
raise "BoC not enough bytes to encode cell descriptors" if remainder.length < 2
|
164
|
+
|
165
|
+
refs_descriptor = remainder.shift
|
166
|
+
level = refs_descriptor >> 5
|
167
|
+
total_refs = refs_descriptor & 7
|
168
|
+
has_hashes = (refs_descriptor & 16) != 0
|
169
|
+
is_exotic = (refs_descriptor & 8) != 0
|
170
|
+
is_absent = total_refs == 7 && has_hashes
|
171
|
+
|
172
|
+
# For absent cells (i.e., external references), only refs descriptor is present
|
173
|
+
# Currently not implemented
|
174
|
+
if is_absent
|
175
|
+
raise "BoC can't deserialize absent cell"
|
176
|
+
end
|
177
|
+
|
178
|
+
raise "BoC cell can't has more than 4 refs #{total_refs}" if total_refs > 4
|
179
|
+
|
180
|
+
bits_descriptor = remainder.shift
|
181
|
+
is_augmented = (bits_descriptor & 1) != 0
|
182
|
+
data_size = (bits_descriptor >> 1) + (is_augmented ? 1 : 0)
|
183
|
+
hashes_size = has_hashes ? (level + 1) * 32 : 0
|
184
|
+
depth_size = has_hashes ? (level + 1) * 2 : 0
|
185
|
+
|
186
|
+
required_bytes = hashes_size + depth_size + data_size + ref_index_size * total_refs
|
187
|
+
|
188
|
+
raise "BoC not enough bytes to encode cell data" if remainder.length < required_bytes
|
189
|
+
|
190
|
+
remainder.shift(hashes_size + depth_size) if has_hashes
|
191
|
+
|
192
|
+
bits = if is_augmented
|
193
|
+
rollback(bytes_to_bits(remainder.shift(data_size)))
|
194
|
+
else
|
195
|
+
bytes_to_bits(remainder.shift(data_size))
|
196
|
+
end
|
197
|
+
|
198
|
+
raise "BoC not enough bytes for an exotic cell type" if is_exotic && bits.length < 8
|
199
|
+
|
200
|
+
type = if is_exotic
|
201
|
+
bits_to_int_uint(bits[0, 8], { type: "int" })
|
202
|
+
else
|
203
|
+
CellType::Ordinary
|
204
|
+
end
|
205
|
+
|
206
|
+
raise "BoC an exotic cell can't be of ordinary type" if is_exotic && type == CellType::Ordinary
|
207
|
+
|
208
|
+
pointer = {
|
209
|
+
type: type,
|
210
|
+
builder: Builder.new(bits.length).store_bits(bits),
|
211
|
+
refs: Array.new(total_refs) { bytes_to_uint(remainder.shift(ref_index_size)) }
|
212
|
+
}
|
213
|
+
|
214
|
+
{ pointer: pointer, remainder: remainder }
|
215
|
+
end
|
216
|
+
|
217
|
+
def deserialize(data, check_merkle_proofs = false)
|
218
|
+
has_merkle_proofs = false
|
219
|
+
bytes = Array.new(data)
|
220
|
+
pointers = []
|
221
|
+
header = deserialize_header(bytes)
|
222
|
+
|
223
|
+
header[:cells_num].times do
|
224
|
+
deserialized = deserialize_cell(header[:cells_data], header[:size_bytes])
|
225
|
+
header[:cells_data] = deserialized[:remainder]
|
226
|
+
pointers.push(deserialized[:pointer])
|
227
|
+
end
|
228
|
+
|
229
|
+
pointers.reverse_each.with_index do |pointer, i|
|
230
|
+
pointer_index = pointers.length - i - 1
|
231
|
+
cell_builder = pointer[:builder]
|
232
|
+
cell_type = pointer[:type]
|
233
|
+
|
234
|
+
pointer[:refs].each do |ref_index|
|
235
|
+
ref_builder = pointers[ref_index][:builder]
|
236
|
+
ref_type = pointers[ref_index][:type]
|
237
|
+
|
238
|
+
raise "Topological order is broken" if ref_index < pointer_index
|
239
|
+
|
240
|
+
if ref_type == CellType::MerkleProof || ref_type == CellType::MerkleUpdate
|
241
|
+
has_merkle_proofs = true
|
242
|
+
end
|
243
|
+
|
244
|
+
cell_builder.store_ref(ref_builder.cell(ref_type))
|
245
|
+
end
|
246
|
+
|
247
|
+
if cell_type == CellType::MerkleProof || cell_type == CellType::MerkleUpdate
|
248
|
+
has_merkle_proofs = true
|
249
|
+
end
|
250
|
+
|
251
|
+
pointers[pointer_index][:cell] = cell_builder.cell(cell_type)
|
252
|
+
end
|
253
|
+
|
254
|
+
raise "BOC does not contain Merkle Proofs" if check_merkle_proofs && !has_merkle_proofs
|
255
|
+
|
256
|
+
header[:root_list].map { |ref_index| pointers[ref_index][:cell] }
|
257
|
+
end
|
258
|
+
|
259
|
+
def depth_first_sort(root)
|
260
|
+
stack = [{
|
261
|
+
cell: Cell.new(refs: root),
|
262
|
+
children: root.length,
|
263
|
+
scanned: 0
|
264
|
+
}]
|
265
|
+
|
266
|
+
cells = []
|
267
|
+
hash_indexes = {}
|
268
|
+
|
269
|
+
process = lambda do |node|
|
270
|
+
ref = node[:cell].refs[node[:scanned]]
|
271
|
+
hash = ref.hash
|
272
|
+
index = hash_indexes[hash]
|
273
|
+
length = index.nil? ? cells.push(cell: ref, hash: hash) : cells.push(cells.delete_at(index))
|
274
|
+
|
275
|
+
stack.push(cell: ref, children: ref.refs.length, scanned: 0)
|
276
|
+
hash_indexes[hash] = length - 1
|
277
|
+
end
|
278
|
+
|
279
|
+
while !stack.empty?
|
280
|
+
current = stack.last
|
281
|
+
|
282
|
+
if current[:children] != current[:scanned]
|
283
|
+
process.call(current)
|
284
|
+
else
|
285
|
+
while !stack.empty? && current && current[:children] == current[:scanned]
|
286
|
+
stack.pop
|
287
|
+
current = stack.last
|
288
|
+
end
|
289
|
+
|
290
|
+
process.call(current) if current
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
result = cells.each_with_index.reduce({ cells: [], hashmap: {} }) do |acc, (el, i)|
|
295
|
+
unless el.nil?
|
296
|
+
acc[:cells].push(el[:cell])
|
297
|
+
acc[:hashmap][el[:hash]] = i
|
298
|
+
end
|
299
|
+
|
300
|
+
acc
|
301
|
+
end
|
302
|
+
|
303
|
+
result
|
304
|
+
end
|
305
|
+
|
306
|
+
def breadth_first_sort(root)
|
307
|
+
stack = Array.new(root)
|
308
|
+
cells = root.map { |el| { cell: el, hash: el.hash } }
|
309
|
+
hash_indexes = cells.map.with_index { |el, i| [el[:hash], i] }.to_h
|
310
|
+
|
311
|
+
while !stack.empty?
|
312
|
+
length = stack.length
|
313
|
+
|
314
|
+
stack.each do |node|
|
315
|
+
node.refs.each do |ref|
|
316
|
+
hash = ref.hash
|
317
|
+
index = hash_indexes[hash]
|
318
|
+
length = index.nil? ? cells.push(cell: ref, hash: hash).size : cells.push(cells.delete_at(index)).size
|
319
|
+
|
320
|
+
stack.push(ref)
|
321
|
+
hash_indexes[hash] = length - 1
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
stack.shift(length)
|
326
|
+
end
|
327
|
+
|
328
|
+
result = cells.each_with_index.reduce({ cells: [], hashmap: {} }) do |acc, (el, i)|
|
329
|
+
unless el.nil?
|
330
|
+
acc[:cells].push(el[:cell])
|
331
|
+
acc[:hashmap][el[:hash]] = i
|
332
|
+
end
|
333
|
+
|
334
|
+
acc
|
335
|
+
end
|
336
|
+
|
337
|
+
result
|
338
|
+
end
|
339
|
+
|
340
|
+
def serialize_cell(cell, hashmap, ref_index_size)
|
341
|
+
representation = cell.get_refs_descriptor +
|
342
|
+
cell.get_bits_descriptor +
|
343
|
+
cell.get_augmented_bits
|
344
|
+
|
345
|
+
serialized = cell.refs.reduce(representation) do |acc, ref|
|
346
|
+
ref_index = hashmap[ref.hash]
|
347
|
+
bits = Array.new(ref_index_size) { |i| (ref_index >> i) & 1 }
|
348
|
+
|
349
|
+
acc + bits.reverse
|
350
|
+
end
|
351
|
+
|
352
|
+
serialized
|
353
|
+
end
|
354
|
+
|
355
|
+
|
356
|
+
def serialize(root, options = {})
|
357
|
+
root = [*root]
|
358
|
+
# TODO: Implement breadthFirstSort and depthFirstSort functions
|
359
|
+
|
360
|
+
has_index = options.fetch(:has_index, false)
|
361
|
+
has_cache_bits = options.fetch(:has_cache_bits, false)
|
362
|
+
hash_crc32 = options.fetch(:hash_crc32, true)
|
363
|
+
topological_order = options.fetch(:topological_order, 'breadth-first')
|
364
|
+
flags = options.fetch(:flags, 0)
|
365
|
+
|
366
|
+
if topological_order == 'breadth-first'
|
367
|
+
breadth = breadth_first_sort(root)
|
368
|
+
cells_list = breadth[:cells]
|
369
|
+
hashmap = breadth[:hashmap]
|
370
|
+
else
|
371
|
+
breadth = depth_first_sort(root)
|
372
|
+
cells_list = breadth[:cells]
|
373
|
+
hashmap = breadth[:hashmap]
|
374
|
+
end
|
375
|
+
|
376
|
+
cells_num = cells_list.size
|
377
|
+
size = cells_num.to_s(2).size
|
378
|
+
size_bytes = [(size.to_f / 8).ceil, 1].max
|
379
|
+
cells_bits = []
|
380
|
+
size_index = []
|
381
|
+
|
382
|
+
cells_list.each do |cell|
|
383
|
+
bits = serialize_cell(cell, hashmap, size_bytes * 8)
|
384
|
+
cells_bits.concat(bits)
|
385
|
+
size_index.push(bits.length / 8)
|
386
|
+
end
|
387
|
+
|
388
|
+
full_size = cells_bits.length / 8
|
389
|
+
offset_bits = full_size.to_s(2).length
|
390
|
+
offset_bytes = [(offset_bits / 8.0).ceil, 1].max
|
391
|
+
builder_size = (32 + 3 + 2 + 3 + 8) +
|
392
|
+
(cells_bits.length) +
|
393
|
+
((size_bytes * 8) * 4) +
|
394
|
+
(offset_bytes * 8) +
|
395
|
+
(has_index ? (cells_list.length * (offset_bytes * 8)) : 0)
|
396
|
+
|
397
|
+
result = Builder.new(builder_size)
|
398
|
+
|
399
|
+
result.store_bytes(REACH_BOC_MAGIC_PREFIX)
|
400
|
+
.store_bit(has_index ? 1 : 0)
|
401
|
+
.store_bit(hash_crc32 ? 1 : 0)
|
402
|
+
.store_bit(has_cache_bits ? 1 : 0)
|
403
|
+
.store_uint(flags, 2)
|
404
|
+
.store_uint(size_bytes, 3)
|
405
|
+
.store_uint(offset_bytes, 8)
|
406
|
+
.store_uint(cells_num, size_bytes * 8)
|
407
|
+
.store_uint(root.length, size_bytes * 8)
|
408
|
+
.store_uint(0, size_bytes * 8)
|
409
|
+
.store_uint(full_size, offset_bytes * 8)
|
410
|
+
.store_uint(0, size_bytes * 8)
|
411
|
+
|
412
|
+
if has_index
|
413
|
+
size_index.each do |index|
|
414
|
+
result.store_uint(index, offset_bytes * 8)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
augmented_bits = augment(result.store_bits(cells_bits).bits)
|
419
|
+
bytes = bits_to_bytes(augmented_bits)
|
420
|
+
|
421
|
+
if hash_crc32
|
422
|
+
hashsum = crc32c_bytes_le(bytes)
|
423
|
+
bytes + hashsum
|
424
|
+
else
|
425
|
+
bytes
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|