tron.rb 1.1.5 → 1.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 585ad45b691372d3698239c2bd3ad6c81a2f70409869867fb8e645ccba7aa29c
4
- data.tar.gz: c45974ccdd37b4ce9ba68d0b395fc03ff08dad33d0997d90665ee376db2dfb0d
3
+ metadata.gz: f59c819dcc9f4459d1f1dfb4ac7fb574641f0328db9e9ddbfcedd8b0fb4d0d61
4
+ data.tar.gz: 78791543da62e51939da9740f996c5f58da444f701215816f1bb126f830b7e0d
5
5
  SHA512:
6
- metadata.gz: b79bee6d4053770cc2f9f2d3fe8971c23a7cc828f59d28364695653f5843be4badab7a6bc0753b715c6ff651ca96dbe9eaaa7a8afb11ce3462e0708ca42bf32a
7
- data.tar.gz: 39a090312157fe64b22ad82d3c76aac72a9ba2be75279ddd276b29f3e5ec282248d5d15ad76fb90debe4d200b984b6c4aa698f8660a0b076cf8db76b5f6f260b
6
+ metadata.gz: e83539da13849989f51dac5885fdf516eab9a06931b8ea898485523e35d20f924b40ac28d8d0136163810f282cd70f7ae6f37ea4584d7f08da49b40b29eeb9da
7
+ data.tar.gz: 5e5fadf2581f2c5c505afc929252878a8f121bd9e1e87c5af74a5c73f22752cb77181a963242e84aa699847f649d35ce57d38fa1c5e869df6f050fe8d913bf03
data/lib/tron/abi.rb CHANGED
@@ -77,6 +77,44 @@ module Tron
77
77
  Util.bin_to_hex(result_binary)
78
78
  end
79
79
 
80
+ # Encode a function call with signature and parameters
81
+ #
82
+ # @param signature [String] function signature like "transfer(address,uint256)"
83
+ # @param parameters [Array] parameter values
84
+ # @return [String] hex-encoded function call (selector + params)
85
+ def self.encode_function_call(signature, parameters = [])
86
+ # Parse function signature to get parameter types
87
+ match = signature.match(/^(\w+)\((.*)\)$/)
88
+ raise ArgumentError, "Invalid function signature: #{signature}" unless match
89
+
90
+ function_name = match[1]
91
+ param_types_str = match[2]
92
+ param_types = param_types_str.empty? ? [] : param_types_str.split(',').map(&:strip)
93
+
94
+ # Get function selector (first 4 bytes of keccak256 hash)
95
+ require_relative 'utils/crypto'
96
+ hash = Utils::Crypto.keccak256(signature)
97
+ selector = Utils::Crypto.bin_to_hex(hash[0, 4])
98
+
99
+ # Encode parameters if any
100
+ if parameters.empty?
101
+ return selector
102
+ end
103
+
104
+ encoded_params = encode(param_types, parameters)
105
+ selector + encoded_params
106
+ end
107
+
108
+ # Decode output from a contract call
109
+ #
110
+ # @param type_str [String] the output type
111
+ # @param hex_data [String] hex-encoded output data
112
+ # @return [Object] decoded value
113
+ def self.decode_output(type_str, hex_data)
114
+ decoded = decode([type_str], hex_data)
115
+ decoded.first
116
+ end
117
+
80
118
  # Convenience method for decoding
81
119
  def self.decode(types, hex_data)
82
120
  # Convert hex to binary at the boundary
@@ -1,6 +1,5 @@
1
1
  require_relative '../utils/http'
2
2
  require_relative '../utils/address'
3
- require_relative '../utils/abi'
4
3
  require_relative '../abi'
5
4
  require_relative 'transaction'
6
5
 
@@ -86,7 +85,7 @@ module Tron
86
85
  endpoint = "#{@base_url}/wallet/triggerconstantcontract"
87
86
 
88
87
  # Encode function call
89
- encoded_data = Utils::ABI.encode_function_call(function, parameters)
88
+ encoded_data = Abi.encode_function_call(function, parameters)
90
89
 
91
90
  # Use 'data' field instead of 'function_selector' (same as trigger_contract)
92
91
  payload = {
@@ -118,7 +117,7 @@ module Tron
118
117
  parameters: [operator_address, payment_id]
119
118
  )
120
119
 
121
- Utils::ABI.decode_output('bool', result)
120
+ Abi.decode_output('bool', result)
122
121
  end
123
122
 
124
123
  # Gets the fee destination (example helper)
@@ -133,7 +132,7 @@ module Tron
133
132
  parameters: [operator_address]
134
133
  )
135
134
 
136
- Utils::ABI.decode_output('address', result)
135
+ Abi.decode_output('address', result)
137
136
  end
138
137
 
139
138
  # Checks if an operator is registered (example helper)
@@ -148,7 +147,7 @@ module Tron
148
147
  parameters: [operator_address]
149
148
  )
150
149
 
151
- Utils::ABI.decode_output('bool', result)
150
+ Abi.decode_output('bool', result)
152
151
  end
153
152
 
154
153
  private
@@ -173,7 +172,7 @@ module Tron
173
172
  endpoint = "#{@base_url}/wallet/triggersmartcontract"
174
173
 
175
174
  # Encode function call (selector + parameters)
176
- encoded_data = Utils::ABI.encode_function_call(function, parameters)
175
+ encoded_data = Abi.encode_function_call(function, parameters)
177
176
 
178
177
  # IMPORTANT: Use 'data' field, not 'function_selector'
179
178
  # When function_selector is provided, the API expects it to be the signature string
data/lib/tron/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  # lib/tron/version.rb
4
4
  module Tron
5
- VERSION = "1.1.5".freeze
5
+ VERSION = "1.1.6".freeze
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tron.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bolo Michelin
@@ -156,7 +156,6 @@ files:
156
156
  - lib/tron/services/resources.rb
157
157
  - lib/tron/services/transaction.rb
158
158
  - lib/tron/signature.rb
159
- - lib/tron/utils/abi.rb
160
159
  - lib/tron/utils/address.rb
161
160
  - lib/tron/utils/cache.rb
162
161
  - lib/tron/utils/crypto.rb
@@ -1,321 +0,0 @@
1
- require 'digest'
2
-
3
- module Tron
4
- module Utils
5
- # Utility class for handling TRON contract ABIs
6
- class ABI
7
- # Type mappings for Solidity to Ruby
8
- SOLIDITY_TYPES = {
9
- 'address' => :address,
10
- 'uint256' => :uint256,
11
- 'uint' => :uint256,
12
- 'bool' => :bool,
13
- 'bytes16' => :bytes16,
14
- 'bytes32' => :bytes32,
15
- 'string' => :string
16
- }.freeze
17
-
18
- # Parse function signature
19
- #
20
- # @param signature [String] the function signature to parse
21
- # @return [Hash] a hash with :name and :params keys
22
- # @raise [ArgumentError] if the signature is invalid
23
- def self.parse_signature(signature)
24
- match = signature.match(/^(\w+)\((.*)\)$/)
25
- raise ArgumentError, "Invalid function signature: #{signature}" unless match
26
-
27
- {
28
- name: match[1],
29
- params: match[2].empty? ? [] : match[2].split(',').map(&:strip)
30
- }
31
- end
32
-
33
- # Encode function call
34
- #
35
- # @param signature [String] the function signature
36
- # @param parameters [Array] the parameter values
37
- # @return [String] the encoded function call
38
- def self.encode_function_call(signature, parameters = [])
39
- parsed = parse_signature(signature)
40
-
41
- # Get function selector (first 4 bytes of keccak256 hash)
42
- selector = function_selector(signature)
43
-
44
- # Encode parameters
45
- encoded_params = encode_parameters(parsed[:params], parameters)
46
-
47
- selector + encoded_params
48
- end
49
-
50
- # Function selector (4-byte hash)
51
- #
52
- # @param signature [String] the function signature
53
- # @return [String] the 4-byte function selector as hex
54
- def self.function_selector(signature)
55
- require_relative 'crypto'
56
- # Note: TRON uses same ABI as Ethereum
57
- # Using Keccak256 for hash
58
- hash = Crypto.keccak256(signature)
59
- # Take only the first 4 bytes (8 hex chars)
60
- Crypto.bin_to_hex(hash[0, 4])
61
- end
62
-
63
- # Encode parameters
64
- #
65
- # @param types [Array<String>] the parameter types
66
- # @param values [Array] the parameter values
67
- # @return [String] the encoded parameters as hex
68
- # @raise [ArgumentError] if there's a mismatch between types and values
69
- def self.encode_parameters(types, values)
70
- raise ArgumentError, "Types and values length mismatch" if types.length != values.length
71
-
72
- encoded_parts = []
73
- dynamic_params = []
74
- dynamic_offset = types.length * 32 # Each static param takes 32 bytes (64 hex chars)
75
-
76
- types.each_with_index do |type, index|
77
- value = values[index]
78
-
79
- case type
80
- when 'address'
81
- # Address is padded to 32 bytes (64 hex chars)
82
- padded_address = encode_address(value)
83
- encoded_parts << padded_address
84
- when 'uint256', 'uint'
85
- # Convert to hex and pad to 32 bytes (64 hex chars)
86
- hex_value = encode_uint256(value)
87
- encoded_parts << hex_value
88
- when 'bool'
89
- # Boolean to uint256 (1 for true, 0 for false)
90
- encoded_parts << encode_bool(value)
91
- when /^bytes(\d+)$/
92
- # Static, fixed-size bytes array, pad to 32 bytes
93
- encoded_parts << encode_bytes($1.to_i, value)
94
- when 'string', 'bytes'
95
- # For dynamic types, add offset and store actual data separately
96
- encoded_parts << dynamic_offset.to_s(16).rjust(64, '0') # offset
97
- # Calculate the actual data for later encoding
98
- dynamic_data = encode_dynamic_parameter(type, value)
99
- dynamic_params << dynamic_data
100
- dynamic_offset += (dynamic_data.length / 2.0).ceil # Increase offset by byte length, rounded up to nearest whole byte
101
- else
102
- raise ArgumentError, "Unsupported ABI type: #{type}"
103
- end
104
- end
105
-
106
- # Encode dynamic parameters
107
- dynamic_parts = []
108
- dynamic_params.each do |data|
109
- dynamic_parts << data
110
- end
111
-
112
- (encoded_parts + dynamic_parts).join
113
- end
114
-
115
- # Encode a single value
116
- #
117
- # @param type [String] the type to encode
118
- # @param value [Object] the value to encode
119
- # @return [String] the encoded value as hex
120
- # @raise [ArgumentError] if the type is unsupported
121
- def self.encode_value(type, value)
122
- case type
123
- when 'address'
124
- encode_address(value)
125
- when 'uint256', 'uint'
126
- encode_uint256(value)
127
- when 'bool'
128
- encode_bool(value)
129
- when /^bytes(\d+)$/
130
- encode_bytes($1.to_i, value)
131
- when 'string'
132
- encode_string(value)
133
- else
134
- raise ArgumentError, "Unsupported type: #{type}"
135
- end
136
- end
137
-
138
- # Encode TRON address (T address to hex)
139
- #
140
- # @param address [String] the TRON address to encode
141
- # @return [String] the encoded address as hex
142
- def self.encode_address(address)
143
- # Convert TRON T-address to hex address
144
- # Remove 'T' prefix, convert base58 to hex, pad to 32 bytes
145
- hex = Address.to_hex(address)
146
- # Strip the 41 prefix for ABI encoding (only use the 20-byte address)
147
- hex = hex[2..-1] if hex.start_with?('41')
148
- hex.rjust(64, '0') # Pad to 64 hex chars (32 bytes)
149
- end
150
-
151
- # Encode uint256
152
- #
153
- # @param value [Integer, String] the value to encode
154
- # @return [String] the encoded uint256 as hex
155
- def self.encode_uint256(value)
156
- value.to_i.to_s(16).rjust(64, '0')
157
- end
158
-
159
- # Encode bool
160
- #
161
- # @param value [Boolean] the boolean value to encode
162
- # @return [String] the encoded boolean as hex
163
- def self.encode_bool(value)
164
- value ? '1'.rjust(64, '0') : '0'.rjust(64, '0')
165
- end
166
-
167
- # Encode bytes
168
- #
169
- # @param size [Integer] the size of the bytes type
170
- # @param value [String] the value to encode
171
- # @return [String] the encoded bytes as hex
172
- def self.encode_bytes(size, value)
173
- hex = value.is_a?(String) ? value.unpack1('H*') : value.to_s
174
- # Limit to the size of the bytes type and pad to 32 bytes
175
- hex_part = hex[0...(size * 2)] # Each byte is 2 hex chars
176
- hex_part.ljust(64, '0')
177
- end
178
-
179
- # Encode string
180
- #
181
- # @param value [String] the string to encode
182
- # @raise [NotImplementedError] since string encoding is handled separately
183
- def self.encode_string(value)
184
- # For dynamic types like string, return the length and data separately
185
- # This will be handled by the main encode_parameters method
186
- raise NotImplementedError, "String encoding is handled via encode_dynamic_parameter"
187
- end
188
-
189
- # Encode bytes (dynamic type)
190
- #
191
- # @param value [String] the bytes value to encode
192
- # @return [String] the encoded dynamic bytes as hex
193
- def self.encode_bytes_dynamic(value)
194
- # For dynamic bytes, encode length and data
195
- bytes_data = value.is_a?(String) ? value : value.to_s
196
- bytes_array = bytes_data.start_with?('0x') ? bytes_data[2..-1].scan(/../) : bytes_data.scan(/../)
197
- length = bytes_array.length.to_s(16).rjust(64, '0')
198
- data = bytes_array.map { |b| b.rjust(2, '0') }.join
199
- # Pad data to 32-byte boundaries (64 hex chars)
200
- padded_length = ((data.length / 64.0).ceil * 64).to_i
201
- padded_data = data.ljust(padded_length, '0')
202
-
203
- length + padded_data
204
- end
205
-
206
- # Decode output
207
- #
208
- # @param type [String] the type to decode
209
- # @param hex_data [String] the hex data to decode
210
- # @return [Object] the decoded value
211
- def self.decode_output(type, hex_data)
212
- case type
213
- when 'bool'
214
- hex_data.to_i(16) != 0
215
- when 'address'
216
- Address.from_hex(hex_data)
217
- when 'uint256', 'uint'
218
- hex_data.to_i(16)
219
- else
220
- hex_data
221
- end
222
- end
223
-
224
- # Decodes parameters based on ABI types
225
- #
226
- # @param types [Array<String>] the types to decode
227
- # @param data [String] the hex data to decode
228
- # @return [Array] the decoded values
229
- # @raise [ArgumentError] if the data is invalid or type is unsupported
230
- def self.decode_parameters(types, data)
231
- # Remove '0x' prefix if present
232
- raw_data = data.start_with?('0x') ? data[2..-1] : data
233
-
234
- # Ensure the data length is valid
235
- if raw_data.length % 64 != 0
236
- raise ArgumentError, "Invalid data length: must be multiple of 64 hex chars"
237
- end
238
-
239
- values = []
240
- pos = 0
241
-
242
- types.each do |type|
243
- # Each parameter is 32 bytes (64 hex chars)
244
- param_hex = raw_data[pos...(pos + 64)]
245
- pos += 64
246
-
247
- case type
248
- when /address/
249
- # Extract the address (last 40 hex chars)
250
- addr_hex = param_hex[-40..-1]
251
- # Add TRON prefix
252
- values << "41#{addr_hex}"
253
- when /uint/, /int/
254
- # Convert hex to integer
255
- values << param_hex.to_i(16)
256
- when /bool/
257
- # Boolean is 0 or 1
258
- values << (param_hex.to_i(16) != 0)
259
- when /string|bytes/
260
- # Dynamic type - get offset first
261
- offset = param_hex.to_i(16) * 2 # Convert to byte position in hex string
262
-
263
- # Extract length of data
264
- length_hex = raw_data[offset...(offset + 64)]
265
- length = length_hex.to_i(16) * 2 # Each byte is 2 hex chars
266
-
267
- # Extract actual data
268
- actual_data = raw_data[(offset + 64)...(offset + 64 + length)]
269
-
270
- if type.start_with?('string')
271
- # Convert hex to string
272
- values << [actual_data].pack('H*')
273
- else
274
- # Return hex string for bytes
275
- values << actual_data
276
- end
277
- else
278
- raise ArgumentError, "Unsupported ABI type for decoding: #{type}"
279
- end
280
- end
281
-
282
- values
283
- end
284
-
285
- private
286
-
287
- # Helper method to encode dynamic parameters
288
- #
289
- # @param type [String] the dynamic type ('string' or 'bytes')
290
- # @param value [Object] the value to encode
291
- # @return [String] the encoded dynamic parameter as hex
292
- # @raise [ArgumentError] if the type is unsupported
293
- def self.encode_dynamic_parameter(type, value)
294
- if type.start_with?('string')
295
- # For strings, we need to encode length and data
296
- str_bytes = value.bytes
297
- length = str_bytes.length.to_s(16).rjust(64, '0')
298
- data = str_bytes.map { |b| b.to_s(16).rjust(2, '0') }.join
299
- # Pad data to 32-byte boundaries (64 hex chars)
300
- padded_length = ((data.length / 64.0).ceil * 64).to_i
301
- padded_data = data.ljust(padded_length, '0')
302
-
303
- length + padded_data
304
- elsif type.start_with?('bytes')
305
- # For bytes, similar to string
306
- bytes_data = value.is_a?(String) ? value : value.to_s
307
- bytes_array = bytes_data.start_with?('0x') ? bytes_data[2..-1].scan(/../) : bytes_data.scan(/../)
308
- length = bytes_array.length.to_s(16).rjust(64, '0')
309
- data = bytes_array.map { |b| b.rjust(2, '0') }.join
310
- # Pad data to 32-byte boundaries (64 hex chars)
311
- padded_length = ((data.length / 64.0).ceil * 64).to_i
312
- padded_data = data.ljust(padded_length, '0')
313
-
314
- length + padded_data
315
- else
316
- raise ArgumentError, "Unsupported dynamic type: #{type}"
317
- end
318
- end
319
- end
320
- end
321
- end