Skip to content

Commit

Permalink
refactor: IssuerTable based on brands rather than Keywords
Browse files Browse the repository at this point in the history
Extracted from Zoe facet parameters (in progress)
  • Loading branch information
Chris-Hibbert committed Jun 9, 2020
1 parent 9cb3050 commit 50af071
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 65 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,8 @@
"lint-check": "yarn workspaces run lint-check",
"test": "yarn workspaces run test",
"build": "yarn workspaces run build"
},
"dependencies": {
"@agoric/store": "^0.1.2"
}
}
101 changes: 57 additions & 44 deletions packages/zoe/src/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,66 +96,79 @@ const makeOfferTable = () => {
const makePayoutMap = makeStore;

// Issuer Table
// Columns: issuer | brand | purse | amountMath
// Columns: brand | issuer | purse | amountMath
//
// The IssuerTable is keyed by brand, but the Issuer is required in order for
// getPromiseForIssuerRecord() to initialize the records. When
// getPromiseForIssuerRecord is called and the record doesn't exist, it stores a
// promise for the record in issuersInProgress, and then builds the record. It
// updates the tables when done.
const makeIssuerTable = () => {
// TODO: make sure this validate function protects against malicious
// misshapen objects rather than just a general check.
// misshapen objects rather than just a general check.
const validateSomewhat = makeValidateProperties(
harden(['issuer', 'brand', 'purse', 'amountMath']),
harden(['brand', 'issuer', 'purse', 'amountMath']),
);

const makeCustomMethods = table => {
const issuersInProgress = makeStore();
const issuerToBrand = makeStore();

// We can't be sure we can build the table entry soon enough that the first
// caller will get the actual data, so we start by saving a promise in the
// inProgress table, and once we have the Issuer, build the record, fill in
// the table, and resolve the promise.
function buildTableEntryAndPlaceHolder(issuer) {
// remote calls which immediately return a promise
const mathHelpersNameP = E(issuer).getMathHelpersName();
const brandP = E(issuer).getBrand();
const purseP = E(issuer).makeEmptyPurse();

// a promise for a synchronously accessible record
const synchronousRecordP = Promise.all([
brandP,
mathHelpersNameP,
purseP,
]).then(([brand, mathHelpersName, purse]) => {
const amountMath = makeAmountMath(brand, mathHelpersName);
const issuerRecord = {
brand,
issuer,
purse,
amountMath,
};
table.create(issuerRecord, brand);
issuerToBrand.init(issuer, brand);
issuersInProgress.delete(issuer);
return table.get(brand);
});
issuersInProgress.init(issuer, synchronousRecordP);
return synchronousRecordP;
}

const customMethods = harden({
getPurseKeywordRecord: issuerKeywordRecord => {
const purseKeywordRecord = {};
Object.keys(issuerKeywordRecord).forEach(keyword => {
purseKeywordRecord[keyword] = table.get(
issuerKeywordRecord[keyword],
).purse;
});
return harden(purseKeywordRecord);
},

// `issuerP` may be a promise, presence, or local object
// `issuerP` may be a promise, presence, or local object. If there's
// already a record, or already a promise for a record, return it.
// Otherwise wrap a promise around building the record so we can return
// the promise until we build the record.
getPromiseForIssuerRecord: issuerP => {
return Promise.resolve(issuerP).then(issuer => {
if (!table.has(issuer)) {
if (issuersInProgress.has(issuer)) {
// a promise which resolves to the issuer record
return issuersInProgress.get(issuer);
}
// remote calls which immediately return a promise
const mathHelpersNameP = E(issuer).getMathHelpersName();
const brandP = E(issuer).getBrand();
const purseP = E(issuer).makeEmptyPurse();

// a promise for a synchronously accessible record
const synchronousRecordP = Promise.all([
brandP,
mathHelpersNameP,
purseP,
]).then(([brand, mathHelpersName, purse]) => {
const amountMath = makeAmountMath(brand, mathHelpersName);
const issuerRecord = {
issuer,
brand,
purse,
amountMath,
};
table.create(issuerRecord, issuer);
issuersInProgress.delete(issuer);
return table.get(issuer);
});
issuersInProgress.init(issuer, synchronousRecordP);
return synchronousRecordP;
if (issuerToBrand.has(issuer)) {
// we always initialize table and issuerToBrand together
return table.get(issuerToBrand.get(issuer));
// eslint-disable-next-line no-else-return
} else if (issuersInProgress.has(issuer)) {
// a promise which resolves to the issuer record
return issuersInProgress.get(issuer);
// eslint-disable-next-line no-else-return
} else {
return buildTableEntryAndPlaceHolder(issuer);
}
return table.get(issuer);
});
},
getPromiseForIssuerRecords: issuerPs =>
Promise.all(issuerPs.map(customMethods.getPromiseForIssuerRecord)),
brandFromIssuer: issuer => issuerToBrand.get(issuer),
});
return customMethods;
};
Expand Down
41 changes: 21 additions & 20 deletions packages/zoe/src/zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,8 @@ const makeZoe = (additionalEndowments = {}) => {
issuerTable,
} = makeTables();

const getAmountMathForBrand = brand => issuerTable.get(brand).amountMath;

/**
* @param {InstanceHandle} instanceHandle
* @param {OfferHandle[]} offerHandles
Expand All @@ -347,36 +349,33 @@ const makeZoe = (additionalEndowments = {}) => {
}
const offerRecords = offerTable.getOffers(offerHandles);

const { issuerKeywordRecord } = instanceTable.get(instanceHandle);

// Remove the offers from the offerTable so that they are no
// longer active.
offerTable.deleteOffers(offerHandles);

// Resolve the payout promises with promises for the payouts
const pursePKeywordRecord = issuerTable.getPurseKeywordRecord(
issuerKeywordRecord,
);
for (const offerRecord of offerRecords) {
const payout = {};
Object.keys(offerRecord.currentAllocation).forEach(keyword => {
payout[keyword] = E(pursePKeywordRecord[keyword]).withdraw(
offerRecord.currentAllocation[keyword],
);
const payoutAmount = offerRecord.currentAllocation[keyword];
const { purse } = issuerTable.get(payoutAmount.brand);
payout[keyword] = E(purse).withdraw(payoutAmount);
});
harden(payout);
payoutMap.get(offerRecord.handle).resolve(payout);
}
};

// presumes global keywords
const getAmountMaths = (instanceHandle, sparseKeywords) => {
const amountMathKeywordRecord = /** @type {Object.<string,AmountMath>} */ ({});
const { issuerKeywordRecord } = instanceTable.get(instanceHandle);
// this method presumes that issuers have all been retrieved by this point
sparseKeywords.forEach(keyword => {
const issuer = issuerKeywordRecord[keyword];
amountMathKeywordRecord[keyword] = issuerTable.get(issuer).amountMath;
const brand = issuerTable.brandFromIssuer(issuerKeywordRecord[keyword]);
amountMathKeywordRecord[keyword] = issuerTable.get(brand).amountMath;
});
return harden(amountMathKeywordRecord);
return amountMathKeywordRecord;
};

const removePurse = issuerRecord =>
Expand Down Expand Up @@ -517,7 +516,7 @@ const makeZoe = (additionalEndowments = {}) => {
makePotentialReallocation(offerHandle, newAllocations[i]),
);

// 3) save the reallocation
// 3. Save the reallocations.
offerTable.updateAmounts(offerHandles, reallocations);
},

Expand Down Expand Up @@ -616,7 +615,9 @@ const makeZoe = (additionalEndowments = {}) => {
);
},
getInstanceRecord: () => instanceTable.get(instanceHandle),
getIssuerRecord: issuer => removePurse(issuerTable.get(issuer)),
getAmountMathForBrand,
getIssuerRecord: issuer =>
removePurse(issuerTable.get(issuerTable.brandFromIssuer(issuer))),
});
return contractFacet;
};
Expand Down Expand Up @@ -783,16 +784,16 @@ const makeZoe = (additionalEndowments = {}) => {
getKeywords(issuerKeywordRecord),
);

proposal = cleanProposal(
const cleanedProposal = cleanProposal(
issuerKeywordRecord,
amountMathKeywordRecord,
proposal,
);

// Promise flow:
// issuer -> purse -> deposit payment -> offerHook -> payout
const giveKeywords = Object.getOwnPropertyNames(proposal.give);
const wantKeywords = Object.getOwnPropertyNames(proposal.want);
const giveKeywords = Object.getOwnPropertyNames(cleanedProposal.give);
const wantKeywords = Object.getOwnPropertyNames(cleanedProposal.want);
const userKeywords = harden([...giveKeywords, ...wantKeywords]);
const paymentDepositedPs = userKeywords.map(keyword => {
const issuer = issuerKeywordRecord[keyword];
Expand All @@ -805,9 +806,9 @@ const makeZoe = (additionalEndowments = {}) => {
return E(purse)
.deposit(
paymentKeywordRecord[keyword],
proposal.give[keyword],
cleanedProposal.give[keyword],
)
.then(_ => proposal.give[keyword]);
.then(_ => cleanedProposal.give[keyword]);
}
// If any other payments are included, they are ignored.
return Promise.resolve(
Expand All @@ -820,7 +821,7 @@ const makeZoe = (additionalEndowments = {}) => {
const notifierRec = produceNotifier();
const offerImmutableRecord = {
instanceHandle,
proposal,
proposal: cleanedProposal,
currentAllocation: arrayToObj(amountsArray, userKeywords),
notifier: notifierRec.notifier,
updater: notifierRec.updater,
Expand All @@ -841,7 +842,7 @@ const makeZoe = (additionalEndowments = {}) => {
payout: payoutMap.get(offerHandle).promise,
outcome: outcomeP,
};
const { exit } = proposal;
const { exit } = cleanedProposal;
const [exitKind] = Object.getOwnPropertyNames(exit);
// Automatically cancel on deadline.
if (exitKind === 'afterDeadline') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ test('ZoeHelpers canTradeWith', t => {

test('ZoeHelpers swap ok', t => {
t.plan(4);
const { moolaR, simoleanR, moola, simoleans } = setup();
const { moolaR, simoleanR, moola, simoleans, amountMaths } = setup();
const leftOfferHandle = harden({});
const rightOfferHandle = harden({});
const cantTradeRightOfferHandle = harden({});
Expand All @@ -465,6 +465,7 @@ test('ZoeHelpers swap ok', t => {
},
keywords: ['Asset', 'Price'],
}),
getAmountMathForBrand: brand => amountMaths.get(brand.getAllegedName()),
getAmountMaths: () =>
harden({ Asset: moolaR.amountMath, Price: simoleanR.amountMath }),
getZoeService: () => {},
Expand Down

0 comments on commit 50af071

Please sign in to comment.