Skip to content

Commit af07f34

Browse files
committed
feat: StatusManager scaffold
- shows the various `StatusManager` states updates (`OBSERVED`, `ADVANCED`, `SETTLED`) via `Advancer` and `Settler` stubs
1 parent 06c998f commit af07f34

File tree

8 files changed

+612
-13
lines changed

8 files changed

+612
-13
lines changed

Diff for: packages/fast-usdc/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@agoric/orchestration": "^0.1.0",
3737
"@agoric/store": "^0.9.2",
3838
"@agoric/vow": "^0.1.0",
39+
"@endo/base64": "^1.0.8",
3940
"@endo/common": "^1.2.7",
4041
"@endo/errors": "^1.2.7",
4142
"@endo/eventual-send": "^1.2.7",

Diff for: packages/fast-usdc/src/exos/advancer.js

+129-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,142 @@
1+
import { assertAllDefined } from '@agoric/internal';
2+
import { VowShape } from '@agoric/vow';
3+
import { makeError, q } from '@endo/errors';
4+
import { E } from '@endo/far';
5+
import { M } from '@endo/patterns';
6+
import { CctpTxEvidenceShape } from '../typeGuards.js';
7+
import { addressTools } from '../utils/address.js';
8+
19
/**
10+
* @import {HostInterface} from '@agoric/async-flow';
11+
* @import {ChainAddress, ChainHub, Denom, DenomAmount, OrchestrationAccount} from '@agoric/orchestration';
12+
* @import {VowTools} from '@agoric/vow';
213
* @import {Zone} from '@agoric/zone';
3-
* @import {TransactionFeed} from './transaction-feed.js';
14+
* @import {CctpTxEvidence, NobleAddress} from '../types.js';
415
* @import {StatusManager} from './status-manager.js';
16+
* @import {TransactionFeed} from './transaction-feed.js';
517
*/
618

7-
import { assertAllDefined } from '@agoric/internal';
8-
919
/**
1020
* @param {Zone} zone
1121
* @param {object} caps
22+
* @param {ChainHub} caps.chainHub
1223
* @param {TransactionFeed} caps.feed
1324
* @param {StatusManager} caps.statusManager
25+
* @param {VowTools} caps.vowTools
1426
*/
15-
export const prepareAdvancer = (zone, { feed, statusManager }) => {
16-
assertAllDefined({ feed, statusManager });
17-
return zone.exo('Fast USDC Advancer', undefined, {});
27+
export const prepareAdvancer = (
28+
zone,
29+
{ chainHub, feed, statusManager, vowTools: { watch } },
30+
) => {
31+
assertAllDefined({ feed, statusManager, watch });
32+
33+
const transferHandler = zone.exo(
34+
'Fast USDC Advance Transfer Handler',
35+
M.interface('TransferHandlerI', {
36+
// TODO confirm undefined, and not bigint (sequence)
37+
onFulfilled: M.call(M.undefined(), {
38+
address: M.string(),
39+
amount: M.bigint(),
40+
index: M.number(),
41+
}).returns(M.undefined()),
42+
onRejected: M.call(M.error(), {
43+
address: M.string(),
44+
amount: M.bigint(),
45+
index: M.number(),
46+
}).returns(M.undefined()),
47+
}),
48+
{
49+
/**
50+
* @param {undefined} result
51+
* @param {{ address: NobleAddress; amount: bigint; index: number; }} ctx
52+
*/
53+
onFulfilled(result, { address, amount, index }) {
54+
// TODO, endow with logger
55+
console.log('@@@fulfilled', { address, amount, index, result });
56+
statusManager.advance(address, amount, index);
57+
},
58+
onRejected(error) {
59+
// XXX retry logic?
60+
// What do we do if we fail, should we keep a Status?
61+
// TODO, endow with logger
62+
console.log('@@@rejected', { error });
63+
},
64+
},
65+
);
66+
67+
return zone.exoClass(
68+
'Fast USDC Advancer',
69+
M.interface('AdvancerI', {
70+
handleEvent: M.call(CctpTxEvidenceShape).returns(VowShape),
71+
}),
72+
/**
73+
* @param {{
74+
* localDenom: Denom;
75+
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
76+
* }} config
77+
*/
78+
config => harden(config),
79+
{
80+
/**
81+
* TODO riff on name. for now, assume this is invoked when a new entry is
82+
* observed via the EventFeed.
83+
*
84+
* @param {CctpTxEvidence} event
85+
*/
86+
handleEvent(event) {
87+
// observe regardless of validation checks
88+
// TODO - should we only observe after validation checks?
89+
const entryIndex = statusManager.observe(event);
90+
91+
const {
92+
params: { EUD },
93+
} = addressTools.getQueryParams(event.aux.recipientAddress);
94+
if (!EUD) {
95+
throw makeError(
96+
`'recipientAddress' does not contain EUD param: ${q(event.aux.recipientAddress)}`,
97+
);
98+
}
99+
100+
// TODO validation checks:
101+
// 1. ensure there's enough $
102+
// 2. ensure we can find chainID
103+
// 3. ~~ensure valid PFM path~~ best observable via .transfer() vow rejection
104+
105+
const { chainId } = chainHub.getChainInfoByAddress(EUD);
106+
107+
/** @type {ChainAddress} */
108+
const destination = harden({
109+
chainId,
110+
value: EUD,
111+
encoding: /** @type {const} */ ('bech32'),
112+
});
113+
114+
/** @type {DenomAmount} */
115+
const amount = harden({
116+
denom: this.state.localDenom,
117+
value: BigInt(event.tx.amount),
118+
});
119+
120+
const transferV = E(this.state.poolAccount).transfer(
121+
destination,
122+
amount,
123+
);
124+
125+
// onFulfilled, update StatusManger with `SETTLED`
126+
// onRejected, TBD
127+
return watch(transferV, transferHandler, {
128+
index: entryIndex,
129+
address: event.tx.forwardingAddress,
130+
amount: amount.value,
131+
});
132+
},
133+
},
134+
{
135+
stateShape: harden({
136+
localDenom: M.string(),
137+
poolAccount: M.remotable(),
138+
}),
139+
},
140+
);
18141
};
19142
harden(prepareAdvancer);

Diff for: packages/fast-usdc/src/exos/settler.js

+81-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,95 @@
1+
import { assertAllDefined } from '@agoric/internal';
2+
import { atob } from '@endo/base64';
3+
import { makeError, q } from '@endo/errors';
4+
import { M } from '@endo/patterns';
5+
6+
import { addressTools } from '../utils/address.js';
7+
18
/**
9+
* @import { FungibleTokenPacketData } from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
10+
* @import {Denom} from '@agoric/orchestration';
11+
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
212
* @import {Zone} from '@agoric/zone';
13+
* @import { NobleAddress } from '../types.js';
314
* @import {StatusManager} from './status-manager.js';
415
*/
516

6-
import { assertAllDefined } from '@agoric/internal';
7-
817
/**
918
* @param {Zone} zone
1019
* @param {object} caps
1120
* @param {StatusManager} caps.statusManager
1221
*/
1322
export const prepareSettler = (zone, { statusManager }) => {
1423
assertAllDefined({ statusManager });
15-
return zone.exo('Fast USDC Settler', undefined, {});
24+
return zone.exoClass(
25+
'Fast USDC Settler',
26+
M.interface('SettlerI', {
27+
receiveUpcall: M.call(M.record()).returns(M.promise()),
28+
}),
29+
/**
30+
*
31+
* @param {{
32+
* sourceChannel: IBCChannelID;
33+
* remoteDenom: Denom
34+
* }} config
35+
*/
36+
config => harden(config),
37+
{
38+
/** @param {VTransferIBCEvent} event */
39+
async receiveUpcall(event) {
40+
if (event.packet.source_channel !== this.state.sourceChannel) {
41+
// only interested in packets from the issuing chain
42+
return;
43+
}
44+
const tx = /** @type {FungibleTokenPacketData} */ (
45+
JSON.parse(atob(event.packet.data))
46+
);
47+
if (tx.denom !== this.state.remoteDenom) {
48+
// only interested in uusdc
49+
return;
50+
}
51+
52+
if (!addressTools.hasQueryParams(tx.receiver)) {
53+
// only interested in receivers with query params
54+
return;
55+
}
56+
57+
const { params } = addressTools.getQueryParams(tx.receiver);
58+
// TODO - what's the schema address parameter schema for FUSDC?
59+
if (!params?.EUD) {
60+
// only interested in receivers with EUD parameter
61+
return;
62+
}
63+
64+
const hasPendingSettlement = statusManager.hasPendingSettlement(
65+
tx.sender,
66+
BigInt(tx.amount),
67+
);
68+
if (!hasPendingSettlement) {
69+
// TODO FAILURE PATH -> put money in recovery account or .transfer to receiver
70+
// TODO should we have a TxStatus for this?
71+
throw makeError(
72+
`🚨 No pending settlement found for ${q(tx.sender)} ${q(tx.amount)}`,
73+
);
74+
}
75+
76+
// TODO disperse funds
77+
// ~1. fee to contractFeeAccount
78+
// ~2. remainder in poolAccount
79+
80+
// update status manager, marking tx `SETTLED`
81+
statusManager.settle(
82+
/** @type {NobleAddress} */ (tx.sender),
83+
BigInt(tx.amount),
84+
);
85+
},
86+
},
87+
{
88+
stateShape: harden({
89+
sourceChannel: M.string(),
90+
remoteDenom: M.string(),
91+
}),
92+
},
93+
);
1694
};
1795
harden(prepareSettler);

0 commit comments

Comments
 (0)