Skip to content

Commit

Permalink
fix: finishing touches to auctions contract
Browse files Browse the repository at this point in the history
  • Loading branch information
amessbee committed May 31, 2024
1 parent eb7d8d8 commit 5d8faac
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 203 deletions.
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
199 changes: 70 additions & 129 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 @@ -23,76 +23,33 @@ 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, AmountMath } from '@agoric/ertp/src/amountMath.js';
import { AmountShape } from '@agoric/ertp/src/typeGuards.js';
import {
atomicRearrange,
atomicTransfer,
} from '@agoric/zoe/src/contractSupport/atomicTransfer.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 = (key, value) =>
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() },
),
};

const isObject = object => {
return object != null && typeof object === 'object';
};

const isDeepEqual = (object1, object2) => {
const objKeys1 = Object.keys(object1);
const objKeys2 = Object.keys(object2);

if (objKeys1.length !== objKeys2.length) return false;

for (var key of objKeys1) {
const value1 = object1[key];
const value2 = object2[key];

const isObjects = isObject(value1) && isObject(value2);

if (
(isObjects && !isDeepEqual(value1, value2)) ||
(!isObjects && value1 !== value2)
) {
return false;
}
}
return true;
};

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

Expand All @@ -101,109 +58,93 @@ export const customTermsShape = meta.customTermsShape;
* - 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 = 1n } = 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() } },
// want: {},
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
//TODO: Instead use the size of bids array
let currentNumOfBids = 0;
const maxBids = 3;

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

// const { offerArgs } = buyerSeat.getOfferArgs();

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

++currentNumOfBids;
bidsRegister.set(currentNumOfBids.toString(), {
buyerSeat,
bidValue: give.Price,
});

console.log(
`New offer received: ${JSON.stringify(
give.Price,
bigintReplacer,
)} ${JSON.stringify(bidsRegister, bigintReplacer)}`,
);
console.log("\n\n\n The number of bids currently available is :", bidsRegister.size);

if (currentNumOfBids == maxBids) {

currentNumOfBids = 0;
// Find maximum bids - if there are multiple bids with maximum value, the first one is picked.
let maxBidValue = harden({ ...give.Price });
// let maxBidValue = give.Price;
let buyerSeat;
bidsRegister.forEach(value => {
// We need to use Amount.isGTE instead
if (AmountMath.isGTE(value.bidValue, maxBidValue)) {
maxBidValue = value.bidValue;
buyerSeat = value.buyerSeat;
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, buyerSeat, want],
[buyerSeat, proceeds, {Price: maxBidValue}]
]));
atomicRearrange(
zcf,
harden([
[newItems, maxBid, want],
[maxBid, proceeds, { Price: maxBid.getCurrentAllocation().Price }],
]),
);
newItems.exit();
buyerSeat.exit(true);
maxBid.exit(true);

bidsRegister.forEach(async value => {
if ( !value.buyerSeat.hasExited() ) {
value.buyerSeat.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.clear();
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

0 comments on commit 5d8faac

Please sign in to comment.