Skip to content

Commit

Permalink
OfferTo as a Zoe Helper - ready for design review (Agoric#2016)
Browse files Browse the repository at this point in the history
* chore: add `offerTo` as a Zoe Helper

* chore: address PR comments
  • Loading branch information
katelynsills authored Dec 7, 2020
1 parent e974022 commit f9441f4
Show file tree
Hide file tree
Showing 7 changed files with 429 additions and 68 deletions.
1 change: 1 addition & 0 deletions packages/zoe/src/contractSupport/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ export {
depositToSeat,
withdrawFromSeat,
saveAllIssuers,
offerTo,
} from './zoeHelpers';
71 changes: 71 additions & 0 deletions packages/zoe/src/contractSupport/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,74 @@
* @param {string} [rightHasExitedMsg]
* @returns {string}
*/

/**
* @typedef {Object} OfferToReturns
*
* The return value of offerTo is a promise for the userSeat for the
* offer to the other contract, and a promise (`deposited`) which
* resolves when the payout for the offer has been deposited to the `toSeat`
* @property {Promise<UserSeat>} userSeatPromise
* @property {Promise<AmountKeywordRecord>} deposited
*/

/**
* @typedef {Record<Keyword,Keyword>} KeywordKeywordRecord
*
* A mapping of keywords to keywords.
*/

/**
* @callback OfferTo
*
* Make an offer to another contract instance (labeled contractB below),
* withdrawing the payments for the offer from a seat in the current
* contract instance (contractA) and depositing the payouts in another
* seat in the current contract instance (contractA).
*
* @param {ContractFacet} zcf
* Zoe Contract Facet for contractA
*
* @param {ERef<Invitation>} invitation
* Invitation to contractB
*
* @param {KeywordKeywordRecord=} keywordMapping
* Mapping of keywords used in contractA to keywords to be used in
* contractB. Note that the pathway to deposit the payout back to
* contractA reverses this mapping.
*
* @param {Proposal} proposal
* The proposal for the offer to be made to contractB
*
* @param {ZCFSeat} fromSeat
* The seat in contractA to take the offer payments from.
*
* @param {ZCFSeat=} toSeat
* The seat in contractA to deposit the payout of the offer to.
* If `toSeat` is not provided, this defaults to the `fromSeat`.
*
* @returns {OfferToReturns}
*/

/**
* @callback Reverse
*
* Given a mapping of keywords to keywords, invert the keys and
* values. This is used to map the offers made to another contract
* back to the keywords used in the first contract.
* @param {KeywordKeywordRecord=} keywordRecord
* @returns {KeywordKeywordRecord }
*/

/**
* @callback MapKeywords
*
* Remap the keywords of an amountKeywordRecord or a
* PaymentPKeywordRecord according to a mapping. This is used to remap
* from keywords used in contractA to keywords used in contractB and
* vice versa in `offerTo`
*
* @param {AmountKeywordRecord | PaymentPKeywordRecord | undefined }
* keywordRecord
* @param {KeywordKeywordRecord} keywordMapping
*/
70 changes: 70 additions & 0 deletions packages/zoe/src/contractSupport/zoeHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import '../../exported';
import { assert, details } from '@agoric/assert';
import { sameStructure } from '@agoric/same-structure';
import { E } from '@agoric/eventual-send';
import { makePromiseKit } from '@agoric/promise-kit';

import { MathKind } from '@agoric/ertp';
import { satisfiesWant } from '../contractFacet/offerSafety';
Expand Down Expand Up @@ -421,3 +422,72 @@ export async function saveAllIssuers(zcf, issuerKeywordRecord = {}) {
);
return Promise.all(issuersPSaved);
}

/** @type {MapKeywords} */
export const mapKeywords = (keywordRecord = {}, keywordMapping) => {
return Object.fromEntries(
Object.entries(keywordRecord).map(([keyword, value]) => {
if (keywordMapping[keyword] === undefined) {
return [keyword, value];
}
return [keywordMapping[keyword], value];
}),
);
};
/** @type {Reverse} */
const reverse = (keywordRecord = {}) => {
return Object.fromEntries(
Object.entries(keywordRecord).map(([key, value]) => [value, key]),
);
};

/** @type {OfferTo} */
export const offerTo = async (
zcf,
invitation,
keywordMapping = {},
proposal,
fromSeat,
toSeat,
) => {
const definedToSeat = toSeat !== undefined ? toSeat : fromSeat;

const zoe = zcf.getZoeService();
const mappingReversed = reverse(keywordMapping);

// the proposal is in the other contract's keywords, but we want to
// use `proposal.give` to withdraw
const payments = await withdrawFromSeat(
zcf,
fromSeat,
// `proposal.give` may be undefined
mapKeywords(proposal.give, mappingReversed),
);

// Map to the other contract's keywords
const paymentsForOtherContract = mapKeywords(payments, keywordMapping);

const userSeatPromise = E(zoe).offer(
invitation,
proposal,
paymentsForOtherContract,
);

const depositedPromiseKit = makePromiseKit();

const doDeposit = async payoutPayments => {
const amounts = await E(userSeatPromise).getCurrentAllocation();

// Map back to the original contract's keywords
const mappedAmounts = mapKeywords(amounts, mappingReversed);
const mappedPayments = mapKeywords(payoutPayments, mappingReversed);
await depositToSeat(zcf, definedToSeat, mappedAmounts, mappedPayments);
depositedPromiseKit.resolve(mappedAmounts);
};

E(userSeatPromise)
.getPayouts()
.then(doDeposit);

return harden({ userSeatPromise, deposited: depositedPromiseKit.promise });
};
79 changes: 27 additions & 52 deletions packages/zoe/src/contracts/loan/liquidate.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,56 @@ import '../../../exported';

import { E } from '@agoric/eventual-send';

import { depositToSeat, withdrawFromSeat } from '../../contractSupport';
import { offerTo } from '../../contractSupport/zoeHelpers';

export const doLiquidation = async (
zcf,
collateralSeat,
autoswapPublicFacetP,
lenderSeat,
) => {
const zoeService = zcf.getZoeService();
const loanMath = zcf.getTerms().maths.Loan;

const allCollateral = collateralSeat.getAmountAllocated('Collateral');

const { Collateral: collateralPayment } = await withdrawFromSeat(
zcf,
collateralSeat,
{
Collateral: allCollateral,
},
);
const swapInvitation = E(autoswapPublicFacetP).makeSwapInInvitation();

const toAmounts = harden({ In: allCollateral });

const proposal = harden({
give: { In: allCollateral },
give: toAmounts,
want: { Out: loanMath.getEmpty() },
});

const payments = harden({ In: collateralPayment });

const swapInvitation = E(autoswapPublicFacetP).makeSwapInInvitation();
const keywordMapping = harden({
Collateral: 'In',
Loan: 'Out',
});

const autoswapUserSeat = E(zoeService).offer(
const { userSeatPromise: autoswapUserSeat, deposited } = await offerTo(
zcf,
swapInvitation,
keywordMapping,
proposal,
payments,
collateralSeat,
lenderSeat,
);

/**
* @param {{ Out: Promise<Payment>; In: Promise<Payment>; }} payouts
*/
const handlePayoutsAndShutdown = async payouts => {
const { Out: loanPayout, In: collateralPayout } = payouts;
const { Out: loanAmount, In: collateralAmount } = await E(
autoswapUserSeat,
).getCurrentAllocation();
// AWAIT ///
const amounts = harden({
Collateral: collateralAmount,
Loan: loanAmount,
});
const payoutPayments = harden({
Collateral: collateralPayout,
Loan: loanPayout,
});
await depositToSeat(zcf, lenderSeat, amounts, payoutPayments);

const closeSuccessfully = () => {
lenderSeat.exit();
collateralSeat.exit();
zcf.shutdown('your loan had to be liquidated');
};

const closeWithFailure = err => {
lenderSeat.kickOut(err);
collateralSeat.kickOut(err);
zcf.shutdownWithFailure(err);
};

await E(autoswapUserSeat)
.getOfferResult()
.then(closeSuccessfully, closeWithFailure);
const closeSuccessfully = () => {
lenderSeat.exit();
collateralSeat.exit();
zcf.shutdown('your loan had to be liquidated');
};

const closeWithFailure = err => {
lenderSeat.fail(err);
collateralSeat.fail(err);
zcf.shutdownWithFailure(err);
};

return E(autoswapUserSeat)
.getPayouts()
.then(handlePayoutsAndShutdown);
const offerResultP = E(autoswapUserSeat).getOfferResult();
await deposited;
await offerResultP.then(closeSuccessfully, closeWithFailure);
};

/**
Expand Down
20 changes: 7 additions & 13 deletions packages/zoe/src/contracts/otcDesk.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { E } from '@agoric/eventual-send';
import { assert } from '@agoric/assert';
import {
trade,
depositToSeat,
withdrawFromSeat,
offerTo,
saveAllIssuers,
assertProposalShape,
} from '../contractSupport';
Expand Down Expand Up @@ -84,21 +83,16 @@ const start = zcf => {
},
});

const payments = await withdrawFromSeat(zcf, marketMakerSeat, assets);
const sellerUserSeat = await E(zoe).offer(
const { userSeatPromise: coveredCallUserSeat } = await offerTo(
zcf,
creatorInvitation,
undefined,
proposal,
payments,
marketMakerSeat,
marketMakerSeat,
);

E(sellerUserSeat)
.getPayouts()
.then(async payoutPayments => {
const amounts = await E(sellerUserSeat).getCurrentAllocation();
await depositToSeat(zcf, marketMakerSeat, amounts, payoutPayments);
});

const option = E(sellerUserSeat).getOfferResult();
const option = E(coveredCallUserSeat).getOfferResult();
return option;
};

Expand Down
Loading

0 comments on commit f9441f4

Please sign in to comment.