Skip to content

Commit 5647c24

Browse files
committed
wip
1 parent b53b98a commit 5647c24

1 file changed

Lines changed: 73 additions & 9 deletions

File tree

packages/crypto-transaction/source/deserializer.ts

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
import { inject, injectable } from "@mainsail/container";
22
import { Contracts, Identifiers } from "@mainsail/contracts";
33
import { BigNumber } from "@mainsail/utils";
4-
import { decodeRlp, ethers, getAddress } from "ethers";
4+
import { decodeRlp, ethers, getAddress, RlpStructuredData } from "ethers";
55

66
@injectable()
77
export class Deserializer implements Contracts.Crypto.TransactionDeserializer {
88
@inject(Identifiers.Cryptography.Transaction.TypeFactory)
99
private readonly transactionTypeFactory!: Contracts.Transactions.TransactionTypeFactory;
1010

11+
@inject(Identifiers.Cryptography.Configuration)
12+
private readonly configuration!: Contracts.Crypto.Configuration;
13+
1114
public async deserialize(serialized: Buffer | string): Promise<Contracts.Crypto.Transaction> {
1215
const data = {} as Contracts.Crypto.TransactionData;
1316

14-
const encodedRlp =
15-
"0x" + (typeof serialized === "string" ? serialized.slice(2) : serialized.toString("hex").slice(2));
17+
let rlpBuffer =
18+
typeof serialized === "string"
19+
? Buffer.from(serialized.startsWith("0x") ? serialized.slice(2) : serialized, "hex")
20+
: serialized;
21+
22+
// Remove type prefix (e.g. 02) if it's a EIP1559 tx (`decodeRlp` expects input to be without)
23+
const isPrefixed = rlpBuffer[0] < 0xc0;
24+
if (isPrefixed) {
25+
if (rlpBuffer[0] !== 0x02) {
26+
throw new Error("expected EIP1559 transaction");
27+
}
28+
29+
rlpBuffer = rlpBuffer.subarray(1);
30+
}
31+
32+
const decoded = decodeRlp(new Uint8Array(rlpBuffer));
33+
34+
if (isPrefixed) {
35+
this.#decodeEIP1559Transaction(decoded, data);
36+
} else {
37+
this.#decodeLegacyTransaction(decoded, data);
38+
}
39+
40+
const instance: Contracts.Crypto.Transaction = this.transactionTypeFactory.create(data);
41+
42+
const eip1559Prefix = 0x02; // marker for Type 2 (EIP1559) transaction which is the standard nowadays
43+
instance.serialized = isPrefixed ? Buffer.concat([Buffer.from([eip1559Prefix]), rlpBuffer]) : rlpBuffer;
44+
45+
return instance;
46+
}
1647

17-
const decoded = decodeRlp(encodedRlp);
48+
#decodeEIP1559Transaction(decoded: RlpStructuredData, data: Contracts.Crypto.TransactionData): void {
49+
if (decoded.length < 9 || decoded.length > 13) {
50+
throw new Error("RLP data out of range");
51+
}
1852

1953
const recipientAddressRaw = this.#parseAddress(decoded[5].toString());
2054

@@ -38,18 +72,48 @@ export class Deserializer implements Contracts.Crypto.TransactionDeserializer {
3872
data.r = decoded[10].toString().slice(2);
3973
data.s = decoded[11].toString().slice(2);
4074

41-
// Legacy second signature
75+
// Legacy second signature is only supported for EIP-1559 transactions
4276
if (decoded.length === 13) {
4377
data.legacySecondSignature = decoded[12].toString().slice(2);
4478
}
4579
}
80+
}
4681

47-
const instance: Contracts.Crypto.Transaction = this.transactionTypeFactory.create(data);
82+
#decodeLegacyTransaction(decoded: RlpStructuredData, data: Contracts.Crypto.TransactionData): void {
83+
if (decoded.length < 6 || decoded.length > 9) {
84+
throw new Error("legacy RLP data out of range");
85+
}
4886

49-
const eip1559Prefix = "02"; // marker for Type 2 (EIP1559) transaction which is the standard nowadays
50-
instance.serialized = Buffer.from(`${eip1559Prefix}${encodedRlp.slice(2)}`, "hex");
87+
// [nonce, gasPrice, gasLimit, to, value, data, v, r, s];
88+
data.nonce = BigNumber.make(this.#parseNumber(decoded[0].toString()));
89+
data.gasPrice = this.#parseNumber(decoded[1].toString());
90+
data.gas = this.#parseNumber(decoded[2].toString());
5191

52-
return instance;
92+
const recipientAddressRaw = this.#parseAddress(decoded[3].toString());
93+
data.to = recipientAddressRaw ? getAddress(recipientAddressRaw) : undefined;
94+
95+
data.value = this.#parseBigNumber(decoded[4].toString());
96+
data.data = this.#parseData(decoded[5].toString());
97+
98+
// NOTE:
99+
// The chainId is encoded in 'v' which is part of the optional signature.
100+
// In the case of absence default to the config for the chainId.
101+
102+
// Signature
103+
if (decoded.length >= 9) {
104+
const v = this.#parseNumber(decoded[6].toString());
105+
const chainId = Math.floor((v - 35) / 2);
106+
107+
data.network = chainId;
108+
109+
const normalizedV = v >= 35 ? ((v - 1) % 2) + 27 : v;
110+
data.v = normalizedV;
111+
112+
data.r = decoded[7].toString().slice(2);
113+
data.s = decoded[8].toString().slice(2);
114+
} else {
115+
data.network = this.configuration.get("network.chainId");
116+
}
53117
}
54118

55119
#parseNumber(value: string): number {

0 commit comments

Comments
 (0)