Skip to content

Commit c646bb6

Browse files
Loan Contract (#1903)
* feat: add basic loan contract and tests * chore: fix typo Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com> * fix: fix egregious import error, remove mention of tokens * Chore: update comment Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com> * chore: update comment Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com> * chore: address PR comments * chore: address PR comments * chore: test performing liquidation from borrow invitation. * fix: schedule a liquidation every time the debt is updated * chore: add test for malformed autoswap * chore: capture info on the priceQuote * chore: drop console.log Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com>
1 parent 36ce679 commit c646bb6

20 files changed

+2198
-2
lines changed

packages/zoe/exported.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import './src/contractFacet/types';
22
import './src/zoeService/types';
33
import './src/contractSupport/types';
4+
import './src/contracts/exported';
45
import './src/types';
56
import './tools/types';
67
import '@agoric/notifier/exported';

packages/zoe/src/contracts/exported.js

+210
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,213 @@
190190
* @property {(query: any, reply: any, requiredFee: Amount) => void} onReply
191191
* notice a successful reply
192192
*/
193+
194+
/**
195+
* @typedef {AsyncIterable<undefined>} PeriodAsyncIterable
196+
*
197+
* The asyncIterable used for notifications that a period has passed,
198+
* on which compound interest will be calculated using the
199+
* interestRate.
200+
*/
201+
202+
/**
203+
* @typedef {number} MMR
204+
* The Maintenance Margin Requirement, in percent. The default is
205+
* 150, meaning that collateral should be worth at least 150% of the
206+
* loan. If the value of the collateral drops below mmr, liquidation
207+
* occurs.
208+
*/
209+
210+
/**
211+
* @typedef {Instance} AutoswapInstance
212+
* The running contract instance for an Autoswap or Multipool
213+
* Autoswap installation. The publicFacet from the Autoswap
214+
* instance is used for producing an invitation to sell the
215+
* collateral on liquidation.
216+
*/
217+
218+
/** @typedef {number} InterestRate
219+
*
220+
* The rate in basis points that will be multiplied with the debt on
221+
* every period to compound interest.
222+
*/
223+
224+
/**
225+
* @typedef LoanTerms
226+
*
227+
* @property {MMR} [mmr=150]
228+
*
229+
* @property {AutoswapInstance} autoswapInstance
230+
*
231+
* @property {PriceAuthority} priceAuthority
232+
*
233+
* Used for getting the current value of collateral and setting
234+
* liquidation triggers.
235+
*
236+
* @property {PeriodAsyncIterable} periodAsyncIterable
237+
*
238+
* @property {InterestRate} interestRate
239+
*/
240+
241+
/**
242+
* @typedef LenderSeatProperty
243+
* @property {ZCFSeat} lenderSeat
244+
*
245+
* The ZCFSeat representing the lender's position in the contract.
246+
*/
247+
248+
/**
249+
* @typedef {LoanTerms & LenderSeatProperty} LoanConfigWithLender
250+
*
251+
* The loan now has a lenderSeat, which is added to the config.
252+
*/
253+
254+
/**
255+
* @typedef BorrowerConfigProperties
256+
*
257+
* @property {ZCFSeat} collateralSeat
258+
*
259+
* The ZCFSeat holding the collateral in escrow after the borrower
260+
* escrows it
261+
*
262+
* @property {() => Amount} getDebt
263+
*
264+
* A function to get the current debt
265+
*
266+
* @property {PromiseKit} liquidationPromiseKit
267+
*
268+
* PromiseKit that includes a promise that resolves to a PriceQuote
269+
* when liquidation is triggered
270+
*/
271+
272+
/**
273+
* @typedef BorrowerConfigPropertiesMinusDebt
274+
*
275+
* @property {ZCFSeat} collateralSeat
276+
*
277+
* The ZCFSeat holding the collateral in escrow after the borrower
278+
* escrows it
279+
*
280+
* @property {PromiseKit} liquidationPromiseKit
281+
*
282+
* PromiseKit that includes a promise that resolves to a PriceQuote
283+
* when liquidation is triggered
284+
*/
285+
286+
/**
287+
* @typedef {LoanConfigWithLender & BorrowerConfigProperties } LoanConfigWithBorrower
288+
*
289+
* The loan has a lender, a borrower, and collateral escrowed.
290+
*/
291+
292+
/**
293+
* @typedef {LoanConfigWithLender & BorrowerConfigPropertiesMinusDebt
294+
* } LoanConfigWithBorrowerMinusDebt
295+
*/
296+
297+
/**
298+
* @callback ScheduleLiquidation
299+
* @param {ContractFacet} zcf
300+
* @param {LoanConfigWithBorrower} config
301+
*/
302+
303+
/**
304+
* @callback MakeLendInvitation
305+
* @param {ContractFacet} zcf
306+
* @param {LoanTerms} config
307+
* @returns {Promise<Invitation>} lendInvitation
308+
*/
309+
310+
/**
311+
* @callback MakeBorrowInvitation
312+
* @param {ContractFacet} zcf
313+
* @param {LoanConfigWithLender} config
314+
* @returns {Promise<Invitation>} borrowInvitation
315+
*/
316+
317+
/**
318+
* @callback MakeCloseLoanInvitation
319+
* @param {ContractFacet} zcf
320+
* @param {LoanConfigWithBorrower} config
321+
* @returns {Promise<Invitation>} closeLoanInvitation
322+
*/
323+
324+
/**
325+
* Allows holder to add collateral to the contract. Exits the seat
326+
* after adding.
327+
*
328+
* @callback MakeAddCollateralInvitation
329+
* @param {ContractFacet} zcf
330+
* @param {LoanConfigWithBorrower} config
331+
* @returns {Promise<Invitation>} addCollateralInvitation
332+
*/
333+
334+
/**
335+
* @callback Liquidate
336+
* @param {ContractFacet} zcf
337+
* @param {LoanConfigWithBorrower} config
338+
* @returns {void}
339+
*/
340+
341+
/**
342+
* @callback MakeDebtCalculator
343+
* @param {DebtCalculatorConfig} debtCalculatorConfig
344+
*/
345+
346+
/**
347+
* @callback CalcInterestFn
348+
* @param {number} oldDebtValue
349+
* @param {number} interestRate
350+
* @returns {number} interest
351+
*/
352+
353+
/**
354+
* @typedef {Object} DebtCalculatorConfig
355+
* @property {CalcInterestFn} calcInterestFn
356+
*
357+
* A function to calculate the interest, given the debt value and an
358+
* interest rate in basis points.
359+
*
360+
* @property {Amount} originalDebt
361+
*
362+
* The debt at the start of the loan, in Loan brand
363+
*
364+
* @property {AmountMath} loanMath
365+
*
366+
* AmountMath for the loan brand
367+
*
368+
* @property {PeriodAsyncIterable} periodAsyncIterable
369+
*
370+
* The AsyncIterable to notify when a period has occurred
371+
*
372+
* @property {number} interestRate
373+
*
374+
* @property {ContractFacet} zcf
375+
*
376+
* @property {LoanConfigWithBorrowerMinusDebt} configMinusGetDebt
377+
*/
378+
379+
/**
380+
* @typedef {Object} BorrowFacet
381+
*
382+
* @property {() => Promise<Invitation>} makeCloseLoanInvitation
383+
*
384+
* Make an invitation to close the loan by repaying the debt
385+
* (including interest).
386+
*
387+
* @property {() => Promise<Invitation>} makeAddCollateralInvitation
388+
*
389+
* Make an invitation to add collateral to protect against liquidation
390+
*
391+
* @property {() => Promise<PriceQuote>} getLiquidationPromise
392+
*
393+
* Get a promise for a priceQuote that will resolve if liquidation
394+
* occurs. The priceQuote is for the value of the collateral that
395+
* triggered the liquidation. This may be lower than expected if the
396+
* price is moving quickly.
397+
*
398+
* @property {() => Notifier<Amount>} getDebtNotifier
399+
*
400+
* Get notified when the current debt (an Amount in the Loan Brand) changes. This will
401+
* increase as interest is added.
402+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// @ts-check
2+
3+
import '../../../exported';
4+
5+
import { assertProposalShape, trade } from '../../contractSupport';
6+
7+
import { scheduleLiquidation } from './scheduleLiquidation';
8+
9+
// Create an invitation to add collateral to the loan. Part of the
10+
// facet given to the borrower.
11+
12+
/** @type {MakeAddCollateralInvitation} */
13+
export const makeAddCollateralInvitation = (zcf, config) => {
14+
const { collateralSeat } = config;
15+
16+
/** @type {OfferHandler} */
17+
const addCollateral = addCollateralSeat => {
18+
assertProposalShape(addCollateralSeat, {
19+
give: { Collateral: null },
20+
want: {},
21+
});
22+
23+
trade(
24+
zcf,
25+
{
26+
seat: collateralSeat,
27+
gains: {
28+
Collateral: addCollateralSeat.getAmountAllocated('Collateral'),
29+
},
30+
},
31+
{
32+
seat: addCollateralSeat,
33+
gains: {},
34+
},
35+
);
36+
addCollateralSeat.exit();
37+
38+
// Schedule the new liquidation trigger. The old one will have an
39+
// outdated quote and will be ignored
40+
scheduleLiquidation(zcf, config);
41+
return 'a warm fuzzy feeling that you are further away from default than ever before';
42+
};
43+
44+
return zcf.makeInvitation(addCollateral, 'addCollateral');
45+
};

0 commit comments

Comments
 (0)