diff --git a/eth/common/eth_types.nim b/eth/common/eth_types.nim index 81117f9..6de6973 100644 --- a/eth/common/eth_types.nim +++ b/eth/common/eth_types.nim @@ -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 diff --git a/eth/common/eth_types_rlp.nim b/eth/common/eth_types_rlp.nim index d895375..0eab790 100644 --- a/eth/common/eth_types_rlp.nim +++ b/eth/common/eth_types_rlp.nim @@ -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. diff --git a/eth/common/transaction.nim b/eth/common/transaction.nim index b03af74..ba113ba 100644 --- a/eth/common/transaction.nim +++ b/eth/common/transaction.nim @@ -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 diff --git a/tests/common/all_tests.nim b/tests/common/all_tests.nim index 4bcf36a..f13315b 100644 --- a/tests/common/all_tests.nim +++ b/tests/common/all_tests.nim @@ -12,4 +12,5 @@ import test_eth_types, test_eth_types_rlp, test_common, - test_eip4844 + test_eip4844, + test_eip7702 diff --git a/tests/common/test_common.nim b/tests/common/test_common.nim index a320d83..b1e4b14 100644 --- a/tests/common/test_common.nim +++ b/tests/common/test_common.nim @@ -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) diff --git a/tests/common/test_eip7702.nim b/tests/common/test_eip7702.nim new file mode 100644 index 0000000..83bd81b --- /dev/null +++ b/tests/common/test_eip7702.nim @@ -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