Skip to content

Commit

Permalink
refactor(zoe)!: new reallocate API to assist with reviewing conservat…
Browse files Browse the repository at this point in the history
…ion of rights (#3184)

* refactor(zoe)!: make it easier to audit conservation of rights changing the `zcfSeat` and `reallocate` API
  • Loading branch information
katelynsills committed Jun 8, 2021
1 parent 79ac7b8 commit f34e5ea
Show file tree
Hide file tree
Showing 48 changed files with 909 additions and 1,215 deletions.
14 changes: 3 additions & 11 deletions packages/pegasus/src/pegasus.js
Expand Up @@ -7,7 +7,6 @@ import { E } from '@agoric/eventual-send';
import { Nat } from '@agoric/nat';
import { parse as parseMultiaddr } from '@agoric/swingset-vat/src/vats/network/multiaddr';
import { assertProposalShape } from '@agoric/zoe/src/contractSupport';
import { AmountMath } from '@agoric/ertp';

import '@agoric/notifier/exported';
import '@agoric/vats/exported';
Expand Down Expand Up @@ -498,16 +497,9 @@ const makePegasus = (zcf, board, namesByAddress) => {
winner,
) => {
// Transfer the amount to our backing seat.
const currentLoser = loser.getAmountAllocated(loserKeyword, localBrand);
const currentWinner = winner.getAmountAllocated(
winnerKeyword,
localBrand,
);
const newLoser = AmountMath.subtract(currentLoser, amount);
const newWinner = AmountMath.add(currentWinner, amount);
const loserStage = loser.stage({ [loserKeyword]: newLoser });
const winnerStage = winner.stage({ [winnerKeyword]: newWinner });
zcf.reallocate(loserStage, winnerStage);
loser.decrementBy({ [loserKeyword]: amount });
winner.incrementBy({ [winnerKeyword]: amount });
zcf.reallocate(loser, winner);
};

// Describe how to retain/redeem real local erights.
Expand Down
18 changes: 9 additions & 9 deletions packages/treasury/src/collectRewardFees.js
@@ -1,6 +1,5 @@
// @ts-check

import { AmountMath } from '@agoric/ertp';
import { offerTo } from '@agoric/zoe/src/contractSupport';
import { E } from '@agoric/eventual-send';

Expand All @@ -17,16 +16,17 @@ export const makeMakeCollectFeesInvitation = (
const { zcfSeat: transferSeat } = zcf.makeEmptySeatKit();
await E.get(offerTo(zcf, invitation, {}, {}, transferSeat)).deposited;

const totalTransferred = AmountMath.add(
feeSeat.getAmountAllocated('RUN', runBrand),
transferSeat.getAmountAllocated('RUN', runBrand),
seat.incrementBy(
feeSeat.decrementBy({ RUN: feeSeat.getAmountAllocated('RUN', runBrand) }),
);
const emptyRunAllocation = { RUN: AmountMath.makeEmpty(runBrand) };
zcf.reallocate(
transferSeat.stage(emptyRunAllocation),
feeSeat.stage(emptyRunAllocation),
seat.stage({ RUN: totalTransferred }),
seat.incrementBy(
transferSeat.decrementBy({
RUN: transferSeat.getAmountAllocated('RUN', runBrand),
}),
);
const totalTransferred = seat.getStagedAllocation().RUN;

zcf.reallocate(transferSeat, feeSeat, seat);
seat.exit();
transferSeat.exit();

Expand Down
41 changes: 23 additions & 18 deletions packages/treasury/src/stablecoinMachine.js
Expand Up @@ -21,7 +21,6 @@ import { E } from '@agoric/eventual-send';
import { assert, details, q } from '@agoric/assert';
import makeStore from '@agoric/store';
import {
trade,
assertProposalShape,
offerTo,
getAmountOut,
Expand Down Expand Up @@ -77,13 +76,23 @@ export async function start(zcf) {
// away fees so the tests show that the funds have been removed.
const { zcfSeat: rewardPoolSeat } = zcf.makeEmptySeatKit();

// We provide an easy way for the vaultManager and vaults to add rewards to
// this seat, without directly exposing the seat to them.
function stageReward(amount) {
const priorReward = rewardPoolSeat.getAmountAllocated('RUN', runBrand);
return rewardPoolSeat.stage({
RUN: AmountMath.add(priorReward, amount, runBrand),
});
/**
* We provide an easy way for the vaultManager and vaults to add rewards to
* the rewardPoolSeat, without directly exposing the rewardPoolSeat to them.
*
* @type {ReallocateReward}
*/
function reallocateReward(amount, fromSeat, otherSeat = undefined) {
rewardPoolSeat.incrementBy(
fromSeat.decrementBy({
RUN: amount,
}),
);
if (otherSeat !== undefined) {
zcf.reallocate(rewardPoolSeat, fromSeat, otherSeat);
} else {
zcf.reallocate(rewardPoolSeat, fromSeat);
}
}

/** @type {Store<Brand,VaultManager>} */
Expand Down Expand Up @@ -154,15 +163,11 @@ export async function start(zcf) {

// trade the governance tokens for collateral, putting the
// collateral on Secondary to be positioned for Autoswap
trade(
zcf,
{
seat,
gains: { Governance: govAmount },
losses: { Collateral: collateralIn },
},
{ seat: govSeat, gains: { Secondary: collateralIn } },
);
seat.incrementBy(govSeat.decrementBy({ Governance: govAmount }));
seat.decrementBy({ Collateral: collateralIn });
govSeat.incrementBy({ Secondary: collateralIn });

zcf.reallocate(govSeat, seat);
// the collateral is now on the temporary seat

// once we've done that, we can put both the collateral and the minted
Expand Down Expand Up @@ -219,7 +224,7 @@ export async function start(zcf) {
collateralBrand,
priceAuthority,
rates,
stageReward,
reallocateReward,
timerService,
loanParams,
liquidationStrategy,
Expand Down
14 changes: 10 additions & 4 deletions packages/treasury/src/types.js
Expand Up @@ -45,10 +45,16 @@
*/

/**
* @callback StageReward return a seat staging (for use in reallocate)
* that will add the indicated amount to the stablecoin machine's reward pool.
* @callback ReallocateReward
*
* Transfer the indicated amount to the stablecoin machine's reward
* pool, taken from the `fromSeat`. Then reallocate over all the seat
* arguments and the rewardPoolSeat.
*
* @param {Amount} amount
* @returns SeatStaging
* @param {ZCFSeat} fromSeat
* @param {ZCFSeat=} otherSeat
* @returns {void}
*/

/**
Expand All @@ -59,7 +65,7 @@
* @property {() => Promise<PriceQuote>} getCollateralQuote
* @property {() => Ratio} getInitialMargin
* @property {() => Ratio} getInterestRate - The annual interest rate on a loan
* @property {StageReward} stageReward
* @property {ReallocateReward} reallocateReward
*/

/**
Expand Down
72 changes: 39 additions & 33 deletions packages/treasury/src/vault.js
Expand Up @@ -4,7 +4,6 @@ import '@agoric/zoe/exported';
import { assert, details as X, q } from '@agoric/assert';
import { E } from '@agoric/eventual-send';
import {
trade,
assertProposalShape,
divideBy,
multiplyBy,
Expand Down Expand Up @@ -165,17 +164,13 @@ export function makeVaultKit(
// take more than you owe
assert(AmountMath.isGTE(runReturned, runDebt));

trade(
zcf,
{
seat: vaultSeat,
gains: { RUN: runDebt }, // return any overpayment
},
{
seat,
gains: { Collateral: getCollateralAllocated(vaultSeat) },
},
// Return any overpayment
vaultSeat.incrementBy(seat.decrementBy({ RUN: runDebt }));
seat.incrementBy(
vaultSeat.decrementBy({ Collateral: getCollateralAllocated(vaultSeat) }),
);
zcf.reallocate(seat, vaultSeat);

seat.exit();
runDebt = AmountMath.makeEmpty(runBrand);
active = false;
Expand Down Expand Up @@ -238,6 +233,19 @@ export function makeVaultKit(
}
}

function transferCollateral(seat) {
const proposal = seat.getProposal();
if (proposal.want.Collateral) {
seat.incrementBy(
vaultSeat.decrementBy({ Collateral: proposal.want.Collateral }),
);
} else if (proposal.give.Collateral) {
vaultSeat.incrementBy(
seat.decrementBy({ Collateral: proposal.give.Collateral }),
);
}
}

// Calculate the target RUN level for the vaultSeat and clientSeat implied
// by the proposal. If the proposal wants collateral, transfer that amount
// from vault to client. If the proposal gives collateral, transfer the
Expand Down Expand Up @@ -271,6 +279,20 @@ export function makeVaultKit(
}
}

function transferRun(seat) {
const proposal = seat.getProposal();
if (proposal.want.RUN) {
seat.incrementBy(vaultSeat.decrementBy({ RUN: proposal.want.RUN }));
} else if (proposal.give.RUN) {
// We don't allow runDebt to be negative, so we'll refund overpayments
const acceptedRun = AmountMath.isGTE(proposal.give.RUN, runDebt)
? runDebt
: proposal.give.RUN;

vaultSeat.incrementBy(seat.decrementBy({ RUN: acceptedRun }));
}
}

// Calculate the fee, the amount to mint and the resulting debt.
function loanFee(proposal, runAfter) {
let newDebt;
Expand Down Expand Up @@ -342,17 +364,9 @@ export function makeVaultKit(
// mint to vaultSeat, then reallocate to reward and client, then burn from
// vaultSeat. Would using a separate seat clarify the accounting?
runMint.mintGains({ RUN: toMint }, vaultSeat);
zcf.reallocate(
vaultSeat.stage({
Collateral: collateralAfter.vault,
RUN: runAfter.vault,
}),
clientSeat.stage({
Collateral: collateralAfter.client,
RUN: runAfter.client,
}),
manager.stageReward(fee),
);
transferCollateral(clientSeat);
transferRun(clientSeat);
manager.reallocateReward(fee, vaultSeat, clientSeat);

runDebt = newDebt;
runMint.burnLosses({ RUN: runAfter.vault }, vaultSeat);
Expand Down Expand Up @@ -395,18 +409,10 @@ export function makeVaultKit(
await assertSufficientCollateral(collateralAmount, runDebt);

runMint.mintGains({ RUN: runDebt }, vaultSeat);
const priorCollateral = getCollateralAllocated(vaultSeat);

const collateralSeatStaging = vaultSeat.stage({
Collateral: AmountMath.add(priorCollateral, collateralAmount),
RUN: AmountMath.makeEmpty(runBrand),
});
const loanSeatStaging = seat.stage({
RUN: wantedRun,
Collateral: AmountMath.makeEmpty(collateralBrand),
});
const stageReward = manager.stageReward(fee);
zcf.reallocate(collateralSeatStaging, loanSeatStaging, stageReward);
seat.incrementBy(vaultSeat.decrementBy({ RUN: wantedRun }));
vaultSeat.incrementBy(seat.decrementBy({ Collateral: collateralAmount }));
manager.reallocateReward(fee, vaultSeat, seat);

updateUiState();

Expand Down
10 changes: 3 additions & 7 deletions packages/treasury/src/vaultManager.js
Expand Up @@ -33,7 +33,7 @@ export function makeVaultManager(
collateralBrand,
priceAuthority,
rates,
stageReward,
reallocateReward,
timerService,
loanParams,
liquidationStrategy,
Expand Down Expand Up @@ -64,7 +64,7 @@ export function makeVaultManager(
runBrand,
);
},
stageReward,
reallocateReward,
};

// A Map from vaultKits to their most recent ratio of debt to
Expand Down Expand Up @@ -169,11 +169,7 @@ export function makeVaultManager(
sortedVaultKits.updateAllDebts();
reschedulePriceCheck();
runMint.mintGains({ RUN: poolIncrement }, poolIncrementSeat);
const poolStage = poolIncrementSeat.stage({
RUN: AmountMath.makeEmpty(runBrand),
});
const poolSeatStaging = stageReward(poolIncrement);
zcf.reallocate(poolStage, poolSeatStaging);
reallocateReward(poolIncrement, poolIncrementSeat);
}

const periodNotifier = E(timerService).makeNotifier(
Expand Down
18 changes: 12 additions & 6 deletions packages/treasury/test/vault-contract-wrapper.js
Expand Up @@ -39,11 +39,17 @@ export async function start(zcf) {
},
};

function stageReward(amount, _fromSeat) {
const priorReward = stableCoinSeat.getAmountAllocated('RUN', runBrand);
return stableCoinSeat.stage({
RUN: AmountMath.add(priorReward, amount),
});
function reallocateReward(amount, fromSeat, otherSeat) {
stableCoinSeat.incrementBy(
fromSeat.decrementBy({
RUN: amount,
}),
);
if (otherSeat !== undefined) {
zcf.reallocate(stableCoinSeat, fromSeat, otherSeat);
} else {
zcf.reallocate(stableCoinSeat, fromSeat);
}
}

/** @type {InnerVaultManager} */
Expand All @@ -61,7 +67,7 @@ export async function start(zcf) {
return makeRatio(5n, runBrand);
},
collateralBrand,
stageReward,
reallocateReward,
};

const SECONDS_PER_HOUR = SECONDS_PER_YEAR / 365n / 24n;
Expand Down

0 comments on commit f34e5ea

Please sign in to comment.