From f068996f086faa4f17bf461c06c1a37752e42806 Mon Sep 17 00:00:00 2001 From: Luqi Pan Date: Mon, 5 Feb 2024 16:39:33 -0800 Subject: [PATCH] feat: add 3 exercises for agoric-exercises --- contract/src/agoric-basics-contract.js | 57 +++++++++- contract/test/test-agoric-basics-contract.js | 104 +++++++++++++++++-- contract/test/test-bundle-source.js | 28 ----- 3 files changed, 149 insertions(+), 40 deletions(-) delete mode 100644 contract/test/test-bundle-source.js diff --git a/contract/src/agoric-basics-contract.js b/contract/src/agoric-basics-contract.js index dba0c83b..0632a769 100644 --- a/contract/src/agoric-basics-contract.js +++ b/contract/src/agoric-basics-contract.js @@ -1,10 +1,59 @@ // @ts-check +import { AmountMath, AmountShape, AssetKind } from '@agoric/ertp'; import { Far } from '@endo/far'; +import '@agoric/zoe/exported.js'; +import { M } from '@endo/patterns'; +import { atomicRearrange } from '@agoric/zoe/src/contractSupport/atomicTransfer.js'; -const greet = who => `Hello, ${who}!`; +const { Fail, quote: q } = assert; -export const start = () => { - return { - publicFacet: Far('Hello', { greet }), +/** + * @param {ZCF<{joinPrice: Amount}>} zcf + */ +export const start = async zcf => { + const gameSeat = zcf.makeEmptySeatKit().zcfSeat; + // TODO: convert to COPY_BAG + const ticketMint = await zcf.makeZCFMint('Ticket', AssetKind.NAT); + + const joinShape = harden({ + give: { Price: AmountShape }, + want: { Tickets: AmountShape }, + exit: M.any(), + }); + + /** @param {ZCFSeat} playerSeat */ + const joinHandler = playerSeat => { + const { give, want } = playerSeat.getProposal(); + + // TODO: update the checks here once AssetKind is converted to COPY_BAG + AmountMath.isGTE( + AmountMath.make(ticketMint.getIssuerRecord().brand, 1n), + want.Tickets, + ) || Fail`${q(want.Tickets)} exceeds 1n ticket`; + + // mintGains mints all amounts in `want` + // For more, see reference: + // https://docs.agoric.com/reference/zoe-api/zcfmint.html#azcfmint-mintgains-gains-zcfseat + const tmp = ticketMint.mintGains(want); + // atomicRearrange rearrange the assets between seats + atomicRearrange( + zcf, + harden([ + [playerSeat, gameSeat, give], + // TODO: add another TransferPart below to pass the exercise 3 test + [tmp, playerSeat, want], + ]), + ); + + playerSeat.exit(true); + return 'welcome to the game'; }; + + const publicFacet = Far('API', { + makeJoinInvitation: () => + zcf.makeInvitation(joinHandler, 'join', undefined, joinShape), + }); + + return { publicFacet }; }; +harden(start); diff --git a/contract/test/test-agoric-basics-contract.js b/contract/test/test-agoric-basics-contract.js index bdbd6470..7e03d5a9 100644 --- a/contract/test/test-agoric-basics-contract.js +++ b/contract/test/test-agoric-basics-contract.js @@ -1,13 +1,101 @@ +/** + * @file Tests for agoric-basics-contract.js + */ + // @ts-check -import '@endo/init'; -import { E } from '@endo/far'; + // eslint-disable-next-line import/no-unresolved -- https://github.com/avajs/ava/issues/2951 -import test from 'ava'; +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import bundleSource from '@endo/bundle-source'; +import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { E, passStyleOf } from '@endo/far'; +import { createRequire } from 'module'; +import { AmountMath, makeIssuerKit } from '@agoric/ertp'; + +test('exercise 1: bundleSource bundles the contract for use with zoe', async t => { + const myRequire = createRequire(import.meta.url); + const contractPath = myRequire.resolve(`../src/agoric-basics-contract.js`); + + // bundleSource() bundles contract and all of its modules into a single artifact + // and is the first thing you need to do in order to deploy a contract + // For more, see reference: https://docs.agoric.com/guides/zoe/#bundling-a-contract + + // TODO: initialize the const `bundle` using bundleSource() to pass the test below + // Tips 1: you can import bundleSource() from @endo/bundle-source + // Tips 2: don't forget to use `await` to get the fulfilled value + const bundle = await bundleSource(contractPath); + + t.is(bundle.moduleFormat, 'endoZipBase64'); + t.true(bundle.endoZipBase64.length > 10_000); +}); + +test('exercise 2: start zoe instance', async t => { + const myRequire = createRequire(import.meta.url); + const contractPath = myRequire.resolve(`../src/agoric-basics-contract.js`); + const bundle = await bundleSource(contractPath); + const { zoeService: zoe } = makeZoeKitForTest(); + const installation = await E(zoe).install(bundle); + t.is(passStyleOf(installation), 'remotable'); + + const playMoney = makeIssuerKit('PlayMoney'); + const playMoneyIssuer = { Price: playMoney.issuer }; + const joinPrice = AmountMath.make(playMoney.brand, 5n); + + // startInstance creates an instance of the installed smart contract, with terms speicific to the instance + // similar to how a JavaScript object is an instance of its class + const { instance } = await E(zoe).startInstance( + installation, + playMoneyIssuer, + { + // TODO: initialize the instance with the expected joinPrice to pass the test below + joinPrice: joinPrice, + }, + ); + const terms = await E(zoe).getTerms(instance); + + t.is(terms.joinPrice, joinPrice); +}); + +test('exercise 3: atomicRearrange', async t => { + const myRequire = createRequire(import.meta.url); + const contractPath = myRequire.resolve(`../src/agoric-basics-contract.js`); + const bundle = await bundleSource(contractPath); + const { zoeService: zoe } = makeZoeKitForTest(); + const installation = await E(zoe).install(bundle); + + const playMoney = makeIssuerKit('PlayMoney'); + const playMoneyIssuer = { Price: playMoney.issuer }; + + const { instance } = await E(zoe).startInstance( + installation, + playMoneyIssuer, + { + joinPrice: AmountMath.make(playMoney.brand, 5n), + }, + ); + const publicFacet = await E(zoe).getPublicFacet(instance); + const terms = await E(zoe).getTerms(instance); + const alicePurse = playMoney.issuer.makeEmptyPurse(); + const amountOfMoney = AmountMath.make(playMoney.brand, 5n); + const moneyPayment = playMoney.mint.mintPayment(amountOfMoney); + alicePurse.deposit(moneyPayment); + + const { issuers, brands, joinPrice } = terms; + const proposal = { + give: { Price: joinPrice }, + want: { Tickets: AmountMath.make(brands.Ticket, 1n) }, + }; + + const Price = await E(alicePurse).withdraw(joinPrice); -import { start } from '../src/agoric-basics-contract.js'; + const toJoin = E(publicFacet).makeJoinInvitation(); -test('contract greets by name', async t => { - const { publicFacet } = start(); - const actual = await E(publicFacet).greet('Bob'); - t.is(actual, 'Hello, Bob!'); + const seat = E(zoe).offer(toJoin, proposal, { Price }); + const tickets = await E(seat).getPayout('Tickets'); + const actual = await E(issuers.Ticket).getAmountOf(tickets); + // How do we complete the trade, i.e deduct the amount from alice's purse and mint her a ticket? + // Hint: use atomicRearrange() + // TODO: update atomicRearrange in agoric-basics-contract.js + t.deepEqual(actual, proposal.want.Tickets); }); diff --git a/contract/test/test-bundle-source.js b/contract/test/test-bundle-source.js deleted file mode 100644 index f5f60f4d..00000000 --- a/contract/test/test-bundle-source.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @file Test using bundleSource() on the contract. - */ - -// @ts-check - -// eslint-disable-next-line import/no-unresolved -- https://github.com/avajs/ava/issues/2951 -import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; - -import bundleSource from '@endo/bundle-source'; -import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; -import { E, passStyleOf } from '@endo/far'; -import { createRequire } from 'module'; - -const myRequire = createRequire(import.meta.url); -const contractPath = myRequire.resolve(`../src/agoric-basics-contract.js`); - -test('bundleSource() bundles the contract for use with zoe', async t => { - const bundle = await bundleSource(contractPath); - t.is(bundle.moduleFormat, 'endoZipBase64'); - t.log(bundle.endoZipBase64Sha512); - t.true(bundle.endoZipBase64.length > 10_000); - - const { zoeService: zoe } = makeZoeKitForTest(); - const installation = await E(zoe).install(bundle); - t.log(installation); - t.is(passStyleOf(installation), 'remotable'); -});