UNPKG

@agoric/zoe

Version:

Zoe: the Smart Contract Framework for Offer Enforcement

130 lines (114 loc) 4.15 kB
import { AmountMath } from '@agoric/ertp'; import { E } from '@endo/eventual-send'; import { q, Fail } from '@endo/errors'; import { deeplyFulfilledObject, objectMap } from '@agoric/internal'; import { provideDurableWeakMapStore } from '@agoric/vat-data'; /// <reference path="./types.js" /> import { cleanKeywords } from '../cleanProposal.js'; /** * @import {WeakMapStore} from '@agoric/store'; */ /** * Store the pool purses whose purpose is to escrow assets, with one * purse per brand. * * @param {import('@agoric/vat-data').Baggage} baggage */ export const provideEscrowStorage = baggage => { /** @type {WeakMapStore<Brand, Purse>} */ const brandToPurse = provideDurableWeakMapStore(baggage, 'brandToPurse'); /** @type {CreatePurse} */ const createPurse = (issuer, brand) => { if (brandToPurse.has(brand)) { return undefined; } return E.when( E(issuer).makeEmptyPurse(), purse => { // Check again after the promise resolves if (!brandToPurse.has(brand)) { brandToPurse.init(brand, purse); } }, err => Fail`A purse could not be created for brand ${brand} because: ${err}`, ); }; /** * @type {ProvideLocalPurse} */ const provideLocalPurse = (issuer, brand) => { if (brandToPurse.has(brand)) { return /** @type {Purse} */ (brandToPurse.get(brand)); } else { const localPurse = issuer.makeEmptyPurse(); brandToPurse.init(brand, localPurse); return localPurse; } }; /** @type {WithdrawPayments} */ const withdrawPayments = allocation => objectMap(allocation, amount => E(brandToPurse.get(amount.brand)).withdraw(amount), ); /** * * Only used internally. Actually deposit a payment or promise for payment. * * @param {ERef<Payment>} paymentP * @param {Amount} amount * @returns {Promise<Amount>} */ const doDepositPayment = (paymentP, amount) => { const purse = brandToPurse.get(amount.brand); return E.when(paymentP, payment => E(purse).deposit(payment, amount)); }; // Proposal is cleaned, but payments are not /** @type {DepositPayments} */ const depositPayments = async (proposal, payments) => { const { give, want } = proposal; const giveKeywords = Object.keys(give); const paymentKeywords = cleanKeywords(payments); // Assert that all of the payment keywords are present in the give // keywords. Proposal.give keywords that do not have matching payments will // be caught in the deposit step. for (const keyword of paymentKeywords) { giveKeywords.includes(keyword) || Fail`The ${q( keyword, )} keyword in the paymentKeywordRecord was not a keyword in proposal.give, which had keywords: ${q( giveKeywords, )}`; } // If any of these deposits hang or fail, then this `await` also // hangs or fails, the offer does not succeed, and any funds that // were deposited into the pool purses are lost. We have a ticket // for giving the user a refund of what was already deposited, and // offer safety and payout liveness are still meaningful as long // as issuers are well-behaved. For more, see // https://github.com/Agoric/agoric-sdk/issues/1271 const depositPs = objectMap(give, (amount, keyword) => { payments[keyword] !== undefined || Fail`The ${q( keyword, )} keyword in proposal.give did not have an associated payment in the paymentKeywordRecord, which had keywords: ${q( paymentKeywords, )}`; return doDepositPayment(payments[keyword], amount); }); const deposits = await deeplyFulfilledObject(depositPs); const initialAllocation = harden({ ...objectMap(want, amount => AmountMath.makeEmptyFromAmount(amount)), // Deposits should win in case of overlapping give/want keywords // (which are not allowed as of 2024-01). ...deposits, }); return initialAllocation; }; return { createPurse, // createPurse does not return a purse provideLocalPurse, withdrawPayments, depositPayments, }; };