Skip to content

Commit

Permalink
feat(fast-usdc): deposit, withdraw liquidity in exchange for shares
Browse files Browse the repository at this point in the history
  • Loading branch information
dckc committed Nov 5, 2024
1 parent fc0d103 commit 9c389e2
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 8 deletions.
100 changes: 100 additions & 0 deletions packages/fast-usdc/src/exos/liquidity-pool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* @import {Zone} from '@agoric/zone';
*/

import { AmountMath } from '@agoric/ertp/src/amountMath.js';
import {
deposit,
depositTransfers,
makeParity,
withdraw,
withdrawTransfers,
} from '../pool-share-math.js';
import { makeProposalShapes } from '../type-guards.js';

/**
* @param {Zone} zone
* @param {ZCF} zcf
* @param {Record<'USDC', Brand<'nat'>>} brands
*/
export const prepareLiquidityPoolKit = (zone, zcf, { USDC }) => {
const interfaceToDo = undefined;
return zone.exoClassKit(
'Fast Liquidity Pool',
interfaceToDo,
/**
* @param {ZCFMint<'nat'>} shareMint
*/
shareMint => {
const { brand: PoolShares } = shareMint.getIssuerRecord();
const proposalShapes = makeProposalShapes({ USDC, PoolShares });
const dust = AmountMath.make(USDC, 1n);
const shareWorth = makeParity(dust, PoolShares);
/*
* TODO: should the pool be an orchestration account,
* visible at the cosmos level?
*
* We could use a short-lived poolSeat for deposit / withdraw.
*/
const { zcfSeat: poolSeat } = zcf.makeEmptySeatKit();
return { shareMint, shareWorth, poolSeat, PoolShares, proposalShapes };
},
{
depositHandler: {
/** @param {ZCFSeat} lp */
handle(lp) {
const { shareWorth, shareMint, poolSeat } = this.state;
const { give, want } = lp.getProposal();
const { ToPool } = give;
const post = deposit(shareWorth, give.ToPool, want.Shares);
const { Shares } = post;
const mint = shareMint.mintGains({ Shares });
zcf.atomicRearrange(
depositTransfers({ pool: poolSeat, lp, mint }, { Shares, ToPool }),
);
this.state.shareWorth = post.shareWorth;
lp.exit();
mint.exit();
return true;
},
},
withdrawHandler: {
/** @param {ZCFSeat} lp */
handle(lp) {
const { shareWorth, shareMint, poolSeat } = this.state;
const { give, want } = lp.getProposal();
const { Shares } = give;
const post = withdraw(shareWorth, Shares, want.FromPool);
const { FromPool } = post;
const { zcfSeat: burn } = zcf.makeEmptySeatKit();
zcf.atomicRearrange(
withdrawTransfers(
{ pool: poolSeat, lp, burn },
{ Shares, FromPool },
),
);
shareMint.burnLosses({ Shares }, burn);
this.state.shareWorth = post.shareWorth;
lp.exit();
burn.exit();
return true;
},
},
public: {
makeDepositInvitation() {
const { depositHandler } = this.facets;
const { deposit: shape } = this.state.proposalShapes;
const d = undefined;
return zcf.makeInvitation(depositHandler, 'Deposit', d, shape);
},
makeWithdrawInvitation() {
const { withdrawHandler } = this.facets;
const { withdraw: shape } = this.state.proposalShapes;
const d = undefined;
return zcf.makeInvitation(withdrawHandler, 'Withdraw', d, shape);
},
},
},
);
};
harden(prepareLiquidityPoolKit);
23 changes: 21 additions & 2 deletions packages/fast-usdc/src/fast-usdc.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { BrandShape } from '@agoric/ertp/src/typeGuards.js';
import { withOrchestration } from '@agoric/orchestration';
import { M } from '@endo/patterns';
import { assertAllDefined } from '@agoric/internal';
import { AssetKind } from '@agoric/ertp';
import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js';
import { prepareTransactionFeed } from './exos/transaction-feed.js';
import { prepareSettler } from './exos/settler.js';
import { prepareAdvancer } from './exos/advancer.js';
import { prepareStatusManager } from './exos/status-manager.js';
import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js';

/**
* @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js';
Expand Down Expand Up @@ -39,17 +42,33 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
assert(tools, 'no tools');
const terms = zcf.getTerms();
assert('USDC' in terms.brands, 'no USDC brand');
assert('PoolShares' in terms.brands, 'no PoolShares brand');

const statusManager = prepareStatusManager(zone);
const feed = prepareTransactionFeed(zone);
const settler = prepareSettler(zone, { statusManager });
const advancer = prepareAdvancer(zone, { feed, statusManager });
const makeLiquidityPoolKit = prepareLiquidityPoolKit(zone, zcf, {
USDC: terms.brands.USDC,
});
assertAllDefined({ feed, settler, advancer, statusManager });

const creatorFacet = zone.exo('Fast USDC Creator', undefined, {});

return harden({ creatorFacet });
// NOTE: all kinds are defined above, before possible remote call.
const shareMint = await provideSingleton(
zone.mapStore('mint'),
'PoolShare',
() =>
zcf.makeZCFMint('PoolShares', AssetKind.NAT, {
decimalPlaces: 6,
}),
);
const publicFacet = zone.makeOnce(
'Fast USDC Public',
() => makeLiquidityPoolKit(shareMint).public,
);

return harden({ creatorFacet, publicFacet });
};
harden(contract);

Expand Down
77 changes: 72 additions & 5 deletions packages/fast-usdc/test/fast-usdc.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,100 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
import { E } from '@endo/far';
import path from 'path';
import { makeNodeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js';
import { AmountMath } from '@agoric/ertp/src/amountMath.js';
import { commonSetup } from './supports.js';

const dirname = path.dirname(new URL(import.meta.url).pathname);

const contractName = 'fast-usdc';
const contractFile = `${dirname}/../src/fast-usdc.contract.js`;
type StartFn = typeof import('../src/fast-usdc.contract.js').start;

test.before('cache bundles', async t => {
const bundleCache = await makeNodeBundleCache('bundles', {}, s => import(s));
t.context = { bundleCache };
});

test('start', async t => {
const {
bootstrap,
brands: { poolShares, usdc },
brands: { usdc },
commonPrivateArgs,
utils,
} = await commonSetup(t);

const { zoe, bundleAndInstall } = await setUpZoeForTest();
const installation: Installation<StartFn> =
await bundleAndInstall(contractFile);
const { zoe } = await setUpZoeForTest();
const bundle = await t.context.bundleCache.load(contractFile, contractName);
const installation: Installation<StartFn> = await E(zoe).install(bundle);

const { creatorFacet } = await E(zoe).startInstance(
installation,
{ PoolShares: poolShares.issuer, USDC: usdc.issuer },
{ USDC: usdc.issuer },
{
poolFee: usdc.make(1n),
contractFee: usdc.make(1n),
},
commonPrivateArgs,
);
t.truthy(creatorFacet);
});

test('LP deposit, withdraw', async t => {
const {
bootstrap,
brands: { usdc },
commonPrivateArgs,
utils,
} = await commonSetup(t);

const { zoe } = await setUpZoeForTest();
const bundle = await t.context.bundleCache.load(contractFile, contractName);
const installation: Installation<StartFn> = await E(zoe).install(bundle);

const { creatorFacet, publicFacet, instance } = await E(zoe).startInstance(
installation,
{ USDC: usdc.issuer },
{
poolFee: usdc.make(1n),
contractFee: usdc.make(1n),
},
commonPrivateArgs,
);
t.truthy(creatorFacet);
const terms = await E(zoe).getTerms(instance);
const { PoolShares } = terms.brands;
const sharePurseP = E(terms.issuers.PoolShares).makeEmptyPurse();
const usdcPurseP = E(terms.issuers.USDC).makeEmptyPurse();

const { make, isGTE } = AmountMath;

{
const toDeposit = await E(publicFacet).makeDepositInvitation();
const proposal = harden({
give: { ToPool: usdc.make(100n) },
want: { Shares: make(PoolShares, 20n) },
});
const payments = { ToPool: utils.pourPayment(proposal.give.ToPool) };
const dSeat = await E(zoe).offer(toDeposit, proposal, payments);
const sharePmt = await E(dSeat).getPayout('Shares');
const amt = await E(sharePurseP).deposit(sharePmt);
t.true(isGTE(amt, proposal.want.Shares));
}

{
const toWithdraw = await E(publicFacet).makeWithdrawInvitation();
const proposal = harden({
give: { Shares: make(PoolShares, 20n) },
want: { FromPool: usdc.make(10n) },
});
const pmt = await E(sharePurseP).withdraw(proposal.give.Shares);
const dSeat = await E(zoe).offer(toWithdraw, proposal, {
Shares: pmt,
});
const usdcPmt = await E(dSeat).getPayout('FromPool');
const amt = await E(usdcPurseP).deposit(usdcPmt);
t.log('withdrew', amt);
t.true(isGTE(amt, proposal.want.FromPool));
}
});
1 change: 0 additions & 1 deletion packages/fast-usdc/test/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ export const commonSetup = async (t: ExecutionContext<any>) => {
vowTools,
},
brands: {
poolShares: withAmountUtils(makeIssuerKit('Fast USDC Pool Shares')),
usdc: usdcSansMint,
},
mocks: {
Expand Down

0 comments on commit 9c389e2

Please sign in to comment.