Skip to content

Commit

Permalink
feat: wire up vats.distributeFees
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed May 4, 2021
1 parent b42da66 commit 9e16332
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 93 deletions.
3 changes: 3 additions & 0 deletions packages/vats/decentral-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"board": {
"sourceSpec": "./src/vat-board.js"
},
"distributeFees": {
"sourceSpec": "./src/vat-distributeFees.js"
},
"ibc": {
"sourceSpec": "./src/vat-ibc.js"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/vats/jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"strictNullChecks": true,
"moduleResolution": "node",
},
"include": ["src/**/*.js", "globals.d.ts"],
"include": ["src/**/*.js", "test/**/*.js", "globals.d.ts"],
}
19 changes: 19 additions & 0 deletions packages/vats/src/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,25 @@ export function buildRootObject(vatPowers, vatParameters) {
E(agoricNames).lookup('instance', 'Pegasus'),
]);

// Start the reward distributor.
const epochTimerService = chainTimerService;
const distributorParams = {
depositsPerUpdate: 51,
updateInterval: 1n, // 1 second
epochInterval: 60n * 60n, // 1 hour
runIssuer: centralIssuer,
runBrand: centralBrand,
};
E(vats.distributeFees)
.buildDistributor(
E(vats.distributeFees).makeTreasuryFeeCollector(zoe, treasuryCreator),
E(bankManager).getDepositFacet(),
epochTimerService,
chainTimerService,
harden(distributorParams),
)
.catch(e => console.error('Error distributing fees', e));

/** @type {undefined | import('@agoric/eventual-send').EOnly<Purse>} */
let centralBootstrapPurse;

Expand Down
30 changes: 22 additions & 8 deletions packages/vats/src/distributeFees.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ export function buildDistributor(treasury, bank, epochTimer, timer, params) {
let lastWallTimeUpdate;
const timerNotifier = E(timer).makeNotifier(0n, updateInterval);

async function scheduleDeposits() {
/**
* @param {(pmt: Payment[]) => void} disposeRejectedPayments
*/
async function scheduleDeposits(disposeRejectedPayments) {
if (!queuedPayments.length) {
return;
}
Expand All @@ -60,12 +63,21 @@ export function buildDistributor(treasury, bank, epochTimer, timer, params) {
if (!queuedPayments.length) {
return;
}
E(bank).depositMultiple(
queuedAccounts.splice(0, depositsPerUpdate),
queuedPayments.splice(0, depositsPerUpdate),
);

scheduleDeposits();
const accounts = queuedAccounts.splice(0, depositsPerUpdate);
const payments = queuedPayments.splice(0, depositsPerUpdate);
E(bank)
.depositMultiple(accounts, payments)
.then(settledResults => {
const rejectedPayments = payments.filter(
(_pmt, i) =>
settledResults[i] && settledResults[i].status === 'rejected',
);

// Redeposit the payments.
disposeRejectedPayments(rejectedPayments);
});
scheduleDeposits(disposeRejectedPayments);
}

async function schedulePayments() {
Expand Down Expand Up @@ -95,7 +107,9 @@ export function buildDistributor(treasury, bank, epochTimer, timer, params) {
queuedPayments.push(...manyPayments.slice(0, manyPayments.length - 1));
queuedAccounts.push(...accounts);

scheduleDeposits();
scheduleDeposits(_pmts => {
// TODO: Somehow reclaim the rejected payments.
});
}

const timeObserver = {
Expand All @@ -109,5 +123,5 @@ export function buildDistributor(treasury, bank, epochTimer, timer, params) {
};

const epochNotifier = E(epochTimer).makeNotifier(0n, epochInterval);
observeNotifier(epochNotifier, timeObserver);
return observeNotifier(epochNotifier, timeObserver);
}
3 changes: 2 additions & 1 deletion packages/vats/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
/**
* @typedef {Object} BankDepositFacet
*
* @property {(accounts: string[], payments: Payment[]) => void} depositMultiple
* @property {(accounts: string[], payments: Payment[]) => Promise<PromiseSettledResult<Amount>[]>} depositMultiple
* @property {() => Notifier<string[]>} getAccountsNotifier
*/

Expand Down Expand Up @@ -84,4 +84,5 @@
* batches of payments are sent to the bank for processing. The parameter
* updateInterval specifies the interval at which updates are sent.
* @param {DistributorParams} params
* @returns {Promise<void>}
*/
217 changes: 136 additions & 81 deletions packages/vats/src/vat-bank.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,138 @@ export function buildRootObject(_vatPowers) {

/** @type {Store<string, Bank>} */
const addressToBank = makeStore('address');

/** @type {NotifierRecord<string[]>} */
const {
notifier: accountsNotifier,
updater: accountsUpdater,
} = makeNotifierKit([...addressToBank.keys()]);

/**
* Create a new personal bank interface for a given address.
*
* @param {string} address lower-level bank account address
* @returns {Bank}
*/
const getBankForAddress = address => {
assert.typeof(address, 'string');
if (addressToBank.has(address)) {
return addressToBank.get(address);
}

/** @type {WeakStore<Brand, VirtualPurse>} */
const brandToVPurse = makeWeakStore('brand');

/** @type {Bank} */
const bank = Far('bank', {
getAssetSubscription() {
return harden(assetSubscription);
},
async getPurse(brand) {
if (brandToVPurse.has(brand)) {
return brandToVPurse.get(brand);
}

const assetRecord = brandToAssetRecord.get(brand);
if (!bankCall) {
// Just emulate with a real purse.
const purse = await E(assetRecord.issuer).makeEmptyPurse();
brandToVPurse.init(brand, purse);
return purse;
}

const addressToUpdater = denomToAddressUpdater.get(
assetRecord.denom,
);

/** @type {NotifierRecord<Amount>} */
const { updater, notifier } = makeNotifierKit();
/** @type {bigint} */
let lastBalanceUpdate = -1n;
/** @type {BalanceUpdater} */
const balanceUpdater = (value, nonce = undefined) => {
if (nonce !== undefined) {
const thisBalanceUpdate = BigInt(nonce);
if (thisBalanceUpdate <= lastBalanceUpdate) {
return;
}
lastBalanceUpdate = thisBalanceUpdate;
}
// Convert the string value to a bigint.
const amt = amountMath.make(brand, BigInt(value));
updater.updateState(amt);
};

// Get the initial balance.
addressToUpdater.init(address, balanceUpdater);
const balanceString = await bankCall({
type: 'VPURSE_GET_BALANCE',
address,
denom: assetRecord.denom,
});
balanceUpdater(balanceString);

// Create and return the virtual purse.
const vpc = makePurseController(
bankCall,
assetRecord.denom,
brand,
address,
notifier,
updateBalances,
);
const vpurse = makeVirtualPurse(vpc, assetRecord);
brandToVPurse.init(brand, vpurse);
return vpurse;
},
});
addressToBank.init(address, bank);
accountsUpdater.updateState([...addressToBank.keys()]);
return bank;
};

const bankDepositFacet = Far('bankDepositFacet', {
getAccountsNotifier() {
return accountsNotifier;
},
/**
* Send many independent deposits. If any of them fail, then you should
* reclaim the corresponding payments since they didn't get deposited.
*
* @param {Array<string>} accounts
* @param {Array<Payment>} payments
* @returns {Promise<PromiseSettledResult<Amount>[]>}
*/
depositMultiple(accounts, payments) {
/**
* @param {string} account
* @param {Payment} payment
*/
const doDeposit = async (account, payment) => {
// We can get the alleged brand, because the purse we send it to
// will do the proper verification as part of deposit.
const brand = await E(payment).getAllegedBrand();
const bank = getBankForAddress(account);
const purse = bank.getPurse(brand);
return E(purse).deposit(payment);
};

// We want just a regular iterable that yields deposit promises.
function* generateDepositPromises() {
const max = Math.max(accounts.length, payments.length);
for (let i = 0; i < max; i += 1) {
// Create a deposit promise.
yield doDeposit(accounts[i], payments[i]);
}
}

// We wait for all deposits to settle so that the whole batch
// completes, even if there are failures with individual accounts,
// payments, or deposits.
return Promise.allSettled(generateDepositPromises());
},
});

return Far('bankManager', {
/**
* Returns assets as they are added to the bank.
Expand All @@ -180,6 +312,9 @@ export function buildRootObject(_vatPowers) {
getAssetSubscription() {
return harden(assetSubscription);
},
getDepositFacet() {
return bankDepositFacet;
},
/**
* Add an asset to the bank, and publish it to the subscriptions.
*
Expand Down Expand Up @@ -214,87 +349,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');
if (addressToBank.has(address)) {
return addressToBank.get(address);
}

/** @type {WeakStore<Brand, VirtualPurse>} */
const brandToVPurse = makeWeakStore('brand');

/** @type {Bank} */
const bank = Far('bank', {
getAssetSubscription() {
return harden(assetSubscription);
},
async getPurse(brand) {
if (brandToVPurse.has(brand)) {
return brandToVPurse.get(brand);
}

const assetRecord = brandToAssetRecord.get(brand);
if (!bankCall) {
// Just emulate with a real purse.
const purse = await E(assetRecord.issuer).makeEmptyPurse();
brandToVPurse.init(brand, purse);
return purse;
}

const addressToUpdater = denomToAddressUpdater.get(
assetRecord.denom,
);

/** @type {NotifierRecord<Amount>} */
const { updater, notifier } = makeNotifierKit();
/** @type {bigint} */
let lastBalanceUpdate = -1n;
/** @type {BalanceUpdater} */
const balanceUpdater = (value, nonce = undefined) => {
if (nonce !== undefined) {
const thisBalanceUpdate = BigInt(nonce);
if (thisBalanceUpdate <= lastBalanceUpdate) {
return;
}
lastBalanceUpdate = thisBalanceUpdate;
}
// Convert the string value to a bigint.
const amt = amountMath.make(brand, BigInt(value));
updater.updateState(amt);
};

// Get the initial balance.
addressToUpdater.init(address, balanceUpdater);
const balanceString = await bankCall({
type: 'VPURSE_GET_BALANCE',
address,
denom: assetRecord.denom,
});
balanceUpdater(balanceString);

// Create and return the virtual purse.
const vpc = makePurseController(
bankCall,
assetRecord.denom,
brand,
address,
notifier,
updateBalances,
);
const vpurse = makeVirtualPurse(vpc, assetRecord);
brandToVPurse.init(brand, vpurse);
return vpurse;
},
});
addressToBank.init(address, bank);
return bank;
},
getBankForAddress,
});
},
});
Expand Down
4 changes: 2 additions & 2 deletions packages/vats/src/vat-distributeFees.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Far } from '@agoric/marshal';
import { buildDistributor } from './distributeFees';
import { buildDistributor, makeTreasuryFeeCollector } from './distributeFees';

export function buildRootObject(_vatPowers) {
return Far('feeDistributor', { buildDistributor });
return Far('feeDistributor', { buildDistributor, makeTreasuryFeeCollector });
}
1 change: 1 addition & 0 deletions packages/vats/test/test-distributeFees.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function makeFakeBank() {
depositMultiple: (a, p) => {
depositAccounts.push(a);
depositPayments.push(p);
return p.map(_pmt => ({ status: 'fulfilled' }));
},

// tools for the fake:
Expand Down

0 comments on commit 9e16332

Please sign in to comment.