eth 0.4.18 → 0.5.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +6 -2
  3. data/.github/workflows/docs.yml +1 -1
  4. data/.github/workflows/spec.yml +52 -0
  5. data/.gitignore +24 -24
  6. data/.gitmodules +3 -3
  7. data/.yardopts +1 -0
  8. data/AUTHORS.txt +27 -0
  9. data/CHANGELOG.md +63 -13
  10. data/Gemfile +12 -4
  11. data/LICENSE.txt +202 -22
  12. data/README.md +231 -76
  13. data/bin/console +4 -4
  14. data/bin/setup +5 -4
  15. data/codecov.yml +6 -0
  16. data/eth.gemspec +23 -19
  17. data/lib/eth/abi/type.rb +178 -0
  18. data/lib/eth/abi.rb +396 -0
  19. data/lib/eth/address.rb +57 -10
  20. data/lib/eth/api.rb +223 -0
  21. data/lib/eth/chain.rb +151 -0
  22. data/lib/eth/client/http.rb +63 -0
  23. data/lib/eth/client/ipc.rb +50 -0
  24. data/lib/eth/client.rb +232 -0
  25. data/lib/eth/constant.rb +71 -0
  26. data/lib/eth/eip712.rb +184 -0
  27. data/lib/eth/key/decrypter.rb +121 -85
  28. data/lib/eth/key/encrypter.rb +180 -99
  29. data/lib/eth/key.rb +134 -45
  30. data/lib/eth/rlp/decoder.rb +114 -0
  31. data/lib/eth/rlp/encoder.rb +78 -0
  32. data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
  33. data/lib/eth/rlp/sedes/binary.rb +97 -0
  34. data/lib/eth/rlp/sedes/list.rb +84 -0
  35. data/lib/eth/rlp/sedes.rb +74 -0
  36. data/lib/eth/rlp.rb +63 -0
  37. data/lib/eth/signature.rb +163 -0
  38. data/lib/eth/solidity.rb +75 -0
  39. data/lib/eth/tx/eip1559.rb +337 -0
  40. data/lib/eth/tx/eip2930.rb +329 -0
  41. data/lib/eth/tx/legacy.rb +297 -0
  42. data/lib/eth/tx.rb +269 -146
  43. data/lib/eth/unit.rb +49 -0
  44. data/lib/eth/util.rb +235 -0
  45. data/lib/eth/version.rb +18 -1
  46. data/lib/eth.rb +34 -67
  47. metadata +47 -95
  48. data/.github/workflows/build.yml +0 -36
  49. data/lib/eth/gas.rb +0 -7
  50. data/lib/eth/open_ssl.rb +0 -395
  51. data/lib/eth/secp256k1.rb +0 -5
  52. data/lib/eth/sedes.rb +0 -39
  53. data/lib/eth/utils.rb +0 -126
@@ -0,0 +1,297 @@
1
+ # Copyright (c) 2016-2022 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Provides the {Eth} module.
16
+ module Eth
17
+
18
+ # Provides the `Tx` module supporting various transaction types.
19
+ module Tx
20
+
21
+ # Provides legacy support for transactions on blockchains that do not
22
+ # implement EIP-1559, EIP-2718, or EIP-2930.
23
+ class Legacy
24
+
25
+ # The transaction nonce provided by the signer.
26
+ attr_reader :signer_nonce
27
+
28
+ # The gas price for the transaction in Wei.
29
+ attr_reader :gas_price
30
+
31
+ # The gas limit for the transaction.
32
+ attr_reader :gas_limit
33
+
34
+ # The recipient address.
35
+ attr_reader :destination
36
+
37
+ # The transaction amount in Wei.
38
+ attr_reader :amount
39
+
40
+ # The transaction data payload.
41
+ attr_reader :payload
42
+
43
+ # The signature `v` byte.
44
+ attr_reader :signature_v
45
+
46
+ # The signature `r` value.
47
+ attr_reader :signature_r
48
+
49
+ # The signature `s` value.
50
+ attr_reader :signature_s
51
+
52
+ # The EIP-155 chain ID field.
53
+ # Ref: https://eips.ethereum.org/EIPS/eip-155
54
+ attr_reader :chain_id
55
+
56
+ # The sender address.
57
+ attr_reader :sender
58
+
59
+ # The transaction type.
60
+ attr_reader :type
61
+
62
+ # Create a legacy transaction object that can be prepared for
63
+ # signature and broadcast. Should not be used unless there is
64
+ # no EIP-1559 support.
65
+ #
66
+ # @param params [Hash] all necessary transaction fields.
67
+ # @option params [Integer] :nonce the signer nonce.
68
+ # @option params [Integer] :gas_price the gas price.
69
+ # @option params [Integer] :gas_limit the gas limit.
70
+ # @option params [Eth::Address] :from the sender address.
71
+ # @option params [Eth::Address] :to the reciever address.
72
+ # @option params [Integer] :value the transaction value.
73
+ # @option params [String] :data the transaction data payload.
74
+ # @param chain_id [Integer] the EIP-155 Chain ID.
75
+ # @raise [ParameterError] if gas limit is too low.
76
+ def initialize(params, chain_id = Chain::ETHEREUM)
77
+ fields = { v: chain_id, r: 0, s: 0 }.merge params
78
+
79
+ # populate optional fields with serializable empty values
80
+ fields[:value] = Tx.sanitize_amount fields[:value]
81
+ fields[:from] = Tx.sanitize_address fields[:from]
82
+ fields[:to] = Tx.sanitize_address fields[:to]
83
+ fields[:data] = Tx.sanitize_data fields[:data]
84
+
85
+ # ensure sane values for all mandatory fields
86
+ fields = Tx.validate_params fields
87
+ fields = Tx.validate_legacy_params fields
88
+
89
+ # ensure gas limit is not too low
90
+ minimum_cost = Tx.estimate_intrinsic_gas fields[:data]
91
+ raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost
92
+
93
+ # populate class attributes
94
+ @signer_nonce = fields[:nonce].to_i
95
+ @gas_price = fields[:gas_price].to_i
96
+ @gas_limit = fields[:gas_limit].to_i
97
+ @sender = fields[:from].to_s
98
+ @destination = fields[:to].to_s
99
+ @amount = fields[:value].to_i
100
+ @payload = fields[:data]
101
+
102
+ # the signature v is set to the chain id for unsigned transactions
103
+ @signature_v = fields[:v]
104
+ @chain_id = chain_id
105
+
106
+ # the signature fields are empty for unsigned transactions.
107
+ @signature_r = fields[:r]
108
+ @signature_s = fields[:s]
109
+
110
+ # last but not least, set the type.
111
+ @type = TYPE_LEGACY
112
+ end
113
+
114
+ # overloads the constructor for decoding raw transactions and creating unsigned copies
115
+ konstructor :decode, :unsigned_copy
116
+
117
+ # Decodes a raw transaction hex into an {Eth::Tx::Legacy}
118
+ # transaction object.
119
+ #
120
+ # @param hex [String] the raw transaction hex-string.
121
+ # @return [Eth::Tx::Legacy] transaction object.
122
+ # @raise [ParameterError] if transaction misses fields.
123
+ def decode(hex)
124
+ bin = Util.hex_to_bin hex
125
+ tx = Rlp.decode bin
126
+
127
+ # decoded transactions always have 9 fields, even if they are empty or zero
128
+ raise ParameterError, "Transaction missing fields!" if tx.size < 9
129
+
130
+ # populate the 9 fields
131
+ nonce = Util.deserialize_big_endian_to_int tx[0]
132
+ gas_price = Util.deserialize_big_endian_to_int tx[1]
133
+ gas_limit = Util.deserialize_big_endian_to_int tx[2]
134
+ to = Util.bin_to_hex tx[3]
135
+ value = Util.deserialize_big_endian_to_int tx[4]
136
+ data = tx[5]
137
+ v = Util.bin_to_hex tx[6]
138
+ r = Util.bin_to_hex tx[7]
139
+ s = Util.bin_to_hex tx[8]
140
+
141
+ # try to recover the chain id from v
142
+ chain_id = Chain.to_chain_id Util.deserialize_big_endian_to_int tx[6]
143
+
144
+ # populate class attributes
145
+ @signer_nonce = nonce.to_i
146
+ @gas_price = gas_price.to_i
147
+ @gas_limit = gas_limit.to_i
148
+ @destination = to.to_s
149
+ @amount = value.to_i
150
+ @payload = data
151
+ @chain_id = chain_id
152
+
153
+ # allows us to force-setting a signature if the transaction is signed already
154
+ _set_signature(v, r, s)
155
+
156
+ unless chain_id.nil?
157
+
158
+ # recover sender address
159
+ public_key = Signature.recover(unsigned_hash, "#{r}#{s}#{v}", chain_id)
160
+ address = Util.public_key_to_address(public_key).to_s
161
+ @sender = Tx.sanitize_address address
162
+ else
163
+
164
+ # keep the 'from' field blank
165
+ @sender = Tx.sanitize_address nil
166
+ end
167
+
168
+ # last but not least, set the type.
169
+ @type = TYPE_LEGACY
170
+ end
171
+
172
+ # Creates an unsigned copy of a transaction.
173
+ #
174
+ # @param tx [Eth::Tx::Legacy] an legacy transaction object.
175
+ # @return [Eth::Tx::Legacy] an unsigned transaction object.
176
+ # @raise [TransactionTypeError] if transaction type does not match.
177
+ def unsigned_copy(tx)
178
+
179
+ # not checking transaction validity unless it's of a different class
180
+ raise TransactionTypeError, "Cannot copy transaction of different type!" unless tx.instance_of? Tx::Legacy
181
+
182
+ # populate class attributes
183
+ @signer_nonce = tx.signer_nonce
184
+ @gas_price = tx.gas_price
185
+ @gas_limit = tx.gas_limit
186
+ @destination = tx.destination
187
+ @amount = tx.amount
188
+ @payload = tx.payload
189
+ @chain_id = tx.chain_id
190
+
191
+ # force-set signature to unsigned
192
+ _set_signature(tx.chain_id, 0, 0)
193
+
194
+ # keep the 'from' field blank
195
+ @sender = Tx.sanitize_address nil
196
+
197
+ # last but not least, set the type.
198
+ @type = TYPE_LEGACY
199
+ end
200
+
201
+ # Sign the transaction with a given key.
202
+ #
203
+ # @param key [Eth::Key] the key-pair to use for signing.
204
+ # @return [String] a transaction hash.
205
+ # @raise [Signature::SignatureError] if transaction is already signed.
206
+ # @raise [Signature::SignatureError] if sender address does not match signing key.
207
+ def sign(key)
208
+ if Tx.is_signed? self
209
+ raise Signature::SignatureError, "Transaction is already signed!"
210
+ end
211
+
212
+ # ensure the sender address matches the given key
213
+ unless @sender.nil? or sender.empty?
214
+ signer_address = Tx.sanitize_address key.address.to_s
215
+ from_address = Tx.sanitize_address @sender
216
+ raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
217
+ end
218
+
219
+ # sign a keccak hash of the unsigned, encoded transaction
220
+ signature = key.sign(unsigned_hash, @chain_id)
221
+ r, s, v = Signature.dissect signature
222
+ @signature_v = v
223
+ @signature_r = r
224
+ @signature_s = s
225
+ return hash
226
+ end
227
+
228
+ # Encodes a raw transaction object.
229
+ #
230
+ # @return [String] a raw, RLP-encoded legacy transaction.
231
+ # @raise [Signature::SignatureError] if the transaction is not yet signed.
232
+ def encoded
233
+ unless Tx.is_signed? self
234
+ raise Signature::SignatureError, "Transaction is not signed!"
235
+ end
236
+ tx_data = []
237
+ tx_data.push Util.serialize_int_to_big_endian @signer_nonce
238
+ tx_data.push Util.serialize_int_to_big_endian @gas_price
239
+ tx_data.push Util.serialize_int_to_big_endian @gas_limit
240
+ tx_data.push Util.hex_to_bin @destination
241
+ tx_data.push Util.serialize_int_to_big_endian @amount
242
+ tx_data.push Rlp::Sedes.binary.serialize @payload
243
+ tx_data.push Util.serialize_int_to_big_endian @signature_v
244
+ tx_data.push Util.serialize_int_to_big_endian @signature_r
245
+ tx_data.push Util.serialize_int_to_big_endian @signature_s
246
+ Rlp.encode tx_data
247
+ end
248
+
249
+ # Gets the encoded, raw transaction hex.
250
+ #
251
+ # @return [String] the raw transaction hex.
252
+ def hex
253
+ Util.bin_to_hex encoded
254
+ end
255
+
256
+ # Gets the transaction hash.
257
+ #
258
+ # @return [String] the transaction hash.
259
+ def hash
260
+ Util.bin_to_hex Util.keccak256 encoded
261
+ end
262
+
263
+ # Encodes the unsigned transaction object, required for signing.
264
+ #
265
+ # @return [String] an RLP-encoded, unsigned transaction.
266
+ def unsigned_encoded
267
+ tx_data = []
268
+ tx_data.push Util.serialize_int_to_big_endian @signer_nonce
269
+ tx_data.push Util.serialize_int_to_big_endian @gas_price
270
+ tx_data.push Util.serialize_int_to_big_endian @gas_limit
271
+ tx_data.push Util.hex_to_bin @destination
272
+ tx_data.push Util.serialize_int_to_big_endian @amount
273
+ tx_data.push Rlp::Sedes.binary.serialize @payload
274
+ tx_data.push Util.serialize_int_to_big_endian @chain_id
275
+ tx_data.push Util.serialize_int_to_big_endian 0
276
+ tx_data.push Util.serialize_int_to_big_endian 0
277
+ Rlp.encode tx_data
278
+ end
279
+
280
+ # Gets the sign-hash required to sign a raw transaction.
281
+ #
282
+ # @return [String] a Keccak-256 hash of an unsigned transaction.
283
+ def unsigned_hash
284
+ Util.keccak256 unsigned_encoded
285
+ end
286
+
287
+ private
288
+
289
+ # Force-sets an existing signature of a decoded transaction.
290
+ def _set_signature(v, r, s)
291
+ @signature_v = v
292
+ @signature_r = r
293
+ @signature_s = s
294
+ end
295
+ end
296
+ end
297
+ end