1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | import { describe, it, before, after, beforeEach, afterEach } from 'mocha'
|
20 | import * as sinon from 'sinon'
|
21 | import BigNumber from 'bignumber.js'
|
22 | import { getSdk, BaseAe, networkId } from './'
|
23 | import { generateKeyPair, encodeBase64Check } from '../../es/utils/crypto'
|
24 | import { unpackTx, buildTx, buildTxHash } from '../../es/tx/builder'
|
25 | import { decode } from '../../es/tx/builder/helpers'
|
26 | import Channel from '../../es/channel'
|
27 | import MemoryAccount from '../../es/account/memory'
|
28 |
|
29 | const wsUrl = process.env.TEST_WS_URL || 'ws://localhost:3014/channel'
|
30 |
|
31 | const identityContract = `
|
32 | contract Identity =
|
33 | entrypoint main(x : int) : int = x
|
34 | `
|
35 |
|
36 | function waitForChannel (channel) {
|
37 | return new Promise(resolve =>
|
38 | channel.on('statusChanged', (status) => {
|
39 | if (status === 'open') {
|
40 | resolve()
|
41 | }
|
42 | })
|
43 | )
|
44 | }
|
45 |
|
46 | describe('Channel', function () {
|
47 | let initiator
|
48 | let responder
|
49 | let initiatorCh
|
50 | let responderCh
|
51 | let responderShouldRejectUpdate
|
52 | let existingChannelId
|
53 | let offchainTx
|
54 | let contractAddress
|
55 | let contractEncodeCall
|
56 | let callerNonce
|
57 | let majorVersion
|
58 | let minorVersion
|
59 | const initiatorSign = sinon.spy((tag, tx) => initiator.signTransaction(tx))
|
60 | const responderSign = sinon.spy((tag, tx) => {
|
61 | if (typeof responderShouldRejectUpdate === 'number') {
|
62 | return responderShouldRejectUpdate
|
63 | }
|
64 | if (responderShouldRejectUpdate) {
|
65 | return null
|
66 | }
|
67 | return responder.signTransaction(tx)
|
68 | })
|
69 | const sharedParams = {
|
70 | url: wsUrl,
|
71 | pushAmount: 3,
|
72 | initiatorAmount: BigNumber('100e18'),
|
73 | responderAmount: BigNumber('100e18'),
|
74 | channelReserve: 0,
|
75 | ttl: 10000,
|
76 | host: 'localhost',
|
77 | port: 3001,
|
78 | lockPeriod: 1,
|
79 | statePassword: 'correct horse battery staple',
|
80 | debug: false
|
81 | }
|
82 |
|
83 | before(async function () {
|
84 | initiator = await getSdk()
|
85 | responder = await BaseAe({ nativeMode: true, networkId, accounts: [] })
|
86 | await responder.addAccount(MemoryAccount({ keypair: generateKeyPair() }), { select: true })
|
87 | sharedParams.initiatorId = await initiator.address()
|
88 | sharedParams.responderId = await responder.address()
|
89 | await initiator.spend(BigNumber('500e18').toString(), await responder.address())
|
90 | const version = initiator.getNodeInfo().version.split(/[.-]/).map(i => parseInt(i, 10))
|
91 | majorVersion = version[0]
|
92 | minorVersion = version[1]
|
93 | })
|
94 |
|
95 | after(() => {
|
96 | initiatorCh.disconnect()
|
97 | responderCh.disconnect()
|
98 | })
|
99 |
|
100 | beforeEach(() => {
|
101 | responderShouldRejectUpdate = false
|
102 | })
|
103 |
|
104 | afterEach(() => {
|
105 | initiatorSign.resetHistory()
|
106 | responderSign.resetHistory()
|
107 | })
|
108 |
|
109 | it('can open a channel', async () => {
|
110 | initiatorCh = await Channel({
|
111 | ...sharedParams,
|
112 | role: 'initiator',
|
113 | sign: initiatorSign
|
114 | })
|
115 | responderCh = await Channel({
|
116 | ...sharedParams,
|
117 | role: 'responder',
|
118 | sign: responderSign
|
119 | })
|
120 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
121 | initiatorCh.round().should.equal(1)
|
122 | responderCh.round().should.equal(1)
|
123 | sinon.assert.calledOnce(initiatorSign)
|
124 | sinon.assert.calledWithExactly(initiatorSign, sinon.match('initiator_sign'), sinon.match.string)
|
125 | sinon.assert.calledOnce(responderSign)
|
126 | sinon.assert.calledWithExactly(responderSign, sinon.match('responder_sign'), sinon.match.string)
|
127 | const expectedTxParams = {
|
128 | initiator: await initiator.address(),
|
129 | responder: await responder.address(),
|
130 | initiatorAmount: sharedParams.initiatorAmount.toString(),
|
131 | responderAmount: sharedParams.responderAmount.toString(),
|
132 | channelReserve: sharedParams.channelReserve.toString(),
|
133 | lockPeriod: sharedParams.lockPeriod.toString()
|
134 | }
|
135 | const { txType: initiatorTxType, tx: initiatorTx } = unpackTx(initiatorSign.firstCall.args[1])
|
136 | const { txType: responderTxType, tx: responderTx } = unpackTx(responderSign.firstCall.args[1])
|
137 | initiatorTxType.should.equal('channelCreate')
|
138 | initiatorTx.should.eql({ ...initiatorTx, ...expectedTxParams })
|
139 | responderTxType.should.equal('channelCreate')
|
140 | responderTx.should.eql({ ...responderTx, ...expectedTxParams })
|
141 | })
|
142 |
|
143 | it('can post update and accept', async () => {
|
144 | responderShouldRejectUpdate = false
|
145 | const roundBefore = initiatorCh.round()
|
146 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
147 | const amount = 1
|
148 | const result = await initiatorCh.update(
|
149 | await initiator.address(),
|
150 | await responder.address(),
|
151 | amount,
|
152 | sign
|
153 | )
|
154 | initiatorCh.round().should.equal(roundBefore + 1)
|
155 | result.accepted.should.equal(true)
|
156 | result.signedTx.should.be.a('string')
|
157 | sinon.assert.notCalled(initiatorSign)
|
158 | sinon.assert.calledOnce(responderSign)
|
159 | sinon.assert.calledWithExactly(
|
160 | responderSign,
|
161 | sinon.match('update_ack'),
|
162 | sinon.match.string,
|
163 | sinon.match({
|
164 | updates: sinon.match([{
|
165 | amount: sinon.match(amount),
|
166 | from: sinon.match(await initiator.address()),
|
167 | to: sinon.match(await responder.address()),
|
168 | op: sinon.match('OffChainTransfer')
|
169 | }])
|
170 | })
|
171 | )
|
172 | sinon.assert.calledOnce(sign)
|
173 | sinon.assert.calledWithExactly(
|
174 | sign,
|
175 | sinon.match.string,
|
176 | sinon.match({
|
177 | updates: sinon.match([{
|
178 | amount: sinon.match(amount),
|
179 | from: sinon.match(await initiator.address()),
|
180 | to: sinon.match(await responder.address()),
|
181 | op: sinon.match('OffChainTransfer')
|
182 | }])
|
183 | })
|
184 | )
|
185 | const { txType } = unpackTx(sign.firstCall.args[0])
|
186 | txType.should.equal('channelOffChain')
|
187 | sign.firstCall.args[1].should.eql({
|
188 | updates: [
|
189 | {
|
190 | amount,
|
191 | from: await initiator.address(),
|
192 | to: await responder.address(),
|
193 | op: 'OffChainTransfer'
|
194 | }
|
195 | ]
|
196 | })
|
197 | })
|
198 |
|
199 | it('can post update and reject', async () => {
|
200 | responderShouldRejectUpdate = true
|
201 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
202 | const amount = 1
|
203 | const roundBefore = initiatorCh.round()
|
204 | const result = await initiatorCh.update(
|
205 | await responder.address(),
|
206 | await initiator.address(),
|
207 | amount,
|
208 | sign
|
209 | )
|
210 | result.accepted.should.equal(false)
|
211 | initiatorCh.round().should.equal(roundBefore)
|
212 | sinon.assert.notCalled(initiatorSign)
|
213 | sinon.assert.calledOnce(responderSign)
|
214 | sinon.assert.calledWithExactly(
|
215 | responderSign,
|
216 | sinon.match('update_ack'),
|
217 | sinon.match.string,
|
218 | sinon.match({
|
219 | updates: sinon.match([{
|
220 | amount: sinon.match(amount),
|
221 | from: sinon.match(await responder.address()),
|
222 | to: sinon.match(await initiator.address()),
|
223 | op: sinon.match('OffChainTransfer')
|
224 | }])
|
225 | })
|
226 | )
|
227 | sinon.assert.calledOnce(sign)
|
228 | sinon.assert.calledWithExactly(
|
229 | sign,
|
230 | sinon.match.string,
|
231 | sinon.match({
|
232 | updates: sinon.match([{
|
233 | amount: sinon.match(amount),
|
234 | from: sinon.match(await responder.address()),
|
235 | to: sinon.match(await initiator.address()),
|
236 | op: sinon.match('OffChainTransfer')
|
237 | }])
|
238 | })
|
239 | )
|
240 | const { txType } = unpackTx(sign.firstCall.args[0])
|
241 | txType.should.equal('channelOffChain')
|
242 | sign.firstCall.args[1].should.eql({
|
243 | updates: [
|
244 | {
|
245 | amount,
|
246 | from: await responder.address(),
|
247 | to: await initiator.address(),
|
248 | op: 'OffChainTransfer'
|
249 | }
|
250 | ]
|
251 | })
|
252 | })
|
253 |
|
254 | it('can abort update sign request', async () => {
|
255 | const errorCode = 12345
|
256 | const result = await initiatorCh.update(
|
257 | await initiator.address(),
|
258 | await responder.address(),
|
259 | 100,
|
260 | () => errorCode
|
261 | )
|
262 | result.should.eql({ accepted: false })
|
263 | })
|
264 |
|
265 | it('can abort update with custom error code', async () => {
|
266 | responderShouldRejectUpdate = 1234
|
267 | const result = await initiatorCh.update(
|
268 | await initiator.address(),
|
269 | await responder.address(),
|
270 | 100,
|
271 | tx => initiator.signTransaction(tx)
|
272 | )
|
273 | result.should.eql({
|
274 | accepted: false,
|
275 | errorCode: responderShouldRejectUpdate,
|
276 | errorMessage: 'user-defined'
|
277 | })
|
278 | })
|
279 |
|
280 | it('can post bignumber update and accept', async () => {
|
281 | responderShouldRejectUpdate = false
|
282 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
283 | const amount = BigNumber('10e18')
|
284 | const result = await initiatorCh.update(
|
285 | await initiator.address(),
|
286 | await responder.address(),
|
287 | amount,
|
288 | sign
|
289 | )
|
290 | result.accepted.should.equal(true)
|
291 | result.signedTx.should.be.a('string')
|
292 | sinon.assert.notCalled(initiatorSign)
|
293 | sinon.assert.calledOnce(responderSign)
|
294 | sinon.assert.calledWithExactly(
|
295 | responderSign,
|
296 | sinon.match('update_ack'),
|
297 | sinon.match.string,
|
298 | sinon.match({
|
299 | updates: sinon.match([{
|
300 | amount: sinon.match(amount.toString()),
|
301 | from: sinon.match(await initiator.address()),
|
302 | to: sinon.match(await responder.address()),
|
303 | op: sinon.match('OffChainTransfer')
|
304 | }])
|
305 | })
|
306 | )
|
307 | sinon.assert.calledOnce(sign)
|
308 | sinon.assert.calledWithExactly(
|
309 | sign,
|
310 | sinon.match.string,
|
311 | sinon.match({
|
312 | updates: sinon.match([{
|
313 | amount: sinon.match(amount.toString()),
|
314 | from: sinon.match(await initiator.address()),
|
315 | to: sinon.match(await responder.address()),
|
316 | op: sinon.match('OffChainTransfer')
|
317 | }])
|
318 | })
|
319 | )
|
320 | const { txType } = unpackTx(sign.firstCall.args[0])
|
321 | txType.should.equal('channelOffChain')
|
322 | sign.firstCall.args[1].should.eql({
|
323 | updates: [
|
324 | {
|
325 | amount: amount.toString(),
|
326 | from: await initiator.address(),
|
327 | to: await responder.address(),
|
328 | op: 'OffChainTransfer'
|
329 | }
|
330 | ]
|
331 | })
|
332 | })
|
333 |
|
334 | it('can post update with metadata', async () => {
|
335 | responderShouldRejectUpdate = true
|
336 | const meta = 'meta 1'
|
337 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
338 | await initiatorCh.update(
|
339 | await initiator.address(),
|
340 | await responder.address(),
|
341 | 100,
|
342 | sign,
|
343 | [meta]
|
344 | )
|
345 | sign.firstCall.args[1].updates.should.eql([
|
346 | sign.firstCall.args[1].updates[0],
|
347 | { data: meta, op: 'OffChainMeta' }
|
348 | ])
|
349 | responderSign.firstCall.args[2].updates.should.eql([
|
350 | responderSign.firstCall.args[2].updates[0],
|
351 | { data: meta, op: 'OffChainMeta' }
|
352 | ])
|
353 | })
|
354 |
|
355 | it('can get proof of inclusion', async () => {
|
356 | const initiatorAddr = await initiator.address()
|
357 | const responderAddr = await responder.address()
|
358 | const params = { accounts: [initiatorAddr, responderAddr] }
|
359 | const initiatorPoi = await initiatorCh.poi(params)
|
360 | const responderPoi = await responderCh.poi(params)
|
361 | initiatorPoi.should.be.a('string')
|
362 | responderPoi.should.be.a('string')
|
363 | const unpackedInitiatorPoi = unpackTx(decode(initiatorPoi, 'pi'), true)
|
364 | const unpackedResponderPoi = unpackTx(decode(responderPoi, 'pi'), true)
|
365 | buildTx(unpackedInitiatorPoi.tx, unpackedInitiatorPoi.txType, { prefix: 'pi' }).tx.should.equal(initiatorPoi)
|
366 | buildTx(unpackedResponderPoi.tx, unpackedResponderPoi.txType, { prefix: 'pi' }).tx.should.equal(responderPoi)
|
367 | })
|
368 |
|
369 | it('can get balances', async () => {
|
370 | const initiatorAddr = await initiator.address()
|
371 | const responderAddr = await responder.address()
|
372 | const addresses = [initiatorAddr, responderAddr]
|
373 | const initiatorBalances = await initiatorCh.balances(addresses)
|
374 | const responderBalances = await responderCh.balances(addresses)
|
375 | initiatorBalances.should.be.an('object')
|
376 | responderBalances.should.be.an('object')
|
377 | initiatorBalances[initiatorAddr].should.be.a('string')
|
378 | initiatorBalances[responderAddr].should.be.a('string')
|
379 | responderBalances[initiatorAddr].should.be.a('string')
|
380 | responderBalances[responderAddr].should.be.a('string')
|
381 | })
|
382 |
|
383 | it('can send a message', async () => {
|
384 | const sender = await initiator.address()
|
385 | const recipient = await responder.address()
|
386 | const info = 'hello world'
|
387 | initiatorCh.sendMessage(info, recipient)
|
388 | const message = await new Promise(resolve => responderCh.on('message', resolve))
|
389 | message.should.eql({
|
390 | channel_id: initiatorCh.id(),
|
391 | from: sender,
|
392 | to: recipient,
|
393 | info
|
394 | })
|
395 | })
|
396 |
|
397 | it('can request a withdraw and accept', async () => {
|
398 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
399 | const amount = BigNumber('2e18')
|
400 | const onOnChainTx = sinon.spy()
|
401 | const onOwnWithdrawLocked = sinon.spy()
|
402 | const onWithdrawLocked = sinon.spy()
|
403 | responderShouldRejectUpdate = false
|
404 | const roundBefore = initiatorCh.round()
|
405 | const result = await initiatorCh.withdraw(
|
406 | amount,
|
407 | sign,
|
408 | { onOnChainTx, onOwnWithdrawLocked, onWithdrawLocked }
|
409 | )
|
410 | result.should.eql({ accepted: true, signedTx: (await initiatorCh.state()).signedTx })
|
411 | initiatorCh.round().should.equal(roundBefore + 1)
|
412 | sinon.assert.called(onOnChainTx)
|
413 | sinon.assert.calledWithExactly(onOnChainTx, sinon.match.string)
|
414 | sinon.assert.calledOnce(onOwnWithdrawLocked)
|
415 | sinon.assert.calledOnce(onWithdrawLocked)
|
416 | sinon.assert.notCalled(initiatorSign)
|
417 | sinon.assert.calledOnce(responderSign)
|
418 | sinon.assert.calledWithExactly(
|
419 | responderSign,
|
420 | sinon.match('withdraw_ack'),
|
421 | sinon.match.string,
|
422 | sinon.match({
|
423 | updates: [{
|
424 | amount: amount.toString(),
|
425 | op: 'OffChainWithdrawal',
|
426 | to: await initiator.address()
|
427 | }]
|
428 | })
|
429 | )
|
430 | sinon.assert.calledOnce(sign)
|
431 | sinon.assert.calledWithExactly(
|
432 | sign,
|
433 | sinon.match.string,
|
434 | sinon.match({
|
435 | updates: [{
|
436 | amount: amount.toString(),
|
437 | op: 'OffChainWithdrawal',
|
438 | to: await initiator.address()
|
439 | }]
|
440 | })
|
441 | )
|
442 | const { txType, tx } = unpackTx(sign.firstCall.args[0])
|
443 | txType.should.equal('channelWithdraw')
|
444 | tx.should.eql({
|
445 | ...tx,
|
446 | toId: await initiator.address(),
|
447 | amount: amount.toString()
|
448 | })
|
449 | })
|
450 |
|
451 | it('can request a withdraw and reject', async () => {
|
452 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
453 | const amount = BigNumber('2e18')
|
454 | const onOnChainTx = sinon.spy()
|
455 | const onOwnWithdrawLocked = sinon.spy()
|
456 | const onWithdrawLocked = sinon.spy()
|
457 | responderShouldRejectUpdate = true
|
458 | const roundBefore = initiatorCh.round()
|
459 | const result = await initiatorCh.withdraw(
|
460 | amount,
|
461 | sign,
|
462 | { onOnChainTx, onOwnWithdrawLocked, onWithdrawLocked }
|
463 | )
|
464 | initiatorCh.round().should.equal(roundBefore)
|
465 | result.should.eql({ ...result, accepted: false })
|
466 | sinon.assert.notCalled(onOnChainTx)
|
467 | sinon.assert.notCalled(onOwnWithdrawLocked)
|
468 | sinon.assert.notCalled(onWithdrawLocked)
|
469 | sinon.assert.notCalled(initiatorSign)
|
470 | sinon.assert.calledOnce(responderSign)
|
471 | sinon.assert.calledWithExactly(
|
472 | responderSign,
|
473 | sinon.match('withdraw_ack'),
|
474 | sinon.match.string,
|
475 | sinon.match({
|
476 | updates: [{
|
477 | amount: amount.toString(),
|
478 | op: 'OffChainWithdrawal',
|
479 | to: await initiator.address()
|
480 | }]
|
481 | })
|
482 | )
|
483 | sinon.assert.calledOnce(sign)
|
484 | sinon.assert.calledWithExactly(
|
485 | sign,
|
486 | sinon.match.string,
|
487 | sinon.match({
|
488 | updates: [{
|
489 | amount: amount.toString(),
|
490 | op: 'OffChainWithdrawal',
|
491 | to: await initiator.address()
|
492 | }]
|
493 | })
|
494 | )
|
495 | const { txType, tx } = unpackTx(sign.firstCall.args[0])
|
496 | txType.should.equal('channelWithdraw')
|
497 | tx.should.eql({
|
498 | ...tx,
|
499 | toId: await initiator.address(),
|
500 | amount: amount.toString()
|
501 | })
|
502 | })
|
503 |
|
504 | it('can abort withdraw sign request', async () => {
|
505 | const errorCode = 12345
|
506 | const result = await initiatorCh.withdraw(
|
507 | 100,
|
508 | () => errorCode
|
509 | )
|
510 | result.should.eql({ accepted: false })
|
511 | })
|
512 |
|
513 | it('can abort withdraw with custom error code', async () => {
|
514 | responderShouldRejectUpdate = 12345
|
515 | const result = await initiatorCh.withdraw(
|
516 | 100,
|
517 | tx => initiator.signTransaction(tx)
|
518 | )
|
519 | result.should.eql({
|
520 | accepted: false,
|
521 | errorCode: responderShouldRejectUpdate,
|
522 | errorMessage: 'user-defined'
|
523 | })
|
524 | })
|
525 |
|
526 | it('can request a deposit and accept', async () => {
|
527 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
528 | const amount = BigNumber('2e18')
|
529 | const onOnChainTx = sinon.spy()
|
530 | const onOwnDepositLocked = sinon.spy()
|
531 | const onDepositLocked = sinon.spy()
|
532 | responderShouldRejectUpdate = false
|
533 | const roundBefore = initiatorCh.round()
|
534 | const result = await initiatorCh.deposit(
|
535 | amount,
|
536 | sign,
|
537 | { onOnChainTx, onOwnDepositLocked, onDepositLocked }
|
538 | )
|
539 | result.should.eql({ accepted: true, signedTx: (await initiatorCh.state()).signedTx })
|
540 | initiatorCh.round().should.equal(roundBefore + 1)
|
541 | sinon.assert.called(onOnChainTx)
|
542 | sinon.assert.calledWithExactly(onOnChainTx, sinon.match.string)
|
543 | sinon.assert.calledOnce(onOwnDepositLocked)
|
544 | sinon.assert.calledOnce(onDepositLocked)
|
545 | sinon.assert.notCalled(initiatorSign)
|
546 | sinon.assert.calledOnce(responderSign)
|
547 | sinon.assert.calledWithExactly(
|
548 | responderSign,
|
549 | sinon.match('deposit_ack'),
|
550 | sinon.match.string,
|
551 | sinon.match({
|
552 | updates: sinon.match([{
|
553 | amount: amount.toString(),
|
554 | op: 'OffChainDeposit',
|
555 | from: await initiator.address()
|
556 | }])
|
557 | })
|
558 | )
|
559 | sinon.assert.calledOnce(sign)
|
560 | sinon.assert.calledWithExactly(
|
561 | sign,
|
562 | sinon.match.string,
|
563 | sinon.match({
|
564 | updates: sinon.match([{
|
565 | amount: amount.toString(),
|
566 | op: 'OffChainDeposit',
|
567 | from: await initiator.address()
|
568 | }])
|
569 | })
|
570 | )
|
571 | const { txType, tx } = unpackTx(sign.firstCall.args[0])
|
572 | txType.should.equal('channelDeposit')
|
573 | tx.should.eql({
|
574 | ...tx,
|
575 | fromId: await initiator.address(),
|
576 | amount: amount.toString()
|
577 | })
|
578 | })
|
579 |
|
580 | it('can request a deposit and reject', async () => {
|
581 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
582 | const amount = BigNumber('2e18')
|
583 | const onOnChainTx = sinon.spy()
|
584 | const onOwnDepositLocked = sinon.spy()
|
585 | const onDepositLocked = sinon.spy()
|
586 | responderShouldRejectUpdate = true
|
587 | const roundBefore = initiatorCh.round()
|
588 | const result = await initiatorCh.deposit(
|
589 | amount,
|
590 | sign,
|
591 | { onOnChainTx, onOwnDepositLocked, onDepositLocked }
|
592 | )
|
593 | initiatorCh.round().should.equal(roundBefore)
|
594 | result.should.eql({ ...result, accepted: false })
|
595 | sinon.assert.notCalled(onOnChainTx)
|
596 | sinon.assert.notCalled(onOwnDepositLocked)
|
597 | sinon.assert.notCalled(onDepositLocked)
|
598 | sinon.assert.notCalled(initiatorSign)
|
599 | sinon.assert.calledOnce(responderSign)
|
600 | sinon.assert.calledWithExactly(
|
601 | responderSign,
|
602 | sinon.match('deposit_ack'),
|
603 | sinon.match.string,
|
604 | sinon.match({
|
605 | updates: [{
|
606 | amount: amount.toString(),
|
607 | op: 'OffChainDeposit',
|
608 | from: await initiator.address()
|
609 | }]
|
610 | })
|
611 | )
|
612 | const { txType, tx } = unpackTx(sign.firstCall.args[0])
|
613 | txType.should.equal('channelDeposit')
|
614 | tx.should.eql({
|
615 | ...tx,
|
616 | fromId: await initiator.address(),
|
617 | amount: amount.toString()
|
618 | })
|
619 | })
|
620 |
|
621 | it('can abort deposit sign request', async () => {
|
622 | const errorCode = 12345
|
623 | const result = await initiatorCh.deposit(
|
624 | 100,
|
625 | () => errorCode
|
626 | )
|
627 | result.should.eql({ accepted: false })
|
628 | })
|
629 |
|
630 | it('can abort deposit with custom error code', async () => {
|
631 | responderShouldRejectUpdate = 12345
|
632 | const result = await initiatorCh.deposit(
|
633 | 100,
|
634 | tx => initiator.signTransaction(tx)
|
635 | )
|
636 | result.should.eql({
|
637 | accepted: false,
|
638 | errorCode: responderShouldRejectUpdate,
|
639 | errorMessage: 'user-defined'
|
640 | })
|
641 | })
|
642 |
|
643 | it('can close a channel', async () => {
|
644 | const sign = sinon.spy(initiator.signTransaction.bind(initiator))
|
645 | const result = await initiatorCh.shutdown(sign)
|
646 | result.should.be.a('string')
|
647 | sinon.assert.notCalled(initiatorSign)
|
648 | sinon.assert.calledOnce(responderSign)
|
649 | sinon.assert.calledWithExactly(
|
650 | responderSign,
|
651 | sinon.match('shutdown_sign_ack'),
|
652 | sinon.match.string,
|
653 | sinon.match.any
|
654 | )
|
655 | sinon.assert.calledOnce(sign)
|
656 | sinon.assert.calledWithExactly(sign, sinon.match.string)
|
657 | const { txType, tx } = unpackTx(sign.firstCall.args[0])
|
658 | txType.should.equal('channelCloseMutual')
|
659 | tx.should.eql({
|
660 | ...tx,
|
661 | fromId: await initiator.address()
|
662 |
|
663 | })
|
664 | })
|
665 |
|
666 | it('can leave a channel', async () => {
|
667 | initiatorCh.disconnect()
|
668 | responderCh.disconnect()
|
669 | initiatorCh = await Channel({
|
670 | ...sharedParams,
|
671 | role: 'initiator',
|
672 | sign: initiatorSign
|
673 | })
|
674 | responderCh = await Channel({
|
675 | ...sharedParams,
|
676 | role: 'responder',
|
677 | sign: responderSign
|
678 | })
|
679 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
680 | initiatorCh.round()
|
681 | const result = await initiatorCh.leave()
|
682 | result.channelId.should.be.a('string')
|
683 | result.signedTx.should.be.a('string')
|
684 | existingChannelId = result.channelId
|
685 | offchainTx = result.signedTx
|
686 | })
|
687 |
|
688 | it('can reestablish a channel', async () => {
|
689 | const existingChannelIdKey =
|
690 | majorVersion > 5 || (majorVersion === 5 && minorVersion >= 2)
|
691 | ? 'existingFsmId'
|
692 | : 'existingChannelId'
|
693 | initiatorCh = await Channel({
|
694 | ...sharedParams,
|
695 | role: 'initiator',
|
696 | port: 3002,
|
697 | [existingChannelIdKey]: existingChannelId,
|
698 | offchainTx,
|
699 | sign: initiatorSign
|
700 | })
|
701 | await waitForChannel(initiatorCh)
|
702 |
|
703 |
|
704 | sinon.assert.notCalled(initiatorSign)
|
705 | sinon.assert.notCalled(responderSign)
|
706 | })
|
707 |
|
708 | it('can solo close a channel', async () => {
|
709 | initiatorCh.disconnect()
|
710 | responderCh.disconnect()
|
711 | initiatorCh = await Channel({
|
712 | ...sharedParams,
|
713 | role: 'initiator',
|
714 | port: 3003,
|
715 | sign: initiatorSign
|
716 | })
|
717 | responderCh = await Channel({
|
718 | ...sharedParams,
|
719 | role: 'responder',
|
720 | port: 3003,
|
721 | sign: responderSign
|
722 | })
|
723 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
724 |
|
725 | const initiatorAddr = await initiator.address()
|
726 | const responderAddr = await responder.address()
|
727 | const { signedTx } = await initiatorCh.update(
|
728 | await initiator.address(),
|
729 | await responder.address(),
|
730 | BigNumber('3e18'),
|
731 | tx => initiator.signTransaction(tx)
|
732 | )
|
733 | const poi = await initiatorCh.poi({
|
734 | accounts: [initiatorAddr, responderAddr]
|
735 | })
|
736 | const balances = await initiatorCh.balances([initiatorAddr, responderAddr])
|
737 | const initiatorBalanceBeforeClose = await initiator.balance(initiatorAddr)
|
738 | const responderBalanceBeforeClose = await responder.balance(responderAddr)
|
739 | const closeSoloTx = await initiator.channelCloseSoloTx({
|
740 | channelId: await initiatorCh.id(),
|
741 | fromId: initiatorAddr,
|
742 | poi,
|
743 | payload: signedTx
|
744 | })
|
745 | const closeSoloTxFee = unpackTx(closeSoloTx).tx.fee
|
746 | await initiator.sendTransaction(await initiator.signTransaction(closeSoloTx), { waitMined: true })
|
747 | const settleTx = await initiator.channelSettleTx({
|
748 | channelId: await initiatorCh.id(),
|
749 | fromId: initiatorAddr,
|
750 | initiatorAmountFinal: balances[initiatorAddr],
|
751 | responderAmountFinal: balances[responderAddr]
|
752 | })
|
753 | const settleTxFee = unpackTx(settleTx).tx.fee
|
754 | await initiator.sendTransaction(await initiator.signTransaction(settleTx), { waitMined: true })
|
755 | const initiatorBalanceAfterClose = await initiator.balance(initiatorAddr)
|
756 | const responderBalanceAfterClose = await responder.balance(responderAddr)
|
757 | new BigNumber(initiatorBalanceAfterClose).minus(initiatorBalanceBeforeClose).plus(closeSoloTxFee).plus(settleTxFee).isEqualTo(
|
758 | new BigNumber(balances[initiatorAddr])
|
759 | ).should.be.equal(true)
|
760 | new BigNumber(responderBalanceAfterClose).minus(responderBalanceBeforeClose).isEqualTo(
|
761 | new BigNumber(balances[responderAddr])
|
762 | ).should.be.equal(true)
|
763 | })
|
764 |
|
765 | it('can dispute via slash tx', async () => {
|
766 | const initiatorAddr = await initiator.address()
|
767 | const responderAddr = await responder.address()
|
768 | initiatorCh.disconnect()
|
769 | responderCh.disconnect()
|
770 | initiatorCh = await Channel({
|
771 | ...sharedParams,
|
772 | lockPeriod: 5,
|
773 | role: 'initiator',
|
774 | sign: initiatorSign,
|
775 | port: 3004
|
776 | })
|
777 | responderCh = await Channel({
|
778 | ...sharedParams,
|
779 | lockPeriod: 5,
|
780 | role: 'responder',
|
781 | sign: responderSign,
|
782 | port: 3004
|
783 | })
|
784 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
785 | const initiatorBalanceBeforeClose = await initiator.balance(initiatorAddr)
|
786 | const responderBalanceBeforeClose = await responder.balance(responderAddr)
|
787 | const oldUpdate = await initiatorCh.update(initiatorAddr, responderAddr, 100, (tx) => initiator.signTransaction(tx))
|
788 | const oldPoi = await initiatorCh.poi({
|
789 | accounts: [initiatorAddr, responderAddr]
|
790 | })
|
791 | const recentUpdate = await initiatorCh.update(initiatorAddr, responderAddr, 100, (tx) => initiator.signTransaction(tx))
|
792 | const recentPoi = await responderCh.poi({
|
793 | accounts: [initiatorAddr, responderAddr]
|
794 | })
|
795 | const recentBalances = await responderCh.balances([initiatorAddr, responderAddr])
|
796 | const closeSoloTx = await initiator.channelCloseSoloTx({
|
797 | channelId: initiatorCh.id(),
|
798 | fromId: initiatorAddr,
|
799 | poi: oldPoi,
|
800 | payload: oldUpdate.signedTx
|
801 | })
|
802 | const closeSoloTxFee = unpackTx(closeSoloTx).tx.fee
|
803 | await initiator.sendTransaction(await initiator.signTransaction(closeSoloTx), { waitMined: true })
|
804 | const slashTx = await responder.channelSlashTx({
|
805 | channelId: responderCh.id(),
|
806 | fromId: responderAddr,
|
807 | poi: recentPoi,
|
808 | payload: recentUpdate.signedTx
|
809 | })
|
810 | const slashTxFee = unpackTx(slashTx).tx.fee
|
811 | await responder.sendTransaction(await responder.signTransaction(slashTx), { waitMined: true })
|
812 | const settleTx = await responder.channelSettleTx({
|
813 | channelId: responderCh.id(),
|
814 | fromId: responderAddr,
|
815 | initiatorAmountFinal: recentBalances[initiatorAddr],
|
816 | responderAmountFinal: recentBalances[responderAddr]
|
817 | })
|
818 | const settleTxFee = unpackTx(settleTx).tx.fee
|
819 | await responder.sendTransaction(await responder.signTransaction(settleTx), { waitMined: true })
|
820 | const initiatorBalanceAfterClose = await initiator.balance(initiatorAddr)
|
821 | const responderBalanceAfterClose = await responder.balance(responderAddr)
|
822 | new BigNumber(initiatorBalanceAfterClose).minus(initiatorBalanceBeforeClose).plus(closeSoloTxFee).isEqualTo(
|
823 | new BigNumber(recentBalances[initiatorAddr])
|
824 | ).should.be.equal(true)
|
825 | new BigNumber(responderBalanceAfterClose).minus(responderBalanceBeforeClose).plus(slashTxFee).plus(settleTxFee).isEqualTo(
|
826 | new BigNumber(recentBalances[responderAddr])
|
827 | ).should.be.equal(true)
|
828 | })
|
829 |
|
830 | it('can create a contract and accept', async () => {
|
831 | initiatorCh.disconnect()
|
832 | responderCh.disconnect()
|
833 | initiatorCh = await Channel({
|
834 | ...sharedParams,
|
835 | role: 'initiator',
|
836 | port: 3005,
|
837 | sign: initiatorSign
|
838 | })
|
839 | responderCh = await Channel({
|
840 | ...sharedParams,
|
841 | role: 'responder',
|
842 | port: 3005,
|
843 | sign: responderSign
|
844 | })
|
845 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
846 | const code = await initiator.compileContractAPI(identityContract, { backend: 'aevm' })
|
847 | const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [], { backend: 'aevm' })
|
848 | const roundBefore = initiatorCh.round()
|
849 | const result = await initiatorCh.createContract({
|
850 | code,
|
851 | callData,
|
852 | deposit: 1000,
|
853 | vmVersion: 6,
|
854 | abiVersion: 1
|
855 | }, async (tx) => initiator.signTransaction(tx))
|
856 | result.should.eql({ accepted: true, address: result.address, signedTx: (await initiatorCh.state()).signedTx })
|
857 | initiatorCh.round().should.equal(roundBefore + 1)
|
858 | contractAddress = result.address
|
859 | contractEncodeCall = (method, args) => initiator.contractEncodeCallDataAPI(identityContract, method, args, { backend: 'aevm' })
|
860 | })
|
861 |
|
862 | it('can create a contract and reject', async () => {
|
863 | responderShouldRejectUpdate = true
|
864 | const code = await initiator.compileContractAPI(identityContract, { backend: 'aevm' })
|
865 | const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [], { backend: 'aevm' })
|
866 | const roundBefore = initiatorCh.round()
|
867 | const result = await initiatorCh.createContract({
|
868 | code,
|
869 | callData,
|
870 | deposit: BigNumber('10e18'),
|
871 | vmVersion: 4,
|
872 | abiVersion: 1
|
873 | }, async (tx) => initiator.signTransaction(tx))
|
874 | initiatorCh.round().should.equal(roundBefore)
|
875 | result.should.eql({ ...result, accepted: false })
|
876 | })
|
877 |
|
878 | it('can abort contract sign request', async () => {
|
879 | const errorCode = 12345
|
880 | const code = await initiator.compileContractAPI(identityContract, { backend: 'aevm' })
|
881 | const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [], { backend: 'aevm' })
|
882 | const result = await initiatorCh.createContract({
|
883 | code,
|
884 | callData,
|
885 | deposit: BigNumber('10e18'),
|
886 | vmVersion: 4,
|
887 | abiVersion: 1
|
888 | }, () => errorCode)
|
889 | result.should.eql({ accepted: false })
|
890 | })
|
891 |
|
892 | it('can abort contract with custom error code', async () => {
|
893 | responderShouldRejectUpdate = 12345
|
894 | const code = await initiator.compileContractAPI(identityContract, { backend: 'aevm' })
|
895 | const callData = await initiator.contractEncodeCallDataAPI(identityContract, 'init', [], { backend: 'aevm' })
|
896 | const result = await initiatorCh.createContract({
|
897 | code,
|
898 | callData,
|
899 | deposit: BigNumber('10e18'),
|
900 | vmVersion: 4,
|
901 | abiVersion: 1
|
902 | }, async (tx) => initiator.signTransaction(tx))
|
903 | result.should.eql({
|
904 | accepted: false,
|
905 | errorCode: responderShouldRejectUpdate,
|
906 | errorMessage: 'user-defined'
|
907 | })
|
908 | })
|
909 |
|
910 | it('can call a contract and accept', async () => {
|
911 | const roundBefore = initiatorCh.round()
|
912 | const result = await initiatorCh.callContract({
|
913 | amount: 0,
|
914 | callData: await contractEncodeCall('main', ['42']),
|
915 | contract: contractAddress,
|
916 | abiVersion: 1
|
917 | }, async (tx) => initiator.signTransaction(tx))
|
918 | result.should.eql({ accepted: true, signedTx: (await initiatorCh.state()).signedTx })
|
919 | initiatorCh.round().should.equal(roundBefore + 1)
|
920 | callerNonce = initiatorCh.round()
|
921 | })
|
922 |
|
923 | it('can call a force progress', async () => {
|
924 | const forceTx = await initiatorCh.forceProgress({
|
925 | amount: 0,
|
926 | callData: await contractEncodeCall('main', ['42']),
|
927 | contract: contractAddress,
|
928 | abiVersion: 1
|
929 | }, async (tx) => initiator.signTransaction(tx))
|
930 | console.log('after done')
|
931 | const hash = buildTxHash(forceTx.tx)
|
932 | const txInfo = await initiator.tx(hash)
|
933 | console.log(txInfo)
|
934 | })
|
935 |
|
936 | it('can call a contract and reject', async () => {
|
937 | responderShouldRejectUpdate = true
|
938 | const roundBefore = initiatorCh.round()
|
939 | const result = await initiatorCh.callContract({
|
940 | amount: 0,
|
941 | callData: await contractEncodeCall('main', ['42']),
|
942 | contract: contractAddress,
|
943 | abiVersion: 1
|
944 | }, async (tx) => initiator.signTransaction(tx))
|
945 | initiatorCh.round().should.equal(roundBefore)
|
946 | result.should.eql({ ...result, accepted: false })
|
947 | })
|
948 |
|
949 | it('can abort contract call sign request', async () => {
|
950 | const errorCode = 12345
|
951 | const result = await initiatorCh.callContract({
|
952 | amount: 0,
|
953 | callData: await contractEncodeCall('main', ['42']),
|
954 | contract: contractAddress,
|
955 | abiVersion: 1
|
956 | }, () => errorCode)
|
957 | result.should.eql({ accepted: false })
|
958 | })
|
959 |
|
960 | it('can abort contract call with custom error code', async () => {
|
961 | responderShouldRejectUpdate = 12345
|
962 | const result = await initiatorCh.callContract({
|
963 | amount: 0,
|
964 | callData: await contractEncodeCall('main', ['42']),
|
965 | contract: contractAddress,
|
966 | abiVersion: 1
|
967 | }, async (tx) => initiator.signTransaction(tx))
|
968 | result.should.eql({
|
969 | accepted: false,
|
970 | errorCode: responderShouldRejectUpdate,
|
971 | errorMessage: 'user-defined'
|
972 | })
|
973 | })
|
974 |
|
975 | it('can get contract call', async () => {
|
976 | const result = await initiatorCh.getContractCall({
|
977 | caller: await initiator.address(),
|
978 | contract: contractAddress,
|
979 | round: callerNonce
|
980 | })
|
981 | result.should.eql({
|
982 | callerId: await initiator.address(),
|
983 | callerNonce,
|
984 | contractId: contractAddress,
|
985 | gasPrice: result.gasPrice,
|
986 | gasUsed: result.gasUsed,
|
987 | height: result.height,
|
988 | log: result.log,
|
989 | returnType: 'ok',
|
990 | returnValue: result.returnValue
|
991 | })
|
992 | const value = await initiator.contractDecodeDataAPI('int', result.returnValue)
|
993 | value.should.eql({ type: 'word', value: 42 })
|
994 | })
|
995 |
|
996 | it('can call a contract using dry-run', async () => {
|
997 | const result = await initiatorCh.callContractStatic({
|
998 | amount: 0,
|
999 | callData: await contractEncodeCall('main', ['42']),
|
1000 | contract: contractAddress,
|
1001 | abiVersion: 1
|
1002 | })
|
1003 | result.should.eql({
|
1004 | callerId: await initiator.address(),
|
1005 | callerNonce: result.callerNonce,
|
1006 | contractId: contractAddress,
|
1007 | gasPrice: result.gasPrice,
|
1008 | gasUsed: result.gasUsed,
|
1009 | height: result.height,
|
1010 | log: result.log,
|
1011 | returnType: 'ok',
|
1012 | returnValue: result.returnValue
|
1013 | })
|
1014 | const value = await initiator.contractDecodeDataAPI('int', result.returnValue)
|
1015 | value.should.eql({ type: 'word', value: 42 })
|
1016 | })
|
1017 |
|
1018 | it('can clean contract calls', async () => {
|
1019 | await initiatorCh.cleanContractCalls()
|
1020 | initiatorCh.getContractCall({
|
1021 | caller: await initiator.address(),
|
1022 | contract: contractAddress,
|
1023 | round: callerNonce
|
1024 | }).should.eventually.be.rejected
|
1025 | })
|
1026 |
|
1027 | it('can get contract state', async () => {
|
1028 | const result = await initiatorCh.getContractState(contractAddress)
|
1029 | result.should.eql({
|
1030 | contract: {
|
1031 | abiVersion: 1,
|
1032 | active: true,
|
1033 | deposit: 1000,
|
1034 | id: contractAddress,
|
1035 | ownerId: await initiator.address(),
|
1036 | referrerIds: [],
|
1037 | vmVersion: 6
|
1038 | },
|
1039 | contractState: result.contractState
|
1040 | })
|
1041 |
|
1042 | })
|
1043 |
|
1044 | it.skip('can post snapshot solo transaction', async () => {
|
1045 | const snapshotSoloTx = await initiator.channelSnapshotSoloTx({
|
1046 | channelId: initiatorCh.id(),
|
1047 | fromId: await initiator.address(),
|
1048 | payload: (await initiatorCh.state()).signedTx
|
1049 | })
|
1050 | await initiator.sendTransaction(await initiator.signTransaction(snapshotSoloTx), { waitMined: true })
|
1051 | })
|
1052 |
|
1053 | it('can reconnect', async () => {
|
1054 | initiatorCh.disconnect()
|
1055 | responderCh.disconnect()
|
1056 | initiatorCh = await Channel({
|
1057 | ...sharedParams,
|
1058 | role: 'initiator',
|
1059 | port: 3006,
|
1060 | sign: initiatorSign
|
1061 | })
|
1062 | responderCh = await Channel({
|
1063 | ...sharedParams,
|
1064 | role: 'responder',
|
1065 | port: 3006,
|
1066 | sign: responderSign
|
1067 | })
|
1068 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
1069 | const result = await initiatorCh.update(
|
1070 | await initiator.address(),
|
1071 | await responder.address(),
|
1072 | 100,
|
1073 | tx => initiator.signTransaction(tx)
|
1074 | )
|
1075 | result.accepted.should.be.true
|
1076 | const channelId = await initiatorCh.id()
|
1077 | const round = initiatorCh.round()
|
1078 | let ch
|
1079 | if (majorVersion > 5 || (majorVersion === 5 && minorVersion >= 2)) {
|
1080 | const fsmId = initiatorCh.fsmId()
|
1081 | initiatorCh.disconnect()
|
1082 | ch = await Channel({
|
1083 | url: sharedParams.url,
|
1084 | host: sharedParams.host,
|
1085 | port: 3006,
|
1086 | role: 'initiator',
|
1087 | existingChannelId: channelId,
|
1088 | existingFsmId: fsmId
|
1089 | })
|
1090 | await waitForChannel(ch)
|
1091 | ch.fsmId().should.equal(fsmId)
|
1092 | } else {
|
1093 | initiatorCh.disconnect()
|
1094 | ch = await Channel.reconnect({
|
1095 | ...sharedParams,
|
1096 | role: 'initiator',
|
1097 | port: 3006,
|
1098 | sign: initiatorSign
|
1099 | }, {
|
1100 | channelId,
|
1101 | round,
|
1102 | role: 'initiator',
|
1103 | pubkey: await initiator.address()
|
1104 | })
|
1105 | await waitForChannel(ch)
|
1106 | }
|
1107 |
|
1108 |
|
1109 |
|
1110 |
|
1111 |
|
1112 |
|
1113 |
|
1114 |
|
1115 |
|
1116 |
|
1117 | ch.state().should.eventually.be.fulfilled
|
1118 | await new Promise(resolve => setTimeout(resolve, 10 * 1000))
|
1119 | })
|
1120 |
|
1121 | it('can post backchannel update', async () => {
|
1122 | async function appendSignature (target, source) {
|
1123 | const { txType, tx: { signatures, encodedTx: { rlpEncoded } } } = unpackTx(target)
|
1124 | const tx = buildTx({
|
1125 | signatures: signatures.concat(unpackTx(source).tx.signatures),
|
1126 | encodedTx: rlpEncoded
|
1127 | }, txType)
|
1128 | return `tx_${encodeBase64Check(tx.rlpEncoded)}`
|
1129 | }
|
1130 |
|
1131 | initiatorCh.disconnect()
|
1132 | responderCh.disconnect()
|
1133 | initiatorCh = await Channel({
|
1134 | ...sharedParams,
|
1135 | role: 'initiator',
|
1136 | port: 3007,
|
1137 | sign: initiatorSign
|
1138 | })
|
1139 | responderCh = await Channel({
|
1140 | ...sharedParams,
|
1141 | role: 'responder',
|
1142 | port: 3007,
|
1143 | sign: responderSign
|
1144 | })
|
1145 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
1146 | initiatorCh.disconnect()
|
1147 | const { accepted } = await responderCh.update(
|
1148 | await initiator.address(),
|
1149 | await responder.address(),
|
1150 | 100,
|
1151 | tx => responder.signTransaction(tx)
|
1152 | )
|
1153 | accepted.should.be.false
|
1154 | const result = await responderCh.update(
|
1155 | await initiator.address(),
|
1156 | await responder.address(),
|
1157 | 100,
|
1158 | async (tx) => appendSignature(
|
1159 | await responder.signTransaction(tx),
|
1160 | await initiator.signTransaction(tx)
|
1161 | )
|
1162 | )
|
1163 | result.accepted.should.equal(true)
|
1164 | result.signedTx.should.be.a('string')
|
1165 | initiatorCh.disconnect()
|
1166 | initiatorCh.disconnect()
|
1167 | })
|
1168 |
|
1169 | describe('throws errors', function () {
|
1170 | before(async function () {
|
1171 | initiatorCh.disconnect()
|
1172 | responderCh.disconnect()
|
1173 | initiatorCh = await Channel({
|
1174 | ...sharedParams,
|
1175 | role: 'initiator',
|
1176 | port: 3008,
|
1177 | sign: initiatorSign
|
1178 | })
|
1179 | responderCh = await Channel({
|
1180 | ...sharedParams,
|
1181 | role: 'responder',
|
1182 | port: 3008,
|
1183 | sign: responderSign
|
1184 | })
|
1185 | await Promise.all([waitForChannel(initiatorCh), waitForChannel(responderCh)])
|
1186 | })
|
1187 |
|
1188 | after(() => {
|
1189 | initiatorCh.disconnect()
|
1190 | responderCh.disconnect()
|
1191 | })
|
1192 |
|
1193 | async function update ({ from, to, amount, sign }) {
|
1194 | return initiatorCh.update(
|
1195 | from || await initiator.address(),
|
1196 | to || await responder.address(),
|
1197 | amount || 1,
|
1198 | sign || initiator.signTransaction
|
1199 | )
|
1200 | }
|
1201 |
|
1202 | it('when posting an update with negative amount', async () => {
|
1203 | return update({ amount: -10 }).should.eventually.be.rejectedWith('Amount cannot be negative')
|
1204 | })
|
1205 |
|
1206 | it('when posting an update with insufficient balance', async () => {
|
1207 | return update({ amount: BigNumber('999e18') }).should.eventually.be.rejectedWith('Insufficient balance')
|
1208 | })
|
1209 |
|
1210 | it('when posting an update with incorrect address', async () => {
|
1211 | return update({ from: 'ak_123' }).should.eventually.be.rejectedWith('Rejected')
|
1212 | })
|
1213 | })
|
1214 | })
|