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
|
||||
TxEip1559 # 2
|
||||
TxEip4844 # 3
|
||||
TxEip7702 # 4
|
||||
|
||||
NetworkPayload* = ref object
|
||||
blobs* : seq[NetworkBlob]
|
||||
commitments* : seq[KzgCommitment]
|
||||
proofs* : seq[KzgProof]
|
||||
|
||||
Authorization* = object
|
||||
chainId*: ChainId
|
||||
address*: EthAddress
|
||||
nonce*: AccountNonce
|
||||
yParity*: uint64
|
||||
R*: UInt256
|
||||
S*: UInt256
|
||||
|
||||
Transaction* = object
|
||||
txType* : TxType # EIP-2718
|
||||
chainId* : ChainId # EIP-2930
|
||||
|
@ -100,6 +109,7 @@ type
|
|||
accessList* : AccessList # EIP-2930
|
||||
maxFeePerBlobGas*: UInt256 # EIP-4844
|
||||
versionedHashes*: VersionedHashes # EIP-4844
|
||||
authorizationList*: seq[Authorization]# EIP-7702
|
||||
V* : uint64
|
||||
R*, S* : UInt256
|
||||
|
||||
|
@ -198,6 +208,7 @@ type
|
|||
# Eip2930Receipt = TxEip2930
|
||||
# Eip1559Receipt = TxEip1559
|
||||
# Eip4844Receipt = TxEip4844
|
||||
# Eip7702Receipt = TxEip7702
|
||||
|
||||
Receipt* = object
|
||||
receiptType* : ReceiptType
|
||||
|
@ -244,6 +255,7 @@ const
|
|||
Eip2930Receipt* = TxEip2930
|
||||
Eip1559Receipt* = TxEip1559
|
||||
Eip4844Receipt* = TxEip4844
|
||||
Eip7702Receipt* = TxEip7702
|
||||
|
||||
EMPTY_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest
|
||||
EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest
|
||||
|
|
|
@ -128,6 +128,31 @@ proc appendTxEip4844(w: var RlpWriter, tx: Transaction) =
|
|||
w.append(tx.R)
|
||||
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) =
|
||||
case tx.txType
|
||||
of TxLegacy:
|
||||
|
@ -138,6 +163,8 @@ proc appendTxPayload(w: var RlpWriter, tx: Transaction) =
|
|||
w.appendTxEip1559(tx)
|
||||
of TxEip4844:
|
||||
w.appendTxEip4844(tx)
|
||||
of TxEip7702:
|
||||
w.appendTxEip7702(tx)
|
||||
|
||||
proc append*(w: var RlpWriter, tx: Transaction) =
|
||||
if tx.txType != TxLegacy:
|
||||
|
@ -229,6 +256,32 @@ proc readTxEip4844(rlp: var Rlp, tx: var Transaction) =
|
|||
rlp.read(tx.R)
|
||||
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 =
|
||||
if rlp.isList:
|
||||
raise newException(RlpTypeMismatch,
|
||||
|
@ -271,6 +324,8 @@ proc readTxPayload(rlp: var Rlp, tx: var Transaction, txType: TxType) =
|
|||
rlp.readTxEip1559(tx)
|
||||
of TxEip4844:
|
||||
rlp.readTxEip4844(tx)
|
||||
of TxEip7702:
|
||||
rlp.readTxEip7702(tx)
|
||||
|
||||
proc readTxTyped(rlp: var Rlp, tx: var Transaction) =
|
||||
let txType = rlp.readTxType()
|
||||
|
@ -377,7 +432,7 @@ proc append*(
|
|||
rlpWriter.append(rlp.encode(tx))
|
||||
|
||||
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.startList(4)
|
||||
|
@ -420,7 +475,7 @@ proc readReceiptTyped(rlp: var Rlp, receipt: var Receipt) =
|
|||
var txVal: ReceiptType
|
||||
if checkedEnumAssign(txVal, recType):
|
||||
case txVal:
|
||||
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt:
|
||||
of Eip2930Receipt, Eip1559Receipt, Eip4844Receipt, Eip7702Receipt:
|
||||
receipt.receiptType = txVal
|
||||
of LegacyReceipt:
|
||||
# The legacy type should not be used here.
|
||||
|
|
|
@ -78,6 +78,22 @@ func rlpEncodeEip4844(tx: Transaction): auto =
|
|||
w.append(tx.versionedHashes)
|
||||
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 =
|
||||
case tx.txType
|
||||
of TxLegacy:
|
||||
|
@ -91,6 +107,8 @@ func rlpEncode*(tx: Transaction): auto =
|
|||
tx.rlpEncodeEip1559
|
||||
of TxEip4844:
|
||||
tx.rlpEncodeEip4844
|
||||
of TxEip7702:
|
||||
tx.rlpEncodeEip7702
|
||||
|
||||
func txHashNoSignature*(tx: Transaction): Hash256 =
|
||||
# Hash transaction without signature
|
||||
|
|
|
@ -12,4 +12,5 @@ import
|
|||
test_eth_types,
|
||||
test_eth_types_rlp,
|
||||
test_common,
|
||||
test_eip4844
|
||||
test_eip4844,
|
||||
test_eip7702
|
||||
|
|
|
@ -145,7 +145,7 @@ proc suite2() =
|
|||
let _ = rlp.decode(receiptBytes, Receipt)
|
||||
|
||||
test "Receipts EIP-2718 encoding - invalid - unsupported tx type":
|
||||
let receiptBytes: seq[byte] = @[0x04]
|
||||
let receiptBytes: seq[byte] = @[0x05]
|
||||
expect UnsupportedRlpError:
|
||||
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