Skip to content

Commit 701eb10

Browse files
authored
Add encodeTransaction for other TxType (#179)
which supports EIP2930,EIP1559,EIP4844,EIP7702. There are some breaking changes: - Use nim-eth's Authorization type in TransactionObject - Remove AuthorizationObject, replaced by Authorization
1 parent ff92d28 commit 701eb10

File tree

9 files changed

+208
-19
lines changed

9 files changed

+208
-19
lines changed

.editorconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
charset = utf-8
7+
8+
[*.{sh,nim,nimble,yml,yaml}]
9+
indent_size = 2
10+
indent_style = space

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ node_modules
1616
nohup.out
1717
hardhat.config.js
1818
package-lock.json
19+
package.json
1920

2021
# Individual test executables
2122
all_tests

tests/test_json_marshalling.nim

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,21 @@ proc rand[M,N](_: type DynamicBytes[M,N]): DynamicBytes[M,N] =
3030
proc rand(_: type Address): Address =
3131
discard randomBytes(distinctBase result)
3232

33+
proc rand(_: type uint64): uint64 =
34+
var res: array[8, byte]
35+
discard randomBytes(res)
36+
result = uint64.fromBytesBE(res)
37+
3338
proc rand[T: Quantity](_: type T): T =
3439
var res: array[8, byte]
3540
discard randomBytes(res)
3641
result = T(uint64.fromBytesBE(res))
3742

43+
proc rand[T: ChainId](_: type T): T =
44+
var res: array[8, byte]
45+
discard randomBytes(res)
46+
result = T(uint64.fromBytesBE(res))
47+
3848
proc rand(_: type RlpEncodedBytes): RlpEncodedBytes =
3949
discard randomBytes(distinctBase result)
4050

@@ -182,7 +192,7 @@ suite "JSON-RPC Quantity":
182192
checkRandomObject(ProofResponse)
183193
checkRandomObject(FilterOptions)
184194
checkRandomObject(TransactionArgs)
185-
checkRandomObject(AuthorizationObject)
195+
checkRandomObject(Authorization)
186196

187197
checkRandomObject(BlockHeader)
188198
checkRandomObject(BlockObject)
@@ -259,3 +269,8 @@ suite "JSON-RPC Quantity":
259269
)
260270
let w = JrpcConv.encode(z)
261271
check w == """{"accessList":[],"error":"error","gasUsed":"0x0"}"""
272+
273+
test "Authorization":
274+
var z: Authorization
275+
let w = JrpcConv.encode(z)
276+
check w == """{"chainId":"0x0","address":"0x0000000000000000000000000000000000000000","nonce":"0x0","v":"0x0","r":"0x0","s":"0x0"}"""

tests/test_signed_tx.nim

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,67 @@ suite "Signed transactions":
6060
let txHex = "0x" & txBytes.toHex
6161
check txHex == "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"
6262

63+
test "encodeTransaction(Transaction, PrivateKey, TxType) EIP-155 test vector":
64+
let
65+
privateKey = PrivateKey.fromHex("0x4646464646464646464646464646464646464646464646464646464646464646").tryGet()
66+
publicKey = privateKey.toPublicKey()
67+
address = publicKey.toCanonicalAddress()
68+
var tx: TransactionArgs
69+
tx.nonce = Opt.some(Quantity(9))
70+
tx.`from` = Opt.some(Address(address))
71+
tx.value = Opt.some(1000000000000000000.u256)
72+
tx.to = Opt.some(Address(hexToByteArray[20]("0x3535353535353535353535353535353535353535")))
73+
tx.gas = Opt.some(Quantity(21000'u64))
74+
tx.gasPrice = Opt.some(Quantity(20000000000'i64))
75+
tx.chainId = Opt.some(Quantity(1))
76+
77+
let txBytes = encodeTransaction(tx, privateKey, TxLegacy)
78+
let txHex = "0x" & txBytes.toHex
79+
check txHex == "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"
80+
81+
# testcase is taken from ethers.js: https://github.com/ethers-io/ethers.js/tree/f5336c19b1adcb5e992745b8a0561eaf8f1f1138/testcases
82+
test "encodeTransaction(Transaction, PrivateKey, TxType) EIP-2930":
83+
let
84+
privateKey = PrivateKey.fromHex("0x2bf558dce44ca98616ee629199215ae5401c97040664637c48e3b74e66bcb3ae").tryGet()
85+
publicKey = privateKey.toPublicKey()
86+
address = publicKey.toCanonicalAddress()
87+
var tx: TransactionArgs
88+
tx.nonce = Opt.some(Quantity(648))
89+
tx.`from` = Opt.some(Address(address))
90+
tx.value = Opt.some(51284.u256)
91+
tx.to = Opt.some(Address(hexToByteArray[20]("0x6Eb893e3466931517a04a17D153a6330c3f2f1dD")))
92+
tx.gas = Opt.some(Quantity(157'u64))
93+
tx.gasPrice = Opt.some(Quantity(9086'u64))
94+
tx.chainId = Opt.some(Quantity(2214903583))
95+
tx.data = Opt.some(hexToSeqByte("0x889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c"))
96+
tx.accessList = Opt.some(newSeq[AccessPair]())
97+
98+
let txBytes = encodeTransaction(tx, privateKey, TxEip2930)
99+
let txHex = "0x" & txBytes.toHex
100+
check txHex == "0x01f89f848404bf1f82028882237e819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc080a0775f29642af1045b40e5beae8e6bce2dc9e222023b7a50372be6824dbb7434fba05dacfff85752a0b9fd860bc751c17235a670d318a8b9494d664c1b87e33ac8dd"
101+
102+
# testcase is taken from ethers.js: https://github.com/ethers-io/ethers.js/tree/f5336c19b1adcb5e992745b8a0561eaf8f1f1138/testcases
103+
test "encodeTransaction(Transaction, PrivateKey, TxType) EIP-1559":
104+
let
105+
privateKey = PrivateKey.fromHex("0x2bf558dce44ca98616ee629199215ae5401c97040664637c48e3b74e66bcb3ae").tryGet()
106+
publicKey = privateKey.toPublicKey()
107+
address = publicKey.toCanonicalAddress()
108+
var tx: TransactionArgs
109+
tx.nonce = Opt.some(Quantity(648))
110+
tx.`from` = Opt.some(Address(address))
111+
tx.value = Opt.some(51284.u256)
112+
tx.to = Opt.some(Address(hexToByteArray[20]("0x6Eb893e3466931517a04a17D153a6330c3f2f1dD")))
113+
tx.gas = Opt.some(Quantity(157'u64))
114+
tx.maxFeePerGas = Opt.some(Quantity(879596102'u64))
115+
tx.maxPriorityFeePerGas = Opt.some(Quantity(2915939'u64))
116+
tx.chainId = Opt.some(Quantity(2214903583))
117+
tx.data = Opt.some(hexToSeqByte("0x889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33c"))
118+
tx.accessList = Opt.some(newSeq[AccessPair]())
119+
120+
let txBytes = encodeTransaction(tx, privateKey, TxEip1559)
121+
let txHex = "0x" & txBytes.toHex
122+
check txHex == "0x02f8a5848404bf1f820288832c7e6384346d9246819d946eb893e3466931517a04a17d153a6330c3f2f1dd82c854b5889e365e59664fb881554ba1175519b5195b1d20390beb806d8f2cda7893e6f79848195dba4c905db6d7257ffb5eefea35f18ae33cc080a0f1003f96c6c6620dd46db36d2ae9f12d363947eb0db088c678b6ad1cf494aa6fa06085b5abbf448de5d622dc820da590cfdb6bb77b41c6650962b998a941f8d701"
123+
63124
test "contract creation and method invocation":
64125
proc test() {.async.} =
65126
let theRNG = HmacDrbgContext.new()

web3.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# those terms.
99

1010
mode = ScriptMode.Verbose
11-
version = "0.3.4"
11+
version = "0.4.0"
1212
author = "Status Research & Development GmbH"
1313
description = "These are the humble beginnings of library similar to web3.[js|py]"
1414
license = "MIT or Apache License 2.0"

web3/conversions.nim

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ ProofResponse.useDefaultSerializationIn JrpcConv
4242
FilterOptions.useDefaultSerializationIn JrpcConv
4343
TransactionArgs.useDefaultSerializationIn JrpcConv
4444
FeeHistoryResult.useDefaultSerializationIn JrpcConv
45-
AuthorizationObject.useDefaultSerializationIn JrpcConv
45+
Authorization.useDefaultSerializationIn JrpcConv
4646

4747
BlockHeader.useDefaultSerializationIn JrpcConv
4848
BlockObject.useDefaultSerializationIn JrpcConv
@@ -203,13 +203,27 @@ proc writeValue*[F: CommonJsonFlavors](w: var JsonWriter[F], v: RlpEncodedBytes)
203203
{.gcsafe, raises: [IOError].} =
204204
writeHexValue w, distinctBase(v)
205205

206+
proc writeValue*[F: CommonJsonFlavors](
207+
w: var JsonWriter[F], v: uint64
208+
) {.gcsafe, raises: [IOError].} =
209+
w.stream.write "\"0x"
210+
w.stream.toHex(v)
211+
w.stream.write "\""
212+
206213
proc writeValue*[F: CommonJsonFlavors](
207214
w: var JsonWriter[F], v: Quantity
208215
) {.gcsafe, raises: [IOError].} =
209216
w.stream.write "\"0x"
210217
w.stream.toHex(distinctBase v)
211218
w.stream.write "\""
212219

220+
proc writeValue*[F: CommonJsonFlavors](
221+
w: var JsonWriter[F], v: ChainId
222+
) {.gcsafe, raises: [IOError].} =
223+
w.stream.write "\"0x"
224+
w.stream.toHex(distinctBase v)
225+
w.stream.write "\""
226+
213227
proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var DynamicBytes)
214228
{.gcsafe, raises: [IOError, JsonReaderError].} =
215229
wrapValueError:
@@ -246,6 +260,14 @@ proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var RlpEncodedB
246260
# skip empty hex
247261
val = RlpEncodedBytes hexToSeqByte(hexStr)
248262

263+
proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var uint64)
264+
{.gcsafe, raises: [IOError, JsonReaderError].} =
265+
let hexStr = r.parseString()
266+
if hexStr.invalidQuantityPrefix:
267+
r.raiseUnexpectedValue("Quantity value has invalid leading 0")
268+
wrapValueError:
269+
val = fromHex[uint64](hexStr)
270+
249271
proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var Quantity)
250272
{.gcsafe, raises: [IOError, JsonReaderError].} =
251273
let hexStr = r.parseString()
@@ -254,6 +276,14 @@ proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var Quantity)
254276
wrapValueError:
255277
val = Quantity fromHex[uint64](hexStr)
256278

279+
proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var ChainId)
280+
{.gcsafe, raises: [IOError, JsonReaderError].} =
281+
let hexStr = r.parseString()
282+
if hexStr.invalidQuantityPrefix:
283+
r.raiseUnexpectedValue("ChainId value has invalid leading 0")
284+
wrapValueError:
285+
val = ChainId fromHex[uint64](hexStr)
286+
257287
proc readValue*[F: CommonJsonFlavors](r: var JsonReader[F], val: var PayloadExecutionStatus)
258288
{.gcsafe, raises: [IOError, JsonReaderError].} =
259289
const enumStrings = static: getEnumStringTable(PayloadExecutionStatus)
@@ -406,4 +436,4 @@ func `$`*(v: TypedTransaction): string {.inline.} =
406436
func `$`*(v: RlpEncodedBytes): string {.inline.} =
407437
"0x" & distinctBase(v).toHex
408438

409-
{.pop.}
439+
{.pop.}

web3/eth_api_types.nim

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import
1313
stint,
1414
./primitives
1515

16-
from eth/common/transactions import AccessPair
16+
from eth/common/transactions import AccessPair, Authorization
1717

1818
export
1919
primitives,
20-
AccessPair
20+
AccessPair,
21+
Authorization
2122

2223
type
2324
SyncObject* = object
@@ -66,7 +67,7 @@ type
6667
proofs*: Opt[seq[KzgProof]]
6768

6869
# EIP-7702
69-
authorizationList*: Opt[seq[AuthorizationObject]]
70+
authorizationList*: Opt[seq[Authorization]]
7071

7172
## A block header object
7273
BlockHeader* = ref object
@@ -145,14 +146,6 @@ type
145146
error*: Opt[string]
146147
gasUsed*: Quantity
147148

148-
AuthorizationObject* = object
149-
chainId*: Quantity
150-
address*: Address
151-
nonce*: Quantity
152-
v*: Quantity
153-
r*: UInt256
154-
s*: UInt256
155-
156149
TransactionObject* = ref object # A transaction object, or null when no transaction was found:
157150
hash*: Hash32 # hash of the transaction.
158151
nonce*: Quantity # the number of transactions made by the sender prior to this one.
@@ -170,13 +163,13 @@ type
170163
s*: UInt256 # ECDSA signature s
171164
yParity*: Opt[Quantity] # ECDSA y parity, none for Legacy, same as v for >= Tx2930
172165
`type`*: Opt[Quantity] # EIP-2718, with 0x0 for Legacy
173-
chainId*: Opt[Quantity] # EIP-159
166+
chainId*: Opt[Quantity] # EIP-155
174167
accessList*: Opt[seq[AccessPair]] # EIP-2930
175168
maxFeePerGas*: Opt[Quantity] # EIP-1559
176169
maxPriorityFeePerGas*: Opt[Quantity] # EIP-1559
177170
maxFeePerBlobGas*: Opt[UInt256] # EIP-4844
178171
blobVersionedHashes*: Opt[seq[VersionedHash]] # EIP-4844
179-
authorizationList*: Opt[seq[AuthorizationObject]] # EIP-7702
172+
authorizationList*: Opt[seq[Authorization]] # EIP-7702
180173

181174
ReceiptObject* = ref object # A transaction receipt object, or null when no receipt was found:
182175
transactionHash*: Hash32 # hash of the transaction.
@@ -313,4 +306,4 @@ func isEIP4844*(args: TransactionArgs): bool =
313306
# Backwards compatibility
314307

315308
type
316-
Topic* {.deprecated.} = Bytes32
309+
Topic* {.deprecated.} = Bytes32

web3/primitives.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ template ethQuantity(typ: type) {.dirty.} =
5454
func `==`*(a, b: typ): bool {.borrow.}
5555

5656
ethQuantity Quantity
57+
ethQuantity ChainId
5758

5859
template toHex*(x: DynamicBytes): string =
5960
toHex(distinctBase x)

web3/transaction_signing.nim

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import
1111
eth_api_types,
1212
eth/common/[keys, transactions_rlp, transaction_utils]
1313

14-
func encodeTransaction*(s: TransactionArgs, pk: PrivateKey): seq[byte] =
14+
func encodeTransactionLegacy(s: TransactionArgs, pk: PrivateKey): seq[byte] =
1515
var tr = Transaction(txType: TxLegacy)
1616
tr.gasLimit = s.gas.get.GasInt
1717
tr.gasPrice = s.gasPrice.get.GasInt
@@ -29,6 +29,84 @@ func encodeTransaction*(s: TransactionArgs, pk: PrivateKey): seq[byte] =
2929
tr.sign(pk, false)
3030
rlp.encode(tr)
3131

32+
func encodeTransactionEip2930(s: TransactionArgs, pk: PrivateKey): seq[byte] =
33+
var tr = Transaction(txType: TxEip2930)
34+
tr.gasLimit = s.gas.get.GasInt
35+
tr.gasPrice = s.gasPrice.get.GasInt
36+
tr.to = s.to
37+
if s.value.isSome:
38+
tr.value = s.value.get
39+
tr.nonce = uint64(s.nonce.get)
40+
tr.payload = s.payload
41+
tr.chainId = ChainId(s.chainId.get)
42+
tr.signature = tr.sign(pk, true)
43+
tr.accessList = s.accessList.get
44+
rlp.encode(tr)
45+
46+
func encodeTransactionEip1559(s: TransactionArgs, pk: PrivateKey): seq[byte] =
47+
var tr = Transaction(txType: TxEip1559)
48+
tr.gasLimit = s.gas.get.GasInt
49+
tr.maxPriorityFeePerGas = s.maxPriorityFeePerGas.get.GasInt
50+
tr.maxFeePerGas = s.maxFeePerGas.get.GasInt
51+
tr.to = s.to
52+
if s.value.isSome:
53+
tr.value = s.value.get
54+
tr.nonce = uint64(s.nonce.get)
55+
tr.payload = s.payload
56+
tr.chainId = ChainId(s.chainId.get)
57+
tr.signature = tr.sign(pk, true)
58+
if s.accessList.isSome:
59+
tr.accessList = s.accessList.get
60+
rlp.encode(tr)
61+
62+
func encodeTransactionEip4844(s: TransactionArgs, pk: PrivateKey): seq[byte] =
63+
var tr = Transaction(txType: TxEip4844)
64+
tr.gasLimit = s.gas.get.GasInt
65+
tr.maxPriorityFeePerGas = s.maxPriorityFeePerGas.get.GasInt
66+
tr.maxFeePerGas = s.maxFeePerGas.get.GasInt
67+
tr.to = s.to
68+
if s.value.isSome:
69+
tr.value = s.value.get
70+
tr.nonce = uint64(s.nonce.get)
71+
tr.payload = s.payload
72+
tr.chainId = ChainId(s.chainId.get)
73+
tr.signature = tr.sign(pk, true)
74+
if s.accessList.isSome:
75+
tr.accessList = s.accessList.get
76+
tr.maxFeePerBlobGas = s.maxFeePerBlobGas.get
77+
tr.versionedHashes = s.blobVersionedHashes.get
78+
rlp.encode(tr)
79+
80+
func encodeTransactionEip7702(s: TransactionArgs, pk: PrivateKey): seq[byte] =
81+
var tr = Transaction(txType: TxEip7702)
82+
tr.gasLimit = s.gas.get.GasInt
83+
tr.maxPriorityFeePerGas = s.maxPriorityFeePerGas.get.GasInt
84+
tr.maxFeePerGas = s.maxFeePerGas.get.GasInt
85+
tr.to = s.to
86+
if s.value.isSome:
87+
tr.value = s.value.get
88+
tr.nonce = uint64(s.nonce.get)
89+
tr.payload = s.payload
90+
tr.chainId = ChainId(s.chainId.get)
91+
tr.signature = tr.sign(pk, true)
92+
if s.accessList.isSome:
93+
tr.accessList = s.accessList.get
94+
tr.authorizationList = s.authorizationList.get
95+
rlp.encode(tr)
96+
97+
func encodeTransaction*(s: TransactionArgs, pk: PrivateKey, txType: TxType = TxLegacy): seq[byte] =
98+
case txType
99+
of TxLegacy:
100+
encodeTransactionLegacy(s, pk)
101+
of TxEip2930:
102+
encodeTransactionEip2930(s, pk)
103+
of TxEip1559:
104+
encodeTransactionEip1559(s, pk)
105+
of TxEip4844:
106+
encodeTransactionEip4844(s, pk)
107+
of TxEip7702:
108+
encodeTransactionEip7702(s, pk)
109+
32110
func encodeTransaction*(s: TransactionArgs, pk: PrivateKey, chainId: ChainId): seq[byte] {.deprecated: "Provide chainId in TransactionArgs".} =
33111
var tr = Transaction(txType: TxLegacy, chainId: chainId)
34112
tr.gasLimit = s.gas.get.GasInt

0 commit comments

Comments
 (0)