-
Notifications
You must be signed in to change notification settings - Fork 212
/
test-coveredCall.js
1000 lines (856 loc) · 33.9 KB
/
test-coveredCall.js
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// eslint-disable-next-line import/no-extraneous-dependencies
import '@agoric/install-ses';
// eslint-disable-next-line import/no-extraneous-dependencies
import test from 'ava';
import bundleSource from '@agoric/bundle-source';
import { E } from '@agoric/eventual-send';
import { sameStructure } from '@agoric/same-structure';
import { makeLocalAmountMath } from '@agoric/ertp';
import buildManualTimer from '../../../tools/manualTimer';
import { setup } from '../setupBasicMints';
import { setupNonFungible } from '../setupNonFungibleMints';
const coveredCallRoot = `${__dirname}/../../../src/contracts/coveredCall`;
const atomicSwapRoot = `${__dirname}/../../../src/contracts/atomicSwap`;
test('zoe - coveredCall', async t => {
t.plan(13);
const {
moolaKit,
simoleanKit,
bucksKit,
moola,
simoleans,
bucks,
zoe,
} = setup();
const makeAlice = async (timer, moolaPayment) => {
const moolaPurse = await E(moolaKit.issuer).makeEmptyPurse();
const simoleanPurse = await E(simoleanKit.issuer).makeEmptyPurse();
const bucksPurse = await E(bucksKit.issuer).makeEmptyPurse();
return {
installCode: async () => {
// pack the contract
const bundle = await bundleSource(coveredCallRoot);
// install the contract
const installationP = E(zoe).install(bundle);
return installationP;
},
startInstance: async installation => {
const issuerKeywordRecord = harden({
Moola: moolaKit.issuer,
Simoleans: simoleanKit.issuer,
Bucks: bucksKit.issuer,
});
const adminP = zoe.startInstance(installation, issuerKeywordRecord);
return adminP;
},
offer: async createCallOptionInvitation => {
const proposal = harden({
give: { Moola: moola(3) },
want: { Simoleans: simoleans(7), Bucks: bucks(2) },
exit: { afterDeadline: { deadline: 1, timer } },
});
const payments = { Moola: moolaPayment };
const seat = await E(zoe).offer(
createCallOptionInvitation,
proposal,
payments,
);
// The result of making the first offer is the call option
// digital asset. It is simultaneously actually an invitation to
// exercise the option.
const invitationP = E(seat).getOfferResult();
return { seat, invitationP };
},
processPayouts: async seat => {
await E(seat)
.getPayout('Moola')
.then(moolaPurse.deposit)
.then(amountDeposited =>
t.deepEqual(
amountDeposited,
moola(0),
`Alice didn't get any of what she put in`,
),
);
await E(seat)
.getPayout('Simoleans')
.then(simoleanPurse.deposit)
.then(amountDeposited =>
t.deepEqual(
amountDeposited,
simoleans(7),
`Alice got exactly what she wanted`,
),
);
await E(seat)
.getPayout('Bucks')
.then(bucksPurse.deposit)
.then(amountDeposited =>
t.deepEqual(
amountDeposited,
bucks(2),
`Alice got exactly what she wanted`,
),
);
},
};
};
const makeBob = (timer, installation, simoleanPayment, bucksPayment) => {
const moolaPurse = moolaKit.issuer.makeEmptyPurse();
const simoleanPurse = simoleanKit.issuer.makeEmptyPurse();
const bucksPurse = bucksKit.issuer.makeEmptyPurse();
return harden({
offer: async untrustedInvitation => {
const invitationIssuer = await E(zoe).getInvitationIssuer();
// Bob is able to use the trusted invitationIssuer from Zoe to
// transform an untrusted invitation that Alice also has access to
const invitation = await E(invitationIssuer).claim(untrustedInvitation);
const invitationValue = await E(zoe).getInvitationDetails(invitation);
t.is(
invitationValue.installation,
installation,
'installation is coveredCall',
);
t.is(invitationValue.description, 'exerciseOption');
t.deepEqual(
invitationValue.underlyingAssets,
{ Moola: moola(3) },
`underlying assets are 3 moola`,
);
t.deepEqual(
invitationValue.strikePrice,
{ Simoleans: simoleans(7), Bucks: bucks(2) },
`strike price is 7 simoleans and 2 bucks, so bob must give that`,
);
t.is(invitationValue.expirationDate, 1);
t.deepEqual(invitationValue.timeAuthority, timer);
const proposal = harden({
give: { StrikePrice1: simoleans(7), StrikePrice2: bucks(2) },
want: { UnderlyingAsset: moola(3) },
exit: { onDemand: null },
});
const payments = {
StrikePrice1: simoleanPayment,
StrikePrice2: bucksPayment,
};
const seat = await E(zoe).offer(invitation, proposal, payments);
t.is(
await E(seat).getOfferResult(),
`The option was exercised. Please collect the assets in your payout.`,
);
return seat;
},
processPayouts: async seat => {
await E(seat)
.getPayout('UnderlyingAsset')
.then(moolaPurse.deposit)
.then(amountDeposited =>
t.deepEqual(amountDeposited, moola(3), `Bob got what he wanted`),
);
await E(seat)
.getPayout('StrikePrice1')
.then(simoleanPurse.deposit)
.then(amountDeposited =>
t.deepEqual(
amountDeposited,
simoleans(0),
`Bob didn't get anything back`,
),
);
await E(seat)
.getPayout('StrikePrice2')
.then(bucksPurse.deposit)
.then(amountDeposited =>
t.deepEqual(
amountDeposited,
bucks(0),
`Bob didn't get anything back`,
),
);
},
});
};
const timer = buildManualTimer(console.log);
// Setup Alice
const aliceMoolaPayment = moolaKit.mint.mintPayment(moola(3));
const alice = await makeAlice(timer, aliceMoolaPayment);
// Alice makes an instance and makes her offer.
const installation = await alice.installCode();
// Setup Bob
const bobSimoleanPayment = simoleanKit.mint.mintPayment(simoleans(7));
const bobBucksPayment = bucksKit.mint.mintPayment(bucks(2));
const bob = makeBob(timer, installation, bobSimoleanPayment, bobBucksPayment);
const { creatorInvitation } = await alice.startInstance(installation);
const { seat: aliceSeat, invitationP } = await alice.offer(creatorInvitation);
// Alice spreads the invitation far and wide with instructions
// on how to use it and Bob decides he wants to be the
// counter-party, without needing to trust Alice at all.
const bobSeat = await bob.offer(invitationP);
await alice.processPayouts(aliceSeat);
await bob.processPayouts(bobSeat);
});
test(`zoe - coveredCall - alice's deadline expires, cancelling alice and bob`, async t => {
t.plan(13);
const { moolaR, simoleanR, moola, simoleans, zoe } = setup();
// Pack the contract.
const bundle = await bundleSource(coveredCallRoot);
const coveredCallInstallation = await zoe.install(bundle);
const timer = buildManualTimer(console.log);
// Setup Alice
const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3));
const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse();
const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
// Setup Bob
const bobSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7));
const bobMoolaPurse = moolaR.issuer.makeEmptyPurse();
const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
// Alice creates a coveredCall instance
const issuerKeywordRecord = harden({
UnderlyingAsset: moolaR.issuer,
StrikePrice: simoleanR.issuer,
});
const { creatorInvitation: aliceInvitation } = await zoe.startInstance(
coveredCallInstallation,
issuerKeywordRecord,
);
// Alice escrows with Zoe
const aliceProposal = harden({
give: { UnderlyingAsset: moola(3) },
want: { StrikePrice: simoleans(7) },
exit: {
afterDeadline: {
deadline: 1,
timer,
},
},
});
const alicePayments = { UnderlyingAsset: aliceMoolaPayment };
// Alice makes an option
const aliceSeat = await zoe.offer(
aliceInvitation,
aliceProposal,
alicePayments,
);
timer.tick();
const optionP = E(aliceSeat).getOfferResult();
// Imagine that Alice sends the option to Bob for free (not done here
// since this test doesn't actually have separate vats/parties)
// Bob inspects the option (an invitation payment) and checks that it is the
// contract instance that he expects as well as that Alice has
// already escrowed.
const invitationIssuer = zoe.getInvitationIssuer();
const bobExclOption = await invitationIssuer.claim(optionP);
const optionValue = await E(zoe).getInvitationDetails(bobExclOption);
t.is(optionValue.installation, coveredCallInstallation);
t.is(optionValue.description, 'exerciseOption');
t.deepEqual(optionValue.underlyingAssets, { UnderlyingAsset: moola(3) });
t.deepEqual(optionValue.strikePrice, { StrikePrice: simoleans(7) });
t.is(optionValue.expirationDate, 1);
t.deepEqual(optionValue.timeAuthority, timer);
const bobPayments = { StrikePrice: bobSimoleanPayment };
const bobProposal = harden({
want: optionValue.underlyingAssets,
give: optionValue.strikePrice,
});
// Bob escrows
const bobSeat = await zoe.offer(bobExclOption, bobProposal, bobPayments);
// TODO is this await safe?
await t.throwsAsync(
() => E(bobSeat).getOfferResult(),
{ message: /The covered call option is expired./ },
'The call option should be expired',
);
const bobMoolaPayout = await E(bobSeat).getPayout('UnderlyingAsset');
const bobSimoleanPayout = await E(bobSeat).getPayout('StrikePrice');
const aliceMoolaPayout = await E(aliceSeat).getPayout('UnderlyingAsset');
const aliceSimoleanPayout = await E(aliceSeat).getPayout('StrikePrice');
// Alice gets back what she put in
t.deepEqual(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(3));
// Alice doesn't get what she wanted
t.deepEqual(
await simoleanR.issuer.getAmountOf(aliceSimoleanPayout),
simoleans(0),
);
// Alice deposits her winnings to ensure she can
await aliceMoolaPurse.deposit(aliceMoolaPayout);
await aliceSimoleanPurse.deposit(aliceSimoleanPayout);
// Bob deposits his winnings to ensure he can
await bobMoolaPurse.deposit(bobMoolaPayout);
await bobSimoleanPurse.deposit(bobSimoleanPayout);
// Assert that the correct outcome was achieved.
// Alice had 3 moola and 0 simoleans.
// Bob had 0 moola and 7 simoleans.
t.deepEqual(aliceMoolaPurse.getCurrentAmount(), moola(3));
t.deepEqual(aliceSimoleanPurse.getCurrentAmount(), simoleans(0));
t.deepEqual(bobMoolaPurse.getCurrentAmount(), moola(0));
t.deepEqual(bobSimoleanPurse.getCurrentAmount(), simoleans(7));
});
// Alice makes a covered call and escrows. She shares the invitation to
// Bob. Bob tries to sell the invitation to Dave through a swap. Can Bob
// trick Dave? Can Dave describe what it is that he wants in the swap
// offer description?
test('zoe - coveredCall with swap for invitation', async t => {
t.plan(24);
// Setup the environment
const timer = buildManualTimer(console.log);
const { moolaR, simoleanR, bucksR, moola, simoleans, bucks, zoe } = setup();
// Pack the contract.
const coveredCallBundle = await bundleSource(coveredCallRoot);
const coveredCallInstallation = await zoe.install(coveredCallBundle);
const atomicSwapBundle = await bundleSource(atomicSwapRoot);
const swapInstallationId = await zoe.install(atomicSwapBundle);
// Setup Alice
// Alice starts with 3 moola
const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3));
const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse();
const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
// Setup Bob
// Bob starts with nothing
const bobMoolaPurse = moolaR.issuer.makeEmptyPurse();
const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
const bobBucksPurse = bucksR.issuer.makeEmptyPurse();
// Setup Dave
// Dave starts with 1 buck
const daveSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7));
const daveBucksPayment = bucksR.mint.mintPayment(bucks(1));
const daveMoolaPurse = moolaR.issuer.makeEmptyPurse();
const daveSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
const daveBucksPurse = bucksR.issuer.makeEmptyPurse();
// Alice creates a coveredCall instance of moola for simoleans
const issuerKeywordRecord = harden({
UnderlyingAsset: moolaR.issuer,
StrikePrice: simoleanR.issuer,
});
const { creatorInvitation: aliceInvitation } = await zoe.startInstance(
coveredCallInstallation,
issuerKeywordRecord,
);
// Alice escrows with Zoe. She specifies her proposal,
// which includes the amounts she gives and wants as well as the exit
// conditions. In this case, she choses an exit condition of after
// the deadline of "100" according to a particular timer. This is
// meant to be something far in the future, and will not be
// reached in this test.
const aliceProposal = harden({
give: { UnderlyingAsset: moola(3) },
want: { StrikePrice: simoleans(7) },
exit: {
afterDeadline: {
deadline: 100, // we will not reach this
timer,
},
},
});
const alicePayments = { UnderlyingAsset: aliceMoolaPayment };
// Alice makes an option.
const aliceSeat = await zoe.offer(
aliceInvitation,
aliceProposal,
alicePayments,
);
const optionP = E(aliceSeat).getOfferResult();
// Imagine that Alice sends the invitation to Bob (not done here since
// this test doesn't actually have separate vats/parties)
// Bob inspects the invitation payment and checks its information against the
// questions that he has about whether it is worth being a counter
// party in the covered call: Did the covered call use the
// expected covered call installation (code)? Does it use the issuers
// that he expects (moola and simoleans)?
const invitationIssuer = zoe.getInvitationIssuer();
const invitationAmountMath = await makeLocalAmountMath(invitationIssuer);
const bobExclOption = await invitationIssuer.claim(optionP);
const optionAmount = await invitationIssuer.getAmountOf(bobExclOption);
const optionDesc = optionAmount.value[0];
t.is(optionDesc.installation, coveredCallInstallation);
t.is(optionDesc.description, 'exerciseOption');
t.deepEqual(optionDesc.underlyingAssets, { UnderlyingAsset: moola(3) });
t.deepEqual(optionDesc.strikePrice, { StrikePrice: simoleans(7) });
t.is(optionDesc.expirationDate, 100);
t.deepEqual(optionDesc.timeAuthority, timer);
// Let's imagine that Bob wants to create a swap to trade this
// invitation for bucks.
const swapIssuerKeywordRecord = harden({
Asset: invitationIssuer,
Price: bucksR.issuer,
});
const { creatorInvitation: bobSwapInvitation } = await zoe.startInstance(
swapInstallationId,
swapIssuerKeywordRecord,
);
// Bob wants to swap an invitation with the same amount as his
// current invitation from Alice. He wants 1 buck in return.
const bobProposalSwap = harden({
give: { Asset: await invitationIssuer.getAmountOf(bobExclOption) },
want: { Price: bucks(1) },
});
const bobPayments = harden({ Asset: bobExclOption });
// Bob escrows his option in the swap
// Bob makes an offer to the swap with his "higher order" invitation
const bobSwapSeat = await zoe.offer(
bobSwapInvitation,
bobProposalSwap,
bobPayments,
);
const daveSwapInvitationP = E(bobSwapSeat).getOfferResult();
// Bob passes the swap invitation to Dave and tells him the
// optionAmounts (basically, the description of the option)
const {
value: [{ instance: swapInstance, installation: daveSwapInstallId }],
} = await invitationIssuer.getAmountOf(daveSwapInvitationP);
const daveSwapIssuers = zoe.getIssuers(swapInstance);
// Dave is looking to buy the option to trade his 7 simoleans for
// 3 moola, and is willing to pay 1 buck for the option. He
// checks that this instance matches what he wants
// Did this swap use the correct swap installation? Yes
t.is(daveSwapInstallId, swapInstallationId);
// Is this swap for the correct issuers and has no other terms? Yes
t.truthy(
sameStructure(
daveSwapIssuers,
harden({
Asset: invitationIssuer,
Price: bucksR.issuer,
}),
),
);
// What's actually up to be bought? Is it the kind of invitation that
// Dave wants? What's the price for that invitation? Is it acceptable
// to Dave? Bob can tell Dave this out of band, and if he lies,
// Dave's offer will be rejected and he will get a refund. Dave
// knows this to be true because he knows the swap.
// Dave escrows his 1 buck with Zoe and forms his proposal
const daveSwapProposal = harden({
want: { Asset: optionAmount },
give: { Price: bucks(1) },
});
const daveSwapPayments = harden({ Price: daveBucksPayment });
const daveSwapSeat = await zoe.offer(
daveSwapInvitationP,
daveSwapProposal,
daveSwapPayments,
);
t.is(
await daveSwapSeat.getOfferResult(),
'The offer has been accepted. Once the contract has been completed, please check your payout',
);
const daveOption = await daveSwapSeat.getPayout('Asset');
const daveBucksPayout = await daveSwapSeat.getPayout('Price');
// Dave exercises his option by making an offer to the covered
// call. First, he escrows with Zoe.
const daveCoveredCallProposal = harden({
want: { UnderlyingAsset: moola(3) },
give: { StrikePrice: simoleans(7) },
});
const daveCoveredCallPayments = harden({
StrikePrice: daveSimoleanPayment,
});
const daveCoveredCallSeat = await zoe.offer(
daveOption,
daveCoveredCallProposal,
daveCoveredCallPayments,
);
t.is(
await E(daveCoveredCallSeat).getOfferResult(),
`The option was exercised. Please collect the assets in your payout.`,
);
// Dave should get 3 moola, Bob should get 1 buck, and Alice
// get 7 simoleans
const daveMoolaPayout = await daveCoveredCallSeat.getPayout(
'UnderlyingAsset',
);
const daveSimoleanPayout = await daveCoveredCallSeat.getPayout('StrikePrice');
const aliceMoolaPayout = await aliceSeat.getPayout('UnderlyingAsset');
const aliceSimoleanPayout = await aliceSeat.getPayout('StrikePrice');
const bobInvitationPayout = await bobSwapSeat.getPayout('Asset');
const bobBucksPayout = await bobSwapSeat.getPayout('Price');
t.deepEqual(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3));
t.deepEqual(
await simoleanR.issuer.getAmountOf(daveSimoleanPayout),
simoleans(0),
);
t.deepEqual(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0));
t.deepEqual(
await simoleanR.issuer.getAmountOf(aliceSimoleanPayout),
simoleans(7),
);
t.deepEqual(
await invitationIssuer.getAmountOf(bobInvitationPayout),
invitationAmountMath.getEmpty(),
);
t.deepEqual(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1));
// Alice deposits her payouts
await aliceMoolaPurse.deposit(aliceMoolaPayout);
await aliceSimoleanPurse.deposit(aliceSimoleanPayout);
// Bob deposits his payouts
await bobBucksPurse.deposit(bobBucksPayout);
// Dave deposits his payouts
await daveMoolaPurse.deposit(daveMoolaPayout);
await daveSimoleanPurse.deposit(daveSimoleanPayout);
await daveBucksPurse.deposit(daveBucksPayout);
t.is(aliceMoolaPurse.getCurrentAmount().value, 0);
t.is(aliceSimoleanPurse.getCurrentAmount().value, 7);
t.is(bobMoolaPurse.getCurrentAmount().value, 0);
t.is(bobSimoleanPurse.getCurrentAmount().value, 0);
t.is(bobBucksPurse.getCurrentAmount().value, 1);
t.is(daveMoolaPurse.getCurrentAmount().value, 3);
t.is(daveSimoleanPurse.getCurrentAmount().value, 0);
t.is(daveBucksPurse.getCurrentAmount().value, 0);
});
// Alice makes a covered call and escrows. She shares the invitation to
// Bob. Bob tries to sell the invitation to Dave through another covered
// call. Can Bob trick Dave? Can Dave describe what it is that he
// wants in his offer description in the second covered call?
test('zoe - coveredCall with coveredCall for invitation', async t => {
t.plan(31);
// Setup the environment
const timer = buildManualTimer(console.log);
const { moolaR, simoleanR, bucksR, moola, simoleans, bucks, zoe } = setup();
// Pack the contract.
const bundle = await bundleSource(coveredCallRoot);
const coveredCallInstallation = await zoe.install(bundle);
// Setup Alice
// Alice starts with 3 moola
const aliceMoolaPayment = moolaR.mint.mintPayment(moola(3));
const aliceMoolaPurse = moolaR.issuer.makeEmptyPurse();
const aliceSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
// Setup Bob
// Bob starts with nothing
const bobMoolaPurse = moolaR.issuer.makeEmptyPurse();
const bobSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
const bobBucksPurse = bucksR.issuer.makeEmptyPurse();
// Setup Dave
// Dave starts with 1 buck and 7 simoleans
const daveSimoleanPayment = simoleanR.mint.mintPayment(simoleans(7));
const daveBucksPayment = bucksR.mint.mintPayment(bucks(1));
const daveMoolaPurse = moolaR.issuer.makeEmptyPurse();
const daveSimoleanPurse = simoleanR.issuer.makeEmptyPurse();
const daveBucksPurse = bucksR.issuer.makeEmptyPurse();
// Alice creates a coveredCall instance of moola for simoleans
const issuerKeywordRecord = harden({
UnderlyingAsset: moolaR.issuer,
StrikePrice: simoleanR.issuer,
});
const {
creatorInvitation: aliceCoveredCallInvitation,
} = await zoe.startInstance(coveredCallInstallation, issuerKeywordRecord);
// Alice escrows with Zoe. She specifies her proposal,
// which include what she wants and gives as well as the exit
// condition. In this case, she choses an exit condition of after
// the deadline of "100" according to a particular timer. This is
// meant to be something far in the future, and will not be
// reached in this test.
const aliceProposal = harden({
give: { UnderlyingAsset: moola(3) },
want: { StrikePrice: simoleans(7) },
exit: {
afterDeadline: {
deadline: 100, // we will not reach this
timer,
},
},
});
const alicePayments = { UnderlyingAsset: aliceMoolaPayment };
// Alice makes a call option, which is an invitation to join the
// covered call contract
const aliceSeat = await zoe.offer(
aliceCoveredCallInvitation,
aliceProposal,
alicePayments,
);
const optionP = await E(aliceSeat).getOfferResult();
// Imagine that Alice sends the invitation to Bob as well as the
// instanceHandle (not done here since this test doesn't actually have
// separate vats/parties)
// Bob inspects the invitation payment and checks its information against the
// questions that he has about whether it is worth being a counter
// party in the covered call: Did the covered call use the
// expected covered call installation (code)? Does it use the issuers
// that he expects (moola and simoleans)?
const invitationIssuer = zoe.getInvitationIssuer();
const invitationAmountMath = await makeLocalAmountMath(invitationIssuer);
const bobExclOption = await invitationIssuer.claim(optionP);
const optionValue = await E(zoe).getInvitationDetails(bobExclOption);
t.is(optionValue.installation, coveredCallInstallation);
t.is(optionValue.description, 'exerciseOption');
t.deepEqual(optionValue.underlyingAssets, { UnderlyingAsset: moola(3) });
t.deepEqual(optionValue.strikePrice, { StrikePrice: simoleans(7) });
t.is(optionValue.expirationDate, 100);
t.deepEqual(optionValue.timeAuthority, timer);
// Let's imagine that Bob wants to create another coveredCall, but
// this time to trade this invitation for bucks.
const issuerKeywordRecord2 = harden({
UnderlyingAsset: invitationIssuer,
StrikePrice: bucksR.issuer,
});
const {
creatorInvitation: bobInvitationForSecondCoveredCall,
} = await zoe.startInstance(coveredCallInstallation, issuerKeywordRecord2);
// Bob wants to swap an invitation with the same amount as his
// current invitation from Alice. He wants 1 buck in return.
const bobProposalSecondCoveredCall = harden({
give: {
UnderlyingAsset: await invitationIssuer.getAmountOf(bobExclOption),
},
want: { StrikePrice: bucks(1) },
exit: {
afterDeadline: {
deadline: 100, // we will not reach this
timer,
},
},
});
const bobPayments = { UnderlyingAsset: bobExclOption };
// Bob escrows his invitation
// Bob makes an offer to the swap with his "higher order" option
const bobSeat = await zoe.offer(
bobInvitationForSecondCoveredCall,
bobProposalSecondCoveredCall,
bobPayments,
);
const invitationForDaveP = E(bobSeat).getOfferResult();
// Bob passes the higher order invitation and
// optionAmounts to Dave
// Dave is looking to buy the option to trade his 7 simoleans for
// 3 moola, and is willing to pay 1 buck for the option. He
// checks that this invitation matches what he wants
const daveExclOption = await invitationIssuer.claim(invitationForDaveP);
const daveOptionValue = await E(zoe).getInvitationDetails(daveExclOption);
t.is(daveOptionValue.installation, coveredCallInstallation);
t.is(daveOptionValue.description, 'exerciseOption');
t.truthy(
bucksR.amountMath.isEqual(
daveOptionValue.strikePrice.StrikePrice,
bucks(1),
),
);
t.is(daveOptionValue.expirationDate, 100);
t.deepEqual(daveOptionValue.timeAuthority, timer);
// What about the underlying asset (the other option)?
t.is(
daveOptionValue.underlyingAssets.UnderlyingAsset.value[0].description,
'exerciseOption',
);
t.is(
daveOptionValue.underlyingAssets.UnderlyingAsset.value[0].expirationDate,
100,
);
t.truthy(
simoleanR.amountMath.isEqual(
daveOptionValue.underlyingAssets.UnderlyingAsset.value[0].strikePrice
.StrikePrice,
simoleans(7),
),
);
t.deepEqual(
daveOptionValue.underlyingAssets.UnderlyingAsset.value[0].timeAuthority,
timer,
);
// Dave's planned proposal
const daveProposalCoveredCall = harden({
want: daveOptionValue.underlyingAssets,
give: { StrikePrice: bucks(1) },
});
// Dave escrows his 1 buck with Zoe and forms his proposal
const daveSecondCoveredCallPayments = { StrikePrice: daveBucksPayment };
const daveSecondCoveredCallSeat = await zoe.offer(
daveExclOption,
daveProposalCoveredCall,
daveSecondCoveredCallPayments,
);
t.is(
await E(daveSecondCoveredCallSeat).getOfferResult(),
`The option was exercised. Please collect the assets in your payout.`,
`dave second offer accepted`,
);
const firstCoveredCallInvitation = await daveSecondCoveredCallSeat.getPayout(
'UnderlyingAsset',
);
const daveBucksPayout = await daveSecondCoveredCallSeat.getPayout(
'StrikePrice',
);
// Dave exercises his option by making an offer to the covered
// call. First, he escrows with Zoe.
const daveFirstCoveredCallProposal = harden({
want: { UnderlyingAsset: moola(3) },
give: { StrikePrice: simoleans(7) },
});
const daveFirstCoveredCallPayments = harden({
StrikePrice: daveSimoleanPayment,
});
const daveFirstCoveredCallSeat = await zoe.offer(
firstCoveredCallInvitation,
daveFirstCoveredCallProposal,
daveFirstCoveredCallPayments,
);
t.is(
await daveFirstCoveredCallSeat.getOfferResult(),
'The option was exercised. Please collect the assets in your payout.',
`dave first offer accepted`,
);
// Dave should get 3 moola, Bob should get 1 buck, and Alice
// get 7 simoleans
const daveMoolaPayout = await daveFirstCoveredCallSeat.getPayout(
'UnderlyingAsset',
);
const daveSimoleanPayout = await daveFirstCoveredCallSeat.getPayout(
'StrikePrice',
);
const aliceMoolaPayout = await aliceSeat.getPayout('UnderlyingAsset');
const aliceSimoleanPayout = await aliceSeat.getPayout('StrikePrice');
const bobInvitationPayout = await bobSeat.getPayout('UnderlyingAsset');
const bobBucksPayout = await bobSeat.getPayout('StrikePrice');
t.deepEqual(await moolaR.issuer.getAmountOf(daveMoolaPayout), moola(3));
t.deepEqual(
await simoleanR.issuer.getAmountOf(daveSimoleanPayout),
simoleans(0),
);
t.deepEqual(await moolaR.issuer.getAmountOf(aliceMoolaPayout), moola(0));
t.deepEqual(
await simoleanR.issuer.getAmountOf(aliceSimoleanPayout),
simoleans(7),
);
t.deepEqual(
await invitationIssuer.getAmountOf(bobInvitationPayout),
invitationAmountMath.getEmpty(),
);
t.deepEqual(await bucksR.issuer.getAmountOf(bobBucksPayout), bucks(1));
// Alice deposits her payouts
await aliceMoolaPurse.deposit(aliceMoolaPayout);
await aliceSimoleanPurse.deposit(aliceSimoleanPayout);
// Bob deposits his payouts
await bobBucksPurse.deposit(bobBucksPayout);
// Dave deposits his payouts
await daveMoolaPurse.deposit(daveMoolaPayout);
await daveSimoleanPurse.deposit(daveSimoleanPayout);
await daveBucksPurse.deposit(daveBucksPayout);
t.is(aliceMoolaPurse.getCurrentAmount().value, 0);
t.is(aliceSimoleanPurse.getCurrentAmount().value, 7);
t.is(bobMoolaPurse.getCurrentAmount().value, 0);
t.is(bobSimoleanPurse.getCurrentAmount().value, 0);
t.is(bobBucksPurse.getCurrentAmount().value, 1);
t.is(daveMoolaPurse.getCurrentAmount().value, 3);
t.is(daveSimoleanPurse.getCurrentAmount().value, 0);
t.is(daveBucksPurse.getCurrentAmount().value, 0);
});
// Alice uses a covered call to sell a cryptoCat to Bob for the
// 'Glorious shield' she has wanted for a long time.
test('zoe - coveredCall non-fungible', async t => {
t.plan(13);
const {
ccIssuer,
rpgIssuer,
ccMint,
rpgMint,
cryptoCats,
rpgItems,
amountMaths,
createRpgItem,
zoe,
} = setupNonFungible();
// install the contract.
const bundle = await bundleSource(coveredCallRoot);
const coveredCallInstallation = await zoe.install(bundle);
const timer = buildManualTimer(console.log);
// Setup Alice
const growlTiger = harden(['GrowlTiger']);
const growlTigerAmount = cryptoCats(growlTiger);
const aliceCcPayment = ccMint.mintPayment(growlTigerAmount);
const aliceCcPurse = ccIssuer.makeEmptyPurse();
const aliceRpgPurse = rpgIssuer.makeEmptyPurse();
// Setup Bob
const aGloriousShield = createRpgItem(
'Glorious Shield',
25,
'a Glorious Shield, burnished to a blinding brightness',
);
const aGloriousShieldAmount = rpgItems(aGloriousShield);
const bobRpgPayment = rpgMint.mintPayment(aGloriousShieldAmount);
const bobCcPurse = ccIssuer.makeEmptyPurse();
const bobRpgPurse = rpgIssuer.makeEmptyPurse();
// Alice creates a coveredCall instance
const issuerKeywordRecord = harden({
UnderlyingAsset: ccIssuer,
StrikePrice: rpgIssuer,
});
// separate issuerKeywordRecord from contract-specific terms
const { creatorInvitation: aliceInvitation } = await zoe.startInstance(
coveredCallInstallation,
issuerKeywordRecord,
);
// Alice escrows with Zoe
const aliceProposal = harden({
give: { UnderlyingAsset: growlTigerAmount },
want: { StrikePrice: aGloriousShieldAmount },
exit: { afterDeadline: { deadline: 1, timer } },
});
const alicePayments = { UnderlyingAsset: aliceCcPayment };
// Alice creates a call option
const aliceSeat = await zoe.offer(
aliceInvitation,
aliceProposal,
alicePayments,
);
const optionP = E(aliceSeat).getOfferResult();
// Imagine that Alice sends the option to Bob for free (not done here
// since this test doesn't actually have separate vats/parties)
// Bob inspects the option (an invitation payment) and checks that it is the
// contract instance that he expects as well as that Alice has
// already escrowed.
const invitationIssuer = zoe.getInvitationIssuer();
const bobExclOption = await invitationIssuer.claim(optionP);
const optionValue = await E(zoe).getInvitationDetails(bobExclOption);
t.is(optionValue.installation, coveredCallInstallation);
t.is(optionValue.description, 'exerciseOption');
t.truthy(
amountMaths
.get('cc')
.isEqual(optionValue.underlyingAssets.UnderlyingAsset, growlTigerAmount),
);
t.truthy(
amountMaths
.get('rpg')
.isEqual(optionValue.strikePrice.StrikePrice, aGloriousShieldAmount),
);
t.is(optionValue.expirationDate, 1);
t.deepEqual(optionValue.timeAuthority, timer);
const bobPayments = { StrikePrice: bobRpgPayment };
const bobProposal = harden({
want: optionValue.underlyingAssets,
give: optionValue.strikePrice,
exit: { onDemand: null },
});
// Bob redeems his invitation and escrows with Zoe
// Bob exercises the option
const bobSeat = await zoe.offer(bobExclOption, bobProposal, bobPayments);
t.is(
await E(bobSeat).getOfferResult(),
`The option was exercised. Please collect the assets in your payout.`,
);
const bobCcPayout = await E(bobSeat).getPayout('UnderlyingAsset');
const bobRpgPayout = await E(bobSeat).getPayout('StrikePrice');
const aliceCcPayout = await E(aliceSeat).getPayout('UnderlyingAsset');
const aliceRpgPayout = await E(aliceSeat).getPayout('StrikePrice');
// Alice gets what Alice wanted
t.deepEqual(
await rpgIssuer.getAmountOf(aliceRpgPayout),
aliceProposal.want.StrikePrice,
);
// Alice didn't get any of what Alice put in
t.deepEqual(
await ccIssuer.getAmountOf(aliceCcPayout),
cryptoCats(harden([])),
);
// Alice deposits her payout to ensure she can
await aliceCcPurse.deposit(aliceCcPayout);
await aliceRpgPurse.deposit(aliceRpgPayout);
// Bob deposits his original payments to ensure he can
await bobCcPurse.deposit(bobCcPayout);
await bobRpgPurse.deposit(bobRpgPayout);
// Assert that the correct payouts were received.
// Alice had growlTiger and no RPG tokens.
// Bob had an empty CryptoCat purse and the Glorious Shield.
t.deepEqual(aliceCcPurse.getCurrentAmount().value, []);
t.deepEqual(aliceRpgPurse.getCurrentAmount().value, aGloriousShield);
t.deepEqual(bobCcPurse.getCurrentAmount().value, ['GrowlTiger']);
t.deepEqual(bobRpgPurse.getCurrentAmount().value, []);
});