Skip to content

Commit

Permalink
Fast USDC beta polish (#10909)
Browse files Browse the repository at this point in the history
closes: #10432

## Description

Clean up for beta release. See clean commits for changes.

### Security Considerations
none

### Scaling Considerations
punting on the extra writes matter that @0xpatrickdev brought up:
- #10718

### Documentation Considerations
none

### Testing Considerations
CI

### Upgrade Considerations
Adds stateShape tests to help quickly detect and schema changes (which presently would prevent upgrade)
  • Loading branch information
mergify[bot] authored Jan 29, 2025
2 parents 6498ae1 + 450b953 commit 83c2ebd
Show file tree
Hide file tree
Showing 45 changed files with 636 additions and 126 deletions.
3 changes: 3 additions & 0 deletions a3p-integration/proposals/b:beta-fast-usdc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# proposal for deploying a beta of Fast USDC

Because this runs before `n:upgrade-next` its base image isn't a build of the local agoric-sdk. So it can't use `yarn link` to get `@agoric/fast-usdc` from the source tree. Instead it sources the packages from NPM using `dev` to get the latest master builds.
6 changes: 0 additions & 6 deletions a3p-integration/proposals/f:fast-usdc/README.md

This file was deleted.

2 changes: 1 addition & 1 deletion packages/fast-usdc/src/cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { addConfigCommands } from './config-commands.js';
import { addOperatorCommands } from './operator-commands.js';
import * as configLib from './config.js';
import transferLib from './transfer.js';
import { makeFile } from '../util/file.js';
import { makeFile } from './util/file.js';
import { addLPCommands } from './lp-commands.js';

const packageJson = JSON.parse(
Expand Down
2 changes: 1 addition & 1 deletion packages/fast-usdc/src/cli/config-commands.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @import {Command} from 'commander';
* @import {File} from '../util/file.js';
* @import {File} from './util/file.js';
* @import * as ConfigHelpers from './config.js';
*/
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/fast-usdc/src/cli/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { stdin as input, stdout as output } from 'node:process';
}} ConfigOpts
*/

/** @import { File } from '../util/file' */
/** @import { File } from './util/file' */

export const init = async (
/** @type {File} */ configFile,
Expand Down
10 changes: 5 additions & 5 deletions packages/fast-usdc/src/cli/transfer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import {
pickEndpoint,
} from '@agoric/client-utils';
import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
import { queryFastUSDCLocalChainAccount } from '../util/agoric.js';
import { depositForBurn, makeProvider } from '../util/cctp.js';
import { queryFastUSDCLocalChainAccount } from './util/agoric.js';
import { depositForBurn, makeProvider } from './util/cctp.js';
import {
makeSigner,
queryForwardingAccount,
registerFwdAccount,
} from '../util/noble.js';
import { queryUSDCBalance } from '../util/bank.js';
} from './util/noble.js';
import { queryUSDCBalance } from './util/bank.js';

/** @import { File } from '../util/file' */
/** @import { File } from './util/file' */
/** @import { VStorage } from '@agoric/client-utils' */
/** @import { SigningStargateClient } from '@cosmjs/stargate' */
/** @import { JsonRpcProvider as ethProvider } from 'ethers' */
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
42 changes: 22 additions & 20 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ const AdvancerKitI = harden({
* }} AdvancerVowCtx
*/

export const stateShape = harden({
notifier: M.remotable(),
borrower: M.remotable(),
poolAccount: M.remotable(),
intermediateRecipient: M.opt(ChainAddressShape),
settlementAddress: M.opt(ChainAddressShape),
});

/**
* @param {Zone} zone
* @param {AdvancerKitPowers} caps
Expand Down Expand Up @@ -114,8 +122,8 @@ export const prepareAdvancerKit = (
AdvancerKitI,
/**
* @param {{
* notifyFacet: import('./settler.js').SettlerKit['notify'];
* borrowerFacet: LiquidityPoolKit['borrower'];
* notifier: import('./settler.js').SettlerKit['notifier'];
* borrower: LiquidityPoolKit['borrower'];
* poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
* settlementAddress: ChainAddress;
* intermediateRecipient?: ChainAddress;
Expand Down Expand Up @@ -165,9 +173,9 @@ export const prepareAdvancerKit = (
const destination = chainHub.makeChainAddress(EUD);

const fullAmount = toAmount(evidence.tx.amount);
const { borrowerFacet, notifyFacet, poolAccount } = this.state;
const { borrower, notifier, poolAccount } = this.state;
// do not advance if we've already received a mint/settlement
const mintedEarly = notifyFacet.checkMintedEarly(
const mintedEarly = notifier.checkMintedEarly(
evidence,
destination,
);
Expand All @@ -178,7 +186,7 @@ export const prepareAdvancerKit = (

const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
// throws if the pool has insufficient funds
borrowerFacet.borrow(tmpSeat, advanceAmount);
borrower.borrow(tmpSeat, advanceAmount);

// this cannot throw since `.isSeen()` is called in the same turn
statusManager.advance(evidence);
Expand Down Expand Up @@ -249,9 +257,9 @@ export const prepareAdvancerKit = (
error,
);
try {
const { borrowerFacet, notifyFacet } = this.state;
notifyFacet.notifyAdvancingResult(restCtx, false);
borrowerFacet.returnToPool(tmpSeat, advanceAmount);
const { borrower, notifier } = this.state;
notifier.notifyAdvancingResult(restCtx, false);
borrower.returnToPool(tmpSeat, advanceAmount);
} catch (e) {
log('🚨 deposit to localOrchAccount failure recovery failed', e);
}
Expand All @@ -263,37 +271,31 @@ export const prepareAdvancerKit = (
* @param {AdvancerVowCtx} ctx
*/
onFulfilled(result, ctx) {
const { notifyFacet } = this.state;
const { notifier } = this.state;
const { advanceAmount, destination, ...detail } = ctx;
log('Advance succeeded', { advanceAmount, destination });
// During development, due to a bug, this call threw.
// The failure was silent (no diagnostics) due to:
// - #10576 Vows do not report unhandled rejections
// For now, the advancer kit relies on consistency between
// notifyFacet, statusManager, and callers of handleTransactionEvent().
// notify, statusManager, and callers of handleTransactionEvent().
// TODO: revisit #10576 during #10510
notifyFacet.notifyAdvancingResult({ destination, ...detail }, true);
notifier.notifyAdvancingResult({ destination, ...detail }, true);
},
/**
* @param {Error} error
* @param {AdvancerVowCtx} ctx
*/
onRejected(error, ctx) {
const { notifyFacet } = this.state;
const { notifier } = this.state;
log('Advance failed', error);
const { advanceAmount: _, ...restCtx } = ctx;
notifyFacet.notifyAdvancingResult(restCtx, false);
notifier.notifyAdvancingResult(restCtx, false);
},
},
},
{
stateShape: harden({
notifyFacet: M.remotable(),
borrowerFacet: M.remotable(),
poolAccount: M.remotable(),
settlementAddress: ChainAddressShape,
intermediateRecipient: M.opt(ChainAddressShape),
}),
stateShape,
},
);
};
Expand Down
22 changes: 20 additions & 2 deletions packages/fast-usdc/src/exos/liquidity-pool.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { AmountMath, AmountShape } from '@agoric/ertp';
import { AmountMath, AmountShape, RatioShape } from '@agoric/ertp';
import {
makeRecorderTopic,
RecorderKitShape,
TopicsRecordShape,
} from '@agoric/zoe/src/contractSupport/topics.js';
} from '@agoric/zoe/src/contractSupport/index.js';
import { SeatShape } from '@agoric/zoe/src/typeGuards.js';
import { M } from '@endo/patterns';
import { Fail, q } from '@endo/errors';
Expand Down Expand Up @@ -48,6 +49,22 @@ const { add, isGTE, makeEmpty } = AmountMath;
* }} RepayPaymentKWR
*/

export const stateShape = harden({
encumberedBalance: AmountShape,
feeSeat: M.remotable(),
poolStats: M.record(),
poolMetricsRecorderKit: RecorderKitShape,
poolSeat: M.remotable(),
PoolShares: M.remotable(),
proposalShapes: {
deposit: M.pattern(),
withdraw: M.pattern(),
withdrawFees: M.pattern(),
},
shareMint: M.remotable(),
shareWorth: RatioShape,
});

/**
* @param {Zone} zone
* @param {ZCF} zcf
Expand Down Expand Up @@ -384,6 +401,7 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
finish: ({ facets: { external } }) => {
void external.publishPoolMetrics();
},
stateShape,
},
);
};
Expand Down
2 changes: 0 additions & 2 deletions packages/fast-usdc/src/exos/operator-kit.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,6 @@ export const prepareOperatorKit = (zone, staticPowers) =>
*/
async SubmitEvidence(evidence, riskAssessment) {
const { operator } = this.facets;
// TODO(bootstrap integration): cause this call to throw and confirm that it
// shows up in the the smart-wallet UpdateRecord `error` property
operator.submitEvidence(evidence, riskAssessment);
return staticPowers.makeInertInvitation(
'evidence was pushed in the invitation maker call',
Expand Down
112 changes: 67 additions & 45 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,61 @@ import {
* @import {Amount, Brand, NatValue, Payment} from '@agoric/ertp';
* @import {Denom, OrchestrationAccount, ChainHub, ChainAddress} from '@agoric/orchestration';
* @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
* @import {IBCChannelID, IBCPacket, VTransferIBCEvent} from '@agoric/vats';
* @import {Zone} from '@agoric/zone';
* @import {HostOf, HostInterface} from '@agoric/async-flow';
* @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js';
* @import {NobleAddress, LiquidityPoolKit, FeeConfig, EvmHash, LogFn, CctpTxEvidence} from '../types.js';
* @import {StatusManager} from './status-manager.js';
*/

/**
* @param {IBCPacket} data
* @param {string} remoteDenom
* @returns {{ nfa: NobleAddress, amount: bigint, EUD: string } | {error: object[]}}
*/
const decodeEventPacket = ({ data }, remoteDenom) => {
// NB: may not be a FungibleTokenPacketData or even JSON
/** @type {FungibleTokenPacketData} */
let tx;
try {
tx = JSON.parse(atob(data));
} catch (e) {
return { error: ['could not parse packet data', data] };
}

// given the sourceChannel check, we can be certain of this cast
const nfa = /** @type {NobleAddress} */ (tx.sender);

if (tx.denom !== remoteDenom) {
const { denom: actual } = tx;
return { error: ['unexpected denom', { actual, expected: remoteDenom }] };
}

let EUD;
try {
({ EUD } = decodeAddressHook(tx.receiver).query);
if (!EUD) {
return { error: ['no EUD parameter', tx.receiver] };
}
if (typeof EUD !== 'string') {
return { error: ['EUD is not a string', EUD] };
}
} catch (e) {
return { error: ['no query params', tx.receiver] };
}

let amount;
try {
amount = BigInt(tx.amount);
} catch (e) {
return { error: ['invalid amount', tx.amount] };
}

return { nfa, amount, EUD };
};
harden(decodeEventPacket);

/**
* NOTE: not meant to be parsable.
*
Expand All @@ -45,6 +92,16 @@ export const makeAdvanceDetailsShape = USDC =>
txHash: EvmHashShape,
});

export const stateShape = harden({
repayer: M.remotable('Repayer'),
settlementAccount: M.remotable('Account'),
registration: M.or(M.undefined(), M.remotable('Registration')),
sourceChannel: M.string(),
remoteDenom: M.string(),
mintedEarly: M.remotable('mintedEarly'),
intermediateRecipient: M.opt(ChainAddressShape),
});

/**
* @param {Zone} zone
* @param {object} caps
Expand Down Expand Up @@ -81,7 +138,7 @@ export const prepareSettler = (
tap: M.interface('SettlerTapI', {
receiveUpcall: M.call(M.record()).returns(M.promise()),
}),
notify: M.interface('SettlerNotifyI', {
notifier: M.interface('SettlerNotifyI', {
notifyAdvancingResult: M.call(
makeAdvanceDetailsShape(USDC),
M.boolean(),
Expand Down Expand Up @@ -148,38 +205,13 @@ export const prepareSettler = (
return;
}

// TODO: why is it safe to cast this without a runtime check?
const tx = /** @type {FungibleTokenPacketData} */ (
JSON.parse(atob(packet.data))
);

// given the sourceChannel check, we can be certain of this cast
const nfa = /** @type {NobleAddress} */ (tx.sender);

if (tx.denom !== remoteDenom) {
const { denom: actual } = tx;
log('unexpected denom', { actual, expected: remoteDenom });
return;
}

let EUD;
try {
({ EUD } = decodeAddressHook(tx.receiver).query);
if (!EUD) {
log('no EUD parameter', tx.receiver);
return;
}
if (typeof EUD !== 'string') {
log('EUD is not a string', EUD);
return;
}
} catch (e) {
log('no query params', tx.receiver);
const decoded = decodeEventPacket(event.packet, remoteDenom);
if ('error' in decoded) {
log('invalid event packet', decoded.error);
return;
}

const amount = BigInt(tx.amount); // TODO: what if this throws?

const { nfa, amount, EUD } = decoded;
const { self } = this.facets;
const found = statusManager.dequeueStatus(nfa, amount);
log('dequeued', found, 'for', nfa, amount);
Expand All @@ -206,7 +238,7 @@ export const prepareSettler = (
}
},
},
notify: {
notifier: {
/**
* @param {object} ctx
* @param {EvmHash} ctx.txHash
Expand Down Expand Up @@ -337,21 +369,11 @@ export const prepareSettler = (
},
},
{
stateShape: harden({
repayer: M.remotable('Repayer'),
settlementAccount: M.remotable('Account'),
registration: M.or(M.undefined(), M.remotable('Registration')),
sourceChannel: M.string(),
remoteDenom: M.string(),
mintedEarly: M.remotable('mintedEarly'),
intermediateRecipient: M.opt(ChainAddressShape),
}),
stateShape,
},
);
};
harden(prepareSettler);

/**
* XXX consider using pickFacet (do we have pickFacets?)
* @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit
*/
// Expose the whole kit because the contract needs `creatorFacet` and the Advancer needs `notifier`
/** @typedef {ReturnType<ReturnType<typeof prepareSettler>>} SettlerKit */
Loading

0 comments on commit 83c2ebd

Please sign in to comment.