-
Notifications
You must be signed in to change notification settings - Fork 1
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(postalSvc): deliver payment using address #2
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// @ts-check | ||
import { E, Far } from '@endo/far'; | ||
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; | ||
|
||
const { keys, values } = Object; | ||
|
||
/** | ||
* @typedef {object} PostalSvcTerms | ||
* @property {import('@agoric/vats').NameHub} namesByAddress | ||
*/ | ||
|
||
/** @param {ZCF<PostalSvcTerms>} zcf */ | ||
export const start = zcf => { | ||
const { namesByAddress, issuers } = zcf.getTerms(); | ||
console.log('postalSvc issuers', Object.keys(issuers)); | ||
|
||
/** | ||
* @param {string} addr | ||
* @returns {ERef<DepositFacet>} | ||
*/ | ||
const getDepositFacet = addr => { | ||
assert.typeof(addr, 'string'); | ||
return E(namesByAddress).lookup(addr, 'depositFacet'); | ||
}; | ||
|
||
/** | ||
* @param {string} addr | ||
* @param {Payment} pmt | ||
*/ | ||
const sendTo = (addr, pmt) => E(getDepositFacet(addr)).receive(pmt); | ||
|
||
/** @param {string} recipient */ | ||
const makeSendInvitation = recipient => { | ||
assert.typeof(recipient, 'string'); | ||
|
||
/** @type {OfferHandler} */ | ||
const handleSend = async seat => { | ||
const { give } = seat.getProposal(); | ||
const depositFacet = await getDepositFacet(recipient); | ||
const payouts = await withdrawFromSeat(zcf, seat, give); | ||
|
||
// XXX partial failure? return payments? | ||
await Promise.all( | ||
values(payouts).map(pmtP => | ||
Promise.resolve(pmtP).then(pmt => E(depositFacet).receive(pmt)), | ||
), | ||
); | ||
seat.exit(); | ||
return `sent ${keys(payouts).join(', ')}`; | ||
}; | ||
|
||
return zcf.makeInvitation(handleSend, 'send'); | ||
}; | ||
|
||
const publicFacet = Far('postalSvc', { | ||
lookup: (...path) => E(namesByAddress).lookup(...path), | ||
getDepositFacet, | ||
sendTo, | ||
makeSendInvitation, | ||
}); | ||
return { publicFacet }; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/** | ||
* @file core eval script* to start the postalSvc contract. | ||
* | ||
* * see test-gimix-proposal.js to make a script from this file. | ||
* | ||
* The `permit` export specifies the corresponding permit. | ||
*/ | ||
// @ts-check | ||
|
||
import { E, Far } from '@endo/far'; | ||
|
||
const { Fail } = assert; | ||
|
||
const trace = (...args) => console.log('start-postalSvc', ...args); | ||
|
||
const fail = msg => { | ||
throw Error(msg); | ||
}; | ||
|
||
/** | ||
* ref https://github.com/Agoric/agoric-sdk/issues/8408#issuecomment-1741445458 | ||
* | ||
* @param {ERef<import('@agoric/vats').NameAdmin>} namesByAddressAdmin | ||
*/ | ||
const fixHub = async namesByAddressAdmin => { | ||
/** @type {import('@agoric/vats').NameHub} */ | ||
const hub = Far('Hub work-around', { | ||
lookup: async (addr, ...rest) => { | ||
await E(namesByAddressAdmin).reserve(addr); | ||
const addressAdmin = await E(namesByAddressAdmin).lookupAdmin(addr); | ||
assert(addressAdmin, 'no admin???'); | ||
const addressHub = E(addressAdmin).readonly(); | ||
if (rest.length === 0) return addressHub; | ||
await E(addressAdmin).reserve(rest[0]); | ||
return E(addressHub).lookup(...rest); | ||
}, | ||
has: _key => Fail`key space not well defined`, | ||
entries: () => Fail`enumeration not supported`, | ||
values: () => Fail`enumeration not supported`, | ||
keys: () => Fail`enumeration not supported`, | ||
}); | ||
return hub; | ||
}; | ||
|
||
/** | ||
* @param {BootstrapPowers} powers | ||
* @param {{ options?: { postalSvc: { | ||
* bundleID: string; | ||
* }}}} config | ||
*/ | ||
export const startPostalSvc = async (powers, config) => { | ||
const { | ||
consume: { zoe, namesByAddressAdmin }, | ||
installation: { | ||
// @ts-expect-error not statically known at genesis | ||
produce: { postalSvc: produceInstallation }, | ||
}, | ||
instance: { | ||
// @ts-expect-error not statically known at genesis | ||
produce: { postalSvc: produceInstance }, | ||
}, | ||
} = powers; | ||
const { bundleID = fail(`no bundleID; try test-gimix-proposal.js?`) } = | ||
config.options?.postalSvc ?? {}; | ||
|
||
/** @type {Installation<import('./postalSvc').start>} */ | ||
const installation = await E(zoe).installBundleID(bundleID); | ||
produceInstallation.resolve(installation); | ||
|
||
const namesByAddress = await fixHub(namesByAddressAdmin); | ||
|
||
const [IST, Invitation] = await Promise.all([ | ||
E(zoe).getFeeIssuer(), | ||
E(zoe).getInvitationIssuer(), | ||
]); | ||
const { instance } = await E(zoe).startInstance( | ||
installation, | ||
{ IST, Invitation }, | ||
{ namesByAddress }, | ||
); | ||
produceInstance.resolve(instance); | ||
|
||
trace('postalSvc started'); | ||
}; | ||
|
||
export const manifest = /** @type {const} */ ({ | ||
[startPostalSvc.name]: { | ||
consume: { | ||
agoricNames: true, | ||
namesByAddress: true, | ||
namesByAddressAdmin: true, | ||
zoe: true, | ||
}, | ||
installation: { | ||
produce: { postalSvc: true }, | ||
}, | ||
instance: { | ||
produce: { postalSvc: true }, | ||
}, | ||
}, | ||
}); | ||
|
||
export const permit = JSON.stringify(Object.values(manifest)[0]); | ||
|
||
// script completion value | ||
startPostalSvc; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// @ts-check | ||
// XXX what's the state-of-the-art in ava setup? | ||
// eslint-disable-next-line import/order | ||
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; | ||
|
||
import { createRequire } from 'module'; | ||
|
||
import { E, Far } from '@endo/far'; | ||
import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; | ||
import { makeNameHubKit, makePromiseSpace } from '@agoric/vats'; | ||
import { makeWellKnownSpaces } from '@agoric/vats/src/core/utils.js'; | ||
import { AmountMath } from '@agoric/ertp/src/amountMath.js'; | ||
import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js'; | ||
import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; | ||
import { startPostalSvc } from '../src/start-postalSvc.js'; | ||
|
||
/** @type {import('ava').TestFn<Awaited<ReturnType<makeTestContext>>>} */ | ||
const test = anyTest; | ||
|
||
const myRequire = createRequire(import.meta.url); | ||
|
||
const assets = { | ||
postalSvc: myRequire.resolve('../src/postalSvc.js'), | ||
}; | ||
|
||
const makeTestContext = async t => { | ||
const bundleCache = await unsafeMakeBundleCache('bundles/'); | ||
|
||
return { bundleCache }; | ||
}; | ||
|
||
test.before(async t => (t.context = await makeTestContext(t))); | ||
|
||
const bootstrap = async log => { | ||
const { produce, consume } = makePromiseSpace(); | ||
|
||
const { admin, vatAdminState } = makeFakeVatAdmin(); | ||
const { zoeService: zoe, feeMintAccess } = makeZoeKitForTest(admin); | ||
|
||
const { nameHub: agoricNames, nameAdmin: agoricNamesAdmin } = | ||
makeNameHubKit(); | ||
const spaces = await makeWellKnownSpaces(agoricNamesAdmin, log, [ | ||
'installation', | ||
'instance', | ||
]); | ||
|
||
const { nameAdmin: namesByAddressAdmin } = makeNameHubKit(); | ||
|
||
produce.zoe.resolve(zoe); | ||
produce.feeMintAccess.resolve(feeMintAccess); | ||
produce.agoricNames.resolve(agoricNames); | ||
produce.namesByAddressAdmin.resolve(namesByAddressAdmin); | ||
|
||
/** @type {BootstrapPowers}} */ | ||
// @ts-expect-error mock | ||
const powers = { produce, consume, ...spaces }; | ||
|
||
return { powers, vatAdminState }; | ||
}; | ||
|
||
test('deliver payment using address', async t => { | ||
t.log('bootstrap'); | ||
const { powers, vatAdminState } = await bootstrap(t.log); | ||
|
||
const { bundleCache } = t.context; | ||
const bundle = await bundleCache.load(assets.postalSvc, 'postalSvc'); | ||
const bundleID = `b1-${bundle.endoZipBase64Sha512}`; | ||
t.log('publish bundle', bundleID.slice(0, 8)); | ||
vatAdminState.installBundle(bundleID, bundle); | ||
|
||
await startPostalSvc(powers, { | ||
options: { postalSvc: { bundleID } }, | ||
}); | ||
|
||
const { agoricNames, zoe, namesByAddressAdmin } = powers.consume; | ||
|
||
const instance = await E(agoricNames).lookup('instance', 'postalSvc'); | ||
|
||
const addr1 = 'agoric1receiver'; | ||
|
||
const rxd = []; | ||
const depositFacet = Far('DepositFacet', { | ||
/** @param {Payment} pmt */ | ||
receive: async pmt => { | ||
rxd.push(pmt); | ||
// XXX should return amount of pmt | ||
}, | ||
}); | ||
|
||
const my = makeNameHubKit(); | ||
my.nameAdmin.update('depositFacet', depositFacet); | ||
await E(namesByAddressAdmin).update(addr1, my.nameHub, my.nameAdmin); | ||
|
||
const { issuers, brands } = await E(zoe).getTerms(instance); | ||
const postalSvc = E(zoe).getPublicFacet(instance); | ||
const purse = await E(issuers.IST).makeEmptyPurse(); | ||
|
||
const pmt1 = await E(purse).withdraw(AmountMath.make(brands.IST, 0n)); | ||
|
||
// XXX should test that return value is amount | ||
t.log('send IST with public facet to', addr1); | ||
await E(postalSvc).sendTo(addr1, pmt1); | ||
t.deepEqual(rxd, [pmt1]); | ||
|
||
{ | ||
const Payment = AmountMath.make(brands.IST, 0n); | ||
const pmt2 = await E(postalSvc).makeSendInvitation(addr1); | ||
const pmt3 = await E(purse).withdraw(Payment); | ||
const Invitation = await E(issuers.Invitation).getAmountOf(pmt2); | ||
const proposal = { give: { Payment, Invitation } }; | ||
t.log('make offer to send IST, Invitation to', addr1); | ||
const seat = E(zoe).offer( | ||
E(postalSvc).makeSendInvitation(addr1), | ||
proposal, | ||
{ Payment: pmt3, Invitation: pmt2 }, | ||
); | ||
// XXX test is overly sensitive to order? | ||
const result = await E(seat).getOfferResult(); | ||
t.is(result, 'sent Invitation, Payment'); | ||
t.deepEqual(rxd, [pmt1, pmt2, pmt3]); | ||
const done = await E(seat).getPayouts(); | ||
} | ||
}); | ||
test.todo('partial failure: send N+1 payments where >= 1 delivery fails'); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this may be a common requirement. I wonder if it's possible for a contract to have a dynamic list of issuers from a nameHub. Could maybe be added to
StandardTerms
from zoe or specified in thestartInstance
function.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if this is a better approach here, saving issuers on the fly. Tying postalSvc to a NameHub like
agoricNames
might preclude wider usage.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Contracts usually add issuers that they are confident that they can rely on, so letting clients unilaterally add issuers is usually a no-no. But in this case, maybe it's ok, since we're just relaying assets that the parties choose to use?