Skip to content

Commit

Permalink
# This is a combination of 3 commits.
Browse files Browse the repository at this point in the history
# This is the 1st commit message:

feat: take out RUN LoC with collateral price, ratio

# The commit message #2 will be skipped:

# chore: explicit index.js in import

# This is the commit message #3:

docs: types for FP utilities
  • Loading branch information
dckc committed Dec 16, 2021
1 parent bf02082 commit 7701131
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 74 deletions.
41 changes: 35 additions & 6 deletions packages/treasury/src/runLoC.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
// @ts-check
import { AmountMath } from '@agoric/ertp';
import { Far } from '@agoric/marshal';
import { assertProposalShape } from '@agoric/zoe/src/contractSupport';
import {
assertIsRatio,
assertProposalShape,
ceilMultiplyBy,
floorMultiplyBy,
} from '@agoric/zoe/src/contractSupport/index.js';

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

/**
* @param { ContractFacet } zcf
* @param {{ feeMintAccess: FeeMintAccess }} privateArgs
*/
const start = async (zcf, privateArgs) => {
const { governedParams } = zcf.getTerms();
const {
governedParams,
issuers,
collateralPrice,
collateralizationRate,
} = zcf.getTerms();
assertIsRatio(collateralPrice);
assertIsRatio(collateralizationRate);
const { feeMintAccess } = privateArgs;

const runMint = await zcf.registerFeeMint('RUN', feeMintAccess);
const { brand: runBrand, issuer: runIssuer } = runMint.getIssuerRecord();

const revealRunBrandToTest = () => {
const { brand: runBrand, issuer: runIssuer } = runMint.getIssuerRecord();

return harden({ runMint, runBrand, runIssuer });
};
zcf.setTestJig(revealRunBrandToTest);

assert(
collateralPrice.numerator.brand === runBrand,
X`${collateralPrice} not in RUN`,
);

/** @type { OfferHandler } */
const handleOffer = (seat, _offerArgs = undefined) => {
assertProposalShape(seat, {
Expand All @@ -30,10 +49,20 @@ const start = async (zcf, privateArgs) => {
want: { RUN: runWanted },
} = seat.getProposal();

console.log('@@TODO: check attestation', a);
assert(Array.isArray(a.value));
// TODO: check that we need to check address here
const [{ address, amountLiened }] = a.value;
const maxAvailable = floorMultiplyBy(amountLiened, collateralPrice);
const collateralizedRun = ceilMultiplyBy(runWanted, collateralizationRate);
assert(
AmountMath.isGTE(maxAvailable, collateralizedRun),
X`${amountLiened} at price ${collateralPrice} not enough to borrow ${runWanted} with ${collateralizationRate}`,
);
runMint.mintGains(harden({ RUN: runWanted }), seat);
seat.exit();
return '@@TODO: this transaction succeeded, but perhaps it should not have.';
return `borrowed ${q(runWanted)} against ${q(amountLiened)} at price ${q(
collateralPrice,
)} and rate ${q(collateralizationRate)}`;
};

const publicFacet = Far('Line of Credit API', {
Expand Down
232 changes: 164 additions & 68 deletions packages/treasury/test/test-runLoC.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Far, makeLoopback } from '@agoric/captp';
import { resolve as metaResolve } from 'import-meta-resolve';
import bundleSource from '@agoric/bundle-source';
import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp';
import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js';
// import { makeLoopback } from '@agoric/captp';

/**
Expand All @@ -20,9 +21,28 @@ import { AmountMath, AssetKind, makeIssuerKit } from '@agoric/ertp';
const { assign, entries, fromEntries, keys, values } = Object;
const { details: X } = assert;

/**
* @param {Record<string, V>} obj
* @param {(v: V) => U} f
* @returns {Record<string, U>}
* @template V
* @template U
*/
const mapValues = (obj, f) =>
fromEntries(entries(obj).map(([p, v]) => [p, f(v)]));
/**
* @param {X[]} xs
* @param {Y[]} ys
* @returns {[X, Y][]}
* @template X
* @template Y
*/
const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]);
/**
* @param {Record<string, ERef<V>>} obj
* @returns {Promise<Record<string, V>>}
* @template V
*/
const allValues = async obj =>
fromEntries(zip(keys(obj), await Promise.all(values(obj))));

Expand Down Expand Up @@ -60,10 +80,12 @@ const genesis = async () => {
feeMintAccess: nonFarFeeMintAccess,
} = makeZoeKit(makeFakeVatAdmin(setJig, makeRemote).admin);
const feePurse = E(nonFarZoeService).makeFeePurse();
const { brand: runBrandThere } = await E(feePurse).getCurrentAmount();
const runBrand = await makeFar(runBrandThere);
const zoeService = await E(nonFarZoeService).bindDefaultFeePurse(feePurse);
const zoe = makeFar(zoeService);
const feeMintAccess = await makeFar(nonFarFeeMintAccess);
return { zoe, feeMintAccess, getJig: () => testJig };
return { zoe, feeMintAccess, runBrand, getJig: () => testJig };
};

test('RUN mint access', async t => {
Expand Down Expand Up @@ -91,27 +113,19 @@ const startAttestation = async (t, zoe) => {
harden({ Underlying: bldIssuerKit.issuer }),
harden({
expiringAttName: 'BldAttGov',
returnableAttName: 'BldAttLoc',
returnableAttName: 'BldAttLoC',
}),
);
return { bldIssuerKit, publicFacet, creatorFacet };
};

const chainState = harden({
currentTime: 10n,
accounts: {
address1: {
total: 500n,
bonded: 200n,
locked: 10n,
},
},
});

/**
* @param {Brand} uBrand
* @param { string } myAddress
* @param {Account} account
* @typedef {{ total: bigint, bonded: bigint, locked: bigint}} Account
*/
const makeStakeReporter = uBrand => {
const makeStakeReporter = (uBrand, myAddress, account) => {
const ubld = v => AmountMath.make(uBrand, v);
return Far('stakeReporter', {
/**
Expand All @@ -120,11 +134,10 @@ const makeStakeReporter = uBrand => {
*/
getAccountState: (address, brand) => {
assert(brand === uBrand, X`unexpected brand: ${brand}`);
const account = chainState.accounts[address];
assert(account, X`no such account: ${address}`);
assert(address === myAddress, X`no such account: ${address}`);
return harden({
...mapValues(account, ubld),
currentTime: chainState.currentTime,
currentTime: 60n,
});
},
});
Expand All @@ -138,67 +151,150 @@ test('start attestation', async t => {
const { brand: bldBrand } = a.bldIssuerKit;
const ubld = v => AmountMath.make(bldBrand, v);

a.creatorFacet.addAuthority(makeStakeReporter(bldBrand));

const attMaker = await E(a.creatorFacet).getAttMaker(
keys(chainState.accounts)[0],
a.creatorFacet.addAuthority(
makeStakeReporter(bldBrand, 'address1', {
total: 10n,
bonded: 9n,
locked: 1n,
}),
);
const expiration = chainState.currentTime + 5n;
const att = await E(attMaker).makeAttestations(ubld(10n), expiration);

const attMaker = await E(a.creatorFacet).getAttMaker('address1');
const expiration = 65n;
const att = await E(attMaker).makeAttestations(ubld(5n), expiration);
t.log('attestation', att);
const pmt = await att.returnable;
t.log({ pmt });
t.pass();
});

test('take out RUN line of credit', async t => {
assert.typeof(t.context, 'object');
assert(t.context);
/**
* @param { string } title
* @param { Object } detail
* @param { Account } detail.account
* @param { bigint } detail.collateral
* @param { bigint } detail.runWanted
* @param { [bigint, bigint] } detail.price
* @param { [bigint, bigint] } detail.rate
* @param { boolean } [detail.failAttestation]
* @param { boolean } [detail.failOffer]
*/
const testLoc = (
title,
{ account, collateral, runWanted, price, rate, failAttestation, failOffer },
) => {
test(title, async t => {
assert.typeof(t.context, 'object');
assert(t.context);

const { zoe, feeMintAccess, getJig } = await genesis();
t.true(!!zoe);
t.true(!!feeMintAccess);
const a = await startAttestation(t, zoe);
const { brand: bldBrand } = a.bldIssuerKit;
a.creatorFacet.addAuthority(makeStakeReporter(bldBrand));
const { zoe, feeMintAccess, runBrand, getJig } = await genesis();
t.true(!!zoe);
t.true(!!feeMintAccess);
const a = await startAttestation(t, zoe);
const { brand: bldBrand } = a.bldIssuerKit;
a.creatorFacet.addAuthority(
makeStakeReporter(bldBrand, 'address1', account),
);
const { returnable: attIssuer } = await E(a.publicFacet).getIssuers();

const installation = await E(zoe).install(t.context.bundles.runLoC);
/** @type {StartLineOfCredit} */
const { publicFacet } = await E(zoe).startInstance(
installation,
undefined,
undefined,
harden({ feeMintAccess }),
);
/** @type {{ runBrand: Brand, runIssuer: Issuer }} */
const { runBrand } = getJig();
/** @param { bigint } value */
const run = value => AmountMath.make(runBrand, value);
/** @param { bigint } value */
const run = value => AmountMath.make(runBrand, value);

const lineOfCreditInvitation = await E(publicFacet).getInvitation();
const collateralPrice = makeRatio(price[0], runBrand, price[1], bldBrand);
const collateralizationRate = makeRatio(rate[0], runBrand, rate[1]);
const installation = await E(zoe).install(t.context.bundles.runLoC);
/** @type {StartLineOfCredit} */
const { publicFacet } = await E(zoe).startInstance(
installation,
harden({ Attestation: attIssuer }),
harden({ collateralPrice, collateralizationRate }),
harden({ feeMintAccess }),
);
/** @type {{ runBrand: Brand, runIssuer: Issuer }} */
const { runIssuer } = getJig();

/** @param { bigint } v */
const ubld = v => AmountMath.make(bldBrand, v);
const lineOfCreditInvitation = await E(publicFacet).getInvitation();

const { returnable: attIssuer } = await E(a.publicFacet).getIssuers();
const addr = keys(chainState.accounts)[0];
const attMaker = await E(a.creatorFacet).getAttMaker(addr);
t.log({ addr, attMaker });

const expiration = chainState.currentTime + 1n;
const attPmt = await E.get(E(attMaker).makeAttestations(ubld(5n), expiration))
.returnable;
t.log({ attPmt });
const attAmt = await E(attIssuer).getAmountOf(attPmt);

const seat = await E(zoe).offer(
lineOfCreditInvitation,
harden({ give: { Attestation: attAmt }, want: { RUN: run(100n) } }),
harden({ Attestation: attPmt }),
);
const result = await E(seat).getOfferResult();
t.true(await E(seat).hasExited());
const p = await allValues(await E(seat).getPayouts());
t.log('payout', p);
t.is(result, '@@TODO');
/** @param { bigint } v */
const ubld = v => AmountMath.make(bldBrand, v);

const addr = 'address1';
const attMaker = await E(a.creatorFacet).getAttMaker(addr);
t.log({ addr, attMaker });

const expiration = 61n;
const tryAttestations = E(attMaker).makeAttestations(
ubld(collateral),
expiration,
);
if (failAttestation) {
await t.throwsAsync(tryAttestations);
return;
}
const attPmt = await E.get(tryAttestations).returnable;
t.log({ attPmt });
const attAmt = await E(attIssuer).getAmountOf(attPmt);

t.log({
give: { Attestation: attAmt },
want: { RUN: run(runWanted) },
collateralPrice,
collateralizationRate,
});

const seat = await E(zoe).offer(
lineOfCreditInvitation,
harden({ give: { Attestation: attAmt }, want: { RUN: run(runWanted) } }),
harden({ Attestation: attPmt }),
);
const result = E(seat).getOfferResult();
if (failOffer) {
await t.throwsAsync(result);
return;
}
const resultValue = await result;
t.log({ resultValue });
t.regex(resultValue, /^borrowed /);

t.true(await E(seat).hasExited());

const p = await allValues(await E(seat).getPayouts());
t.log('payout', p);
t.deepEqual(await E(runIssuer).getAmountOf(p.RUN), run(runWanted));
});
};

testLoc('borrow 100 RUN against 6000 BLD at 1.25, 5x', {
runWanted: 100n,
collateral: 6000n,
account: { total: 10_000n, bonded: 9_000n, locked: 10n },
price: [125n, 100n],
rate: [5n, 1n],
});

testLoc('borrow 151 RUN against 600 BLD at 1.25, 5x', {
runWanted: 151n,
collateral: 600n,
price: [125n, 100n],
account: { total: 10_000n, bonded: 9_000n, locked: 10n },
rate: [5n, 1n],
failOffer: true,
});

testLoc('borrow 100 RUN against 600 BLD at 0.15, 5x', {
runWanted: 100n,
collateral: 600n,
price: [15n, 100n],
account: { total: 10_000n, bonded: 9_000n, locked: 10n },
rate: [5n, 1n],
failOffer: true,
});

testLoc('borrow against 6000 BLD without enough staked', {
runWanted: 100n,
collateral: 6000n,
account: { total: 10_000n, bonded: 5_000n, locked: 10n },
price: [125n, 100n],
rate: [5n, 1n],
failAttestation: true,
});

0 comments on commit 7701131

Please sign in to comment.