Skip to content

Commit

Permalink
feat(vats): provisionPolicy trades provided assets for IST
Browse files Browse the repository at this point in the history
  • Loading branch information
dckc committed Sep 8, 2022
1 parent 779ce4d commit 560b676
Show file tree
Hide file tree
Showing 3 changed files with 312 additions and 73 deletions.
2 changes: 1 addition & 1 deletion packages/vats/src/core/basic-behaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export const makeAddressNameHubs = async ({
produce.namesByAddressAdmin.resolve(namesByAddressAdmin);

const perAddress = address => {
const myAddressNameAdmin = makeMyAddressNameAdmin(address);
const { myAddressNameAdmin } = makeMyAddressNameAdmin(address);
return { agoricNames, namesByAddress, myAddressNameAdmin };
};

Expand Down
171 changes: 99 additions & 72 deletions packages/vats/src/provisionPolicy.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,26 @@
// @jessie-check
// @ts-check

import { fit, M } from '@agoric/store';
import { fit, M, makeScalarMapStore } from '@agoric/store';
import { E, Far } from '@endo/far';
// TODO: move to narrower package?
import {
makeMyAddressNameAdmin,
PowerFlags,
} from '@agoric/vats/src/core/basic-behaviors.js';
import {
depositToSeat,
withdrawFromSeat,
} from '@agoric/zoe/src/contractSupport.js';
import { observeIteration, observeNotifier } from '@agoric/notifier';
import { AmountMath, AmountShape } from '@agoric/ertp';
import { makeMyAddressNameAdmin, PowerFlags } from './core/basic-behaviors.js';

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

const privateShape = harden({
poolBank: M.remotable('bank'),
});

/**
* Given attenuated access to the funding purse,
* handle requests to provision smart wallets.
*
* @typedef {{
* perAccountInitialAmount: Amount<'nat'>,
* namesByAddress: NameHub,
* }} ProvisionTerms
*
* TODO: ERef<GovernedCreatorFacet<ProvisionCreator>>
*
* @type {ContractStartFn<ProvisionPublic, *,
* ProvisionTerms>}
* @param {(address: string, depositFacet: ERef<DepositFacet>) => Promise<void>} sendInitialPayment
*/
const start = zcf => {
// TODO: make initial amount governed
const { perAccountInitialAmount, namesByAddress } = zcf.getTerms();
const { brand } = perAccountInitialAmount;
const initialAmounts = harden({
Funds: perAccountInitialAmount,
});
const proposalShape = harden({
give: { Funds: { brand } },
want: {},
exit: M.any(),
});

const { zcfSeat: pool } = zcf.makeEmptySeatKit();

/** @type {OfferHandler} */
const depositHook = seat => {
const proposal = seat.getProposal();
fit(proposal, proposalShape);
pool.incrementBy(seat.decrementBy(proposal.give));
zcf.reallocate(seat, pool);
};

const publicFacet = Far('Provisioning Pool public', {
makeDepositInvitation: () => zcf.makeInvitation(depositHook, 'Deposit'),
});

/** @param {string} address */
const tryLookup = async address =>
E(namesByAddress)
.lookup(address)
.catch(_notFound => undefined);

const makeBridgeProvisionTool = sendInitialPayment => {
/**
* @param {{
* bankManager: ERef<BankManager>,
Expand All @@ -78,35 +39,17 @@ const start = zcf => {
const { address, powerFlags } = obj;
console.info('PLEASE_PROVISION', address, powerFlags);

const hubBefore = await tryLookup(address);
assert(!hubBefore, 'already provisioned');

if (!powerFlags.includes(PowerFlags.SMART_WALLET)) {
return;
}
const initialPmt = await withdrawFromSeat(
zcf,
pool,
initialAmounts,
).then(r => r.Funds);

const { nameHub, myAddressNameAdmin } = makeMyAddressNameAdmin(address);
await E(namesByAddressAdmin).update(address, nameHub);
const depositFacet = nameHub.lookup('depositFacet');

// don't wait for this here, since their deposit facet
// might not be there yet
E(depositFacet)
.receive(initialPmt)
.catch(e => {
console.error(X`initial deposit for ${q(address)} failed: ${q(e)}`);
depositToSeat(
zcf,
pool,
initialAmounts,
harden({ Funds: initialPmt }),
);
});
void sendInitialPayment(address, depositFacet);

const bank = E(bankManager).getBankForAddress(address);
await E(walletFactory).provideSmartWallet(
Expand All @@ -117,8 +60,92 @@ const start = zcf => {
console.info('provisioned', address, powerFlags);
},
});
return makeHandler;
};

const creatorFacet = Far('Provisioning Pool creator', { makeHandler });
/**
* @typedef {StandardTerms & {
* perAccountInitialAmount: Amount<'nat'>,
* }} ProvisionTerms
*
* @typedef {{
* poolBank: import('@endo/far').FarRef<import('./vat-bank.js').Bank>,
* }} ProvisionPrivate
* TODO: ERef<GovernedCreatorFacet<ProvisionCreator>>
*
* @type {ContractStartFn<unknown, unknown,
* ProvisionTerms, ProvisionPrivate>}
*/
export const start = (zcf, privateArgs) => {
// TODO: make initial amount governed
const { perAccountInitialAmount } = zcf.getTerms();
fit(privateArgs, privateShape, 'provisionPolicy privateArgs');
const { poolBank } = privateArgs;
fit(perAccountInitialAmount, AmountShape, 'perAccountInitialAmount');
const fundPurse = E(poolBank).getPurse(perAccountInitialAmount.brand);

const zoe = zcf.getZoeService();
const swap = async (payIn, amount, instance) => {
const psmPub = E(zoe).getPublicFacet(instance);
const proposal = harden({ give: { In: amount } });
const invitation = E(psmPub).makeWantMintedInvitation();
const seat = E(zoe).offer(invitation, proposal, { In: payIn });
const payout = await E(seat).getPayout('Out');
return E(fundPurse).deposit(payout);
};

return { publicFacet, creatorFacet };
/** @type {MapStore<Brand, Instance>} */
const brandToPSM = makeScalarMapStore();

observeIteration(E(poolBank).getAssetSubscription(), {
updateState: async desc => {
console.log('new asset', desc.brand);
await zcf.saveIssuer(desc.issuer, desc.issuerName);
const exchangePurse = E(poolBank).getPurse(desc.brand);
observeNotifier(E(exchangePurse).getCurrentAmountNotifier(), {
updateState: async amount => {
console.log('balance update', amount);
if (AmountMath.isEmpty(amount)) {
return;
}
if (!brandToPSM.has(desc.brand)) {
console.error('funds arrived but no PSM instance', desc.brand);
return;
}
const instance = brandToPSM.get(desc.brand);
const payment = E(exchangePurse).withdraw(amount);
await swap(payment, amount, instance).catch(async reason => {
console.error(X`swap failed: ${reason}`);
const resolvedPayment = await payment;
E(exchangePurse).deposit(resolvedPayment);
});
},
fail: reason => console.error(reason),
});
},
});

const sendInitialPayment = async (address, depositFacet) => {
const initialPmt = await E(fundPurse).withdraw(perAccountInitialAmount);

return E(depositFacet)
.receive(initialPmt)
.catch(e => {
console.error(X`initial deposit for ${q(address)} failed: ${q(e)}`);
E(fundPurse).deposit(initialPmt);
});
};

const creatorFacet = Far('Provisioning Pool creator', {
makeHandler: makeBridgeProvisionTool(sendInitialPayment),
initPSM: (brand, instance) => {
fit(brand, M.remotable('brand'));
fit(instance, M.remotable('instance'));
brandToPSM.init(brand, instance);
},
});

return { creatorFacet };
};

harden(start);
Loading

0 comments on commit 560b676

Please sign in to comment.