diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index 4061b14f4f8..93a34e26bf1 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -507,6 +507,7 @@ type cosmosInitAction struct { ChainID string `json:"chainID"` BootstrapAddress string `json:"bootstrapAddress"` BootstrapValue string `json:"bootstrapValue"` + DonationValue string `json:"donationValue"` } // MakeCodecs constructs the *std.Codec and *codec.LegacyAmino instances used by @@ -527,7 +528,7 @@ func (app *GaiaApp) MustInitController(ctx sdk.Context) { app.controllerInited = true /* - FIXME: Get this from genesis! + FIXME: Get these from genesis! - name: mallory type: local address: agoric1z8rldx498tf49p5ze04jhakyumkh7vyxku7e0p @@ -542,6 +543,7 @@ func (app *GaiaApp) MustInitController(ctx sdk.Context) { os.Exit(1) } bootstrapValue := "50000000000" + donationValue := "5000000" // Begin initializing the controller here. action := &cosmosInitAction{ @@ -552,6 +554,7 @@ func (app *GaiaApp) MustInitController(ctx sdk.Context) { ChainID: ctx.ChainID(), BootstrapAddress: bootstrapAddr.String(), BootstrapValue: bootstrapValue, + DonationValue: donationValue, } bz, err := json.Marshal(action) if err == nil { diff --git a/packages/dapp-svelte-wallet/api/src/internal-types.js b/packages/dapp-svelte-wallet/api/src/internal-types.js index dd6828aafb5..9c58c1ef79b 100644 --- a/packages/dapp-svelte-wallet/api/src/internal-types.js +++ b/packages/dapp-svelte-wallet/api/src/internal-types.js @@ -57,7 +57,7 @@ * @property {(path: string[], val: T) => void} addPath * @property {(petname: Petname, val: T) => void} renamePetname * @property {(petname: Petname) => void} deletePetname - * @property {(petname: Petname, val: T) => void} suggestPetname + * @property {(petname: Petname, val: T) => Petname} suggestPetname * @property {string} kind */ diff --git a/packages/dapp-svelte-wallet/api/src/lib-wallet.js b/packages/dapp-svelte-wallet/api/src/lib-wallet.js index 72a32df9f09..0347ecac7eb 100644 --- a/packages/dapp-svelte-wallet/api/src/lib-wallet.js +++ b/packages/dapp-svelte-wallet/api/src/lib-wallet.js @@ -637,17 +637,26 @@ export function makeWallet({ return `instance ${q(petname)} successfully added to wallet`; }; - const makeEmptyPurse = async ( + /** + * This function is marked internal and unsafe because it permits importing a + * shared purse (which we don't necessarily trust). + * + * @param {Petname} brandPetname + * @param {Petname} petnameForPurse + * @param {boolean} defaultAutoDeposit + * @param {(issuer: Issuer) => ERef} importPurse + */ + const internalUnsafeImportPurse = async ( brandPetname, petnameForPurse, - defaultAutoDeposit = false, - purseMaker = issuer => E(issuer).makeEmptyPurse(), + defaultAutoDeposit, + importPurse, ) => { const brand = brandMapping.petnameToVal.get(brandPetname); const { issuer } = brandTable.getByBrand(brand); /** @type {Purse} */ - const purse = await purseMaker(issuer); + const purse = await importPurse(issuer); purseToBrand.init(purse, brand); petnameForPurse = purseMapping.suggestPetname(petnameForPurse, purse); @@ -675,6 +684,19 @@ export function makeWallet({ }); }; + // This function is exposed to the walletAdmin. + const makeEmptyPurse = ( + brandPetname, + petnameForPurse, + defaultAutoDeposit = false, + ) => + internalUnsafeImportPurse( + brandPetname, + petnameForPurse, + defaultAutoDeposit, + issuer => E(issuer).makeEmptyPurse(), + ); + async function deposit(pursePetname, payment) { const purse = purseMapping.petnameToVal.get(pursePetname); return E(purse).deposit(payment); @@ -1526,13 +1548,18 @@ export function makeWallet({ zoeInvitePurse = wallet.getPurse(ZOE_INVITE_PURSE_PETNAME); }; + // Importing assets as virtual purses from the bank is a highly-trusted path. + // We don't want to expose this mechanism to the user, in case they shoot + // themselves in the foot with it by importing an asset/virtual purse they + // don't really trust. const importBankAssets = async bank => { observeIteration(E(bank).getAssetSubscription(), { async updateState({ proposedName, issuerName, issuer, brand }) { try { await addIssuer(issuerName, issuer); const purse = await E(bank).getPurse(brand); - await wallet.makeEmptyPurse( + // We can import this purse, because we trust the bank. + await internalUnsafeImportPurse( issuerName, proposedName, true, diff --git a/packages/vats/src/bootstrap.js b/packages/vats/src/bootstrap.js index c9524874f18..150e3bcfb16 100644 --- a/packages/vats/src/bootstrap.js +++ b/packages/vats/src/bootstrap.js @@ -29,10 +29,6 @@ const QUOTE_INTERVAL = 5 * 60; const BASIS_POINTS_DENOM = 10000n; -// TODO: decide on initial value to be distributed. This would give -// 20,000 users each 1 display unit of RUN -const BOOTSTRAP_PAYMENT_VALUE = 20000n * 10n ** 6n; - console.debug(`loading bootstrap.js`); // Used for coordinating on an index in comms for the provisioning service @@ -48,7 +44,6 @@ function makeVattpFrom(vats) { } export function buildRootObject(vatPowers, vatParameters) { - console.error('%%%HAVE vatParameters', vatParameters); const { D } = vatPowers; async function setupCommandDevice(httpVat, cmdDevice, roles) { await E(httpVat).setCommandDevice(cmdDevice, roles); @@ -63,6 +58,9 @@ export function buildRootObject(vatPowers, vatParameters) { vatAdminSvc, noFakeCurrencies, ) { + /** @type {ERef>} */ + const bankVat = vats.bank; + // Create singleton instances. const [ bankManager, @@ -73,7 +71,7 @@ export function buildRootObject(vatPowers, vatParameters) { zoe, { priceAuthority, adminFacet: priceAuthorityAdmin }, ] = await Promise.all([ - bridgeManager && E(vats.bank).makeBankManager(bridgeManager), + bridgeManager ? E(bankVat).makeBankManager(bridgeManager) : undefined, E(vats.sharing).getSharingService(), E(vats.registrar).getSharedRegistrar(), E(vats.board).getBoard(), @@ -95,7 +93,7 @@ export function buildRootObject(vatPowers, vatParameters) { nameAdmin: pegasusConnectionsAdmin, } = makeNameHubKit(); - async function installEconomy() { + async function installEconomy(bootstrapPaymentValue) { // Create a mapping from all the nameHubs we create to their corresponding // nameAdmin. /** @type {Store} */ @@ -124,7 +122,7 @@ export function buildRootObject(vatPowers, vatParameters) { nameAdmins, priceAuthority, zoe, - bootstrapPaymentValue: BOOTSTRAP_PAYMENT_VALUE, + bootstrapPaymentValue, }), installPegasusOnChain({ agoricNames, @@ -137,8 +135,13 @@ export function buildRootObject(vatPowers, vatParameters) { return treasuryCreator; } + const { bootstrapAddress, donationValue = '0', bootstrapValue = '0' } = + (vatParameters && vatParameters.argv && vatParameters.argv.bootMsg) || {}; + const bootstrapPaymentValue = BigInt(bootstrapValue); + const donationPaymentValue = BigInt(donationValue); + // Now we can bootstrap the economy! - const treasuryCreator = await installEconomy(); + const treasuryCreator = await installEconomy(bootstrapPaymentValue); const [ centralIssuer, @@ -152,34 +155,35 @@ export function buildRootObject(vatPowers, vatParameters) { E(agoricNames).lookup('instance', 'Pegasus'), ]); - async function payThePiper(bootMsg) { - if (!bankManager || !bootMsg) { - return; - } - // We just transfer the bootstrapValue in central tokens to the low-level - // bootstrapAddress. - const { bootstrapAddress, bootstrapValue } = bootMsg; - if (!bootstrapAddress || !bootstrapValue) { + /** @type {undefined | import('@agoric/eventual-send').EOnly} */ + let centralBootstrapPurse; + + // We just transfer the bootstrapValue in central tokens to the low-level + // bootstrapAddress. + async function depositCentralBootstrapPayment() { + if (!bankManager || !bootstrapAddress || !bootstrapPaymentValue) { return; } + await E(bankManager).addAsset( + 'urun', + CENTRAL_ISSUER_NAME, + 'Agoric RUN currency', + harden({ issuer: centralIssuer, brand: centralBrand }), + ); const bank = await E(bankManager).getBankForAddress(bootstrapAddress); const pmt = await E(treasuryCreator).getBootstrapPayment( - amountMath.make(BigInt(bootstrapValue), centralBrand), + amountMath.make(bootstrapPaymentValue, centralBrand), ); - const purse = E(bank).getPurse(centralBrand); - await E(purse).deposit(pmt); + centralBootstrapPurse = E(bank).getPurse(centralBrand); + await E(centralBootstrapPurse).deposit(pmt); } - false && - (await payThePiper( - vatParameters && vatParameters.argv && vatParameters.argv.bootMsg, - )); + await depositCentralBootstrapPayment(); /** @type {[string, import('./issuers').IssuerInitializationRecord]} */ const CENTRAL_ISSUER_ENTRY = [ CENTRAL_ISSUER_NAME, { issuer: centralIssuer, - defaultPurses: [['Agoric RUN currency', 0]], tradesGivenCentral: [[1, 1]], }, ]; @@ -214,7 +218,7 @@ export function buildRootObject(vatPowers, vatParameters) { issuerName, harden({ ...record, brand, issuer }), ); - if (!record.bankDenom || !record.bankPurse) { + if (!record.bankDenom || !record.bankPurse || !bankManager) { return issuer; } @@ -510,6 +514,23 @@ export function buildRootObject(vatPowers, vatParameters) { const bank = await (bankManager && E(bankManager).getBankForAddress(address)); + + /** @param {NatValue} value */ + async function donateCentralFromBootstrap(value) { + if (!bank || !centralBootstrapPurse) { + return; + } + const provisionAmount = amountMath.make(value, centralBrand); + const centralUserPurse = E(bank).getPurse(centralBrand); + const centralPayment = await E(centralBootstrapPurse).withdraw( + provisionAmount, + ); + await E(centralUserPurse).deposit( + /** @type {Payment} */ (centralPayment), + ); + } + await donateCentralFromBootstrap(donationPaymentValue); + const bundle = harden({ ...additionalPowers, agoricNames, diff --git a/packages/vats/src/vat-bank.js b/packages/vats/src/vat-bank.js index 834ded5d7a2..f10eb5a4fb8 100644 --- a/packages/vats/src/vat-bank.js +++ b/packages/vats/src/vat-bank.js @@ -63,21 +63,34 @@ const makePurseController = ( }, }); +/** + * @typedef {Object} AssetIssuerKit + * @property {Mint} [mint] + * @property {Issuer} issuer + * @property {Brand} brand + */ + +/** + * @typedef {AssetIssuerKit & { denom: string }} AssetRecord + */ + +/** + * @typedef {Object} Bank + * @property {() => Subscription} + * getAssetSubscription Returns assets as they are added to the bank + * @property {(brand: Brand) => VirtualPurse} getPurse Find any existing vpurse (keyed by address and brand) or create a + * new one. + */ + export function buildRootObject(_vatPowers) { return Far('bankMaker', { /** * @param {import('./bridge').BridgeManager} bridgeMgr */ async makeBankManager(bridgeMgr) { - /** - * @typedef {Object} BrandRecord - * @property {ERef} issuer - * @property {ERef} mint - */ - const bankCall = obj => E(bridgeMgr).toBridge('bank', obj); - /** @type {Store} */ + /** @type {Store} */ const brandToAssetRecord = makeStore('brand'); /** @type {Store void>>} */ const denomToAddressUpdater = makeStore('denom'); @@ -121,6 +134,7 @@ export function buildRootObject(_vatPowers) { publication: assetPublication, } = makeSubscriptionKit(); + /** @type {Store} */ const addressToBank = makeStore('address'); return Far('bankManager', { /** @@ -137,7 +151,7 @@ export function buildRootObject(_vatPowers) { * @param {string} denom lower-level denomination string * @param {string} issuerName * @param {string} proposedName - * @param {IssuerKit} kit ERTP issuer kit (mint, brand, issuer) + * @param {AssetIssuerKit} kit ERTP issuer kit (mint, brand, issuer) */ async addAsset(denom, issuerName, proposedName, kit) { assert.typeof(denom, 'string'); @@ -169,6 +183,7 @@ export function buildRootObject(_vatPowers) { * Create a new personal bank interface for a given address. * * @param {string} address lower-level bank account address + * @returns {Bank} */ getBankForAddress(address) { assert.typeof(address, 'string'); @@ -178,21 +193,12 @@ export function buildRootObject(_vatPowers) { /** @type {Store} */ const brandToVPurse = makeStore('brand'); + + /** @type {Bank} */ const bank = Far('bank', { - /** - * Returns assets as they are added to the bank. - * - * @returns {Subscription} - */ getAssetSubscription() { return harden(assetSubscription); }, - /** - * Find any existing vpurse (keyed by address and brand) or create a - * new one. - * - * @param {Brand} brand - */ async getPurse(brand) { if (brandToVPurse.has(brand)) { return brandToVPurse.get(brand);