-
Notifications
You must be signed in to change notification settings - Fork 212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: advancer integrates with LP and contract #10518
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
import { AmountMath, AmountShape, PaymentShape } from '@agoric/ertp'; | ||
import { assertAllDefined } from '@agoric/internal'; | ||
import { AmountMath, AmountShape } from '@agoric/ertp'; | ||
import { assertAllDefined, makeTracer } from '@agoric/internal'; | ||
import { ChainAddressShape } from '@agoric/orchestration'; | ||
import { pickFacet } from '@agoric/vat-data'; | ||
import { VowShape } from '@agoric/vow'; | ||
|
@@ -15,31 +15,25 @@ const { isGTE } = AmountMath; | |
/** | ||
* @import {HostInterface} from '@agoric/async-flow'; | ||
* @import {NatAmount} from '@agoric/ertp'; | ||
* @import {ChainAddress, ChainHub, Denom, DenomAmount, OrchestrationAccount} from '@agoric/orchestration'; | ||
* @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration'; | ||
* @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js'; | ||
* @import {VowTools} from '@agoric/vow'; | ||
* @import {Zone} from '@agoric/zone'; | ||
* @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js'; | ||
* @import {StatusManager} from './status-manager.js'; | ||
*/ | ||
|
||
/** | ||
* Expected interface from LiquidityPool | ||
* | ||
* @typedef {{ | ||
* lookupBalance(): NatAmount; | ||
* borrow(amount: Amount<"nat">): Promise<Payment<"nat">>; | ||
* repay(payments: PaymentKeywordRecord): Promise<void> | ||
* }} AssetManagerFacet | ||
* @import {LiquidityPoolKit} from './liquidity-pool.js'; | ||
*/ | ||
|
||
/** | ||
* @typedef {{ | ||
* chainHub: ChainHub; | ||
* feeConfig: FeeConfig; | ||
* log: LogFn; | ||
* localTransfer: ZoeTools['localTransfer']; | ||
* log?: LogFn; | ||
* statusManager: StatusManager; | ||
* usdc: { brand: Brand<'nat'>; denom: Denom; }; | ||
* vowTools: VowTools; | ||
* zcf: ZCF; | ||
* }} AdvancerKitPowers | ||
*/ | ||
|
||
|
@@ -49,13 +43,15 @@ const AdvancerKitI = harden({ | |
handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(), | ||
}), | ||
depositHandler: M.interface('DepositHandlerI', { | ||
onFulfilled: M.call(AmountShape, { | ||
onFulfilled: M.call(M.undefined(), { | ||
amount: AmountShape, | ||
destination: ChainAddressShape, | ||
payment: PaymentShape, | ||
tmpSeat: M.remotable(), | ||
}).returns(VowShape), | ||
onRejected: M.call(M.error(), { | ||
amount: AmountShape, | ||
destination: ChainAddressShape, | ||
payment: PaymentShape, | ||
tmpSeat: M.remotable(), | ||
}).returns(), | ||
}), | ||
transferHandler: M.interface('TransferHandlerI', { | ||
|
@@ -77,7 +73,16 @@ const AdvancerKitI = harden({ | |
*/ | ||
export const prepareAdvancerKit = ( | ||
zone, | ||
{ chainHub, feeConfig, log, statusManager, usdc, vowTools: { watch, when } }, | ||
{ | ||
chainHub, | ||
feeConfig, | ||
localTransfer, | ||
log = makeTracer('Advancer', true), | ||
statusManager, | ||
usdc, | ||
vowTools: { watch, when }, | ||
zcf, | ||
} = /** @type {AdvancerKitPowers} */ ({}), | ||
) => { | ||
assertAllDefined({ | ||
chainHub, | ||
|
@@ -95,8 +100,8 @@ export const prepareAdvancerKit = ( | |
AdvancerKitI, | ||
/** | ||
* @param {{ | ||
* assetManagerFacet: AssetManagerFacet; | ||
* poolAccount: ERef<HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>>; | ||
* borrowerFacet: LiquidityPoolKit['borrower']; | ||
* poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>; | ||
* }} config | ||
*/ | ||
config => harden(config), | ||
|
@@ -115,8 +120,7 @@ export const prepareAdvancerKit = ( | |
async handleTransactionEvent(evidence) { | ||
await null; | ||
try { | ||
// TODO poolAccount might be a vow we need to unwrap | ||
const { assetManagerFacet, poolAccount } = this.state; | ||
const { borrowerFacet, poolAccount } = this.state; | ||
const { recipientAddress } = evidence.aux; | ||
const { EUD } = addressTools.getQueryParams( | ||
recipientAddress, | ||
|
@@ -129,14 +133,12 @@ export const prepareAdvancerKit = ( | |
const advanceAmount = feeTools.calculateAdvance(requestedAmount); | ||
|
||
// TODO: consider skipping and using `borrow()`s internal balance check | ||
const poolBalance = assetManagerFacet.lookupBalance(); | ||
const poolBalance = borrowerFacet.getBalance(); | ||
if (!isGTE(poolBalance, requestedAmount)) { | ||
log( | ||
`Insufficient pool funds`, | ||
`Requested ${q(advanceAmount)} but only have ${q(poolBalance)}`, | ||
); | ||
Comment on lines
138
to
140
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: I wonder if this makes use of the ses console as it should. There's nothing to censor here. But "a more powerful distributed causal console" could be really handy. Not for this PR, but I wonder when. |
||
// report `requestedAmount`, not `advancedAmount`... do we need to | ||
// communicate net to `StatusManger` in case fees change in between? | ||
statusManager.observe(evidence); | ||
Comment on lines
-138
to
-139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's an interesting idea: compute the split at advancement time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, something to keep in our back packet. When |
||
return; | ||
} | ||
|
@@ -152,19 +154,29 @@ export const prepareAdvancerKit = ( | |
return; | ||
} | ||
|
||
const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit(); | ||
const amountKWR = harden({ USDC: advanceAmount }); | ||
try { | ||
const payment = await assetManagerFacet.borrow(advanceAmount); | ||
const depositV = E(poolAccount).deposit(payment); | ||
void watch(depositV, this.facets.depositHandler, { | ||
destination, | ||
payment, | ||
}); | ||
borrowerFacet.borrow(tmpSeat, amountKWR); | ||
} catch (e) { | ||
// `.borrow()` might fail if the balance changes since we | ||
// requested it. TODO - how to handle this? change ADVANCED -> OBSERVED? | ||
// Note: `depositHandler` handles the `.deposit()` failure | ||
// We do not expect this to fail since there are no turn boundaries | ||
// between .getBalance() and .borrow(). | ||
// We catch to report outside of the normal error flow since this is | ||
// not expected. | ||
log('🚨 advance borrow failed', q(e).toString()); | ||
} | ||
|
||
const depositV = localTransfer( | ||
tmpSeat, | ||
// @ts-expect-error LocalAccountMethods vs OrchestrationAccount | ||
poolAccount, | ||
amountKWR, | ||
); | ||
void watch(depositV, this.facets.depositHandler, { | ||
amount: advanceAmount, | ||
destination, | ||
tmpSeat, | ||
}); | ||
} catch (e) { | ||
log('Advancer error:', q(e).toString()); | ||
statusManager.observe(evidence); | ||
|
@@ -173,31 +185,33 @@ export const prepareAdvancerKit = ( | |
}, | ||
depositHandler: { | ||
/** | ||
* @param {NatAmount} amount amount returned from deposit | ||
* @param {{ destination: ChainAddress; payment: Payment<'nat'> }} ctx | ||
* @param {undefined} result | ||
* @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx | ||
*/ | ||
onFulfilled(amount, { destination }) { | ||
onFulfilled(result, { amount, destination }) { | ||
const { poolAccount } = this.state; | ||
const transferV = E(poolAccount).transfer( | ||
destination, | ||
/** @type {DenomAmount} */ ({ | ||
denom: usdc.denom, | ||
value: amount.value, | ||
}), | ||
); | ||
const transferV = E(poolAccount).transfer(destination, { | ||
denom: usdc.denom, | ||
value: amount.value, | ||
}); | ||
return watch(transferV, this.facets.transferHandler, { | ||
destination, | ||
amount, | ||
}); | ||
}, | ||
/** | ||
* @param {Error} error | ||
* @param {{ destination: ChainAddress; payment: Payment<'nat'> }} ctx | ||
* @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx | ||
*/ | ||
onRejected(error, { payment }) { | ||
// TODO return live payment from ctx to LP | ||
onRejected(error, { tmpSeat }) { | ||
// TODO return seat allocation from ctx to LP? | ||
log('🚨 advance deposit failed', q(error).toString()); | ||
log('TODO live payment to return to LP', q(payment).toString()); | ||
// TODO #10510 (comprehensive error testing) determine | ||
// course of action here | ||
Comment on lines
+207
to
+209
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this stuff is why #10510 looks to me like "the other 80% of the work". The local deposit would only fail due to things in our control at design-time (i.e. bugs), but the remote transfer can fail for runtime reasons (timeout). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The is well-put - I should include this verbiage as a code comment. Agree we can almost guarantee |
||
log( | ||
'TODO live payment on seat to return to LP', | ||
q(tmpSeat).toString(), | ||
); | ||
}, | ||
}, | ||
transferHandler: { | ||
|
@@ -206,23 +220,24 @@ export const prepareAdvancerKit = ( | |
* @param {{ destination: ChainAddress; amount: NatAmount; }} ctx | ||
*/ | ||
onFulfilled(result, { destination, amount }) { | ||
// TODO vstorage update? | ||
// TODO vstorage update? We don't currently have a status for | ||
// Advanced + transferV settled | ||
log( | ||
'Advance transfer fulfilled', | ||
q({ amount, destination, result }).toString(), | ||
); | ||
}, | ||
onRejected(error) { | ||
// XXX retry logic? | ||
// What do we do if we fail, should we keep a Status? | ||
// TODO #10510 (comprehensive error testing) determine | ||
// course of action here. This might fail due to timeout. | ||
log('Advance transfer rejected', q(error).toString()); | ||
}, | ||
}, | ||
}, | ||
{ | ||
stateShape: harden({ | ||
assetManagerFacet: M.remotable(), | ||
poolAccount: M.or(VowShape, M.remotable()), | ||
borrowerFacet: M.remotable(), | ||
poolAccount: M.remotable(), | ||
}), | ||
}, | ||
); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,22 +11,27 @@ import { | |
} from '@agoric/orchestration'; | ||
import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js'; | ||
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; | ||
import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js'; | ||
import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; | ||
import { E } from '@endo/far'; | ||
import { M, objectMap } from '@endo/patterns'; | ||
import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; | ||
import { prepareAdvancer } from './exos/advancer.js'; | ||
import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js'; | ||
import { prepareSettler } from './exos/settler.js'; | ||
import { prepareStatusManager } from './exos/status-manager.js'; | ||
import { prepareTransactionFeedKit } from './exos/transaction-feed.js'; | ||
import { defineInertInvitation } from './utils/zoe.js'; | ||
import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js'; | ||
import * as flows from './fast-usdc.flows.js'; | ||
|
||
const trace = makeTracer('FastUsdc'); | ||
|
||
/** | ||
* @import {Denom} from '@agoric/orchestration'; | ||
* @import {HostInterface} from '@agoric/async-flow'; | ||
* @import {OrchestrationAccount} from '@agoric/orchestration'; | ||
* @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js'; | ||
* @import {Vow} from '@agoric/vow'; | ||
* @import {Zone} from '@agoric/zone'; | ||
* @import {OperatorKit} from './exos/operator-kit.js'; | ||
* @import {CctpTxEvidence, FeeConfig} from './types.js'; | ||
|
@@ -73,35 +78,22 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |
); | ||
const statusManager = prepareStatusManager(zone); | ||
const makeSettler = prepareSettler(zone, { statusManager }); | ||
const { chainHub, vowTools } = tools; | ||
const { chainHub, orchestrateAll, vowTools } = tools; | ||
const { localTransfer } = makeZoeTools(zcf, vowTools); | ||
const makeAdvancer = prepareAdvancer(zone, { | ||
chainHub, | ||
feeConfig, | ||
log: trace, | ||
localTransfer, | ||
usdc: harden({ | ||
brand: terms.brands.USDC, | ||
denom: terms.usdcDenom, | ||
}), | ||
statusManager, | ||
vowTools, | ||
zcf, | ||
}); | ||
const makeFeedKit = prepareTransactionFeedKit(zone, zcf); | ||
assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager }); | ||
const feedKit = makeFeedKit(); | ||
const advancer = makeAdvancer( | ||
// @ts-expect-error FIXME | ||
{}, | ||
); | ||
// Connect evidence stream to advancer | ||
void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), { | ||
updateState(evidence) { | ||
try { | ||
void advancer.handleTransactionEvent(evidence); | ||
} catch (err) { | ||
trace('🚨 Error handling transaction event', err); | ||
} | ||
}, | ||
}); | ||
const makeLiquidityPoolKit = prepareLiquidityPoolKit( | ||
zone, | ||
zcf, | ||
|
@@ -114,16 +106,19 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |
'test of forcing evidence', | ||
); | ||
|
||
const { makeLocalAccount } = orchestrateAll(flows, {}); | ||
|
||
const creatorFacet = zone.exo('Fast USDC Creator', undefined, { | ||
/** @type {(operatorId: string) => Promise<Invitation<OperatorKit>>} */ | ||
async makeOperatorInvitation(operatorId) { | ||
// eslint-disable-next-line no-use-before-define | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't realize that #9361 hadn't landed. I'll try to get that in now. |
||
return feedKit.creator.makeOperatorInvitation(operatorId); | ||
}, | ||
/** | ||
* @param {{ USDC: Amount<'nat'>}} amounts | ||
*/ | ||
testBorrow(amounts) { | ||
console.log('🚧🚧 UNTIL: borrow is integrated 🚧🚧', amounts); | ||
console.log('🚧🚧 UNTIL: borrow is integrated (#10388) 🚧🚧', amounts); | ||
const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit(); | ||
// eslint-disable-next-line no-use-before-define | ||
poolKit.borrower.borrow(tmpAssetManagerSeat, amounts); | ||
|
@@ -136,7 +131,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |
* @returns {Promise<AmountKeywordRecord>} | ||
*/ | ||
async testRepay(amounts, payments) { | ||
console.log('🚧🚧 UNTIL: repay is integrated 🚧🚧', amounts); | ||
console.log('🚧🚧 UNTIL: repay is integrated (#10388) 🚧🚧', amounts); | ||
const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit(); | ||
await depositToSeat( | ||
zcf, | ||
|
@@ -164,6 +159,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |
* @param {CctpTxEvidence} evidence | ||
*/ | ||
makeTestPushInvitation(evidence) { | ||
// eslint-disable-next-line no-use-before-define | ||
void advancer.handleTransactionEvent(evidence); | ||
return makeTestInvitation(); | ||
}, | ||
|
@@ -207,6 +203,34 @@ export const contract = async (zcf, privateArgs, zone, tools) => { | |
makeLiquidityPoolKit(shareMint, privateArgs.storageNode), | ||
); | ||
|
||
const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit()); | ||
|
||
const poolAccountV = | ||
// cast to HostInterface | ||
/** @type { Vow<HostInterface<OrchestrationAccount<{chainId: 'agoric';}>>>} */ ( | ||
/** @type {unknown}*/ ( | ||
zone.makeOnce('Pool Local Orch Account', () => makeLocalAccount()) | ||
) | ||
); | ||
const poolAccount = await vowTools.when(poolAccountV); | ||
0xpatrickdev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const advancer = zone.makeOnce('Advancer', () => | ||
makeAdvancer({ | ||
borrowerFacet: poolKit.borrower, | ||
poolAccount, | ||
}), | ||
); | ||
// Connect evidence stream to advancer | ||
void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), { | ||
updateState(evidence) { | ||
try { | ||
void advancer.handleTransactionEvent(evidence); | ||
} catch (err) { | ||
trace('🚨 Error handling transaction event', err); | ||
} | ||
}, | ||
}); | ||
|
||
return harden({ creatorFacet, publicFacet }); | ||
}; | ||
harden(contract); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/** | ||
* @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration'; | ||
*/ | ||
|
||
/** | ||
* @satisfies {OrchestrationFlow} | ||
* @param {Orchestrator} orch | ||
*/ | ||
export const makeLocalAccount = async orch => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this our first flow? I thought we would be able to ship this without async-flow. This is fine since it runs once but I'm curious what it would take to do this without a flow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Less complicated than a const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit(
zone,
{
makeRecorderKit,
zcf,
timerService,
vowTools,
chainHub,
localchain,
zoeTools,
},
);
const account = await when(E(localchain).makeAccount());
const address = await when(E(account).getAddress());
const { holder } = makeLocalOrchestrationAccountKit({
account,
address: harden({
value: address,
encoding: 'bech32',
chainId: localChainInfo.chainId,
}),
// FIXME storage path https://github.com/Agoric/agoric-sdk/issues/9066
storageNode: childNode,
}); Something we can revisit . Also - I'm reminded that #9066 might need some love as we are getting to launch this FUSDC. |
||
const agoricChain = await orch.getChain('agoric'); | ||
return agoricChain.makeAccount(); | ||
}; | ||
harden(makeLocalAccount); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only see
zcf.makeEmptySeatKit
used. Narrow this to that 1 method?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree this would be helpful towards limiting authority, but I don't think we can do this without making a new exo with something like
prepareGuardedAttenuator
or the like.Would you still like to see this in this PR? I also suspect
Settler
will need the same - I can add a TODO if you're planning to tackle this there?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Never mind.