Skip to content

Commit

Permalink
feat: Advancer exo behaviors
Browse files Browse the repository at this point in the history
- refs: #10390
  • Loading branch information
0xpatrickdev committed Nov 11, 2024
1 parent a92b0ec commit 229333d
Show file tree
Hide file tree
Showing 4 changed files with 369 additions and 141 deletions.
119 changes: 88 additions & 31 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AmountMath, BrandShape } from '@agoric/ertp';
import { assertAllDefined } from '@agoric/internal';
import { ChainAddressShape } from '@agoric/orchestration';
import { VowShape } from '@agoric/vow';
Expand Down Expand Up @@ -28,7 +29,7 @@ import { addressTools } from '../utils/address.js';
*/
export const prepareAdvancer = (
zone,
{ chainHub, feed, log, statusManager, vowTools: { watch } },
{ chainHub, feed, log, statusManager, vowTools: { watch, when } },
) => {
assertAllDefined({ feed, statusManager, watch });

Expand All @@ -51,6 +52,7 @@ export const prepareAdvancer = (
* @param {{ destination: ChainAddress; amount: bigint; }} ctx
*/
onFulfilled(result, { destination, amount }) {
// TODO vstorage update?
log(
'Advance transfer fulfilled',
q({ amount, destination, result }).toString(),
Expand All @@ -67,57 +69,112 @@ export const prepareAdvancer = (
return zone.exoClass(
'Fast USDC Advancer',
M.interface('AdvancerI', {
handleTransactionEvent: M.call(CctpTxEvidenceShape).returns(VowShape),
handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(
M.or(M.undefined(), VowShape),
),
}),
/**
* @param {{
* localDenom: Denom;
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
* usdcBrand: Brand<'nat'>;
* }} config
*/
config => harden(config),
{
/** @param {CctpTxEvidence} evidence */
handleTransactionEvent(evidence) {
// TODO EventFeed will perform input validation checks.
const { recipientAddress } = evidence.aux;
const { EUD } = addressTools.getQueryParams(recipientAddress).params;
if (!EUD) {
statusManager.observe(evidence);
throw makeError(
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
);
}
/**
* Returns a Promise for a Vow , since we we `await vt.when()`the
* `poolAccount.getBalance()` call. `getBalance()` interfaces with
* `BankManager` and `ChainHub`, so we can expect the calls to resolve
* promptly. We also don't care how many time `.getBalance` is called,
* and watched vows are not dependent on its result.
*
* This also might return `undefined` (unwrapped) if precondition checks
* fail.
*
* We do not expect any callers to depend on the settlement of
* `handleTransactionEvent` - errors caught are communicated to the
* `StatusManager` - so we don't need to concern ourselves with
* preserving the vow chain for callers.
*
* @param {CctpTxEvidence} evidence
*/
async handleTransactionEvent(evidence) {
await null;
try {
const { recipientAddress } = evidence.aux;
const { EUD } = addressTools.getQueryParams(recipientAddress).params;
if (!EUD) {
throw makeError(
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
);
}

// TODO #10391 this can throw, and should make a status update in the catch
const destination = chainHub.makeChainAddress(EUD);
// this will throw if the bech32 prefix is not found, but is handled by the catch
const destination = chainHub.makeChainAddress(EUD);

/** @type {DenomAmount} */
const requestedAmount = harden({
denom: this.state.localDenom,
value: BigInt(evidence.tx.amount),
});
/** @type {DenomAmount} */
const requestedAmount = harden({
denom: this.state.localDenom,
value: BigInt(evidence.tx.amount),
});
/**
* Ensure there's enough funds in poolAccount.
*
* It's safe to await here since we don't care how many
* times we call `getBalance`. Our later Vow call - `transferV` and
* its ctx - are also not reliant on the consistency of this value.
*/
const poolBalance = await when(
E(this.state.poolAccount).getBalance(this.state.localDenom),
);

// TODO #10391 ensure there's enough funds in poolAccount
if (
!AmountMath.isGTE(
AmountMath.make(this.state.usdcBrand, poolBalance.value),
AmountMath.make(this.state.usdcBrand, requestedAmount.value),
)
) {
log(
`Insufficient pool funds`,
`Requested ${q(requestedAmount)} but only have ${q(poolBalance)}`,
);
statusManager.observe(evidence);
return;
}

const transferV = E(this.state.poolAccount).transfer(
destination,
requestedAmount,
);
try {
// mark as Advanced since `transferV` initiates the advance
// will throw if we've already .skipped or .advanced this evidence
statusManager.advance(evidence);
} catch (e) {
// only anticipated error is `assertNotSeen`, so
// intercept the catch so we don't call .skip which
// also performs this check
log('Advancer error:', q(e).toString());
return;
}

// mark as Advanced since `transferV` initiates the advance
statusManager.advance(evidence);
const transferV = E(this.state.poolAccount).transfer(
destination,
requestedAmount,
);

return watch(transferV, transferHandler, {
destination,
amount: requestedAmount.value,
});
return watch(transferV, transferHandler, {
destination,
amount: requestedAmount.value,
});
} catch (e) {
log(`Advancer error:`, q(e).toString());
statusManager.observe(evidence);
}
},
},
{
stateShape: harden({
localDenom: M.string(),
poolAccount: M.remotable(),
usdcBrand: BrandShape,
}),
},
);
Expand Down
Loading

0 comments on commit 229333d

Please sign in to comment.