1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
import {
BigNumber,
BigNumberish,
BytesLike,
ContractReceipt,
ethers,
Event,
} from 'ethers'
const formatNumber = (value: BigNumberish, name: string): Uint8Array => {
const result = ethers.utils.stripZeros(BigNumber.from(value).toHexString())
if (result.length > 32) {
throw new Error(`invalid length for ${name}`)
}
return result
}
const handleNumber = (value: string): BigNumber => {
if (value === '0x') {
return ethers.constants.Zero
}
return BigNumber.from(value)
}
const handleAddress = (value: string): string => {
if (value === '0x') {
// @ts-ignore
return null
}
return ethers.utils.getAddress(value)
}
export enum SourceHashDomain {
UserDeposit = 0,
L1InfoDeposit = 1,
}
interface DepositTxOpts {
sourceHash?: string
from: string
to: string | null
mint: BigNumberish
value: BigNumberish
gas: BigNumberish
data: string
domain?: SourceHashDomain
l1BlockHash?: string
logIndex?: BigNumberish
sequenceNumber?: BigNumberish
}
interface DepostTxExtraOpts {
domain?: SourceHashDomain
l1BlockHash?: string
logIndex?: BigNumberish
sequenceNumber?: BigNumberish
}
export class DepositTx {
public type = '0x7E'
private _sourceHash?: string
public from: string
public to: string | null
public mint: BigNumberish
public value: BigNumberish
public gas: BigNumberish
public data: BigNumberish
public domain?: SourceHashDomain
public l1BlockHash?: string
public logIndex?: BigNumberish
public sequenceNumber?: BigNumberish
constructor(opts: Partial<DepositTxOpts> = {}) {
this._sourceHash = opts.sourceHash
this.from = opts.from!
this.to = opts.to!
this.mint = opts.mint!
this.value = opts.value!
this.gas = opts.gas!
this.data = opts.data!
this.domain = opts.domain
this.l1BlockHash = opts.l1BlockHash
this.logIndex = opts.logIndex
this.sequenceNumber = opts.sequenceNumber
}
hash() {
const encoded = this.encode()
return ethers.utils.keccak256(encoded)
}
sourceHash() {
if (!this._sourceHash) {
let marker: string
switch (this.domain) {
case SourceHashDomain.UserDeposit:
marker = BigNumber.from(this.logIndex).toHexString()
break
case SourceHashDomain.L1InfoDeposit:
marker = BigNumber.from(this.sequenceNumber).toHexString()
break
default:
throw new Error(`Unknown domain: ${this.domain}`)
}
if (!this.l1BlockHash) {
throw new Error('Need l1BlockHash to compute sourceHash')
}
const l1BlockHash = this.l1BlockHash
const input = ethers.utils.hexConcat([
l1BlockHash,
ethers.utils.zeroPad(marker, 32),
])
const depositIDHash = ethers.utils.keccak256(input)
const domain = BigNumber.from(this.domain).toHexString()
const domainInput = ethers.utils.hexConcat([
ethers.utils.zeroPad(domain, 32),
depositIDHash,
])
this._sourceHash = ethers.utils.keccak256(domainInput)
}
return this._sourceHash
}
encode() {
const fields: any = [
this.sourceHash() || '0x',
ethers.utils.getAddress(this.from) || '0x',
this.to != null ? ethers.utils.getAddress(this.to) : '0x',
formatNumber(this.mint || 0, 'mint'),
formatNumber(this.value || 0, 'value'),
formatNumber(this.gas || 0, 'gas'),
this.data || '0x',
]
return ethers.utils.hexConcat([this.type, ethers.utils.RLP.encode(fields)])
}
decode(raw: BytesLike, extra: DepostTxExtraOpts = {}) {
const payload = ethers.utils.arrayify(raw)
const transaction = ethers.utils.RLP.decode(payload.slice(1))
this._sourceHash = transaction[0]
this.from = handleAddress(transaction[1])
this.to = handleAddress(transaction[2])
this.mint = handleNumber(transaction[3])
this.value = handleNumber(transaction[4])
this.gas = handleNumber(transaction[5])
this.data = transaction[6]
if ('l1BlockHash' in extra) {
this.l1BlockHash = extra.l1BlockHash
}
if ('domain' in extra) {
this.domain = extra.domain
}
if ('logIndex' in extra) {
this.logIndex = extra.logIndex
}
if ('sequenceNumber' in extra) {
this.sequenceNumber = extra.sequenceNumber
}
return this
}
static decode(raw: BytesLike, extra?: DepostTxExtraOpts): DepositTx {
return new this().decode(raw, extra)
}
fromL1Receipt(receipt: ContractReceipt, index: number): DepositTx {
if (!receipt.events) {
throw new Error('cannot parse receipt')
}
const event = receipt.events[index]
if (!event) {
throw new Error(`event index ${index} does not exist`)
}
return this.fromL1Event(event)
}
static fromL1Receipt(receipt: ContractReceipt, index: number): DepositTx {
return new this({}).fromL1Receipt(receipt, index)
}
fromL1Event(event: Event): DepositTx {
if (event.event !== 'TransactionDeposited') {
throw new Error(`incorrect event type: ${event.event}`)
}
if (typeof event.args === 'undefined') {
throw new Error('no event args')
}
if (typeof event.args.from === 'undefined') {
throw new Error('"from" undefined')
}
this.from = event.args.from
if (typeof event.args.isCreation === 'undefined') {
throw new Error('"isCreation" undefined')
}
if (typeof event.args.to === 'undefined') {
throw new Error('"to" undefined')
}
this.to = event.args.isCreation ? null : event.args.to
if (typeof event.args.mint === 'undefined') {
throw new Error('"mint" undefined')
}
this.mint = event.args.mint
if (typeof event.args.value === 'undefined') {
throw new Error('"value" undefined')
}
this.value = event.args.value
if (typeof event.args.gasLimit === 'undefined') {
throw new Error('"gasLimit" undefined')
}
this.gas = event.args.gasLimit
if (typeof event.args.data === 'undefined') {
throw new Error('"data" undefined')
}
this.data = event.args.data
this.domain = SourceHashDomain.UserDeposit
this.l1BlockHash = event.blockHash
this.logIndex = event.logIndex
return this
}
static fromL1Event(event: Event): DepositTx {
return new this({}).fromL1Event(event)
}
}