tron.rb 1.1.9 → 1.2.2

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: 232c93f724a0aeb89150ec237af9c4a6d504b3635aabff7c833e9b71660a8752
4
- data.tar.gz: 4403e025c9d31a49e26da6cde92cfeb184df9626d97d76d662eb43ae3644728b
3
+ metadata.gz: b5cd08e96e3394fc24fb6626f6a36eda41ee19b69c5ae1dc7c80de31cb357464
4
+ data.tar.gz: aed0379a31288c32a74bd7d4aa644c88a5c07899782f853c8a2d0498fe277e01
5
5
  SHA512:
6
- metadata.gz: 8032ab96d92f191b7a3c6923104d656a5edb451a0498b57d339fc823084d3041b3da618ed106d015d60d17c99fb50d11a1b0228d8c1ee61ab80876feaf535a43
7
- data.tar.gz: a4bdca985bb3cf4389fd045711a2f7a3bcea9f15ddc4902a3ccb4c5e1e624d50140ae2ee1b0438fb23eb95f5e9e98e39c3316b060c877d6b22a27a45cb093df4
6
+ metadata.gz: 8d127adf6c6f7c3477b4627295725454bfe3e59a409f42d18756d7a6e023d4f0b922c8314cede795a1d291644a309c9a00a03236b206dbec279fc59423d03f8f
7
+ data.tar.gz: ed54942d4329923a49af1f9399157cca3d157b857fe13810e86111707bac6ca645ace01753b1db268cc2f68fce1eeb6b8bfcb7fd22d9c26fdd3486022cb87e8b
@@ -6,6 +6,22 @@ module Tron
6
6
  module Constant
7
7
  extend self
8
8
 
9
+ # Maximum value for uint256 (2^256 - 1)
10
+ # @return [Integer] maximum value for uint256
11
+ UINT_MAX = 2**256 - 1
12
+
13
+ # Minimum value for uint256
14
+ # @return [Integer] minimum value for uint256
15
+ UINT_MIN = 0
16
+
17
+ # Maximum value for int256 (2^255 - 1)
18
+ # @return [Integer] maximum value for int256
19
+ INT_MAX = 2**255 - 1
20
+
21
+ # Minimum value for int256 (-(2^255))
22
+ # @return [Integer] minimum value for int256
23
+ INT_MIN = -(2**255)
24
+
9
25
  # Byte zero constant
10
26
  # @return [String] binary string containing zero byte
11
27
  BYTE_ZERO = "\x00".b
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../type'
4
+ require_relative '../util'
5
+ require_relative '../constant'
6
+ require_relative '../../utils/address'
7
+
8
+ module Tron
9
+ module Abi
10
+ module Packed
11
+ # Provides a utility module to assist in packed encoding ABIs.
12
+ module Encoder
13
+ extend self
14
+
15
+ # Encodes a specific value in non-standard packed encoding mode.
16
+ #
17
+ # @param type [String] type to be encoded.
18
+ # @param arg [String|Number] value to be encoded.
19
+ # @return [String] the packed encoded type.
20
+ # @raise [EncodingError] if value does not match type.
21
+ # @raise [ArgumentError] if encoding fails for type.
22
+ def type(type, arg)
23
+ case type
24
+ when /^uint(\d+)$/
25
+ uint(arg, $1.to_i / 8)
26
+ when /^int(\d+)$/
27
+ int(arg, $1.to_i / 8)
28
+ when "bool"
29
+ bool(arg)
30
+ when /^ureal(\d+)x(\d+)$/, /^ufixed(\d+)x(\d+)$/
31
+ ufixed(arg, $1.to_i / 8, $2.to_i)
32
+ when /^real(\d+)x(\d+)$/, /^fixed(\d+)x(\d+)$/
33
+ fixed(arg, $1.to_i / 8, $2.to_i)
34
+ when "string"
35
+ string(arg)
36
+ when /^bytes(\d+)$/
37
+ bytes(arg, $1.to_i)
38
+ when "bytes"
39
+ string(arg)
40
+ when /^tuple\((.+)\)$/
41
+ tuple($1.split(","), arg)
42
+ when /^hash(\d+)$/
43
+ hash(arg, $1.to_i / 8)
44
+ when "address"
45
+ address(arg)
46
+ when /^(.+)\[\]$/
47
+ array($1, arg)
48
+ when /^(.+)\[(\d+)\]$/
49
+ fixed_array($1, arg, $2.to_i)
50
+ else
51
+ raise EncodingError, "Unhandled type: #{type}"
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ # Properly encodes unsigned integers.
58
+ def uint(value, byte_size)
59
+ raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
60
+ raise ValueOutOfBounds, "Number out of range: #{value}" if value > Constant::UINT_MAX or value < Constant::UINT_MIN
61
+ i = value.to_i
62
+ Util.zpad_int(i, byte_size)
63
+ end
64
+
65
+ # Properly encodes signed integers.
66
+ def int(value, byte_size)
67
+ raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
68
+ raise ValueOutOfBounds, "Number out of range: #{value}" if value > Constant::INT_MAX or value < Constant::INT_MIN
69
+ real_size = byte_size * 8
70
+ i = value.to_i % 2 ** real_size
71
+ Util.zpad_int(i, byte_size)
72
+ end
73
+
74
+ # Properly encodes booleans.
75
+ def bool(value)
76
+ raise EncodingError, "Argument is not bool: #{value}" unless [TrueClass, FalseClass].any? { |bool_class| value.is_a?(bool_class) }
77
+ (value ? "\x01" : "\x00").b
78
+ end
79
+
80
+ # Properly encodes unsigned fixed-point numbers.
81
+ def ufixed(value, byte_size, decimals)
82
+ raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
83
+ raise ValueOutOfBounds, value unless value >= 0 and value < 2 ** decimals
84
+ scaled_value = (value * (10 ** decimals)).to_i
85
+ uint(scaled_value, byte_size)
86
+ end
87
+
88
+ # Properly encodes signed fixed-point numbers.
89
+ def fixed(value, byte_size, decimals)
90
+ raise ArgumentError, "Don't know how to handle this input." unless value.is_a? Numeric
91
+ raise ValueOutOfBounds, value unless value >= -2 ** (decimals - 1) and value < 2 ** (decimals - 1)
92
+ scaled_value = (value * (10 ** decimals)).to_i
93
+ int(scaled_value, byte_size)
94
+ end
95
+
96
+ # Properly encodes byte(-string)s.
97
+ def bytes(value, length)
98
+ raise EncodingError, "Expecting String: #{value}" unless value.is_a? String
99
+ value = handle_hex_string(value, length)
100
+ raise ArgumentError, "Value must be a string of length #{length}" unless value.is_a?(String) && value.bytesize == length
101
+ value.b
102
+ end
103
+
104
+ # Properly encodes (byte-)strings.
105
+ def string(value)
106
+ raise ArgumentError, "Value must be a string" unless value.is_a?(String)
107
+ value.b
108
+ end
109
+
110
+ # Properly encodes tuples.
111
+ def tuple(types, values)
112
+ # Call the solidity_packed function to handle tuple encoding
113
+ Tron::Abi.solidity_packed(types, values)
114
+ end
115
+
116
+ # Properly encodes hash-strings.
117
+ def hash(value, byte_size)
118
+ raise EncodingError, "Argument too long: #{value}" unless byte_size > 0 and byte_size <= 32
119
+ hash_bytes = handle_hex_string(value, byte_size)
120
+ hash_bytes.b
121
+ end
122
+
123
+ # Properly encodes addresses.
124
+ def address(value)
125
+ if value.is_a?(String) && value.start_with?('T') && value.length == 34
126
+ # TRON address in Base58 format - convert to hex
127
+ hex_addr = Utils::Address.to_hex(value)
128
+ # Remove the 0x41 prefix and return raw bytes
129
+ Util.hex_to_bin(hex_addr[2..-1])
130
+ elsif value.is_a? Integer
131
+ # address from integer
132
+ Util.zpad_int(value, 20)
133
+ elsif value.size == 20
134
+ # address from raw 20-byte address
135
+ value
136
+ elsif value.size == 40
137
+ # address from hexadecimal address (without 0x prefix)
138
+ Util.hex_to_bin(value)
139
+ elsif value.size == 42 && value[0, 2] == "0x"
140
+ # address from hexadecimal address with 0x prefix
141
+ Util.hex_to_bin(value[2..-1])
142
+ else
143
+ raise EncodingError, "Could not parse address: #{value}"
144
+ end
145
+ end
146
+
147
+ # Properly encodes dynamic-sized arrays.
148
+ def array(type, values)
149
+ # For packed encoding, we don't encode array length, just concatenate values
150
+ values.map { |value| type(type, value) }.join.b
151
+ end
152
+
153
+ # Properly encodes fixed-size arrays.
154
+ def fixed_array(type, values, size)
155
+ raise ArgumentError, "Array size does not match" unless values.size == size
156
+ # For packed encoding, concatenate values directly
157
+ values.map { |value| type(type, value) }.join.b
158
+ end
159
+
160
+ # The ABI encoder needs to be able to determine between a hex `"123"`
161
+ # and a binary `"123"` string.
162
+ def handle_hex_string(val, len)
163
+ if Util.prefixed?(val) || (len == val.size / 2 && Util.hex?(val))
164
+ # There is no way telling whether a string is hex or binary with certainty
165
+ # in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string.
166
+ # Additionally, if the string size is exactly the double of the expected
167
+ # binary size, we can assume a hex value.
168
+ Util.hex_to_bin(val)
169
+ else
170
+ # Everything else will be assumed binary or raw string.
171
+ val.b
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'packed/encoder'
data/lib/tron/abi/util.rb CHANGED
@@ -26,14 +26,15 @@ module Tron
26
26
  ((x + 31) / 32).floor * 32
27
27
  end
28
28
 
29
- # Pads an integer to 32 bytes in binary format
29
+ # Pads an integer to a specified number of bytes in binary format
30
30
  #
31
31
  # @param x [Integer] the integer to pad
32
+ # @param len [Integer] the number of bytes to pad to (default: 32)
32
33
  # @return [String] the padded integer as a binary string
33
- def zpad_int(x)
34
+ def zpad_int(x, len = 32)
34
35
  # Ensure x is positive for modulo operation
35
- x = x % (2 ** 256) if x >= 2 ** 256 || x < 0
36
- [x.to_s(16).rjust(64, '0')].pack('H*')
36
+ x = x % (2 ** (8 * len)) if x >= 2 ** (8 * len) || x < 0
37
+ [x.to_s(16).rjust(len * 2, '0')].pack('H*')
37
38
  end
38
39
 
39
40
  # Pads a string to a specified length with null bytes
data/lib/tron/abi.rb CHANGED
@@ -22,6 +22,7 @@ module Tron
22
22
  require_relative 'abi/event'
23
23
  require_relative 'abi/util'
24
24
  require_relative 'abi/constant'
25
+ require_relative 'abi/packed'
25
26
 
26
27
  # For address handling functionality
27
28
  require_relative 'utils/address'
@@ -139,6 +140,21 @@ module Tron
139
140
  types
140
141
  end
141
142
 
143
+ # Encodes values using Solidity packed encoding (no padding to 32 bytes)
144
+ #
145
+ # @param types [Array<String>] array of type strings
146
+ # @param values [Array] array of values to encode
147
+ # @return [String] binary string of packed encoded values
148
+ def self.solidity_packed(types, values)
149
+ raise ArgumentError, "Types and values must be the same length" if types.length != values.length
150
+
151
+ packed = types.zip(values).map do |type, value|
152
+ Tron::Abi::Packed::Encoder.type(type, value)
153
+ end.join
154
+
155
+ packed.force_encoding(Encoding::ASCII_8BIT)
156
+ end
157
+
142
158
  # Decode output from a contract call
143
159
  #
144
160
  # @param type_str [String] the output type
data/lib/tron/key.rb CHANGED
@@ -146,6 +146,32 @@ module Tron
146
146
  sign(hashed_message)
147
147
  end
148
148
 
149
+ # Signs a typed data hash using TRON TIP-191 standard
150
+ # This is used for structured data signing (similar to EIP-712 but with TIP-191 prefix)
151
+ # The hash should be the keccak256 of the packed structured data
152
+ #
153
+ # @param hash [String] binary hash (32 bytes) of the structured data
154
+ # @return [String] signature as hexadecimal string with v value adjusted to 27/28
155
+ def sign_typed_data(hash)
156
+ # Add TRON TIP-191 prefix for 32-byte hashes
157
+ # Ref: https://github.com/tronprotocol/tips/blob/master/tip-191.md
158
+ prefix = "\x19Tron Signed Message:\n32"
159
+ prefixed_data = prefix.b + hash
160
+ prefixed_hash = Tron::Utils::Crypto.keccak256(prefixed_data)
161
+
162
+ # Sign the prefixed hash
163
+ context = Secp256k1::Context.new
164
+ compact, recovery_id = context.sign_recoverable(@private_key, prefixed_hash).compact
165
+ signature = compact.bytes
166
+
167
+ # Adjust v value to 27 or 28 (standard Ethereum/TRON signature format)
168
+ # Instead of recovery_id (0 or 1), use 27 + recovery_id
169
+ v = recovery_id + 27
170
+ signature << v
171
+
172
+ Tron::Utils::Crypto.bin_to_hex(signature.pack('c*'))
173
+ end
174
+
149
175
  # Verifies a signature against a data blob
150
176
  #
151
177
  # @param blob [String] the original signed data
@@ -41,6 +41,144 @@ module Tron
41
41
  Utils::HTTP.post(endpoint, payload)
42
42
  end
43
43
 
44
+ # Gets detailed transaction information by transaction ID (includes error details)
45
+ #
46
+ # @param tx_id [String] the transaction ID (txID)
47
+ # @return [Hash] the detailed transaction information from the blockchain
48
+ def get_transaction_info(tx_id)
49
+ endpoint = "#{@base_url}/wallet/gettransactioninfobyid"
50
+ payload = { value: tx_id }
51
+
52
+ Utils::HTTP.post(endpoint, payload)
53
+ end
54
+
55
+ # Waits for transaction confirmation
56
+ #
57
+ # @param tx_id [String] the transaction ID to wait for
58
+ # @param max_attempts [Integer] maximum number of attempts to check (default: 15)
59
+ # @return [Hash] the transaction information when confirmed
60
+ # @raise [RuntimeError] if transaction is reverted or times out
61
+ def wait_for_transaction(tx_id, max_attempts = 15)
62
+ print "Waiting for transaction: #{tx_id}"
63
+
64
+ max_attempts.times do |i|
65
+ sleep 2
66
+ print '.'
67
+
68
+ begin
69
+ tx_info = get_transaction(tx_id)
70
+
71
+ if tx_info && tx_info['ret'] && tx_info['ret'][0]
72
+ result = tx_info['ret'][0]['contractRet']
73
+
74
+ if result == 'SUCCESS'
75
+ puts "\nāœ“ Transaction confirmed"
76
+ return tx_info
77
+ end
78
+
79
+ if result == 'REVERT'
80
+ puts "\nāŒ Transaction reverted"
81
+
82
+ # Get the actual revert reason from transaction info
83
+ error_message = extract_revert_reason(tx_id)
84
+
85
+ # Raise with just the error message
86
+ raise "Transaction failed: #{error_message}"
87
+ end
88
+ end
89
+ rescue RuntimeError => e
90
+ # If it's already a formatted error, re-raise it
91
+ raise e if e.message.start_with?('Transaction failed:')
92
+ # Transaction might not be available yet, continue waiting
93
+ next if i < max_attempts - 1
94
+ raise e
95
+ rescue => e
96
+ # Transaction might not be available yet, continue waiting
97
+ next if i < max_attempts - 1
98
+ raise e
99
+ end
100
+ end
101
+
102
+ raise 'Transaction timeout'
103
+ end
104
+
105
+ private
106
+
107
+ # Extracts the revert reason from a failed transaction
108
+ #
109
+ # @param tx_id [String] the transaction ID
110
+ # @return [String] the decoded error message
111
+ def extract_revert_reason(tx_id)
112
+ tx_detail = get_transaction_info(tx_id)
113
+
114
+ # Debug: Print the full transaction info
115
+ if ENV['DEBUG']
116
+ puts "\nšŸ” DEBUG - Full transaction info:"
117
+ puts JSON.pretty_generate(tx_detail) rescue puts(tx_detail.inspect)
118
+ end
119
+
120
+ # Try to get error from contractResult (this contains the ABI-encoded revert reason)
121
+ if tx_detail && tx_detail['contractResult'] && !tx_detail['contractResult'].empty?
122
+ contract_result = tx_detail['contractResult'][0]
123
+ puts "šŸ” DEBUG - contractResult: #{contract_result}" if ENV['DEBUG']
124
+
125
+ # Check if this is an Error(string) revert (starts with 08c379a0)
126
+ if contract_result && contract_result.start_with?('08c379a0')
127
+ begin
128
+ # ABI encoding for Error(string):
129
+ # - 4 bytes (8 hex chars): function selector (08c379a0)
130
+ # - 32 bytes (64 hex chars): offset to string data
131
+ # - 32 bytes (64 hex chars): length of string
132
+ # - N bytes: actual string data
133
+
134
+ # Skip selector (8 chars) and offset (64 chars)
135
+ data = contract_result[8 + 64..-1]
136
+
137
+ # Read length (next 64 chars)
138
+ length_hex = data[0...64]
139
+ length = length_hex.to_i(16)
140
+ puts "šŸ” DEBUG - String length: #{length}" if ENV['DEBUG']
141
+
142
+ # Read string data (next length * 2 hex chars)
143
+ string_hex = data[64, length * 2]
144
+ puts "šŸ” DEBUG - String hex: #{string_hex}" if ENV['DEBUG']
145
+
146
+ # Decode hex to UTF-8
147
+ error_message = [string_hex].pack('H*').force_encoding('UTF-8')
148
+ puts "šŸ” DEBUG - Decoded error: #{error_message}" if ENV['DEBUG']
149
+
150
+ return error_message unless error_message.empty?
151
+ rescue => e
152
+ puts "šŸ” DEBUG - Error decoding contractResult: #{e.message}" if ENV['DEBUG']
153
+ puts "šŸ” DEBUG - Backtrace: #{e.backtrace.first(3).join("\n")}" if ENV['DEBUG']
154
+ # If decoding fails, fall through
155
+ end
156
+ end
157
+ end
158
+
159
+ # Try to get error from resMessage (hex-encoded error string)
160
+ if tx_detail && tx_detail['resMessage']
161
+ error_hex = tx_detail['resMessage']
162
+ puts "šŸ” DEBUG - resMessage (hex): #{error_hex}" if ENV['DEBUG']
163
+
164
+ begin
165
+ # Decode hex to UTF-8 string
166
+ error_message = [error_hex].pack('H*').force_encoding('UTF-8')
167
+ # Clean up the message (remove null bytes and control characters)
168
+ error_message = error_message.gsub(/[\x00-\x1f\x7f]/, '').strip
169
+
170
+ puts "šŸ” DEBUG - Decoded resMessage: #{error_message}" if ENV['DEBUG']
171
+ return error_message unless error_message.empty?
172
+ rescue => e
173
+ puts "šŸ” DEBUG - Error decoding resMessage: #{e.message}" if ENV['DEBUG']
174
+ # If decoding fails, continue to try other methods
175
+ end
176
+ end
177
+
178
+ # Default to generic revert message
179
+ 'REVERT opcode executed'
180
+ end
181
+
44
182
  private
45
183
 
46
184
  # Signs a transaction locally using the private key
@@ -104,8 +242,26 @@ module Tron
104
242
 
105
243
  # Check if the transaction was successful
106
244
  unless response['result']
107
- error = response['Error'] || response['error'] || 'Unknown error'
108
- raise "Transaction failed: #{error}"
245
+ error_message = response['Error'] || response['error'] || response['message'] || 'Unknown error'
246
+
247
+ # Build detailed error message
248
+ error_details = ["āŒ Transaction broadcast failed: #{error_message}"]
249
+
250
+ # Add code if available
251
+ if response['code']
252
+ error_details << " Error code: #{response['code']}"
253
+ end
254
+
255
+ # Add transaction ID if available
256
+ if response['txid'] || response['txID']
257
+ tx_id = response['txid'] || response['txID']
258
+ error_details << " Transaction ID: #{tx_id}"
259
+ end
260
+
261
+ # Add full response for debugging
262
+ error_details << " Full response: #{response.inspect}"
263
+
264
+ raise error_details.join("\n")
109
265
  end
110
266
 
111
267
  response
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.9".freeze
5
+ VERSION = "1.2.2".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.9
4
+ version: 1.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bolo Michelin
@@ -141,6 +141,8 @@ files:
141
141
  - lib/tron/abi/encoder.rb
142
142
  - lib/tron/abi/event.rb
143
143
  - lib/tron/abi/function.rb
144
+ - lib/tron/abi/packed.rb
145
+ - lib/tron/abi/packed/encoder.rb
144
146
  - lib/tron/abi/type.rb
145
147
  - lib/tron/abi/util.rb
146
148
  - lib/tron/cache.rb