Skip to content

Commit

Permalink
feat(fast-usdc): support risk assessment arg
Browse files Browse the repository at this point in the history
  • Loading branch information
samsiegart committed Dec 20, 2024
1 parent 7b7ceb9 commit 2ebddaf
Show file tree
Hide file tree
Showing 20 changed files with 516 additions and 75 deletions.
50 changes: 50 additions & 0 deletions packages/boot/test/fast-usdc/fast-usdc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,56 @@ test.serial('makes usdc advance', async t => {
await documentStorageSchema(t, storage, doc);
});

test.serial('skips usdc advance when risks identified', async t => {
const { walletFactoryDriver: wd, storage } = t.context;
const oracles = await Promise.all([
wd.provideSmartWallet('agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8'),
wd.provideSmartWallet('agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr'),
wd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'),
]);

const EUD = 'dydx1riskyeud';
const lastNodeValue = storage.getValues('published.fastUsdc').at(-1);
const { settlementAccount } = JSON.parse(NonNullish(lastNodeValue));
const evidence = MockCctpTxEvidences.AGORIC_PLUS_DYDX(
// mock with the read settlementAccount address
encodeAddressHook(settlementAccount, { EUD }),
);

await Promise.all(
oracles.map(wallet =>
wallet.sendOffer({
id: 'submit-mock-evidence-dydx-risky',
invitationSpec: {
source: 'continuing',
previousOffer: 'claim-oracle-invitation',
invitationMakerName: 'SubmitEvidence',
invitationArgs: [evidence, { risksIdentified: ['TOO_LARGE_AMOUNT'] }],
},
proposal: {},
}),
),
);
await eventLoopIteration();

t.deepEqual(
storage
.getValues(`published.fastUsdc.txns.${evidence.txHash}`)
.map(defaultSerializer.parse),
[
{ evidence, status: 'OBSERVED' }, // observation includes evidence observed
{ status: 'ADVANCE_SKIPPED', risksIdentified: ['TOO_LARGE_AMOUNT'] },
],
);

const doc = {
node: `fastUsdc.txns`,
owner: `the Ethereum transactions upon which Fast USDC is acting`,
showValue: defaultSerializer.parse,
};
await documentStorageSchema(t, storage, doc);
});

test.serial('restart contract', async t => {
const { EV } = t.context.runUtils;
await null;
Expand Down
25 changes: 25 additions & 0 deletions packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,28 @@ Generated by [AVA](https://avajs.dev).
},
],
]

## skips usdc advance when risks identified

> Under "published", the "fastUsdc.txns" node is delegated to the Ethereum transactions upon which Fast USDC is acting.
> The example below illustrates the schema of the data published there.
>
> See also board marshalling conventions (_to appear_).
[
[
'published.fastUsdc.txns.0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702',
{
status: 'ADVANCING',
},
],
[
'published.fastUsdc.txns.0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
{
risksIdentified: [
'TOO_LARGE_AMOUNT',
],
status: 'ADVANCE_SKIPPED',
},
],
]
Binary file modified packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap
Binary file not shown.
8 changes: 5 additions & 3 deletions packages/fast-usdc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ stateDiagram-v2

```mermaid
stateDiagram-v2
Observed --> Advancing
Observed --> Forwarding:Minted
Observed --> AdvanceSkipped : Risks identified
Observed --> Advancing : No risks, can advance
Observed --> Forwarding : No risks, Mint deposited before advance
Forwarding --> Forwarded
Advancing --> Advanced
Advanced --> Disbursed
AdvanceFailed --> Forwarding
AdvanceSkipped --> Forwarding : Mint deposited
AdvanceFailed --> Forwarding : Mint deposited
Advancing --> AdvanceFailed
Forwarding --> ForwardFailed
```
4 changes: 4 additions & 0 deletions packages/fast-usdc/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const TxStatus = /** @type {const} */ ({
Advanced: 'ADVANCED',
/** IBC transfer failed (timed out) */
AdvanceFailed: 'ADVANCE_FAILED',
/** Advance skipped and waiting for forward */
AdvanceSkipped: 'ADVANCE_SKIPPED',
/** settlement for matching advance received and funds disbursed */
Disbursed: 'DISBURSED',
/** fallback: do not collect fees */
Expand Down Expand Up @@ -42,5 +44,7 @@ export const PendingTxStatus = /** @type {const} */ ({
AdvanceFailed: 'ADVANCE_FAILED',
/** IBC transfer is complete */
Advanced: 'ADVANCED',
/** Advance skipped and waiting for forward */
AdvanceSkipped: 'ADVANCE_SKIPPED',
});
harden(PendingTxStatus);
16 changes: 11 additions & 5 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { E } from '@endo/far';
import { M, mustMatch } from '@endo/patterns';
import { Fail, q } from '@endo/errors';
import {
CctpTxEvidenceShape,
AddressHookShape,
EvmHashShape,
EvidenceWithRiskShape,
} from '../type-guards.js';
import { makeFeeTools } from '../utils/fees.js';

Expand All @@ -22,7 +22,7 @@ import { makeFeeTools } from '../utils/fees.js';
* @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
* @import {VowTools} from '@agoric/vow';
* @import {Zone} from '@agoric/zone';
* @import {CctpTxEvidence, AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress} from '../types.js';
* @import {CctpTxEvidence, AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
* @import {StatusManager} from './status-manager.js';
* @import {LiquidityPoolKit} from './liquidity-pool.js';
*/
Expand Down Expand Up @@ -55,7 +55,7 @@ const AdvancerVowCtxShape = M.splitRecord(
/** type guards internal to the AdvancerKit */
const AdvancerKitI = harden({
advancer: M.interface('AdvancerI', {
handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(),
handleTransactionEvent: M.callWhen(EvidenceWithRiskShape).returns(),
setIntermediateRecipient: M.call(ChainAddressShape).returns(),
}),
depositHandler: M.interface('DepositHandlerI', {
Expand Down Expand Up @@ -137,16 +137,22 @@ export const prepareAdvancerKit = (
* `StatusManager` - so we don't need to concern ourselves with
* preserving the vow chain for callers.
*
* @param {CctpTxEvidence} evidence
* @param {EvidenceWithRisk} evidenceWithRisk
*/
async handleTransactionEvent(evidence) {
async handleTransactionEvent({ evidence, risk }) {
await null;
try {
if (statusManager.hasBeenObserved(evidence)) {
log('txHash already seen:', evidence.txHash);
return;
}

if (risk.risksIdentified?.length) {
log('risks identified, skipping advance');
statusManager.skipAdvance(evidence, risk.risksIdentified);
return;
}

const { borrowerFacet, poolAccount, settlementAddress } =
this.state;
const { recipientAddress } = evidence.aux;
Expand Down
24 changes: 15 additions & 9 deletions packages/fast-usdc/src/exos/operator-kit.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { makeTracer } from '@agoric/internal';
import { Fail } from '@endo/errors';
import { M } from '@endo/patterns';
import { CctpTxEvidenceShape } from '../type-guards.js';
import { CctpTxEvidenceShape, RiskAssessmentShape } from '../type-guards.js';

const trace = makeTracer('TxOperator');

/**
* @import {Zone} from '@agoric/zone';
* @import {CctpTxEvidence} from '../types.js';
* @import {CctpTxEvidence, RiskAssessment} from '../types.js';
*/

/**
* @typedef {object} OperatorPowers
* @property {(evidence: CctpTxEvidence, operatorId: string) => void} attest
* @property {(evidence: CctpTxEvidence, riskAssessment: RiskAssessment, operatorId: string) => void} attest
*/

/**
Expand All @@ -31,11 +31,15 @@ const OperatorKitI = {
}),

invitationMakers: M.interface('InvitationMakers', {
SubmitEvidence: M.call(CctpTxEvidenceShape).returns(M.promise()),
SubmitEvidence: M.call(CctpTxEvidenceShape)
.optional(RiskAssessmentShape)
.returns(M.promise()),
}),

operator: M.interface('Operator', {
submitEvidence: M.call(CctpTxEvidenceShape).returns(),
submitEvidence: M.call(CctpTxEvidenceShape)
.optional(RiskAssessmentShape)
.returns(),
getStatus: M.call().returns(M.record()),
}),
};
Expand Down Expand Up @@ -81,13 +85,14 @@ export const prepareOperatorKit = (zone, staticPowers) =>
* fluxAggregator contract used for price oracles.
*
* @param {CctpTxEvidence} evidence
* @param {RiskAssessment} [riskAssessment]
* @returns {Promise<Invitation>}
*/
async SubmitEvidence(evidence) {
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);
operator.submitEvidence(evidence, riskAssessment);
return staticPowers.makeInertInvitation(
'evidence was pushed in the invitation maker call',
);
Expand All @@ -98,12 +103,13 @@ export const prepareOperatorKit = (zone, staticPowers) =>
* submit evidence from this operator
*
* @param {CctpTxEvidence} evidence
* @param {RiskAssessment} [riskAssessment]
* @returns {void}
*/
submitEvidence(evidence) {
submitEvidence(evidence, riskAssessment = {}) {
const { state } = this;
!state.disabled || Fail`submitEvidence for disabled operator`;
state.powers.attest(evidence, state.operatorId);
state.powers.attest(evidence, riskAssessment, state.operatorId);
},
/** @returns {OperatorStatus} */
getStatus() {
Expand Down
1 change: 1 addition & 0 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export const prepareSettler = (
return;

case PendingTxStatus.Observed:
case PendingTxStatus.AdvanceSkipped:
case PendingTxStatus.AdvanceFailed:
return self.forward(found.txHash, nfa, amount, EUD);

Expand Down
33 changes: 29 additions & 4 deletions packages/fast-usdc/src/exos/status-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
/**
* @import {MapStore, SetStore} from '@agoric/store';
* @import {Zone} from '@agoric/zone';
* @import {CctpTxEvidence, NobleAddress, PendingTx, EvmHash, LogFn, TransactionRecord} from '../types.js';
* @import {CctpTxEvidence, NobleAddress, PendingTx, EvmHash, LogFn, TransactionRecord, EvidenceWithRisk, RiskAssessment} from '../types.js';
*/

/**
Expand Down Expand Up @@ -146,8 +146,9 @@ export const prepareStatusManager = (
*
* @param {CctpTxEvidence} evidence
* @param {PendingTxStatus} status
* @param {string[]} [risksIdentified]
*/
const initPendingTx = (evidence, status) => {
const initPendingTx = (evidence, status, risksIdentified) => {
const { txHash } = evidence;
if (seenTxs.has(txHash)) {
throw makeError(`Transaction already seen: ${q(txHash)}`);
Expand All @@ -160,7 +161,9 @@ export const prepareStatusManager = (
harden({ ...evidence, status }),
);
publishEvidence(txHash, evidence);
if (status !== PendingTxStatus.Observed) {
if (status === PendingTxStatus.AdvanceSkipped) {
void publishTxnRecord(txHash, harden({ status, risksIdentified }));
} else if (status !== PendingTxStatus.Observed) {
// publishEvidence publishes Observed
void publishTxnRecord(txHash, harden({ status }));
}
Expand Down Expand Up @@ -194,6 +197,9 @@ export const prepareStatusManager = (
// TODO: naming scheme for transition events
advance: M.call(CctpTxEvidenceShape).returns(M.undefined()),
advanceOutcome: M.call(M.string(), M.nat(), M.boolean()).returns(),
skipAdvance: M.call(CctpTxEvidenceShape, M.arrayOf(M.string())).returns(
M.undefined(),
),
observe: M.call(CctpTxEvidenceShape).returns(M.undefined()),
hasBeenObserved: M.call(CctpTxEvidenceShape).returns(M.boolean()),
deleteCompletedTxs: M.call().returns(M.undefined()),
Expand All @@ -203,6 +209,7 @@ export const prepareStatusManager = (
txHash: EvmHashShape,
status: M.or(
PendingTxStatus.Advanced,
PendingTxStatus.AdvanceSkipped,
PendingTxStatus.AdvanceFailed,
PendingTxStatus.Observed,
),
Expand All @@ -224,14 +231,32 @@ export const prepareStatusManager = (
/**
* Add a new transaction with ADVANCING status
*
* NB: this acts like observe() but skips recording the OBSERVED state
* NB: this acts like observe() but subsequently records an ADVANCING
* state
*
* @param {CctpTxEvidence} evidence
*/
advance(evidence) {
initPendingTx(evidence, PendingTxStatus.Advancing);
},

/**
* Add a new transaction with ADVANCE_SKIPPED status
*
* NB: this acts like observe() but subsequently records an
* ADVANCE_SKIPPED state along with risks identified
*
* @param {CctpTxEvidence} evidence
* @param {string[]} risksIdentified
*/
skipAdvance(evidence, risksIdentified) {
initPendingTx(
evidence,
PendingTxStatus.AdvanceSkipped,
risksIdentified,
);
},

/**
* Record result of ADVANCING
*
Expand Down
Loading

0 comments on commit 2ebddaf

Please sign in to comment.