mirror of https://github.com/status-im/nim-eth.git
Implement EIP-7702 Transaction and Receipt (#731)
* Implement EIP-7702 Transaction and Receipt
This commit is contained in:
parent
59715353db
commit
4eecab27ef
|
@ -80,12 +80,21 @@ type
|
||||||
TxEip2930 # 1
|
TxEip2930 # 1
|
||||||
TxEip1559 # 2
|
TxEip1559 # 2
|
||||||
TxEip4844 # 3
|
TxEip4844 # 3
|
||||||
|
TxEip7702 # 4
|
||||||
|
|
||||||
NetworkPayload* = ref object
|
NetworkPayload* = ref object
|
||||||
blobs* : seq[NetworkBlob]
|
blobs* : seq[NetworkBlob]
|
||||||
commitments* : seq[KzgCommitment]
|
commitments* : seq[KzgCommitment]
|
||||||
proofs* : seq[KzgProof]
|
proofs* : seq[KzgProof]
|
||||||
|
|
||||||
|
Authorization* = object
|
||||||
|
chainId*: ChainId
|
||||||
|
address*: EthAddress
|
||||||
|
nonce*: AccountNonce
|
||||||
|
yParity*: uint64
|
||||||
|
R*: UInt256
|
||||||
|
S*: UInt256
|
||||||
|
|
||||||
Transaction* = object
|
Transaction* = object
|
||||||
txType* : TxType # EIP-2718
|
txType* : TxType # EIP-2718
|
||||||
chainId* : ChainId # EIP-2930
|
chainId* : ChainId # EIP-2930
|
||||||
|
@ -100,6 +109,7 @@ type
|
||||||
accessList* : AccessList # EIP-2930
|
accessList* : AccessList # EIP-2930
|
||||||
maxFeePerBlobGas*: UInt256 # EIP-4844
|
maxFeePerBlobGas*: UInt256 # EIP-4844
|
||||||
versionedHashes*: VersionedHashes # EIP-4844
|
versionedHashes*: VersionedHashes # EIP-4844
|
||||||
|
authorizationList*: seq[Authorization]# EIP-7702
|
||||||
V* : uint64
|
V* : uint64
|
||||||
R*, S* : UInt256
|
R*, S* : UInt256
|
||||||
|
|
||||||
|
@ -198,6 +208,7 @@ type
|
||||||
# Eip2930Receipt = TxEip2930
|
# Eip2930Receipt = TxEip2930
|
||||||
# Eip1559Receipt = TxEip1559
|
# Eip1559Receipt = TxEip1559
|
||||||
# Eip4844Receipt = TxEip4844
|
# Eip4844Receipt = TxEip4844
|
||||||
|
# Eip7702Receipt = TxEip7702
|
||||||
|
|
||||||
Receipt* = object
|
Receipt* = object
|
||||||
receiptType* : ReceiptType
|
receiptType* : ReceiptType
|
||||||
|
@ -244,6 +255,7 @@ const
|
||||||
Eip2930Receipt* = TxEip2930
|
Eip2930Receipt* = TxEip2930
|
||||||
Eip1559Receipt* = TxEip1559
|
Eip1559Receipt* = TxEip1559
|
||||||
Eip4844Receipt* = TxEip4844
|
Eip4844Receipt* = TxEip4844
|
||||||
|
Eip7702Receipt* = TxEip7702
|
||||||
|
|
||||||
EMPTY_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
|
EMPTY_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
|
||||||
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
|
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
|
||||||
|
|
|
@ -128,6 +128,31 @@ proc appendTxEip4844(w: var RlpWriter, tx: Transaction) =
|
||||||
w.append(tx.R)
|
w.append(tx.R)
|
||||||
w.append(tx.S)
|
w.append(tx.S)
|
||||||
|
|
||||||
|
proc append*(w: var RlpWriter, x: Authorization) =
|
||||||
|
w.startList(6)
|
||||||
|
w.append(x.chainId.uint64)
|
||||||
|
w.append(x.address)
|
||||||
|
w.append(x.nonce)
|
||||||
|
w.append(x.yParity)
|
||||||
|
w.append(x.R)
|
||||||
|
w.append(x.S)
|
||||||
|
|
||||||
|
proc appendTxEip7702(w: var RlpWriter, tx: Transaction) =
|
||||||
|
w.startList(13)
|
||||||
|
w.append(tx.chainId.uint64)
|
||||||
|
w.append(tx.nonce)
|
||||||
|
w.append(tx.maxPriorityFeePerGas)
|
||||||
|
w.append(tx.maxFeePerGas)
|
||||||
|
w.append(tx.gasLimit)
|
||||||
|
w.append(tx.to)
|
||||||
|
w.append(tx.value)
|
||||||
|
w.append(tx.payload)
|
||||||
|
w.append(tx.accessList)
|
||||||
|
w.append(tx.authorizationList)
|
||||||
|
w.append(tx.V)
|
||||||
|
w.append(tx.R)
|
||||||
|
w.append(tx.S)
|
||||||
|
|
||||||
proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
|
proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
|
||||||
case tx.txType
|
case tx.txType
|
||||||
of TxLegacy:
|
of TxLegacy:
|
||||||
|
@ -138,6 +163,8 @@ proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
|
||||||
w.appendTxEip1559(tx)
|
w.appendTxEip1559(tx)
|
||||||
of TxEip4844:
|
of TxEip4844:
|
||||||
w.appendTxEip4844(tx)
|
w.appendTxEip4844(tx)
|
||||||
|
of TxEip7702:
|
||||||
|
w.appendTxEip7702(tx)
|
||||||
|
|
||||||
proc append*(w: var RlpWriter, tx: Transaction) =
|
proc append*(w: var RlpWriter, tx: Transaction) =
|
||||||
if tx.txType != TxLegacy:
|
if tx.txType != TxLegacy:
|
||||||
|
@ -229,6 +256,32 @@ proc readTxEip4844(rlp: var Rlp, tx: var Transaction) =
|
||||||
rlp.read(tx.R)
|
rlp.read(tx.R)
|
||||||
rlp.read(tx.S)
|
rlp.read(tx.S)
|
||||||
|
|
||||||
|
proc read*(rlp: var Rlp, T: type Authorization): T =
|
||||||
|
rlp.tryEnterList()
|
||||||
|
result.chainId = rlp.read(uint64).ChainId
|
||||||
|
rlp.read(result.address)
|
||||||
|
rlp.read(result.nonce)
|
||||||
|
rlp.read(result.yParity)
|
||||||
|
rlp.read(result.R)
|
||||||
|
rlp.read(result.S)
|
||||||
|
|
||||||
|
proc readTxEip7702(rlp: var Rlp, tx: var Transaction) =
|
||||||
|
tx.txType = TxEip7702
|
||||||
|
rlp.tryEnterList()
|
||||||
|
tx.chainId = rlp.read(uint64).ChainId
|
||||||
|
rlp.read(tx.nonce)
|
||||||
|
rlp.read(tx.maxPriorityFeePerGas)
|
||||||
|
rlp.read(tx.maxFeePerGas)
|
||||||
|
rlp.read(tx.gasLimit)
|
||||||
|
rlp.read(tx.to)
|
||||||
|
rlp.read(tx.value)
|
||||||
|
rlp.read(tx.payload)
|
||||||
|
rlp.read(tx.accessList)
|
||||||
|
rlp.read(tx.authorizationList)
|
||||||
|
rlp.read(tx.V)
|
||||||
|
rlp.read(tx.R)
|
||||||
|
rlp.read(tx.S)
|
||||||
|
|
||||||
proc readTxType(rlp: var Rlp): TxType =
|
proc readTxType(rlp: var Rlp): TxType =
|
||||||
if rlp.isList:
|
if rlp.isList:
|
||||||
raise newException(RlpTypeMismatch,
|
raise newException(RlpTypeMismatch,
|
||||||
|
@ -271,6 +324,8 @@ proc readTxPayload(rlp: var Rlp, tx: var Transaction, txType: TxType) =
|
||||||
rlp.readTxEip1559(tx)
|
rlp.readTxEip1559(tx)
|
||||||
of TxEip4844:
|
of TxEip4844:
|
||||||
rlp.readTxEip4844(tx)
|
rlp.readTxEip4844(tx)
|
||||||
|
of TxEip7702:
|
||||||
|
rlp.readTxEip7702(tx)
|
||||||
|
|
||||||
proc readTxTyped(rlp: var Rlp, tx: var Transaction) =
|
proc readTxTyped(rlp: var Rlp, tx: var Transaction) =
|
||||||
let txType = rlp.readTxType()
|
let txType = rlp.readTxType()
|
||||||
|
@ -377,7 +432,7 @@ proc append*(
|
||||||
rlpWriter.append(rlp.encode(tx))
|
rlpWriter.append(rlp.encode(tx))
|
||||||
|
|
||||||
proc append*(w: var RlpWriter, rec: Receipt) =
|
proc append*(w: var RlpWriter, rec: Receipt) =
|
||||||
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt, Eip4844Receipt}:
|
if rec.receiptType in {Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt}:
|
||||||
w.append(rec.receiptType.uint)
|
w.append(rec.receiptType.uint)
|
||||||
|
|
||||||
w.startList(4)
|
w.startList(4)
|
||||||
|
@ -420,7 +475,7 @@ proc readReceiptTyped(rlp: var Rlp, receipt: var Receipt) =
|
||||||
var txVal: ReceiptType
|
var txVal: ReceiptType
|
||||||
if checkedEnumAssign(txVal, recType):
|
if checkedEnumAssign(txVal, recType):
|
||||||
case txVal:
|
case txVal:
|
||||||
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt:
|
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt:
|
||||||
receipt.receiptType = txVal
|
receipt.receiptType = txVal
|
||||||
of LegacyReceipt:
|
of LegacyReceipt:
|
||||||
# The legacy type should not be used here.
|
# The legacy type should not be used here.
|
||||||
|
|
|
@ -78,6 +78,22 @@ func rlpEncodeEip4844(tx: Transaction): auto =
|
||||||
w.append(tx.versionedHashes)
|
w.append(tx.versionedHashes)
|
||||||
w.finish()
|
w.finish()
|
||||||
|
|
||||||
|
func rlpEncodeEip7702(tx: Transaction): auto =
|
||||||
|
var w = initRlpWriter()
|
||||||
|
w.append(TxEip7702)
|
||||||
|
w.startList(10)
|
||||||
|
w.append(tx.chainId.uint64)
|
||||||
|
w.append(tx.nonce)
|
||||||
|
w.append(tx.maxPriorityFeePerGas)
|
||||||
|
w.append(tx.maxFeePerGas)
|
||||||
|
w.append(tx.gasLimit)
|
||||||
|
w.append(tx.to)
|
||||||
|
w.append(tx.value)
|
||||||
|
w.append(tx.payload)
|
||||||
|
w.append(tx.accessList)
|
||||||
|
w.append(tx.authorizationList)
|
||||||
|
w.finish()
|
||||||
|
|
||||||
func rlpEncode*(tx: Transaction): auto =
|
func rlpEncode*(tx: Transaction): auto =
|
||||||
case tx.txType
|
case tx.txType
|
||||||
of TxLegacy:
|
of TxLegacy:
|
||||||
|
@ -91,6 +107,8 @@ func rlpEncode*(tx: Transaction): auto =
|
||||||
tx.rlpEncodeEip1559
|
tx.rlpEncodeEip1559
|
||||||
of TxEip4844:
|
of TxEip4844:
|
||||||
tx.rlpEncodeEip4844
|
tx.rlpEncodeEip4844
|
||||||
|
of TxEip7702:
|
||||||
|
tx.rlpEncodeEip7702
|
||||||
|
|
||||||
func txHashNoSignature*(tx: Transaction): Hash256 =
|
func txHashNoSignature*(tx: Transaction): Hash256 =
|
||||||
# Hash transaction without signature
|
# Hash transaction without signature
|
||||||
|
|
|
@ -12,4 +12,5 @@ import
|
||||||
test_eth_types,
|
test_eth_types,
|
||||||
test_eth_types_rlp,
|
test_eth_types_rlp,
|
||||||
test_common,
|
test_common,
|
||||||
test_eip4844
|
test_eip4844,
|
||||||
|
test_eip7702
|
||||||
|
|
|
@ -145,7 +145,7 @@ proc suite2() =
|
||||||
let _ = rlp.decode(receiptBytes, Receipt)
|
let _ = rlp.decode(receiptBytes, Receipt)
|
||||||
|
|
||||||
test "Receipts EIP-2718 encoding - invalid - unsupported tx type":
|
test "Receipts EIP-2718 encoding - invalid - unsupported tx type":
|
||||||
let receiptBytes: seq[byte] = @[0x04]
|
let receiptBytes: seq[byte] = @[0x05]
|
||||||
expect UnsupportedRlpError:
|
expect UnsupportedRlpError:
|
||||||
let _ = rlp.decode(receiptBytes, Receipt)
|
let _ = rlp.decode(receiptBytes, Receipt)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
# Nimbus
|
||||||
|
# Copyright (c) 2024 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||||
|
# http://opensource.org/licenses/MIT)
|
||||||
|
# at your option. This file may not be copied, modified, or distributed except
|
||||||
|
# according to those terms.
|
||||||
|
{.used.}
|
||||||
|
|
||||||
|
import
|
||||||
|
stew/byteutils,
|
||||||
|
results,
|
||||||
|
unittest2,
|
||||||
|
../../eth/common,
|
||||||
|
../../eth/rlp,
|
||||||
|
../../eth/common/transaction,
|
||||||
|
../../eth/keys
|
||||||
|
|
||||||
|
const
|
||||||
|
recipient = hexToByteArray[20]("095e7baea6a6c7c4c2dfeb977efac326af552d87")
|
||||||
|
zeroG1 = hexToByteArray[48]("0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
source = hexToByteArray[20]("0x0000000000000000000000000000000000000001")
|
||||||
|
storageKey= default(StorageKey)
|
||||||
|
accesses = @[AccessPair(address: source, storageKeys: @[storageKey])]
|
||||||
|
abcdef = hexToSeqByte("abcdef")
|
||||||
|
authList = @[Authorization(
|
||||||
|
chainID: 1.ChainId,
|
||||||
|
address: source,
|
||||||
|
nonce: 2.AccountNonce,
|
||||||
|
yParity: 3,
|
||||||
|
R: 4.u256,
|
||||||
|
S: 5.u256
|
||||||
|
)]
|
||||||
|
|
||||||
|
proc tx0(i: int): Transaction =
|
||||||
|
Transaction(
|
||||||
|
txType: TxEip7702,
|
||||||
|
chainId: 1.ChainId,
|
||||||
|
nonce: i.AccountNonce,
|
||||||
|
maxPriorityFeePerGas: 2.GasInt,
|
||||||
|
maxFeePerGas: 3.GasInt,
|
||||||
|
gasLimit: 4.GasInt,
|
||||||
|
to: Opt.some recipient,
|
||||||
|
value: 5.u256,
|
||||||
|
payload: abcdef,
|
||||||
|
accessList: accesses,
|
||||||
|
authorizationList: authList
|
||||||
|
)
|
||||||
|
|
||||||
|
func `==`(a, b: ChainId): bool =
|
||||||
|
a.uint64 == b.uint64
|
||||||
|
|
||||||
|
template roundTrip(txFunc: untyped, i: int) =
|
||||||
|
let tx = txFunc(i)
|
||||||
|
let bytes = rlp.encode(tx)
|
||||||
|
let tx2 = rlp.decode(bytes, Transaction)
|
||||||
|
let bytes2 = rlp.encode(tx2)
|
||||||
|
check bytes == bytes2
|
||||||
|
|
||||||
|
template read[T](rlp: var Rlp, val: var T) =
|
||||||
|
val = rlp.read(type val)
|
||||||
|
|
||||||
|
proc read[T](rlp: var Rlp, val: var Opt[T]) =
|
||||||
|
if rlp.blobLen != 0:
|
||||||
|
val = Opt.some(rlp.read(T))
|
||||||
|
else:
|
||||||
|
rlp.skipElem
|
||||||
|
|
||||||
|
proc readTx(rlp: var Rlp, tx: var Transaction) =
|
||||||
|
rlp.tryEnterList()
|
||||||
|
tx.chainId = rlp.read(uint64).ChainId
|
||||||
|
rlp.read(tx.nonce)
|
||||||
|
rlp.read(tx.maxPriorityFeePerGas)
|
||||||
|
rlp.read(tx.maxFeePerGas)
|
||||||
|
rlp.read(tx.gasLimit)
|
||||||
|
rlp.read(tx.to)
|
||||||
|
rlp.read(tx.value)
|
||||||
|
rlp.read(tx.payload)
|
||||||
|
rlp.read(tx.accessList)
|
||||||
|
rlp.read(tx.authorizationList)
|
||||||
|
|
||||||
|
proc decodeTxEip7702(bytes: openArray[byte]): Transaction =
|
||||||
|
var rlp = rlpFromBytes(bytes)
|
||||||
|
result.txType = TxType(rlp.getByteValue)
|
||||||
|
rlp.position += 1
|
||||||
|
readTx(rlp, result)
|
||||||
|
|
||||||
|
suite "Transaction EIP-7702 tests":
|
||||||
|
test "Tx RLP roundtrip":
|
||||||
|
roundTrip(tx0, 1)
|
||||||
|
|
||||||
|
test "Tx Sign":
|
||||||
|
const
|
||||||
|
keyHex = "63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376"
|
||||||
|
|
||||||
|
var
|
||||||
|
tx = tx0(2)
|
||||||
|
|
||||||
|
let
|
||||||
|
privateKey = PrivateKey.fromHex(keyHex).valueOr:
|
||||||
|
echo "ERROR: ", error
|
||||||
|
quit(QuitFailure)
|
||||||
|
rlpTx = rlpEncode(tx)
|
||||||
|
sig = sign(privateKey, rlpTx).toRaw
|
||||||
|
|
||||||
|
tx.V = sig[64].uint64
|
||||||
|
tx.R = UInt256.fromBytesBE(sig[0..31])
|
||||||
|
tx.S = UInt256.fromBytesBE(sig[32..63])
|
||||||
|
|
||||||
|
let
|
||||||
|
bytes = rlp.encode(tx)
|
||||||
|
decodedTx = rlp.decode(bytes, Transaction)
|
||||||
|
decodedNoSig = decodeTxEip7702(rlpTx)
|
||||||
|
|
||||||
|
var
|
||||||
|
expectedTx = tx0(2)
|
||||||
|
|
||||||
|
check expectedTx == decodedNoSig
|
||||||
|
|
||||||
|
expectedTx.V = tx.V
|
||||||
|
expectedTx.R = tx.R
|
||||||
|
expectedTx.S = tx.S
|
||||||
|
|
||||||
|
check expectedTx == tx
|
||||||
|
|
||||||
|
test "Receipt RLP roundtrip":
|
||||||
|
let rec = Receipt(
|
||||||
|
receiptType: Eip7702Receipt,
|
||||||
|
isHash: false,
|
||||||
|
status: false,
|
||||||
|
cumulativeGasUsed: 100.GasInt)
|
||||||
|
|
||||||
|
let bytes = rlp.encode(rec)
|
||||||
|
let zz = rlp.decode(bytes, Receipt)
|
||||||
|
let bytes2 = rlp.encode(zz)
|
||||||
|
check bytes2 == bytes
|
Loading…
Reference in New Issue