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,362 @@
|
|
1
|
+
module TonSdkRuby
|
2
|
+
|
3
|
+
HASH_BITS = 256
|
4
|
+
DEPTH_BITS = 16
|
5
|
+
|
6
|
+
module CellType
|
7
|
+
Ordinary = -1
|
8
|
+
PrunedBranch = 1
|
9
|
+
LibraryReference = 2
|
10
|
+
MerkleProof = 3
|
11
|
+
MerkleUpdate = 4
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_ordinary(bits, refs)
|
15
|
+
if bits.length > 1023
|
16
|
+
raise "Ordinary cell can't have more than 1023 bits, got #{bits.length}"
|
17
|
+
end
|
18
|
+
|
19
|
+
if refs.length > 4
|
20
|
+
raise "Ordinary cell can't have more than 4 refs, got #{refs.length}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_pruned_branch(bits, refs)
|
25
|
+
min_size = 8 + 8 + (1 * (HASH_BITS + DEPTH_BITS))
|
26
|
+
|
27
|
+
if bits.length < min_size
|
28
|
+
raise "Pruned Branch cell can't have less than (8 + 8 + 256 + 16) bits, got #{bits.length}"
|
29
|
+
end
|
30
|
+
|
31
|
+
if refs.length != 0
|
32
|
+
raise "Pruned Branch cell can't have refs, got #{refs.length}"
|
33
|
+
end
|
34
|
+
|
35
|
+
type = bits_to_int_uint(bits[0...8], { type: 'int' })
|
36
|
+
|
37
|
+
if type != CellType::PrunedBranch
|
38
|
+
raise "Pruned Branch cell type must be exactly #{CellType::PrunedBranch}, got #{type}"
|
39
|
+
end
|
40
|
+
|
41
|
+
mask = Mask.new(bits_to_int_uint(bits[8...16], { type: 'uint' }))
|
42
|
+
|
43
|
+
if mask.level < 1 || mask.level > 3
|
44
|
+
raise "Pruned Branch cell level must be >= 1 and <= 3, got #{mask.level}"
|
45
|
+
end
|
46
|
+
|
47
|
+
hash_count = mask.apply(mask.level - 1)[:hashCount]
|
48
|
+
size = 8 + 8 + (hash_count * (HASH_BITS + DEPTH_BITS))
|
49
|
+
|
50
|
+
if bits.length != size
|
51
|
+
raise "Pruned Branch cell with level #{mask.level} must have exactly #{size} bits, got #{bits.length}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_library_reference(bits, refs)
|
56
|
+
# Type + hash
|
57
|
+
size = 8 + HASH_BITS
|
58
|
+
|
59
|
+
if bits.length != size
|
60
|
+
raise "Library Reference cell must have exactly (8 + 256) bits, got \"#{bits.length}\""
|
61
|
+
end
|
62
|
+
|
63
|
+
if refs.length != 0
|
64
|
+
raise "Library Reference cell can't have refs, got \"#{refs.length}\""
|
65
|
+
end
|
66
|
+
|
67
|
+
type = bits_to_int_uint(bits[0..7], { type: 'int' })
|
68
|
+
|
69
|
+
if type != CellType::LibraryReference
|
70
|
+
raise "Library Reference cell type must be exactly #{CellType::LibraryReference}, got \"#{type}\""
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_merkle_proof(bits, refs)
|
75
|
+
# Type + hash + depth
|
76
|
+
size = 8 + HASH_BITS + DEPTH_BITS
|
77
|
+
|
78
|
+
if bits.length != size
|
79
|
+
raise "Merkle Proof cell must have exactly (8 + 256 + 16) bits, got \"#{bits.length}\""
|
80
|
+
end
|
81
|
+
|
82
|
+
if refs.length != 1
|
83
|
+
raise "Merkle Proof cell must have exactly 1 ref, got \"#{refs.length}\""
|
84
|
+
end
|
85
|
+
|
86
|
+
type = bits_to_int_uint(bits[0..7], { type: 'int' })
|
87
|
+
|
88
|
+
if type != CellType::MerkleProof
|
89
|
+
raise "Merkle Proof cell type must be exactly #{CellType::MerkleProof}, got \"#{type}\""
|
90
|
+
end
|
91
|
+
|
92
|
+
data = bits[8..-1]
|
93
|
+
proof_hash = bits_to_hex(data[0..(HASH_BITS - 1)])
|
94
|
+
proof_depth = bits_to_int_uint(data[HASH_BITS..(HASH_BITS + DEPTH_BITS - 1)], { type: 'uint' })
|
95
|
+
ref_hash = refs[0].hash(0)
|
96
|
+
ref_depth = refs[0].depth(0)
|
97
|
+
|
98
|
+
if proof_hash != ref_hash
|
99
|
+
raise "Merkle Proof cell ref hash must be exactly \"#{proof_hash}\", got \"#{ref_hash}\""
|
100
|
+
end
|
101
|
+
|
102
|
+
if proof_depth != ref_depth
|
103
|
+
raise "Merkle Proof cell ref depth must be exactly \"#{proof_depth}\", got \"#{ref_depth}\""
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate_merkle_update(bits, refs)
|
108
|
+
size = 8 + (2 * (256 + 16))
|
109
|
+
|
110
|
+
if bits.length != size
|
111
|
+
raise "Merkle Update cell must have exactly (8 + (2 * (256 + 16))) bits, got #{bits.length}"
|
112
|
+
end
|
113
|
+
|
114
|
+
if refs.length != 2
|
115
|
+
raise "Merkle Update cell must have exactly 2 refs, got #{refs.length}"
|
116
|
+
end
|
117
|
+
|
118
|
+
type = bits_to_int_uint(bits[0..7], { type: 'int' })
|
119
|
+
|
120
|
+
if type != CellType::MerkleUpdate
|
121
|
+
raise "Merkle Update cell type must be exactly #{CellType::MerkleUpdate}, got #{type}"
|
122
|
+
end
|
123
|
+
|
124
|
+
data = bits[8..-1]
|
125
|
+
hashes = [data[0..255], data[256..511]].map { |el| bits_to_hex(el) }
|
126
|
+
depths = [data[512..527], data[528..543]].map { |el| bits_to_int_uint(el, { type: 'uint' }) }
|
127
|
+
|
128
|
+
refs.each_with_index do |ref, i|
|
129
|
+
proof_hash = hashes[i]
|
130
|
+
proof_depth = depths[i]
|
131
|
+
ref_hash = ref.hash(0)
|
132
|
+
ref_depth = ref.depth(0)
|
133
|
+
|
134
|
+
if proof_hash != ref_hash
|
135
|
+
raise "Merkle Update cell ref ##{i} hash must be exactly '#{proof_hash}', got '#{ref_hash}'"
|
136
|
+
end
|
137
|
+
|
138
|
+
if proof_depth != ref_depth
|
139
|
+
raise "Merkle Update cell ref ##{i} depth must be exactly '#{proof_depth}', got '#{ref_depth}'"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def get_mapper(type)
|
145
|
+
map = {
|
146
|
+
CellType::Ordinary => {
|
147
|
+
validate: method(:validate_ordinary),
|
148
|
+
mask: -> (_b, r) { Mask.new(r.reduce(0) { |acc, el| acc | el.mask.value }) }
|
149
|
+
},
|
150
|
+
CellType::PrunedBranch => {
|
151
|
+
validate: method(:validate_pruned_branch),
|
152
|
+
mask: lambda { |b| Mask.new(bits_to_int_uint(b[8..15], { type: 'uint' })) }
|
153
|
+
},
|
154
|
+
CellType::LibraryReference => {
|
155
|
+
validate: method(:validate_library_reference),
|
156
|
+
mask: -> { Mask.new(0) }
|
157
|
+
},
|
158
|
+
CellType::MerkleProof => {
|
159
|
+
validate: method(:validate_merkle_proof),
|
160
|
+
mask: lambda { |_b, r| Mask.new(r[0].mask.value >> 1) }
|
161
|
+
},
|
162
|
+
CellType::MerkleUpdate => {
|
163
|
+
validate: method(:validate_merkle_update),
|
164
|
+
mask: lambda { |_b, r| Mask.new((r[0].mask.value | r[1].mask.value) >> 1) }
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
result = map[type]
|
169
|
+
|
170
|
+
if result.nil?
|
171
|
+
raise 'Unknown cell type'
|
172
|
+
end
|
173
|
+
|
174
|
+
result
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
class Cell
|
179
|
+
attr_accessor :bits, :refs, :type, :mask
|
180
|
+
attr_reader :hashes, :depths
|
181
|
+
private :bits, :refs, :type, :mask, :hashes, :depths
|
182
|
+
|
183
|
+
def initialize(options = {})
|
184
|
+
options = { bits: [], refs: [], type: CellType::Ordinary }.merge(options)
|
185
|
+
|
186
|
+
mapper = get_mapper(options[:type])
|
187
|
+
validate = mapper[:validate]
|
188
|
+
mask = mapper[:mask]
|
189
|
+
|
190
|
+
validate.call(options[:bits], options[:refs])
|
191
|
+
@mask = mask.call(options[:bits], options[:refs])
|
192
|
+
@type = options[:type]
|
193
|
+
@bits = options[:bits]
|
194
|
+
@refs = options[:refs]
|
195
|
+
@depths = {}
|
196
|
+
@hashes = {}
|
197
|
+
|
198
|
+
init()
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get current Cell instance bits
|
202
|
+
def bits
|
203
|
+
@bits.dup
|
204
|
+
end
|
205
|
+
|
206
|
+
# Get current Cell instance refs
|
207
|
+
def refs
|
208
|
+
@refs.dup
|
209
|
+
end
|
210
|
+
|
211
|
+
# Get current Cell instance Mask (that includes level, hashes count, etc...)
|
212
|
+
def mask
|
213
|
+
@mask
|
214
|
+
end
|
215
|
+
|
216
|
+
# Get current Cell instance CellType
|
217
|
+
def type
|
218
|
+
@type
|
219
|
+
end
|
220
|
+
|
221
|
+
# Check if current Cell instance is exotic type
|
222
|
+
def exotic
|
223
|
+
@type != CellType::Ordinary
|
224
|
+
end
|
225
|
+
|
226
|
+
# Calculate depth descriptor
|
227
|
+
def self.get_depth_descriptor(depth)
|
228
|
+
descriptor = [(depth / 256).floor, depth % 256].pack('C*')
|
229
|
+
bytes_to_bits(descriptor)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Get current Cell instance refs descriptor
|
233
|
+
def get_refs_descriptor(mask = nil)
|
234
|
+
value = @refs.length +
|
235
|
+
(exotic ? 8 : 0) +
|
236
|
+
((mask ? mask.value : @mask.value) * 32)
|
237
|
+
|
238
|
+
descriptor = [value].pack('C')
|
239
|
+
bytes_to_bits(descriptor)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Get current Cell instance bits descriptor
|
243
|
+
def get_bits_descriptor
|
244
|
+
value = (@bits.length / 8.0).ceil + (@bits.length / 8.0).floor
|
245
|
+
descriptor = [value].pack('C')
|
246
|
+
bytes_to_bits(descriptor)
|
247
|
+
end
|
248
|
+
|
249
|
+
# Get current Cell instance augmented bits
|
250
|
+
def get_augmented_bits
|
251
|
+
augment(@bits)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Get cell's hash in hex (max level by default)
|
255
|
+
def hash(level = 3)
|
256
|
+
return @hashes[@mask.apply(level).hash_index] if @type != CellType::PrunedBranch
|
257
|
+
|
258
|
+
hash_index = @mask.apply(level).hash_index
|
259
|
+
this_hash_index = @mask.hash_index
|
260
|
+
skip = 16 + hash_index * HASH_BITS
|
261
|
+
|
262
|
+
if hash_index != this_hash_index
|
263
|
+
bits_to_hex(@bits[skip...(skip + HASH_BITS)])
|
264
|
+
else
|
265
|
+
@hashes[0]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Get cell's depth (max level by default)
|
270
|
+
def depth(level = 3)
|
271
|
+
return @depths[@mask.apply(level).hash_index] if @type != CellType::PrunedBranch
|
272
|
+
|
273
|
+
hash_index = @mask.apply(level).hash_index
|
274
|
+
this_hash_index = @mask.hash_index
|
275
|
+
skip = 16 + this_hash_index * HASH_BITS + hash_index * DEPTH_BITS
|
276
|
+
|
277
|
+
if hash_index != this_hash_index
|
278
|
+
bits_to_int_uint(@bits[skip...(skip + DEPTH_BITS)], type: 'uint')
|
279
|
+
else
|
280
|
+
@depths[0]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Get Slice from current instance
|
285
|
+
def parse
|
286
|
+
Slice.parse(self)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Print cell as fift-hex
|
290
|
+
def print(indent = 1, size = 0)
|
291
|
+
# TODO: fix this logic
|
292
|
+
|
293
|
+
bits = @bits.dup
|
294
|
+
are_divisible = bits.length % 4 == 0
|
295
|
+
augmented = are_divisible ? bits : augment(bits, 4)
|
296
|
+
fift_hex = "#{bits_to_hex(augmented).upcase}#{are_divisible ? '' : '_'}"
|
297
|
+
output = ["#{' ' * (indent * size)}x{#{fift_hex}}\n"]
|
298
|
+
|
299
|
+
@refs.each do |ref|
|
300
|
+
output.push(ref.print(indent, size + 1))
|
301
|
+
end
|
302
|
+
|
303
|
+
output.join('')
|
304
|
+
end
|
305
|
+
|
306
|
+
# Checks Cell equality by comparing cell hashes
|
307
|
+
def eq(cell)
|
308
|
+
hash == cell.hash
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
private
|
313
|
+
|
314
|
+
def init
|
315
|
+
has_refs = @refs.length.positive?
|
316
|
+
is_merkle = [CellType::MerkleProof, CellType::MerkleUpdate].include?(@type)
|
317
|
+
is_pruned_branch = @type == CellType::PrunedBranch
|
318
|
+
hash_index_offset = is_pruned_branch ? @mask.hash_count - 1 : 0
|
319
|
+
|
320
|
+
hash_index = 0
|
321
|
+
(0..@mask.level).each do |level_index|
|
322
|
+
next unless @mask.is_significant(level_index)
|
323
|
+
next if hash_index < hash_index_offset
|
324
|
+
|
325
|
+
if (hash_index == hash_index_offset && level_index != 0 && !is_pruned_branch) ||
|
326
|
+
(hash_index != hash_index_offset && level_index == 0 && is_pruned_branch)
|
327
|
+
raise 'Can\'t deserialize cell'
|
328
|
+
end
|
329
|
+
ref_level = level_index + (is_merkle ? 1 : 0)
|
330
|
+
refs_descriptor = get_refs_descriptor(@mask.apply(level_index))
|
331
|
+
bits_descriptor = get_bits_descriptor
|
332
|
+
data = if hash_index != hash_index_offset
|
333
|
+
hex_to_bits(@hashes[hash_index - hash_index_offset - 1])
|
334
|
+
else
|
335
|
+
get_augmented_bits
|
336
|
+
end
|
337
|
+
depth_repr = []
|
338
|
+
hash_repr = []
|
339
|
+
depth = 0
|
340
|
+
@refs.each do |ref|
|
341
|
+
ref_depth = ref.depth(ref_level)
|
342
|
+
ref_hash = ref.hash(ref_level)
|
343
|
+
|
344
|
+
depth_repr.concat(Cell.get_depth_descriptor(ref_depth))
|
345
|
+
hash_repr.concat(hex_to_bits(ref_hash))
|
346
|
+
depth = [depth, ref_depth].max
|
347
|
+
end
|
348
|
+
representation = refs_descriptor + bits_descriptor + data + depth_repr + hash_repr
|
349
|
+
|
350
|
+
if @refs.length.positive? && depth >= 1024
|
351
|
+
raise 'Cell depth can\'t be more than 1024'
|
352
|
+
end
|
353
|
+
dest = hash_index - hash_index_offset
|
354
|
+
|
355
|
+
@depths[dest] = depth + (has_refs ? 1 : 0)
|
356
|
+
@hashes[dest] = sha256(bits_to_bytes(representation))
|
357
|
+
hash_index += 1
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
end
|