From 9809fe7503afc4d010ea27ce44d73cb8dafa2d01 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 7 Jan 2025 17:55:32 -0600 Subject: [PATCH 01/11] feat(fast-usdc): creatorFacet method to withdraw fees - test: 1 positive, 1 negative - liquidity pool: feeRecipient facet, withdrawFeeHandler facet - diagnostics for insufficient fees available - contract: creatorFacet method - typed proposal shape --- .../fast-usdc/snapshots/fast-usdc.test.ts.md | 122 +++++++++++------- packages/fast-usdc/src/exos/liquidity-pool.js | 40 +++++- packages/fast-usdc/src/fast-usdc.contract.js | 7 + packages/fast-usdc/src/pool-share-math.js | 3 + packages/fast-usdc/src/type-guards.js | 6 +- .../fast-usdc/test/fast-usdc.contract.test.ts | 48 ++++++- .../snapshots/fast-usdc.contract.test.ts.md | 2 + .../snapshots/fast-usdc.contract.test.ts.snap | Bin 5841 -> 5882 bytes 8 files changed, 175 insertions(+), 53 deletions(-) diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md index 68ea14d4de8..b223719d6ec 100644 --- a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md +++ b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md @@ -132,51 +132,6 @@ Generated by [AVA](https://avajs.dev). ], ] -## writes pool metrics to vstorage - -> Under "published", the "fastUsdc.poolMetrics" node is delegated to FastUSC LiquidityPool exo. -> The example below illustrates the schema of the data published there. -> -> See also board marshalling conventions (_to appear_). - - [ - [ - 'published.fastUsdc.poolMetrics', - { - encumberedBalance: { - brand: Object @Alleged: USDC brand {}, - value: 0n, - }, - shareWorth: { - denominator: { - brand: Object @Alleged: PoolShares brand {}, - value: 1n, - }, - numerator: { - brand: Object @Alleged: USDC brand {}, - value: 1n, - }, - }, - totalBorrows: { - brand: Object @Alleged: USDC brand {}, - value: 0n, - }, - totalContractFees: { - brand: Object @Alleged: USDC brand {}, - value: 0n, - }, - totalPoolFees: { - brand: Object @Alleged: USDC brand {}, - value: 0n, - }, - totalRepays: { - brand: Object @Alleged: USDC brand {}, - value: 0n, - }, - }, - ], - ] - ## writes account addresses to vstorage > Under "published", the "fastUsdc" node is delegated to FastUSDC contract. @@ -230,7 +185,66 @@ Generated by [AVA](https://avajs.dev). [ 'published.fastUsdc.txns.0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', { - status: 'ADVANCING', + split: { + ContractFee: { + brand: Object @Alleged: USDC brand {}, + value: 302000n, + }, + PoolFee: { + brand: Object @Alleged: USDC brand {}, + value: 1208000n, + }, + Principal: { + brand: Object @Alleged: USDC brand {}, + value: 148490000n, + }, + }, + status: 'DISBURSED', + }, + ], + ] + +## writes pool metrics to vstorage + +> Under "published", the "fastUsdc.poolMetrics" node is delegated to FastUSC LiquidityPool exo. +> The example below illustrates the schema of the data published there. +> +> See also board marshalling conventions (_to appear_). + + [ + [ + 'published.fastUsdc.poolMetrics', + { + encumberedBalance: { + brand: Object @Alleged: USDC brand {}, + value: 0n, + }, + shareWorth: { + denominator: { + brand: Object @Alleged: PoolShares brand {}, + value: 150000001n, + }, + numerator: { + brand: Object @Alleged: USDC brand {}, + value: 151208001n, + }, + }, + totalBorrows: { + brand: Object @Alleged: USDC brand {}, + value: 148490000n, + }, + totalContractFees: { + brand: Object @Alleged: USDC brand {}, + value: 302000n, + }, + totalPoolFees: { + brand: Object @Alleged: USDC brand {}, + value: 1208000n, + }, + totalRepays: { + brand: Object @Alleged: USDC brand {}, + value: 148490000n, + }, }, ], ] @@ -246,7 +260,21 @@ Generated by [AVA](https://avajs.dev). [ 'published.fastUsdc.txns.0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702', { - status: 'ADVANCING', + split: { + ContractFee: { + brand: Object @Alleged: USDC brand {}, + value: 302000n, + }, + PoolFee: { + brand: Object @Alleged: USDC brand {}, + value: 1208000n, + }, + Principal: { + brand: Object @Alleged: USDC brand {}, + value: 148490000n, + }, + }, + status: 'DISBURSED', }, ], [ diff --git a/packages/fast-usdc/src/exos/liquidity-pool.js b/packages/fast-usdc/src/exos/liquidity-pool.js index 9116fb08062..34b16ce807c 100644 --- a/packages/fast-usdc/src/exos/liquidity-pool.js +++ b/packages/fast-usdc/src/exos/liquidity-pool.js @@ -1,4 +1,4 @@ -import { AmountMath } from '@agoric/ertp'; +import { AmountMath, AmountShape } from '@agoric/ertp'; import { makeRecorderTopic, TopicsRecordShape, @@ -107,11 +107,18 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { withdrawHandler: M.interface('withdrawHandler', { handle: M.call(SeatShape, M.any()).returns(M.promise()), }), + withdrawFeesHandler: M.interface('withdrawFeesHandler', { + handle: M.call(SeatShape, M.any()).returns(M.promise()), + }), public: M.interface('public', { makeDepositInvitation: M.call().returns(M.promise()), makeWithdrawInvitation: M.call().returns(M.promise()), getPublicTopics: M.call().returns(TopicsRecordShape), }), + feeRecipient: M.interface('feeRecipient', { + getContractFeeBalance: M.call().returns(AmountShape), + makeWithdrawFeesInvitation: M.call().returns(M.promise()), + }), }, /** * @param {ZCFMint<'nat'>} shareMint @@ -326,6 +333,21 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { external.publishPoolMetrics(); }, }, + withdrawFeesHandler: { + /** @param {ZCFSeat} seat */ + async handle(seat) { + const { feeSeat } = this.state; + + const { want } = seat.getProposal(); + const available = feeSeat.getAmountAllocated('USDC', want.USDC.brand); + isGTE(available, want.USDC) || + Fail`cannot withdraw ${want.USDC}; only ${available} available`; + + // COMMIT POINT + zcf.atomicRearrange(harden([[feeSeat, seat, want]])); + seat.exit(); + }, + }, public: { makeDepositInvitation() { return zcf.makeInvitation( @@ -353,6 +375,22 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => { }; }, }, + feeRecipient: { + getContractFeeBalance() { + const { feeSeat } = this.state; + /** @type {Amount<'nat'>} */ + const balance = feeSeat.getCurrentAllocation().USDC; + return balance; + }, + makeWithdrawFeesInvitation() { + return zcf.makeInvitation( + this.facets.withdrawFeesHandler, + 'Withdraw Fees', + undefined, + this.state.proposalShapes.withdrawFees, + ); + }, + }, }, { finish: ({ facets: { external } }) => { diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 8970b8a8117..cfd060f86f3 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -161,6 +161,13 @@ export const contract = async (zcf, privateArgs, zone, tools) => { removeOperator(operatorId) { return feedKit.creator.removeOperator(operatorId); }, + async getContractFeeBalance() { + return poolKit.feeRecipient.getContractFeeBalance(); + }, + /** @type {() => Promise>} */ + async makeWithdrawFeesInvitation() { + return poolKit.feeRecipient.makeWithdrawFeesInvitation(); + }, async connectToNoble() { return vowTools.when(nobleAccountV, nobleAccount => { trace('nobleAccount', nobleAccount); diff --git a/packages/fast-usdc/src/pool-share-math.js b/packages/fast-usdc/src/pool-share-math.js index d208c8aadf6..1d9994a985d 100644 --- a/packages/fast-usdc/src/pool-share-math.js +++ b/packages/fast-usdc/src/pool-share-math.js @@ -44,6 +44,9 @@ export const makeParity = (numerator, denominatorBrand) => { * withdraw: { * give: { PoolShare: Amount<'nat'> } * want: { USDC: Amount<'nat'> }, + * }, + * withdrawFees: { + * want: { USDC: Amount<'nat'> } * } * }} USDCProposalShapes */ diff --git a/packages/fast-usdc/src/type-guards.js b/packages/fast-usdc/src/type-guards.js index b365f53dbb7..82f801044c2 100644 --- a/packages/fast-usdc/src/type-guards.js +++ b/packages/fast-usdc/src/type-guards.js @@ -34,7 +34,11 @@ export const makeProposalShapes = ({ PoolShares, USDC }) => { give: { PoolShare: makeNatAmountShape(PoolShares, 1n) }, want: { USDC: makeNatAmountShape(USDC, 1n) }, }); - return harden({ deposit, withdraw }); + /** @type {TypedPattern} */ + const withdrawFees = M.splitRecord({ + want: { USDC: makeNatAmountShape(USDC, 1n) }, + }); + return harden({ deposit, withdraw, withdrawFees }); }; /** @type {TypedPattern} */ diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index ebd1bd5caea..1ad44bac49d 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -5,6 +5,7 @@ import { decodeAddressHook, encodeAddressHook, } from '@agoric/cosmic-proto/address-hooks.js'; +import type { Amount, Issuer, NatValue, Purse } from '@agoric/ertp'; import { AmountMath } from '@agoric/ertp/src/amountMath.js'; import { eventLoopIteration, @@ -31,15 +32,14 @@ import { E } from '@endo/far'; import { matches } from '@endo/patterns'; import { makePromiseKit } from '@endo/promise-kit'; import path from 'path'; -import type { Amount, Issuer, NatValue, Purse } from '@agoric/ertp'; -import type { OperatorKit } from '../src/exos/operator-kit.js'; +import type { OperatorOfferResult } from '../src/exos/transaction-feed.js'; import type { FastUsdcSF } from '../src/fast-usdc.contract.js'; -import { CctpTxEvidenceShape, PoolMetricsShape } from '../src/type-guards.js'; +import type { USDCProposalShapes } from '../src/pool-share-math.js'; +import { PoolMetricsShape } from '../src/type-guards.js'; import type { CctpTxEvidence, FeeConfig, PoolMetrics } from '../src/types.js'; import { makeFeeTools } from '../src/utils/fees.js'; import { MockCctpTxEvidences } from './fixtures.js'; import { commonSetup, uusdcOnAgoric } from './supports.js'; -import type { OperatorOfferResult } from '../src/exos/transaction-feed.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -780,6 +780,46 @@ test.serial('STORY05(cont): LPs withdraw all liquidity', async t => { t.truthy(b); }); +test.serial('withdraw fees using creatorFacet', async t => { + const { + startKit: { zoe, creatorFacet }, + common: { + brands: { usdc }, + }, + } = t.context; + const proposal: USDCProposalShapes['withdrawFees'] = { + want: { USDC: usdc.units(1.25) }, + }; + + const usdPurse = await E(usdc.issuer).makeEmptyPurse(); + { + const balancePre = await E(creatorFacet).getContractFeeBalance(); + t.log('contract fee balance before withdrawal', balancePre); + const toWithdraw = await E(creatorFacet).makeWithdrawFeesInvitation(); + const seat = E(zoe).offer(toWithdraw, proposal); + await t.notThrowsAsync(E(seat).getOfferResult()); + const payout = await E(seat).getPayout('USDC'); + const amt = await E(usdPurse).deposit(payout); + t.log('withdrew fees', amt); + t.deepEqual(amt, usdc.units(1.25)); + const balancePost = await E(creatorFacet).getContractFeeBalance(); + t.log('contract fee balance after withdrawal', balancePost); + t.deepEqual(AmountMath.subtract(balancePre, usdc.units(1.25)), balancePost); + } + + { + const toWithdraw = await E(creatorFacet).makeWithdrawFeesInvitation(); + const tooMuch = { USDC: usdc.units(20) }; + const seat = E(zoe).offer(toWithdraw, { want: tooMuch }); + await t.throwsAsync(E(seat).getOfferResult(), { + message: /cannot withdraw {.*}; only {.*} available/, + }); + const payout = await E(seat).getPayout('USDC'); + const amt = await E(usdPurse).deposit(payout); + t.deepEqual(amt, usdc.units(0)); + } +}); + test.serial('STORY09: insufficient liquidity: no FastUSDC option', async t => { // STORY09 - As the Fast USDC end user, // I should see the option to use Fast USDC unavailable diff --git a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md index abab8df6efd..2c66c2e5ca9 100644 --- a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md +++ b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md @@ -587,8 +587,10 @@ Generated by [AVA](https://avajs.dev). borrower: Object @Alleged: Liquidity Pool borrower {}, depositHandler: Object @Alleged: Liquidity Pool depositHandler {}, external: Object @Alleged: Liquidity Pool external {}, + feeRecipient: Object @Alleged: Liquidity Pool feeRecipient {}, public: Object @Alleged: Liquidity Pool public {}, repayer: Object @Alleged: Liquidity Pool repayer {}, + withdrawFeesHandler: Object @Alleged: Liquidity Pool withdrawFeesHandler {}, withdrawHandler: Object @Alleged: Liquidity Pool withdrawHandler {}, }, 'Liquidity Pool_kindHandle': 'Alleged: kind', diff --git a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap index 07715403500491ee6710fbbd04df01f97ac872bf..0f69db9c66b2cfec576de27fa49d215477176f60 100644 GIT binary patch literal 5882 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 Qe^@;9Kkqgy9oTgM0AadvVE_OC literal 5841 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 Date: Tue, 7 Jan 2025 18:04:18 -0600 Subject: [PATCH 02/11] core(fast-usdc): fix FastUSDCKit['creatorFacet'] type --- packages/fast-usdc/src/start-fast-usdc.core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fast-usdc/src/start-fast-usdc.core.js b/packages/fast-usdc/src/start-fast-usdc.core.js index 1474c17fdc6..8960096633d 100644 --- a/packages/fast-usdc/src/start-fast-usdc.core.js +++ b/packages/fast-usdc/src/start-fast-usdc.core.js @@ -79,7 +79,7 @@ const POOL_METRICS = 'poolMetrics'; * }} FastUSDCCorePowers * * @typedef {StartedInstanceKitWithLabel & { - * creatorFacet: Awaited>['creatorFacet']; + * creatorFacet: StartedInstanceKit['creatorFacet']; * privateArgs: StartParams['privateArgs']; * }} FastUSDCKit */ From 57ff31735708f25a3e0d748b948d63b9b4de9591 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 8 Jan 2025 22:50:05 -0600 Subject: [PATCH 03/11] feat(fast-usdc): core eval script to distribute fees --- .../fast-usdc/src/distribute-fees.core.js | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 packages/fast-usdc/src/distribute-fees.core.js diff --git a/packages/fast-usdc/src/distribute-fees.core.js b/packages/fast-usdc/src/distribute-fees.core.js new file mode 100644 index 00000000000..edd1d9ab9e0 --- /dev/null +++ b/packages/fast-usdc/src/distribute-fees.core.js @@ -0,0 +1,93 @@ +/** @file core eval module to collect fees. */ +import { AmountMath } from '@agoric/ertp'; +import { floorMultiplyBy } from '@agoric/zoe/src/contractSupport/index.js'; +import { E } from '@endo/far'; +import { makeTracer } from '@agoric/internal'; +import { fromExternalConfig } from './utils/config-marshal.js'; + +/** + * @import {DepositFacet} from '@agoric/ertp'; + * @import {FastUSDCCorePowers} from '@agoric/fast-usdc/src/start-fast-usdc.core.js'; + * @import {CopyRecord} from '@endo/pass-style' + * @import {BootstrapManifestPermit} from '@agoric/vats/src/core/lib-boot.js' + * @import {LegibleCapData} from './utils/config-marshal.js' + */ + +/** + * @typedef {{ destinationAddress: string } & + * ({ feePortion: Ratio} | {fixedFees: Amount<'nat'>}) & + * CopyRecord + * } FeeDistributionTerms + */ + +const kwUSDC = 'USDC'; // keyword in AmountKeywordRecord +const issUSDC = 'USDC'; // issuer name + +const trace = makeTracer('FUCF', true); + +/** + * @param {BootstrapPowers & FastUSDCCorePowers } permittedPowers + * @param {{ options: LegibleCapData<{ feeTerms: FeeDistributionTerms}> }} config + */ +export const distributeFees = async (permittedPowers, config) => { + trace('distributeFees...', config.options); + + const { agoricNames, namesByAddress, zoe } = permittedPowers.consume; + /** @type {Brand<'nat'>} */ + const usdcBrand = await E(agoricNames).lookup('brand', issUSDC); + /** @type {{ feeTerms: FeeDistributionTerms}} */ + const { feeTerms: terms } = fromExternalConfig(config.options, { + USDC: usdcBrand, + }); + + const { creatorFacet } = await permittedPowers.consume.fastUsdcKit; + const want = { + [kwUSDC]: await ('fixedFees' in terms + ? terms.fixedFees + : E(creatorFacet) + .getContractFeeBalance() + .then(balance => floorMultiplyBy(balance, terms.feePortion))), + }; + const proposal = harden({ want }); + + /** @type {DepositFacet} */ + const depositFacet = await E(namesByAddress).lookup( + terms.destinationAddress, + 'depositFacet', + ); + trace('to:', terms.destinationAddress, depositFacet); + + const toWithdraw = await E(creatorFacet).makeWithdrawFeesInvitation(); + trace('invitation:', toWithdraw, 'proposal:', proposal); + const seat = E(zoe).offer(toWithdraw, proposal); + const result = await E(seat).getOfferResult(); + trace('offer result', result); + const payout = await E(seat).getPayout(kwUSDC); + /** @type {Amount<'nat'>} */ + // @ts-expect-error USDC is a nat brand + const rxd = await E(depositFacet).receive(payout); + trace('received', rxd); + if (!AmountMath.isGTE(rxd, proposal.want[kwUSDC])) { + trace('🚨 expected', proposal.want[kwUSDC], 'got', rxd); + } + trace('done'); +}; +harden(distributeFees); + +/** @satisfies {BootstrapManifestPermit} */ +const permit = { + consume: { + fastUsdcKit: true, + agoricNames: true, + namesByAddress: true, + zoe: true, + }, +}; + +/** + * @param {unknown} _utils + * @param {Parameters[1]} config + */ +export const getManifestForDistributeFees = (_utils, { options }) => { + return { manifest: { [distributeFees.name]: permit }, options }; +}; From 27989b95cb49745ec4f736ee968ea512f920ead6 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 14 Jan 2025 16:09:26 -0600 Subject: [PATCH 04/11] refactor: extract publishDisplayInfo - prune unused type imports - FastLP token name is decided; prune UNTIL --- .../fast-usdc/src/start-fast-usdc.core.js | 38 +++++-------------- packages/fast-usdc/src/utils/core-eval.js | 23 +++++++++-- 2 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/fast-usdc/src/start-fast-usdc.core.js b/packages/fast-usdc/src/start-fast-usdc.core.js index 8960096633d..6f7efc8f590 100644 --- a/packages/fast-usdc/src/start-fast-usdc.core.js +++ b/packages/fast-usdc/src/start-fast-usdc.core.js @@ -1,22 +1,22 @@ import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; -import { Fail } from '@endo/errors'; import { E } from '@endo/far'; -import { makeMarshal } from '@endo/marshal'; import { FastUSDCConfigShape } from './type-guards.js'; import { fromExternalConfig } from './utils/config-marshal.js'; -import { inviteOracles, publishFeedPolicy } from './utils/core-eval.js'; +import { + inviteOracles, + publishDisplayInfo, + publishFeedPolicy, +} from './utils/core-eval.js'; /** - * @import {Amount, Brand, DepositFacet, Issuer, Payment} from '@agoric/ertp'; - * @import {TypedPattern} from '@agoric/internal' + * @import {Brand, Issuer} from '@agoric/ertp'; * @import {Instance, StartParams} from '@agoric/zoe/src/zoeService/utils' * @import {Board} from '@agoric/vats' * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js' * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js' - * @import {Passable} from '@endo/pass-style' * @import {LegibleCapData} from './utils/config-marshal.js' * @import {FastUsdcSF} from './fast-usdc.contract.js' - * @import {FeedPolicy, FastUSDCConfig} from './types.js' + * @import {FastUSDCConfig} from './types.js' */ const ShareAssetInfo = /** @type {const} */ harden({ @@ -46,26 +46,6 @@ const makePublishingStorageKit = async (path, { chainStorage, board }) => { return { storageNode, marshaller }; }; -const BOARD_AUX = 'boardAux'; -const marshalData = makeMarshal(_val => Fail`data only`); -/** - * @param {Brand} brand - * @param {Pick} powers - */ -const publishDisplayInfo = async (brand, { board, chainStorage }) => { - // chainStorage type includes undefined, which doesn't apply here. - // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247 - const boardAux = E(chainStorage).makeChildNode(BOARD_AUX); - const [id, displayInfo, allegedName] = await Promise.all([ - E(board).getId(brand), - E(brand).getDisplayInfo(), - E(brand).getAllegedName(), - ]); - const node = E(boardAux).makeChildNode(id); - const aux = marshalData.toCapData(harden({ allegedName, displayInfo })); - await E(node).setValue(JSON.stringify(aux)); -}; - const POOL_METRICS = 'poolMetrics'; /** @@ -245,10 +225,10 @@ export const getManifestForFastUSDC = ( board: true, }, issuer: { - produce: { FastLP: true }, // UNTIL #10432 + produce: { FastLP: true }, }, brand: { - produce: { FastLP: true }, // UNTIL #10432 + produce: { FastLP: true }, }, instance: { produce: { fastUsdc: true }, diff --git a/packages/fast-usdc/src/utils/core-eval.js b/packages/fast-usdc/src/utils/core-eval.js index 70784d97eb2..d5ca7d72abc 100644 --- a/packages/fast-usdc/src/utils/core-eval.js +++ b/packages/fast-usdc/src/utils/core-eval.js @@ -6,9 +6,7 @@ import { makeMarshal } from '@endo/marshal'; const trace = makeTracer('FUCoreEval'); /** - * @import {Amount, Brand, DepositFacet, Issuer, Payment} from '@agoric/ertp'; - * @import {Passable} from '@endo/pass-style' - * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js' + * @import {Brand, DepositFacet} from '@agoric/ertp'; * @import {FastUSDCKit} from '../start-fast-usdc.core.js' * @import {FeedPolicy} from '../types.js' */ @@ -54,3 +52,22 @@ export const inviteOracles = async ( }), ); }; + +const BOARD_AUX = 'boardAux'; +/** + * @param {Brand} brand + * @param {Pick} powers + */ +export const publishDisplayInfo = async (brand, { board, chainStorage }) => { + // chainStorage type includes undefined, which doesn't apply here. + // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247 + const boardAux = E(chainStorage).makeChildNode(BOARD_AUX); + const [id, displayInfo, allegedName] = await Promise.all([ + E(board).getId(brand), + E(brand).getDisplayInfo(), + E(brand).getAllegedName(), + ]); + const node = E(boardAux).makeChildNode(id); + const aux = marshalData.toCapData(harden({ allegedName, displayInfo })); + await E(node).setValue(JSON.stringify(aux)); +}; From d56b6fed4135d17a0b4ccd4d38216979dd3b15ea Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 8 Jan 2025 22:58:13 -0600 Subject: [PATCH 05/11] chore(boot): snapshot non-trivial FastUSDC metrics move metrics test after advance --- .../boot/test/fast-usdc/fast-usdc.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index 308b020e32d..69d52204b06 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -175,16 +175,6 @@ test.serial('writes fee config to vstorage', async t => { await documentStorageSchema(t, storage, doc); }); -test.serial('writes pool metrics to vstorage', async t => { - const { storage } = t.context; - const doc = { - node: 'fastUsdc.poolMetrics', - owner: 'FastUSC LiquidityPool exo', - showValue: defaultSerializer.parse, - }; - await documentStorageSchema(t, storage, doc); -}); - test.serial('writes account addresses to vstorage', async t => { const { storage } = t.context; const doc = { @@ -345,6 +335,16 @@ test.serial('makes usdc advance', async t => { await documentStorageSchema(t, storage, doc); }); +test.serial('writes pool metrics to vstorage', async t => { + const { storage } = t.context; + const doc = { + node: 'fastUsdc.poolMetrics', + owner: 'FastUSC LiquidityPool exo', + showValue: defaultSerializer.parse, + }; + await documentStorageSchema(t, storage, doc); +}); + test.serial('skips usdc advance when risks identified', async t => { const { walletFactoryDriver: wfd, storage } = t.context; const oracles = await Promise.all([ From 7776298772a6e5178573fc3078ab0a5fbfea9f8a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 8 Jan 2025 23:52:23 -0600 Subject: [PATCH 06/11] feat(boot): distribute FastUSDC contract fee builder, test --- .../boot/test/fast-usdc/fast-usdc.test.ts | 89 +++++++++++++++--- .../snapshots/fast-usdc.test.ts.snap | Bin 3158 -> 3399 bytes .../scripts/fast-usdc/fast-usdc-fees.build.js | 75 +++++++++++++++ 3 files changed, 150 insertions(+), 14 deletions(-) create mode 100644 packages/builders/scripts/fast-usdc/fast-usdc-fees.build.js diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index 69d52204b06..cdd599cb545 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -5,15 +5,11 @@ import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; import { configurations } from '@agoric/fast-usdc/src/utils/deploy-config.js'; import { MockCctpTxEvidences } from '@agoric/fast-usdc/test/fixtures.js'; import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; -import { - BridgeId, - deeplyFulfilledObject, - NonNullish, - objectMap, -} from '@agoric/internal'; +import { BridgeId, NonNullish } from '@agoric/internal'; import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js'; import { defaultSerializer } from '@agoric/internal/src/storage-test-utils.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; import { Fail } from '@endo/errors'; import { makeMarshal } from '@endo/marshal'; import { @@ -288,7 +284,9 @@ test.serial('makes usdc advance', async t => { const EUD = 'dydx1anything'; const lastNodeValue = storage.getValues('published.fastUsdc').at(-1); - const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue)); + const { settlementAccount, poolAccount } = JSON.parse( + NonNullish(lastNodeValue), + ); const evidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO( // mock with the real settlementAccount address encodeAddressHook(settlementAccount, { EUD }), @@ -317,16 +315,53 @@ test.serial('makes usdc advance', async t => { ); harness?.resetRunPolicy(); - t.deepEqual( + const getTxStatus = txHash => storage - .getValues(`published.fastUsdc.txns.${evidence.txHash}`) - .map(defaultSerializer.parse), - [ - { evidence, status: 'OBSERVED' }, // observation includes evidence observed - { status: 'ADVANCING' }, - ], + .getValues(`published.fastUsdc.txns.${txHash}`) + .map(defaultSerializer.parse); + + t.deepEqual(getTxStatus(evidence.txHash), [ + { evidence, status: 'OBSERVED' }, // observation includes evidence observed + { status: 'ADVANCING' }, + ]); + + const { runInbound } = t.context.bridgeUtils; + await runInbound( + BridgeId.VTRANSFER, + buildVTransferEvent({ + sender: poolAccount, + target: poolAccount, + sourceChannel: 'channel-62', + sequence: '1', + }), ); + // in due course, minted USDC arrives + await runInbound( + BridgeId.VTRANSFER, + + buildVTransferEvent({ + sequence: '1', // arbitrary; not used + amount: evidence.tx.amount, + denom: 'uusdc', + sender: evidence.tx.forwardingAddress, + target: settlementAccount, + receiver: encodeAddressHook(settlementAccount, { EUD }), + sourceChannel: evidence.aux.forwardingChannel, + destinationChannel: 'channel-62', // fetchedChainInfo + // destinationChannel: evidence.aux.forwardingChannel, + // sourceChannel: 'channel-62', // fetchedChainInfo + }), + ); + + await eventLoopIteration(); + t.like(getTxStatus(evidence.txHash), [ + { status: 'OBSERVED' }, + { status: 'ADVANCING' }, + { status: 'ADVANCED' }, + { status: 'DISBURSED', split: { ContractFee: { value: 302000n } } }, + ]); + const doc = { node: `fastUsdc.txns`, owner: `the Ethereum transactions upon which Fast USDC is acting`, @@ -345,6 +380,32 @@ test.serial('writes pool metrics to vstorage', async t => { await documentStorageSchema(t, storage, doc); }); +test.serial('distributes fees per BLD staker decision', async t => { + const { walletFactoryDriver: wd, buildProposal, evalProposal } = t.context; + + const ContractFee = 302000n; // see split above + t.is(((ContractFee - 250000n) * 5n) / 10n, 26000n); + const cases = [ + { dest: 'agoric1a', args: ['--fixedFees', '0.25'], rxd: '250000' }, + { dest: 'agoric1b', args: ['--feePortion', '0.5'], rxd: '26000' }, + ]; + for (const { dest, args, rxd } of cases) { + await wd.provideSmartWallet(dest); + const materials = buildProposal( + '@agoric/builders/scripts/fast-usdc/fast-usdc-fees.build.js', + ['--destinationAddress', dest, ...args], + ); + await evalProposal(materials); + + const { getOutboundMessages } = t.context.bridgeUtils; + const found = getOutboundMessages(BridgeId.BANK).find( + msg => msg.recipient === dest && msg.type === 'VBANK_GIVE', + ); + t.log('dest vbank msg', found); + t.like(found, { amount: rxd }); + } +}); + test.serial('skips usdc advance when risks identified', async t => { const { walletFactoryDriver: wfd, storage } = t.context; const oracles = await Promise.all([ diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap index 852bea26923c4c262982e74aba56d0bd8dca7277..48697d3e3c23878c14b6d7c15b43ea3e01505438 100644 GIT binary patch literal 3399 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 | --feePortion ] --destinationAddress

...'; + +const xVatCtx = /** @type {const} */ ({ + /** @type {Brand<'nat'>} */ + USDC: Far('USDC Brand'), +}); +const { USDC } = xVatCtx; +const USDC_DECIMALS = 6n; +const unit = AmountMath.make(USDC, 10n ** USDC_DECIMALS); + +/** + * @param {unknown} _utils + * @param {FeeDistributionTerms} feeTerms + * @satisfies {CoreEvalBuilder} + */ +export const feeProposalBuilder = async (_utils, feeTerms) => { + return harden({ + sourceSpec: '@agoric/fast-usdc/src/distribute-fees.core.js', + /** @type {[string, Parameters[1]]} */ + getManifestCall: [ + getManifestForDistributeFees.name, + { options: toExternalConfig(harden({ feeTerms }), xVatCtx) }, + ], + }); +}; + +/** @type {DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { writeCoreEval } = await makeHelpers(homeP, endowments); + /** @type {{ values: Record }} */ + const { + values: { destinationAddress, ...opt }, + } = parseArgs({ + args: endowments.scriptArgs, + options: { + destinationAddress: { type: 'string' }, + fixedFees: { type: 'string' }, + feePortion: { type: 'string' }, + }, + }); + if (!destinationAddress) assert.fail(usage); + if (opt.fixedFees && opt.feePortion) assert.fail(usage); + + /** @type {FeeDistributionTerms} */ + const feeTerms = { + destinationAddress, + ...((opt.fixedFees && { + fixedFees: multiplyBy(unit, parseRatio(opt.fixedFees, USDC)), + }) || + (opt.feePortion && { + feePortion: parseRatio(opt.feePortion, USDC), + }) || + assert.fail(usage)), + }; + await writeCoreEval('eval-fast-usdc-fees', utils => + feeProposalBuilder(utils, feeTerms), + ); +}; From 5472228237defc189a53140ce92c0e1f2f1da6ff Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 14 Jan 2025 13:08:47 -0600 Subject: [PATCH 07/11] test: skip fast-usdc in test:xs pending #10847 --- packages/boot/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/boot/package.json b/packages/boot/package.json index 1226c3b8065..facfda69714 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -9,12 +9,13 @@ "build": "exit 0", "clean": "rm -rf bundles/config.*", "test": "ava", - "test:xs": "SWINGSET_WORKER_TYPE=xs-worker ava test/bootstrapTests test/upgrading test/fast-usdc", + "test:xs": "SWINGSET_WORKER_TYPE=xs-worker ava test/bootstrapTests test/upgrading", "lint-fix": "yarn lint:eslint --fix", "lint": "run-s --continue-on-error lint:*", "lint:types": "tsc", "lint:eslint": "eslint ." }, + "$scripts-note": "fast-usdc skipped in test:xs pending #10847", "keywords": [], "author": "Agoric", "license": "Apache-2.0", From 5ff84e2ff6393e2723ca01c9c81323abe44288ed Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 13 Jan 2025 14:33:46 -0600 Subject: [PATCH 08/11] chore: generateMnemonic() allows caller to inject source of randomness --- multichain-testing/tools/wallet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multichain-testing/tools/wallet.ts b/multichain-testing/tools/wallet.ts index 44e62ce684c..b7144d97dae 100644 --- a/multichain-testing/tools/wallet.ts +++ b/multichain-testing/tools/wallet.ts @@ -1,8 +1,8 @@ import { Bip39, Random } from '@cosmjs/crypto'; import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; -export function generateMnemonic(): string { - return Bip39.encode(Random.getBytes(16)).toString(); +export function generateMnemonic(getBytes = Random.getBytes): string { + return Bip39.encode(getBytes(16)).toString(); } export const createWallet = async ( From ca5100acf64727c6f032184e5275bdcefa25c5ef Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 14 Jan 2025 18:32:07 -0600 Subject: [PATCH 09/11] docs(e2e-tools): note BLD needed for provisionSmartWallet --- multichain-testing/tools/e2e-tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multichain-testing/tools/e2e-tools.js b/multichain-testing/tools/e2e-tools.js index 448f8b81895..f99d43e0aa4 100644 --- a/multichain-testing/tools/e2e-tools.js +++ b/multichain-testing/tools/e2e-tools.js @@ -559,7 +559,7 @@ export const makeE2ETools = async ( runCoreEval: buildAndRunCoreEval, /** * @param {string} address - * @param {Record} amount + * @param {Record} amount - should include BLD to pay for provisioning */ provisionSmartWallet: (address, amount) => provisionSmartWallet(address, amount, { From fa458fa1374151ae9676e4efe60e0a80bf0a6048 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 13 Jan 2025 14:35:18 -0600 Subject: [PATCH 10/11] refactor(fast-usdc.test): organize imports --- .../test/fast-usdc/fast-usdc.test.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/multichain-testing/test/fast-usdc/fast-usdc.test.ts b/multichain-testing/test/fast-usdc/fast-usdc.test.ts index c86361e20b6..2ef014bb98c 100644 --- a/multichain-testing/test/fast-usdc/fast-usdc.test.ts +++ b/multichain-testing/test/fast-usdc/fast-usdc.test.ts @@ -1,27 +1,27 @@ import anyTest from '@endo/ses-ava/prepare-endo.js'; -import type { TestFn } from 'ava'; import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js'; +import type { QueryBalanceResponseSDKType } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; import { AmountMath } from '@agoric/ertp'; -import type { Denom } from '@agoric/orchestration'; -import { divideBy, multiplyBy } from '@agoric/zoe/src/contractSupport/ratio.js'; -import type { IBCChannelID } from '@agoric/vats'; -import { makeDoOffer, type WalletDriver } from '../../tools/e2e-tools.js'; -import { makeDenomTools } from '../../tools/asset-info.js'; -import { createWallet } from '../../tools/wallet.js'; -import { makeQueryClient } from '../../tools/query.js'; -import { commonSetup, type SetupContextWithWallets } from '../support.js'; -import { makeFeedPolicyPartial, oracleMnemonics } from './config.js'; -import { makeRandomDigits } from '../../tools/random.js'; -import { makeTracer } from '@agoric/internal'; +import type { USDCProposalShapes } from '@agoric/fast-usdc/src/pool-share-math.js'; import type { CctpTxEvidence, EvmAddress, PoolMetrics, } from '@agoric/fast-usdc/src/types.js'; +import { makeTracer } from '@agoric/internal'; +import type { Denom } from '@agoric/orchestration'; import type { CurrentWalletRecord } from '@agoric/smart-wallet/src/smartWallet.js'; -import type { QueryBalanceResponseSDKType } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; -import type { USDCProposalShapes } from '@agoric/fast-usdc/src/pool-share-math.js'; +import type { IBCChannelID } from '@agoric/vats'; +import { divideBy, multiplyBy } from '@agoric/zoe/src/contractSupport/ratio.js'; +import type { TestFn } from 'ava'; +import { makeDenomTools } from '../../tools/asset-info.js'; +import { makeDoOffer, type WalletDriver } from '../../tools/e2e-tools.js'; +import { makeQueryClient } from '../../tools/query.js'; +import { makeRandomDigits } from '../../tools/random.js'; +import { createWallet } from '../../tools/wallet.js'; +import { commonSetup, type SetupContextWithWallets } from '../support.js'; +import { makeFeedPolicyPartial, oracleMnemonics } from './config.js'; const log = makeTracer('MCFU'); From cfa9f78c0533239ffed99b09b21187151d560b12 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 13 Jan 2025 14:37:25 -0600 Subject: [PATCH 11/11] test(multichain): distribute FastUSDC contract fees --- .../test/fast-usdc/fast-usdc.test.ts | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/multichain-testing/test/fast-usdc/fast-usdc.test.ts b/multichain-testing/test/fast-usdc/fast-usdc.test.ts index 2ef014bb98c..81de8db9128 100644 --- a/multichain-testing/test/fast-usdc/fast-usdc.test.ts +++ b/multichain-testing/test/fast-usdc/fast-usdc.test.ts @@ -33,6 +33,7 @@ const makeRandomNumber = () => Math.random(); const test = anyTest as TestFn< SetupContextWithWallets & { lpUser: WalletDriver; + feeUser: WalletDriver; oracleWds: WalletDriver[]; nobleAgoricChannelId: IBCChannelID; usdcOnOsmosis: Denom; @@ -41,7 +42,7 @@ const test = anyTest as TestFn< } >; -const accounts = [...keys(oracleMnemonics), 'lp']; +const accounts = [...keys(oracleMnemonics), 'lp', 'feeDest']; const contractName = 'fastUsdc'; const contractBuilder = '../packages/builders/scripts/fast-usdc/start-fast-usdc.build.js'; @@ -95,10 +96,12 @@ test.before(async t => { USDC: 8_000n, BLD: 100n, }); + const feeUser = await provisionSmartWallet(wallets['feeDest'], { BLD: 100n }); t.context = { ...common, lpUser, + feeUser, oracleWds, nobleAgoricChannelId, usdcOnOsmosis, @@ -401,6 +404,33 @@ const advanceAndSettleScenario = test.macro({ }, }); +test('distribute FastUSDC contract fees', async t => { + const io = t.context; + const queryClient = makeQueryClient( + await io.useChain('agoric').getRestEndpoint(), + ); + const builder = + '../packages/builders/scripts/fast-usdc/fast-usdc-fees.build.js'; + + const opts = { + destinationAddress: io.wallets['feeDest'], + feePortion: 0.25, + }; + t.log('build, run proposal to distribute fees', opts); + await io.deployBuilder(builder, { + ...opts, + feePortion: `${opts.feePortion}`, + }); + + const { balance } = await io.retryUntilCondition( + () => queryClient.queryBalance(opts.destinationAddress, io.usdcDenom), + ({ balance }) => !!balance && BigInt(balance.amount) > 0n, + `fees received at ${opts.destinationAddress}`, + ); + t.log('fees received', balance); + t.truthy(balance?.amount); +}); + test.serial(advanceAndSettleScenario, LP_DEPOSIT_AMOUNT / 4n, 'osmosis'); test.serial(advanceAndSettleScenario, LP_DEPOSIT_AMOUNT / 8n, 'noble'); test.serial(advanceAndSettleScenario, LP_DEPOSIT_AMOUNT / 5n, 'agoric');