Class: Klay::Tx::Eip1559

Inherits:
Object
  • Object
show all
Defined in:
lib/klay/tx/eip1559.rb

Overview

Provides support for EIP-1559 transactions utilizing EIP-2718 types and envelopes. Ref: https://eips.ethereum.org/EIPS/eip-1559

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params) ⇒ Eip1559

Create a type-2 (EIP-1559) transaction payload object that can be prepared for envelope, signature and broadcast. Ref: https://eips.ethereum.org/EIPS/eip-1559

Parameters:

  • params (Hash)

    all necessary transaction fields.

Options Hash (params):

  • :chain_id (Integer)

    the chain ID.

  • :nonce (Integer)

    the signer nonce.

  • :priority_fee (Integer)

    the max priority fee per gas.

  • :max_gas_fee (Integer)

    the max transaction fee per gas.

  • :gas_limit (Integer)

    the gas limit.

  • :from (Klay::Address)

    the sender address.

  • :to (Klay::Address)

    the reciever address.

  • :value (Integer)

    the transaction value.

  • :data (String)

    the transaction data payload.

  • :access_list (Array)

    an optional access list.

Raises:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/klay/tx/eip1559.rb', line 86

def initialize(params)
  fields = { recovery_id: nil, r: 0, s: 0 }.merge params

  # populate optional fields with serializable empty values
  fields[:chain_id] = Tx.sanitize_chain fields[:chain_id]
  fields[:from] = Tx.sanitize_address fields[:from]
  fields[:to] = Tx.sanitize_address fields[:to]
  fields[:value] = Tx.sanitize_amount fields[:value]
  fields[:data] = Tx.sanitize_data fields[:data]

  # ensure sane values for all mandatory fields
  fields = Tx.validate_params fields
  fields[:access_list] = Tx.sanitize_list fields[:access_list]

  # ensure gas limit is not too low
  minimum_cost = Tx.estimate_intrinsic_gas fields[:data], fields[:access_list]
  raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost

  # populate class attributes
  @signer_nonce = fields[:nonce].to_i
  @max_priority_fee_per_gas = fields[:priority_fee].to_i
  @max_fee_per_gas = fields[:max_gas_fee].to_i
  @gas_limit = fields[:gas_limit].to_i
  @sender = fields[:from].to_s
  @destination = fields[:to].to_s
  @amount = fields[:value].to_i
  @payload = fields[:data]
  @access_list = fields[:access_list]

  # the signature v is set to the chain id for unsigned transactions
  @signature_y_parity = fields[:recovery_id]
  @chain_id = fields[:chain_id]

  # the signature fields are empty for unsigned transactions.
  @signature_r = fields[:r]
  @signature_s = fields[:s]

  # last but not least, set the type.
  @type = TYPE_1559
end

Instance Attribute Details

#access_listObject (readonly)

An optional EIP-2930 access list. Ref: https://eips.ethereum.org/EIPS/eip-2930



53
54
55
# File 'lib/klay/tx/eip1559.rb', line 53

def access_list
  @access_list
end

#amountObject (readonly)

The transaction amount in Wei.



46
47
48
# File 'lib/klay/tx/eip1559.rb', line 46

def amount
  @amount
end

#chain_idObject (readonly)

The EIP-155 Chain ID. Ref: https://eips.ethereum.org/EIPS/eip-155



28
29
30
# File 'lib/klay/tx/eip1559.rb', line 28

def chain_id
  @chain_id
end

#destinationObject (readonly)

The recipient address.



43
44
45
# File 'lib/klay/tx/eip1559.rb', line 43

def destination
  @destination
end

#gas_limitObject (readonly)

The gas limit for the transaction.



40
41
42
# File 'lib/klay/tx/eip1559.rb', line 40

def gas_limit
  @gas_limit
end

#max_fee_per_gasObject (readonly)

The transaction max fee per gas in Wei.



37
38
39
# File 'lib/klay/tx/eip1559.rb', line 37

def max_fee_per_gas
  @max_fee_per_gas
end

#max_priority_fee_per_gasObject (readonly)

The transaction max priority fee per gas in Wei.



34
35
36
# File 'lib/klay/tx/eip1559.rb', line 34

def max_priority_fee_per_gas
  @max_priority_fee_per_gas
end

#payloadObject (readonly)

The transaction data payload.



49
50
51
# File 'lib/klay/tx/eip1559.rb', line 49

def payload
  @payload
end

#senderObject (readonly)

The sender address.



65
66
67
# File 'lib/klay/tx/eip1559.rb', line 65

def sender
  @sender
end

#signature_rObject (readonly)

The signature r value.



59
60
61
# File 'lib/klay/tx/eip1559.rb', line 59

def signature_r
  @signature_r
end

#signature_sObject (readonly)

The signature s value.



62
63
64
# File 'lib/klay/tx/eip1559.rb', line 62

def signature_s
  @signature_s
end

#signature_y_parityObject (readonly)

The signature's y-parity byte (not v).



56
57
58
# File 'lib/klay/tx/eip1559.rb', line 56

def signature_y_parity
  @signature_y_parity
end

#signer_nonceObject (readonly)

The transaction nonce provided by the signer.



31
32
33
# File 'lib/klay/tx/eip1559.rb', line 31

def signer_nonce
  @signer_nonce
end

#typeObject (readonly)

The transaction type.



68
69
70
# File 'lib/klay/tx/eip1559.rb', line 68

def type
  @type
end

Instance Method Details

#decode(hex) ⇒ Klay::Tx::Eip1559

Decodes a raw transaction hex into an Klay::Tx::Eip1559 transaction object.

Parameters:

  • hex (String)

    the raw transaction hex-string.

Returns:

Raises:



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/klay/tx/eip1559.rb', line 138

def decode(hex)
  hex = Util.remove_hex_prefix hex
  type = hex[0, 2]
  raise TransactionTypeError, "Invalid transaction type #{type}!" if type.to_i(16) != TYPE_1559

  bin = Util.hex_to_bin hex[2..]
  tx = Rlp.decode bin

  # decoded transactions always have 9 + 3 fields, even if they are empty or zero
  raise ParameterError, "Transaction missing fields!" if tx.size < 9

  # populate the 9 payload fields
  chain_id = Util.deserialize_big_endian_to_int tx[0]
  nonce = Util.deserialize_big_endian_to_int tx[1]
  priority_fee = Util.deserialize_big_endian_to_int tx[2]
  max_gas_fee = Util.deserialize_big_endian_to_int tx[3]
  gas_limit = Util.deserialize_big_endian_to_int tx[4]
  to = Util.bin_to_hex tx[5]
  value = Util.deserialize_big_endian_to_int tx[6]
  data = tx[7]
  access_list = tx[8]

  # populate class attributes
  @chain_id = chain_id.to_i
  @signer_nonce = nonce.to_i
  @max_priority_fee_per_gas = priority_fee.to_i
  @max_fee_per_gas = max_gas_fee.to_i
  @gas_limit = gas_limit.to_i
  @destination = to.to_s
  @amount = value.to_i
  @payload = data
  @access_list = access_list

  # populate the 3 signature fields
  if tx.size == 9
    _set_signature(nil, 0, 0)
  elsif tx.size == 12
    recovery_id = Util.bin_to_hex(tx[9]).to_i(16)
    r = Util.bin_to_hex tx[10]
    s = Util.bin_to_hex tx[11]

    # allows us to force-setting a signature if the transaction is signed already
    _set_signature(recovery_id, r, s)
  else
    raise_error DecoderError, "Cannot decode EIP-1559 payload!"
  end

  # last but not least, set the type.
  @type = TYPE_1559

  # recover sender address
  v = Chain.to_v recovery_id, chain_id
  public_key = Signature.recover(unsigned_hash, "#{r}#{s}#{v.to_s(16)}", chain_id)
  address = Util.public_key_to_address(public_key).to_s
  @sender = Tx.sanitize_address address
end

#encodedString

Encodes a raw transaction object, wraps it in an EIP-2718 envelope with an EIP-1559 type prefix.

Returns:

  • (String)

    a raw, RLP-encoded EIP-1559 type transaction object.

Raises:



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/klay/tx/eip1559.rb', line 259

def encoded
  unless Tx.is_signed? self
    raise Signature::SignatureError, "Transaction is not signed!"
  end
  tx_data = []
  tx_data.push Util.serialize_int_to_big_endian @chain_id
  tx_data.push Util.serialize_int_to_big_endian @signer_nonce
  tx_data.push Util.serialize_int_to_big_endian @max_priority_fee_per_gas
  tx_data.push Util.serialize_int_to_big_endian @max_fee_per_gas
  tx_data.push Util.serialize_int_to_big_endian @gas_limit
  tx_data.push Util.hex_to_bin @destination
  tx_data.push Util.serialize_int_to_big_endian @amount
  tx_data.push Rlp::Sedes.binary.serialize @payload
  tx_data.push Rlp::Sedes.infer(@access_list).serialize @access_list
  tx_data.push Util.serialize_int_to_big_endian @signature_y_parity
  tx_data.push Util.serialize_int_to_big_endian @signature_r
  tx_data.push Util.serialize_int_to_big_endian @signature_s
  tx_encoded = Rlp.encode tx_data

  # create an EIP-2718 envelope with EIP-1559 type payload
  tx_type = Util.serialize_int_to_big_endian @type
  return "#{tx_type}#{tx_encoded}"
end

#hashString

Gets the transaction hash.

Returns:

  • (String)

    the transaction hash.



293
294
295
# File 'lib/klay/tx/eip1559.rb', line 293

def hash
  Util.bin_to_hex Util.keccak256 encoded
end

#hexString

Gets the encoded, enveloped, raw transaction hex.

Returns:

  • (String)

    the raw transaction hex.



286
287
288
# File 'lib/klay/tx/eip1559.rb', line 286

def hex
  Util.bin_to_hex encoded
end

#sign(key) ⇒ String

Sign the transaction with a given key.

Parameters:

  • key (Klay::Key)

    the key-pair to use for signing.

Returns:

  • (String)

    a transaction hash.

Raises:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/klay/tx/eip1559.rb', line 232

def sign(key)
  if Tx.is_signed? self
    raise Signature::SignatureError, "Transaction is already signed!"
  end

  # ensure the sender address matches the given key
  unless @sender.nil? or sender.empty?
    signer_address = Tx.sanitize_address key.address.to_s
    from_address = Tx.sanitize_address @sender
    raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
  end

  # sign a keccak hash of the unsigned, encoded transaction
  signature = key.sign(unsigned_hash, @chain_id)
  r, s, v = Signature.dissect signature
  recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
  @signature_y_parity = recovery_id
  @signature_r = r
  @signature_s = s
  return hash
end

#unsigned_copy(tx) ⇒ Klay::Tx::Eip1559

Creates an unsigned copy of a transaction payload.

Parameters:

Returns:

Raises:



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/klay/tx/eip1559.rb', line 200

def unsigned_copy(tx)

  # not checking transaction validity unless it's of a different class
  raise TransactionTypeError, "Cannot copy transaction of different payload type!" unless tx.instance_of? Tx::Eip1559

  # populate class attributes
  @signer_nonce = tx.signer_nonce
  @max_priority_fee_per_gas = tx.max_priority_fee_per_gas
  @max_fee_per_gas = tx.max_fee_per_gas
  @gas_limit = tx.gas_limit
  @destination = tx.destination
  @amount = tx.amount
  @payload = tx.payload
  @access_list = tx.access_list
  @chain_id = tx.chain_id

  # force-set signature to unsigned
  _set_signature(nil, 0, 0)

  # keep the 'from' field blank
  @sender = Tx.sanitize_address nil

  # last but not least, set the type.
  @type = TYPE_1559
end

#unsigned_encodedString

Encodes the unsigned transaction payload in an EIP-1559 envelope, required for signing.

Returns:

  • (String)

    an RLP-encoded, unsigned, enveloped EIP-1559 transaction.



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/klay/tx/eip1559.rb', line 301

def unsigned_encoded
  tx_data = []
  tx_data.push Util.serialize_int_to_big_endian @chain_id
  tx_data.push Util.serialize_int_to_big_endian @signer_nonce
  tx_data.push Util.serialize_int_to_big_endian @max_priority_fee_per_gas
  tx_data.push Util.serialize_int_to_big_endian @max_fee_per_gas
  tx_data.push Util.serialize_int_to_big_endian @gas_limit
  tx_data.push Util.hex_to_bin @destination
  tx_data.push Util.serialize_int_to_big_endian @amount
  tx_data.push Rlp::Sedes.binary.serialize @payload
  tx_data.push Rlp::Sedes.infer(@access_list).serialize @access_list
  tx_encoded = Rlp.encode tx_data

  # create an EIP-2718 envelope with EIP-1559 type payload (unsigned)
  tx_type = Util.serialize_int_to_big_endian @type
  return "#{tx_type}#{tx_encoded}"
end

#unsigned_hashString

Gets the sign-hash required to sign a raw transaction.

Returns:

  • (String)

    a Keccak-256 hash of an unsigned transaction.



322
323
324
# File 'lib/klay/tx/eip1559.rb', line 322

def unsigned_hash
  Util.keccak256 unsigned_encoded
end