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

feat(vault): liquidation penalty handled by liquidation contracts #5343

Merged
merged 20 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from 19 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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from './assertions.js';
import { CONTRACT_ELECTORATE } from './governParam.js';

const { details: X } = assert;
const { details: X, quote: q } = assert;

/**
* @param {ParamManagerBase} paramManager
Expand All @@ -29,6 +29,10 @@ const assertElectorateMatches = (paramManager, governedParams) => {
const {
[CONTRACT_ELECTORATE]: { value: paramElectorate },
} = governedParams;
assert(
paramElectorate,
X`Missing ${q(CONTRACT_ELECTORATE)} term in ${q(governedParams)}`,
);
assert(
keyEQ(managerElectorate, paramElectorate),
X`Electorate in manager (${managerElectorate})} incompatible with terms (${paramElectorate}`,
Expand Down
6 changes: 5 additions & 1 deletion packages/run-protocol/src/proposals/core-proposal.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ const SHARED_MAIN_MANIFEST = harden({
},
},
instance: {
consume: { amm: 'amm', economicCommittee: 'economicCommittee' },
consume: {
amm: 'amm',
economicCommittee: 'economicCommittee',
reserve: 'reserve',
},
produce: {
VaultFactory: 'VaultFactory',
Treasury: 'VaultFactory',
Expand Down
38 changes: 23 additions & 15 deletions packages/run-protocol/src/proposals/econ-behaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export const startVaultFactory = async (
{
consume: {
chainTimerService,
priceAuthority,
priceAuthority: priceAuthorityP,
zoe,
feeMintAccess: feeMintAccessP, // ISSUE: why doeszn't Zoe await this?
economicCommitteeCreatorFacet: electorateCreatorFacet,
Expand Down Expand Up @@ -296,27 +296,35 @@ export const startVaultFactory = async (
loanFee: makeRatio(0n, centralBrand, BASIS_POINTS),
};

const [ammInstance, electorateInstance, contractGovernorInstall] =
await Promise.all([
instance.consume.amm,
instance.consume.economicCommittee,
contractGovernor,
]);
const [
ammInstance,
electorateInstance,
contractGovernorInstall,
reserveInstance,
] = await Promise.all([
instance.consume.amm,
instance.consume.economicCommittee,
contractGovernor,
instance.consume.reserve,
]);
const ammPublicFacet = await E(zoe).getPublicFacet(ammInstance);
const feeMintAccess = await feeMintAccessP;
const pa = await priceAuthority;
const priceAuthority = await priceAuthorityP;
const reservePublicFacet = await E(zoe).getPublicFacet(reserveInstance);
const timer = await chainTimerService;
const vaultFactoryTerms = makeGovernedTerms(
pa,
loanParams,
installations.liquidate,
const vaultFactoryTerms = makeGovernedTerms({
priceAuthority,
reservePublicFacet,
loanTiming: loanParams,
liquidationInstall: installations.liquidate,
timer,
invitationAmount,
vaultManagerParams,
ammPublicFacet,
liquidationDetailTerms(centralBrand),
AmountMath.make(centralBrand, minInitialDebt),
);
liquidationTerms: liquidationDetailTerms(centralBrand),
minInitialDebt: AmountMath.make(centralBrand, minInitialDebt),
bootstrapPaymentValue: 0n,
});

const governorTerms = harden({
timer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const trace = makeTracer('LiqI', false);
* price impact on the AMM of any one sale. Each block it will compute
* a tranche of collateral to sell, where the size is a function of
* the amount of that collateral in the AMM pool and the desired price impact.
* It presently consults the AMM and Oracle for whether to sell.
*
* The next revision of this will work as follows...
*
* It then gets 3 prices for the current tranche:
* - AMM quote - compute XYK locally based on the pool sizes
* - Reserve quote - based on a low price at which the Reserve will purchase
Expand All @@ -47,6 +51,7 @@ const trace = makeTracer('LiqI', false);
* @typedef {{
* amm: XYKAMMPublicFacet,
* priceAuthority: PriceAuthority,
* reservePublicFacet: AssetReservePublicFacet,
* timerService: TimerService,
* debtBrand: Brand,
* MaxImpactBP: NatValue,
Expand All @@ -59,6 +64,7 @@ const start = async zcf => {
const {
amm,
priceAuthority,
reservePublicFacet,
timerService,
debtBrand,
MaxImpactBP,
Expand All @@ -74,6 +80,10 @@ const start = async zcf => {
const asFloat = (numerator, denominator) =>
Number(numerator) / Number(denominator);

// TODO(5467)) distribute penalties to the reserve
assert(reservePublicFacet, 'Missing reservePublicFacet');
const { zcfSeat: penaltyPoolSeat } = zcf.makeEmptySeatKit();

/**
* Compute the tranche size whose sale on the AMM would have
* a price impact of MAX_IMPACT_BP.
Expand Down Expand Up @@ -242,16 +252,28 @@ const start = async zcf => {
trace('offerResult', { amounts });
}

const debtorHook = async (debtorSeat, { debt: originalDebt }) => {
trace('LIQ', originalDebt);
/**
* @param {ZCFSeat} debtorSeat
* @param {object} options
* @param {Amount<'nat'>} options.debt Debt before penalties
* @param {Ratio} options.penaltyRate
*/
const handleLiquidateOffer = async (
debtorSeat,
{ debt: originalDebt, penaltyRate },
) => {
assertProposalShape(debtorSeat, {
give: { In: null },
});
assert(
originalDebt.brand === debtBrand,
X`Cannot liquidate to ${originalDebt.brand}`,
);
for await (const t of processTranches(debtorSeat, originalDebt)) {
const penalty = ceilMultiplyBy(originalDebt, penaltyRate);
const debtWithPenalty = AmountMath.add(originalDebt, penalty);
trace('LIQ', { originalDebt, debtWithPenalty });

for await (const t of processTranches(debtorSeat, debtWithPenalty)) {
Chris-Hibbert marked this conversation as resolved.
Show resolved Hide resolved
const { collateral, oracleLimit, ammProceeds, debt } = t;
trace(`OFFER TO DEBT: `, {
collateral,
Expand All @@ -276,15 +298,28 @@ const start = async zcf => {
});
}
}

// Now we need to know how much was sold so we can pay off the debt.
// We can use this seat because only liquidation adds debt brand to it..
const debtPaid = debtorSeat.getAmountAllocated('Out', debtBrand);
const penaltyPaid = AmountMath.min(penalty, debtPaid);

// Allocate penalty portion of proceeds to a seat that will hold it for transfer to reserve
penaltyPoolSeat.incrementBy(
debtorSeat.decrementBy(harden({ Out: penaltyPaid })),
);
zcf.reallocate(penaltyPoolSeat, debtorSeat);

debtorSeat.exit();
trace('exit seat');
};

/**
* @type {ERef<Liquidator>}
*/
const creatorFacet = Far('debtorInvitationCreator', {
makeLiquidateInvitation: () => zcf.makeInvitation(debtorHook, 'Liquidate'),
const creatorFacet = Far('debtorInvitationCreator (incrementally)', {
makeLiquidateInvitation: () =>
zcf.makeInvitation(handleLiquidateOffer, 'Liquidate'),
});

return harden({ creatorFacet });
Expand Down
43 changes: 34 additions & 9 deletions packages/run-protocol/src/vaultFactory/liquidateMinimum.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// @ts-check

import { E } from '@endo/eventual-send';
import { offerTo } from '@agoric/zoe/src/contractSupport/index.js';
import {
ceilMultiplyBy,
offerTo,
} from '@agoric/zoe/src/contractSupport/index.js';
import { AmountMath } from '@agoric/ertp';
import { Far } from '@endo/marshal';

Expand All @@ -26,10 +29,19 @@ const start = async zcf => {

/**
* @param {ZCFSeat} debtorSeat
* @param {{ debt: Amount<'nat'> }} options
* @param {object} options
* @param {Amount<'nat'>} options.debt Debt before penalties
* @param {Ratio} options.penaltyRate
*/
const debtorHook = async (debtorSeat, { debt }) => {
const debtBrand = debt.brand;
const handleLiquidationOffer = async (
debtorSeat,
{ debt: originalDebt, penaltyRate },
) => {
// XXX does not distribute penalties anywhere
const { zcfSeat: penaltyPoolSeat } = zcf.makeEmptySeatKit();
const penalty = ceilMultiplyBy(originalDebt, penaltyRate);
const debtWithPenalty = AmountMath.add(originalDebt, penalty);
const debtBrand = originalDebt.brand;
const {
give: { In: amountIn },
} = debtorSeat.getProposal();
Expand All @@ -39,31 +51,44 @@ const start = async zcf => {
give: { In: amountIn },
want: { Out: AmountMath.makeEmpty(debtBrand) },
});
trace(`OFFER TO DEBT: `, debt, amountIn);
trace(`OFFER TO DEBT: `, debtWithPenalty, amountIn);
const { deposited } = await offerTo(
zcf,
swapInvitation,
undefined, // The keywords were mapped already
liqProposal,
debtorSeat,
debtorSeat,
{ stopAfter: debt },
{ stopAfter: debtWithPenalty },
);
const amounts = await deposited;
trace(`Liq results`, {
debt,
debtWithPenalty,
amountIn,
paid: debtorSeat.getCurrentAllocation(),
amounts,
});

// Now we need to know how much was sold so we can pay off the debt.
// We can use this seat because only liquidation adds debt brand to it..
const debtPaid = debtorSeat.getAmountAllocated('Out', debtBrand);
const penaltyPaid = AmountMath.min(penalty, debtPaid);

// Allocate penalty portion of proceeds to a seat that will hold it for transfer to reserve
penaltyPoolSeat.incrementBy(
debtorSeat.decrementBy(harden({ Out: penaltyPaid })),
);
zcf.reallocate(penaltyPoolSeat, debtorSeat);

debtorSeat.exit();
};

/**
* @type {ERef<Liquidator>}
*/
const creatorFacet = Far('debtorInvitationCreator', {
makeLiquidateInvitation: () => zcf.makeInvitation(debtorHook, 'Liquidate'),
const creatorFacet = Far('debtorInvitationCreator (minimum)', {
makeLiquidateInvitation: () =>
Copy link
Member Author

@turadg turadg May 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed à la #5179

zcf.makeInvitation(handleLiquidationOffer, 'Liquidate'),
});

return harden({ creatorFacet });
Expand Down
Loading