Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Auction Contract to Offer Up Dapp #85

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions contract/,tx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"height":"1085","txhash":"97AD126A4FBF7A4522275A12FD21EEF14FE2A502D0A2E489785CEAA56CA1F9A5","codespace":"","code":0,"data":"12260A242F636F736D6F732E62616E6B2E763162657461312E4D736753656E64526573706F6E7365","raw_log":"[{\"msg_index\":0,\"events\":[{\"type\":\"coin_received\",\"attributes\":[{\"key\":\"receiver\",\"value\":\"agoric1rwwley550k9mmk6uq6mm6z4udrg8kyuyvfszjk\"},{\"key\":\"amount\",\"value\":\"321000000ubld\"}]},{\"type\":\"coin_spent\",\"attributes\":[{\"key\":\"spender\",\"value\":\"agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl\"},{\"key\":\"amount\",\"value\":\"321000000ubld\"}]},{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"/cosmos.bank.v1beta1.MsgSend\"},{\"key\":\"sender\",\"value\":\"agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"agoric1rwwley550k9mmk6uq6mm6z4udrg8kyuyvfszjk\"},{\"key\":\"sender\",\"value\":\"agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl\"},{\"key\":\"amount\",\"value\":\"321000000ubld\"}]}]}]","logs":[{"msg_index":0,"log":"","events":[{"type":"coin_received","attributes":[{"key":"receiver","value":"agoric1rwwley550k9mmk6uq6mm6z4udrg8kyuyvfszjk"},{"key":"amount","value":"321000000ubld"}]},{"type":"coin_spent","attributes":[{"key":"spender","value":"agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl"},{"key":"amount","value":"321000000ubld"}]},{"type":"message","attributes":[{"key":"action","value":"/cosmos.bank.v1beta1.MsgSend"},{"key":"sender","value":"agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl"},{"key":"module","value":"bank"}]},{"type":"transfer","attributes":[{"key":"recipient","value":"agoric1rwwley550k9mmk6uq6mm6z4udrg8kyuyvfszjk"},{"key":"sender","value":"agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl"},{"key":"amount","value":"321000000ubld"}]}]}],"info":"","gas_wanted":"82400","gas_used":"65647","tx":null,"timestamp":"","events":[{"type":"tx","attributes":[{"key":"ZmVl","value":"","index":true},{"key":"ZmVlX3BheWVy","value":"YWdvcmljMWVzdHNld3Q2anFzeDc3cHdjeGtuNWFoMGpxZ3U4cmhnZmx3ZmRs","index":true}]},{"type":"tx","attributes":[{"key":"YWNjX3NlcQ==","value":"YWdvcmljMWVzdHNld3Q2anFzeDc3cHdjeGtuNWFoMGpxZ3U4cmhnZmx3ZmRsLzYw","index":true}]},{"type":"tx","attributes":[{"key":"c2lnbmF0dXJl","value":"MW1MQS9GRTlsM09EMW9yUkN3LzdBbFdWTGNKRnorMjRWcldGenpkTUEvOUhrcEhYU1N6Q3JpQmtwazlDakFCd0pXVElGajBMTEtJUTFncUl1cU5kRVE9PQ==","index":true}]},{"type":"message","attributes":[{"key":"YWN0aW9u","value":"L2Nvc21vcy5iYW5rLnYxYmV0YTEuTXNnU2VuZA==","index":true}]},{"type":"coin_spent","attributes":[{"key":"c3BlbmRlcg==","value":"YWdvcmljMWVzdHNld3Q2anFzeDc3cHdjeGtuNWFoMGpxZ3U4cmhnZmx3ZmRs","index":true},{"key":"YW1vdW50","value":"MzIxMDAwMDAwdWJsZA==","index":true}]},{"type":"coin_received","attributes":[{"key":"cmVjZWl2ZXI=","value":"YWdvcmljMXJ3d2xleTU1MGs5bW1rNnVxNm1tNno0dWRyZzhreXV5dmZzempr","index":true},{"key":"YW1vdW50","value":"MzIxMDAwMDAwdWJsZA==","index":true}]},{"type":"transfer","attributes":[{"key":"cmVjaXBpZW50","value":"YWdvcmljMXJ3d2xleTU1MGs5bW1rNnVxNm1tNno0dWRyZzhreXV5dmZzempr","index":true},{"key":"c2VuZGVy","value":"YWdvcmljMWVzdHNld3Q2anFzeDc3cHdjeGtuNWFoMGpxZ3U4cmhnZmx3ZmRs","index":true},{"key":"YW1vdW50","value":"MzIxMDAwMDAwdWJsZA==","index":true}]},{"type":"message","attributes":[{"key":"c2VuZGVy","value":"YWdvcmljMWVzdHNld3Q2anFzeDc3cHdjeGtuNWFoMGpxZ3U4cmhnZmx3ZmRs","index":true}]},{"type":"message","attributes":[{"key":"bW9kdWxl","value":"YmFuaw==","index":true}]}]}
2 changes: 1 addition & 1 deletion contract/src/offer-up-proposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const startOfferUpContract = async permittedPowers => {
const istIssuer = await istIssuerP;
const istBrand = await istBrandP;

const terms = { tradePrice: AmountMath.make(istBrand, 25n * CENT) };
const terms = { minBidPrice: AmountMath.make(istBrand, 25n * CENT), maxBids: 3n };

// agoricNames gets updated each time; the promise space only once XXXXXXX
const installation = await offerUpInstallationP;
Expand Down
150 changes: 82 additions & 68 deletions contract/src/offer-up.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* to make an invitation.
* 3. client makes an offer using the invitation, along with
* a proposal (with give and want) and payments. Zoe escrows the payments, and then
* 4. Zoe invokes the offer handler specified in step 2 -- here {@link tradeHandler}.
* 4. Zoe invokes the offer handler specified in step 2 -- here {@link bidHandler}.
*
* @see {@link https://docs.agoric.com/guides/zoe/|Zoe Overview} for a walk-thru of this contract
* @see {@link https://docs.agoric.com/guides/js-programming/hardened-js.html|Hardened JavaScript}
Expand All @@ -20,45 +20,36 @@
// @ts-check

import { Far } from '@endo/far';
import { M, getCopyBagEntries } from '@endo/patterns';

Check failure on line 23 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

'getCopyBagEntries' is defined but never used. Allowed unused vars must match /^_/u
import { AssetKind } from '@agoric/ertp/src/amountMath.js';
import { AssetKind, AmountMath } from '@agoric/ertp/src/amountMath.js';
import { AmountShape } from '@agoric/ertp/src/typeGuards.js';
import { atomicRearrange } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
import '@agoric/zoe/exported.js';

const { Fail, quote: q } = assert;
// Added for debugging/console.log
const bigintReplacer = (_, value) =>
typeof value === 'bigint' ? value.toString() : value;

// #region bag utilities
/** @type { (xs: bigint[]) => bigint } */
const sum = xs => xs.reduce((acc, x) => acc + x, 0n);

/**
* @param {import('@endo/patterns').CopyBag} bag
* @returns {bigint[]}
*/
const bagCounts = bag => {
const entries = getCopyBagEntries(bag);
return entries.map(([_k, ct]) => ct);
};
// #endregion

/**
* In addition to the standard `issuers` and `brands` terms,
* this contract is parameterized by terms for price and,
* optionally, a maximum number of items sold for that price (default: 3).
* this contract is parameterized by terms for bid value and,
* optionally, a maximum number of bids before closing an auction (default: 3).
*
* @typedef {{
* tradePrice: Amount;
* maxItems?: bigint;
* }} OfferUpTerms
* minBidPrice: Amount;
* maxBids?: bigint;
* }} AuctionTerms
*/

export const meta = {
customTermsShape: M.splitRecord(
{ tradePrice: AmountShape },
{ minBidPrice: AmountShape },
{ maxItems: M.bigint() },
),
};

// compatibility with an earlier contract metadata API
export const customTermsShape = meta.customTermsShape;

Expand All @@ -67,70 +58,93 @@
* - creates a new non-fungible asset type for Items, and
* - handles offers to buy up to `maxItems` items at a time.
*
* @param {ZCF<OfferUpTerms>} zcf
* @param {ZCF<AuctionTerms>} zcf
*/
export const start = async zcf => {
const { tradePrice, maxItems = 3n } = zcf.getTerms();

/**
* a new ERTP mint for items, accessed thru the Zoe Contract Facet.
* Note: `makeZCFMint` makes the associated brand and issuer available
* in the contract's terms.
*
* AssetKind.COPY_BAG can express non-fungible (or rather: semi-fungible)
* amounts such as: 3 potions and 1 map.
*/
const { minBidPrice, maxBids = 3n } = zcf.getTerms();

const itemMint = await zcf.makeZCFMint('Item', AssetKind.COPY_BAG);
const { brand: itemBrand } = itemMint.getIssuerRecord();

/**
* a pattern to constrain proposals given to {@link tradeHandler}
*
* The `Price` amount must be >= `tradePrice` term.
* The `Items` amount must use the `Item` brand and a bag value.
*/
const proposalShape = harden({
give: { Price: M.gte(tradePrice) },
give: { Price: M.gte(minBidPrice) },
want: { Items: { brand: itemBrand, value: M.bag() } },
exit: M.any(),
});

/** a seat for allocating proceeds of sales */
/** a seat for allocating proceeds of auction sales */
const proceeds = zcf.makeEmptySeatKit().zcfSeat;
// A register to keep all the bids of all items
let bidsRegister = new Map();

Check failure on line 78 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

'bidsRegister' is never reassigned. Use 'const' instead

/** @type {OfferHandler} */
const tradeHandler = buyerSeat => {
const bidHandler = bidderSeat => {
// give and want are guaranteed by Zoe to match proposalShape
const { want } = buyerSeat.getProposal();

sum(bagCounts(want.Items.value)) <= maxItems ||
Fail`max ${q(maxItems)} items allowed: ${q(want.Items)}`;

const newItems = itemMint.mintGains(want);
atomicRearrange(
zcf,
harden([
// price from buyer to proceeds
[buyerSeat, proceeds, { Price: tradePrice }],
// new items to buyer
[newItems, buyerSeat, want],
]),
);

buyerSeat.exit(true);
newItems.exit();
return 'trade complete';
const { want } = bidderSeat.getProposal();
const bidItem = JSON.stringify(want.Items.value, bigintReplacer);
console.log(' bidItem is : ', bidItem);

/**
From the bidsRegister Map object, get the array of bids for the given item if it exists otherwise create one.

Check warning on line 88 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

Expected JSDoc block to be aligned
and then place the current bid in that array.
then count the number of bids in that array and check whether it is equal to the maxBids.
If the number of bids are equal to max bid, we want to find out value of the max bid in the array.
*/

// Check if the item already has a bid array in the Map; if not, create one
if (!bidsRegister.has(bidItem)) {
bidsRegister.set(bidItem, []);
}

// Get the existing bid array for the item
let bids = bidsRegister.get(bidItem);

Check failure on line 100 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

'bids' is never reassigned. Use 'const' instead

// Add the current bid to the bid array
bids.push(bidderSeat);

// Check if the number of bids is equal to maxBids
if (bids.length == maxBids) {

Check failure on line 106 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

Expected '===' and instead saw '=='
console.log('Maximum bids reached for this item.');

// Find the maximum bid in the array
// Initialize the maximum bid as the first bid in the array
let maxBid = bids[0];
// Loop through the bids array to find the maximum bid
for (let i = 1; i < bids.length; i++) {

Check failure on line 113 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

Unary operator '++' used
if (
AmountMath.isGTE(
bids[i].getCurrentAllocation().Price,
maxBid.getCurrentAllocation().Price,
)
) {
maxBid = bids[i];
}
}

const newItems = itemMint.mintGains(want);
atomicRearrange(
zcf,
harden([
[newItems, maxBid, want],
[maxBid, proceeds, { Price: maxBid.getCurrentAllocation().Price }],
]),
);
newItems.exit();
maxBid.exit(true);

bids.forEach(async bidderSeatItem => {

Check warning on line 135 in contract/src/offer-up.contract.js

View workflow job for this annotation

GitHub Actions / unit

Prefer for...of instead of Array.forEach
console.log(JSON.stringify(bidderSeatItem));
if (!bidderSeatItem.hasExited()) {
bidderSeatItem.exit(true);
}
});
bidsRegister.set(bidItem, []);
}
return 'bid placed.';
};

/**
* Make an invitation to trade for items.
*
* Proposal Keywords used in offers using these invitations:
* - give: `Price`
* - want: `Items`
*/
const makeTradeInvitation = () =>
zcf.makeInvitation(tradeHandler, 'buy items', undefined, proposalShape);
zcf.makeInvitation(bidHandler, 'bid on items', undefined, proposalShape);

// Mark the publicFacet Far, i.e. reachable from outside the contract
const publicFacet = Far('Items Public Facet', {
Expand Down
Loading
Loading