mppx 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/env.d.ts +19 -0
- package/dist/internal/env.d.ts.map +1 -0
- package/dist/internal/env.js +55 -0
- package/dist/internal/env.js.map +1 -0
- package/dist/proxy/Service.d.ts +0 -1
- package/dist/proxy/Service.d.ts.map +1 -1
- package/dist/proxy/Service.js +3 -3
- package/dist/proxy/Service.js.map +1 -1
- package/dist/server/Mppx.d.ts +2 -2
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +2 -1
- package/dist/server/Mppx.js.map +1 -1
- package/package.json +1 -1
- package/src/Expires.test.ts +111 -0
- package/src/internal/env.ts +54 -0
- package/src/mcp-sdk/server/Transport.test.ts +171 -0
- package/src/proxy/Proxy.test.ts +15 -17
- package/src/proxy/Service.ts +3 -3
- package/src/server/Mppx.ts +5 -4
- package/src/tempo/client/ChannelOps.test.ts +290 -0
- package/src/tempo/client/Session.test.ts +467 -0
- package/src/tempo/stream/Chain.test.ts +511 -0
- package/src/tempo/stream/Channel.test.ts +108 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { type Address, encodeFunctionData, erc20Abi, type Hex } from 'viem'
|
|
2
|
+
import { Addresses, Transaction } from 'viem/tempo'
|
|
3
|
+
import { beforeAll, describe, expect, test } from 'vitest'
|
|
4
|
+
import {
|
|
5
|
+
closeChannelOnChain,
|
|
6
|
+
deployEscrow,
|
|
7
|
+
openChannel,
|
|
8
|
+
signOpenChannel,
|
|
9
|
+
signTopUpChannel,
|
|
10
|
+
topUpChannel,
|
|
11
|
+
} from '~test/tempo/stream.js'
|
|
12
|
+
import { accounts, asset, chain, client, fundAccount } from '~test/tempo/viem.js'
|
|
13
|
+
import {
|
|
14
|
+
broadcastOpenTransaction,
|
|
15
|
+
broadcastTopUpTransaction,
|
|
16
|
+
closeOnChain,
|
|
17
|
+
getOnChainChannel,
|
|
18
|
+
settleOnChain,
|
|
19
|
+
verifyTopUpTransaction,
|
|
20
|
+
} from './Chain.js'
|
|
21
|
+
import { signVoucher } from './Voucher.js'
|
|
22
|
+
|
|
23
|
+
const UINT128_MAX = 2n ** 128n - 1n
|
|
24
|
+
|
|
25
|
+
describe('assertUint128 (via settleOnChain / closeOnChain)', () => {
|
|
26
|
+
const mockClient = { chain: { id: 42431 } } as any
|
|
27
|
+
const dummyEscrow = '0x0000000000000000000000000000000000000001' as Address
|
|
28
|
+
const dummyChannelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
|
|
29
|
+
|
|
30
|
+
test('settleOnChain rejects amounts exceeding uint128', async () => {
|
|
31
|
+
await expect(
|
|
32
|
+
settleOnChain(mockClient, dummyEscrow, {
|
|
33
|
+
channelId: dummyChannelId,
|
|
34
|
+
cumulativeAmount: UINT128_MAX + 1n,
|
|
35
|
+
signature: '0xsig' as Hex,
|
|
36
|
+
}),
|
|
37
|
+
).rejects.toThrow('cumulativeAmount exceeds uint128 range')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('settleOnChain rejects negative amounts', async () => {
|
|
41
|
+
await expect(
|
|
42
|
+
settleOnChain(mockClient, dummyEscrow, {
|
|
43
|
+
channelId: dummyChannelId,
|
|
44
|
+
cumulativeAmount: -1n,
|
|
45
|
+
signature: '0xsig' as Hex,
|
|
46
|
+
}),
|
|
47
|
+
).rejects.toThrow('cumulativeAmount exceeds uint128 range')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('closeOnChain rejects amounts exceeding uint128', async () => {
|
|
51
|
+
await expect(
|
|
52
|
+
closeOnChain(mockClient, dummyEscrow, {
|
|
53
|
+
channelId: dummyChannelId,
|
|
54
|
+
cumulativeAmount: UINT128_MAX + 1n,
|
|
55
|
+
signature: '0xsig' as Hex,
|
|
56
|
+
}),
|
|
57
|
+
).rejects.toThrow('cumulativeAmount exceeds uint128 range')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('closeOnChain throws when no account available', async () => {
|
|
61
|
+
await expect(
|
|
62
|
+
closeOnChain(mockClient, dummyEscrow, {
|
|
63
|
+
channelId: dummyChannelId,
|
|
64
|
+
cumulativeAmount: 1_000_000n,
|
|
65
|
+
signature: '0xsig' as Hex,
|
|
66
|
+
}),
|
|
67
|
+
).rejects.toThrow('no account available')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('on-chain', () => {
|
|
72
|
+
const payer = accounts[2]
|
|
73
|
+
const recipient = accounts[0].address
|
|
74
|
+
const currency = asset
|
|
75
|
+
|
|
76
|
+
let escrowContract: Address
|
|
77
|
+
let saltCounter = 0
|
|
78
|
+
|
|
79
|
+
beforeAll(async () => {
|
|
80
|
+
escrowContract = await deployEscrow()
|
|
81
|
+
await fundAccount({ address: payer.address, token: Addresses.pathUsd })
|
|
82
|
+
await fundAccount({ address: payer.address, token: currency })
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
function nextSalt(): Hex {
|
|
86
|
+
saltCounter++
|
|
87
|
+
return `0x${saltCounter.toString(16).padStart(64, '0')}` as Hex
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe('getOnChainChannel', () => {
|
|
91
|
+
test('reads channel state after opening', async () => {
|
|
92
|
+
const salt = nextSalt()
|
|
93
|
+
const deposit = 10_000_000n
|
|
94
|
+
|
|
95
|
+
const { channelId } = await openChannel({
|
|
96
|
+
escrow: escrowContract,
|
|
97
|
+
payer,
|
|
98
|
+
payee: recipient,
|
|
99
|
+
token: currency,
|
|
100
|
+
deposit,
|
|
101
|
+
salt,
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const channel = await getOnChainChannel(client, escrowContract, channelId)
|
|
105
|
+
|
|
106
|
+
expect(channel.deposit).toBe(deposit)
|
|
107
|
+
expect(channel.finalized).toBe(false)
|
|
108
|
+
expect(channel.settled).toBe(0n)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('verifyTopUpTransaction', () => {
|
|
113
|
+
test('rejects when channel is finalized', async () => {
|
|
114
|
+
const salt = nextSalt()
|
|
115
|
+
const deposit = 5_000_000n
|
|
116
|
+
|
|
117
|
+
const { channelId } = await openChannel({
|
|
118
|
+
escrow: escrowContract,
|
|
119
|
+
payer,
|
|
120
|
+
payee: recipient,
|
|
121
|
+
token: currency,
|
|
122
|
+
deposit,
|
|
123
|
+
salt,
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const signature = await signVoucher(
|
|
127
|
+
client,
|
|
128
|
+
payer,
|
|
129
|
+
{ channelId, cumulativeAmount: 0n },
|
|
130
|
+
escrowContract,
|
|
131
|
+
chain.id,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
await closeChannelOnChain({
|
|
135
|
+
escrow: escrowContract,
|
|
136
|
+
payee: accounts[0],
|
|
137
|
+
channelId,
|
|
138
|
+
cumulativeAmount: 0n,
|
|
139
|
+
signature,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
await expect(verifyTopUpTransaction(client, escrowContract, channelId, 0n)).rejects.toThrow(
|
|
143
|
+
'channel is finalized on-chain',
|
|
144
|
+
)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
test('rejects when deposit did not increase', async () => {
|
|
148
|
+
const salt = nextSalt()
|
|
149
|
+
const deposit = 5_000_000n
|
|
150
|
+
|
|
151
|
+
const { channelId } = await openChannel({
|
|
152
|
+
escrow: escrowContract,
|
|
153
|
+
payer,
|
|
154
|
+
payee: recipient,
|
|
155
|
+
token: currency,
|
|
156
|
+
deposit,
|
|
157
|
+
salt,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
await expect(
|
|
161
|
+
verifyTopUpTransaction(client, escrowContract, channelId, deposit),
|
|
162
|
+
).rejects.toThrow('channel deposit did not increase')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
test('succeeds when deposit increased via topUp', async () => {
|
|
166
|
+
const salt = nextSalt()
|
|
167
|
+
const initialDeposit = 5_000_000n
|
|
168
|
+
const topUpAmount = 3_000_000n
|
|
169
|
+
|
|
170
|
+
const { channelId } = await openChannel({
|
|
171
|
+
escrow: escrowContract,
|
|
172
|
+
payer,
|
|
173
|
+
payee: recipient,
|
|
174
|
+
token: currency,
|
|
175
|
+
deposit: initialDeposit,
|
|
176
|
+
salt,
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
await topUpChannel({
|
|
180
|
+
escrow: escrowContract,
|
|
181
|
+
payer,
|
|
182
|
+
channelId,
|
|
183
|
+
token: currency,
|
|
184
|
+
amount: topUpAmount,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const result = await verifyTopUpTransaction(client, escrowContract, channelId, initialDeposit)
|
|
188
|
+
|
|
189
|
+
expect(result.deposit).toBe(initialDeposit + topUpAmount)
|
|
190
|
+
})
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
describe('broadcastOpenTransaction', () => {
|
|
194
|
+
test('rejects when payee does not match recipient', async () => {
|
|
195
|
+
const wrongPayee = accounts[3].address
|
|
196
|
+
const salt = nextSalt()
|
|
197
|
+
|
|
198
|
+
const { channelId, serializedTransaction } = await signOpenChannel({
|
|
199
|
+
escrow: escrowContract,
|
|
200
|
+
payer,
|
|
201
|
+
payee: wrongPayee,
|
|
202
|
+
token: currency,
|
|
203
|
+
deposit: 5_000_000n,
|
|
204
|
+
salt,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
await expect(
|
|
208
|
+
broadcastOpenTransaction({
|
|
209
|
+
client,
|
|
210
|
+
serializedTransaction,
|
|
211
|
+
escrowContract,
|
|
212
|
+
channelId,
|
|
213
|
+
recipient,
|
|
214
|
+
currency,
|
|
215
|
+
}),
|
|
216
|
+
).rejects.toThrow('open transaction payee does not match server recipient')
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
test('rejects when token does not match currency', async () => {
|
|
220
|
+
const wrongToken = Addresses.pathUsd
|
|
221
|
+
const salt = nextSalt()
|
|
222
|
+
|
|
223
|
+
const { channelId, serializedTransaction } = await signOpenChannel({
|
|
224
|
+
escrow: escrowContract,
|
|
225
|
+
payer,
|
|
226
|
+
payee: recipient,
|
|
227
|
+
token: wrongToken,
|
|
228
|
+
deposit: 5_000_000n,
|
|
229
|
+
salt,
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
await expect(
|
|
233
|
+
broadcastOpenTransaction({
|
|
234
|
+
client,
|
|
235
|
+
serializedTransaction,
|
|
236
|
+
escrowContract,
|
|
237
|
+
channelId,
|
|
238
|
+
recipient,
|
|
239
|
+
currency,
|
|
240
|
+
}),
|
|
241
|
+
).rejects.toThrow('open transaction token does not match server currency')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('successful broadcast returns txHash and onChain state', async () => {
|
|
245
|
+
const salt = nextSalt()
|
|
246
|
+
const deposit = 10_000_000n
|
|
247
|
+
|
|
248
|
+
const { channelId, serializedTransaction } = await signOpenChannel({
|
|
249
|
+
escrow: escrowContract,
|
|
250
|
+
payer,
|
|
251
|
+
payee: recipient,
|
|
252
|
+
token: currency,
|
|
253
|
+
deposit,
|
|
254
|
+
salt,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
const result = await broadcastOpenTransaction({
|
|
258
|
+
client,
|
|
259
|
+
serializedTransaction,
|
|
260
|
+
escrowContract,
|
|
261
|
+
channelId,
|
|
262
|
+
recipient,
|
|
263
|
+
currency,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
expect(result.txHash).toBeDefined()
|
|
267
|
+
expect(result.onChain.deposit).toBe(deposit)
|
|
268
|
+
expect(result.onChain.finalized).toBe(false)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('fee-payer: rejects unauthorized calls', async () => {
|
|
272
|
+
const salt = nextSalt()
|
|
273
|
+
const deposit = 5_000_000n
|
|
274
|
+
|
|
275
|
+
const { channelId, serializedTransaction } = await signOpenChannel({
|
|
276
|
+
escrow: escrowContract,
|
|
277
|
+
payer,
|
|
278
|
+
payee: recipient,
|
|
279
|
+
token: currency,
|
|
280
|
+
deposit,
|
|
281
|
+
salt,
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const transferData = encodeFunctionData({
|
|
285
|
+
abi: erc20Abi,
|
|
286
|
+
functionName: 'transfer',
|
|
287
|
+
args: [recipient, deposit],
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
const deserialized = Transaction.deserialize(
|
|
291
|
+
serializedTransaction as Transaction.TransactionSerializedTempo,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const tampered = (await Transaction.serialize({
|
|
295
|
+
...deserialized,
|
|
296
|
+
calls: [
|
|
297
|
+
...((deserialized as any).calls ?? []),
|
|
298
|
+
{ to: '0x8888888888888888888888888888888888888888' as Address, data: transferData },
|
|
299
|
+
],
|
|
300
|
+
} as any)) as unknown as Hex
|
|
301
|
+
|
|
302
|
+
await expect(
|
|
303
|
+
broadcastOpenTransaction({
|
|
304
|
+
client,
|
|
305
|
+
serializedTransaction: tampered,
|
|
306
|
+
escrowContract,
|
|
307
|
+
channelId,
|
|
308
|
+
recipient,
|
|
309
|
+
currency,
|
|
310
|
+
feePayer: accounts[0],
|
|
311
|
+
}),
|
|
312
|
+
).rejects.toThrow('fee-sponsored open transaction contains an unauthorized call')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test('duplicate broadcast returns fallback with txHash undefined', async () => {
|
|
316
|
+
const salt = nextSalt()
|
|
317
|
+
const deposit = 5_000_000n
|
|
318
|
+
|
|
319
|
+
const { channelId, serializedTransaction } = await signOpenChannel({
|
|
320
|
+
escrow: escrowContract,
|
|
321
|
+
payer,
|
|
322
|
+
payee: recipient,
|
|
323
|
+
token: currency,
|
|
324
|
+
deposit,
|
|
325
|
+
salt,
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
await broadcastOpenTransaction({
|
|
329
|
+
client,
|
|
330
|
+
serializedTransaction,
|
|
331
|
+
escrowContract,
|
|
332
|
+
channelId,
|
|
333
|
+
recipient,
|
|
334
|
+
currency,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const result = await broadcastOpenTransaction({
|
|
338
|
+
client,
|
|
339
|
+
serializedTransaction,
|
|
340
|
+
escrowContract,
|
|
341
|
+
channelId,
|
|
342
|
+
recipient,
|
|
343
|
+
currency,
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
expect(result.txHash).toBeUndefined()
|
|
347
|
+
expect(result.onChain.deposit).toBe(deposit)
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
describe('broadcastTopUpTransaction', () => {
|
|
352
|
+
test('rejects channelId mismatch', async () => {
|
|
353
|
+
const salt = nextSalt()
|
|
354
|
+
const deposit = 5_000_000n
|
|
355
|
+
const topUpAmount = 3_000_000n
|
|
356
|
+
|
|
357
|
+
const { channelId } = await openChannel({
|
|
358
|
+
escrow: escrowContract,
|
|
359
|
+
payer,
|
|
360
|
+
payee: recipient,
|
|
361
|
+
token: currency,
|
|
362
|
+
deposit,
|
|
363
|
+
salt,
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
const { serializedTransaction } = await signTopUpChannel({
|
|
367
|
+
escrow: escrowContract,
|
|
368
|
+
payer,
|
|
369
|
+
channelId,
|
|
370
|
+
token: currency,
|
|
371
|
+
amount: topUpAmount,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
const wrongChannelId =
|
|
375
|
+
'0x0000000000000000000000000000000000000000000000000000000000000099' as Hex
|
|
376
|
+
|
|
377
|
+
await expect(
|
|
378
|
+
broadcastTopUpTransaction({
|
|
379
|
+
client,
|
|
380
|
+
serializedTransaction,
|
|
381
|
+
escrowContract,
|
|
382
|
+
channelId: wrongChannelId,
|
|
383
|
+
declaredDeposit: topUpAmount,
|
|
384
|
+
previousDeposit: deposit,
|
|
385
|
+
}),
|
|
386
|
+
).rejects.toThrow('topUp transaction channelId does not match payload channelId')
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
test('rejects amount mismatch', async () => {
|
|
390
|
+
const salt = nextSalt()
|
|
391
|
+
const deposit = 5_000_000n
|
|
392
|
+
const topUpAmount = 3_000_000n
|
|
393
|
+
|
|
394
|
+
const { channelId } = await openChannel({
|
|
395
|
+
escrow: escrowContract,
|
|
396
|
+
payer,
|
|
397
|
+
payee: recipient,
|
|
398
|
+
token: currency,
|
|
399
|
+
deposit,
|
|
400
|
+
salt,
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
const { serializedTransaction } = await signTopUpChannel({
|
|
404
|
+
escrow: escrowContract,
|
|
405
|
+
payer,
|
|
406
|
+
channelId,
|
|
407
|
+
token: currency,
|
|
408
|
+
amount: topUpAmount,
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
await expect(
|
|
412
|
+
broadcastTopUpTransaction({
|
|
413
|
+
client,
|
|
414
|
+
serializedTransaction,
|
|
415
|
+
escrowContract,
|
|
416
|
+
channelId,
|
|
417
|
+
declaredDeposit: 9_999_999n,
|
|
418
|
+
previousDeposit: deposit,
|
|
419
|
+
}),
|
|
420
|
+
).rejects.toThrow('topUp transaction amount')
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
test('successful broadcast returns txHash and newDeposit', async () => {
|
|
424
|
+
const salt = nextSalt()
|
|
425
|
+
const deposit = 5_000_000n
|
|
426
|
+
const topUpAmount = 3_000_000n
|
|
427
|
+
|
|
428
|
+
const { channelId } = await openChannel({
|
|
429
|
+
escrow: escrowContract,
|
|
430
|
+
payer,
|
|
431
|
+
payee: recipient,
|
|
432
|
+
token: currency,
|
|
433
|
+
deposit,
|
|
434
|
+
salt,
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
const { serializedTransaction } = await signTopUpChannel({
|
|
438
|
+
escrow: escrowContract,
|
|
439
|
+
payer,
|
|
440
|
+
channelId,
|
|
441
|
+
token: currency,
|
|
442
|
+
amount: topUpAmount,
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
const result = await broadcastTopUpTransaction({
|
|
446
|
+
client,
|
|
447
|
+
serializedTransaction,
|
|
448
|
+
escrowContract,
|
|
449
|
+
channelId,
|
|
450
|
+
declaredDeposit: topUpAmount,
|
|
451
|
+
previousDeposit: deposit,
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
expect(result.txHash).toBeDefined()
|
|
455
|
+
expect(result.newDeposit).toBe(deposit + topUpAmount)
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
test('fee-payer: rejects unauthorized calls', async () => {
|
|
459
|
+
const salt = nextSalt()
|
|
460
|
+
const deposit = 5_000_000n
|
|
461
|
+
const topUpAmount = 3_000_000n
|
|
462
|
+
|
|
463
|
+
const { channelId } = await openChannel({
|
|
464
|
+
escrow: escrowContract,
|
|
465
|
+
payer,
|
|
466
|
+
payee: recipient,
|
|
467
|
+
token: currency,
|
|
468
|
+
deposit,
|
|
469
|
+
salt,
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
const { serializedTransaction } = await signTopUpChannel({
|
|
473
|
+
escrow: escrowContract,
|
|
474
|
+
payer,
|
|
475
|
+
channelId,
|
|
476
|
+
token: currency,
|
|
477
|
+
amount: topUpAmount,
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
const transferData = encodeFunctionData({
|
|
481
|
+
abi: erc20Abi,
|
|
482
|
+
functionName: 'transfer',
|
|
483
|
+
args: [recipient, topUpAmount],
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
const deserialized = Transaction.deserialize(
|
|
487
|
+
serializedTransaction as Transaction.TransactionSerializedTempo,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
const tampered = (await Transaction.serialize({
|
|
491
|
+
...deserialized,
|
|
492
|
+
calls: [
|
|
493
|
+
...((deserialized as any).calls ?? []),
|
|
494
|
+
{ to: '0x8888888888888888888888888888888888888888' as Address, data: transferData },
|
|
495
|
+
],
|
|
496
|
+
} as any)) as unknown as Hex
|
|
497
|
+
|
|
498
|
+
await expect(
|
|
499
|
+
broadcastTopUpTransaction({
|
|
500
|
+
client,
|
|
501
|
+
serializedTransaction: tampered,
|
|
502
|
+
escrowContract,
|
|
503
|
+
channelId,
|
|
504
|
+
declaredDeposit: topUpAmount,
|
|
505
|
+
previousDeposit: deposit,
|
|
506
|
+
feePayer: accounts[0],
|
|
507
|
+
}),
|
|
508
|
+
).rejects.toThrow('fee-sponsored topUp transaction contains an unauthorized call')
|
|
509
|
+
})
|
|
510
|
+
})
|
|
511
|
+
}) // end describe('on-chain')
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { AbiParameters, Hash } from 'ox'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
import * as Channel from './Channel.js'
|
|
4
|
+
|
|
5
|
+
describe('computeId', () => {
|
|
6
|
+
const defaultParams: Channel.computeId.Parameters = {
|
|
7
|
+
payer: '0x1111111111111111111111111111111111111111',
|
|
8
|
+
payee: '0x2222222222222222222222222222222222222222',
|
|
9
|
+
token: '0x3333333333333333333333333333333333333333',
|
|
10
|
+
salt: '0x0000000000000000000000000000000000000000000000000000000000000001',
|
|
11
|
+
authorizedSigner: '0x4444444444444444444444444444444444444444',
|
|
12
|
+
escrowContract: '0x5555555555555555555555555555555555555555',
|
|
13
|
+
chainId: 42431,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('returns deterministic hash for fixed inputs', () => {
|
|
17
|
+
const id1 = Channel.computeId(defaultParams)
|
|
18
|
+
const id2 = Channel.computeId(defaultParams)
|
|
19
|
+
expect(id1).toBe(id2)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('matches manual keccak256(abi.encode(...))', () => {
|
|
23
|
+
const encoded = AbiParameters.encode(
|
|
24
|
+
AbiParameters.from([
|
|
25
|
+
'address payer',
|
|
26
|
+
'address payee',
|
|
27
|
+
'address token',
|
|
28
|
+
'bytes32 salt',
|
|
29
|
+
'address authorizedSigner',
|
|
30
|
+
'address escrowContract',
|
|
31
|
+
'uint256 chainId',
|
|
32
|
+
]),
|
|
33
|
+
[
|
|
34
|
+
defaultParams.payer,
|
|
35
|
+
defaultParams.payee,
|
|
36
|
+
defaultParams.token,
|
|
37
|
+
defaultParams.salt,
|
|
38
|
+
defaultParams.authorizedSigner,
|
|
39
|
+
defaultParams.escrowContract,
|
|
40
|
+
BigInt(defaultParams.chainId),
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
const expected = Hash.keccak256(encoded)
|
|
44
|
+
expect(Channel.computeId(defaultParams)).toBe(expected)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('different payer produces different id', () => {
|
|
48
|
+
const id1 = Channel.computeId(defaultParams)
|
|
49
|
+
const id2 = Channel.computeId({
|
|
50
|
+
...defaultParams,
|
|
51
|
+
payer: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
|
52
|
+
})
|
|
53
|
+
expect(id1).not.toBe(id2)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('different salt produces different id', () => {
|
|
57
|
+
const id1 = Channel.computeId(defaultParams)
|
|
58
|
+
const id2 = Channel.computeId({
|
|
59
|
+
...defaultParams,
|
|
60
|
+
salt: '0x0000000000000000000000000000000000000000000000000000000000000002',
|
|
61
|
+
})
|
|
62
|
+
expect(id1).not.toBe(id2)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('different chainId produces different id', () => {
|
|
66
|
+
const id1 = Channel.computeId(defaultParams)
|
|
67
|
+
const id2 = Channel.computeId({ ...defaultParams, chainId: 1 })
|
|
68
|
+
expect(id1).not.toBe(id2)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('chainId is encoded as uint256', () => {
|
|
72
|
+
const largeChainId = 2 ** 32
|
|
73
|
+
const id = Channel.computeId({ ...defaultParams, chainId: largeChainId })
|
|
74
|
+
expect(id).toMatch(/^0x[0-9a-f]{64}$/)
|
|
75
|
+
|
|
76
|
+
const encoded = AbiParameters.encode(
|
|
77
|
+
AbiParameters.from([
|
|
78
|
+
'address payer',
|
|
79
|
+
'address payee',
|
|
80
|
+
'address token',
|
|
81
|
+
'bytes32 salt',
|
|
82
|
+
'address authorizedSigner',
|
|
83
|
+
'address escrowContract',
|
|
84
|
+
'uint256 chainId',
|
|
85
|
+
]),
|
|
86
|
+
[
|
|
87
|
+
defaultParams.payer,
|
|
88
|
+
defaultParams.payee,
|
|
89
|
+
defaultParams.token,
|
|
90
|
+
defaultParams.salt,
|
|
91
|
+
defaultParams.authorizedSigner,
|
|
92
|
+
defaultParams.escrowContract,
|
|
93
|
+
BigInt(largeChainId),
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
expect(id).toBe(Hash.keccak256(encoded))
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('result is a 0x-prefixed 32-byte hex string', () => {
|
|
100
|
+
const id = Channel.computeId(defaultParams)
|
|
101
|
+
expect(id).toMatch(/^0x[0-9a-f]{64}$/)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('chainId 0 is valid', () => {
|
|
105
|
+
const id = Channel.computeId({ ...defaultParams, chainId: 0 })
|
|
106
|
+
expect(id).toMatch(/^0x[0-9a-f]{64}$/)
|
|
107
|
+
})
|
|
108
|
+
})
|