-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- shows the various `StatusManager` states updates (`OBSERVED`, `ADVANCED`, `SETTLED`) via `Advancer` and `Settler` stubs
- Loading branch information
1 parent
06c998f
commit af07f34
Showing
8 changed files
with
612 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,142 @@ | ||
import { assertAllDefined } from '@agoric/internal'; | ||
import { VowShape } from '@agoric/vow'; | ||
import { makeError, q } from '@endo/errors'; | ||
import { E } from '@endo/far'; | ||
import { M } from '@endo/patterns'; | ||
import { CctpTxEvidenceShape } from '../typeGuards.js'; | ||
import { addressTools } from '../utils/address.js'; | ||
|
||
/** | ||
* @import {HostInterface} from '@agoric/async-flow'; | ||
* @import {ChainAddress, ChainHub, Denom, DenomAmount, OrchestrationAccount} from '@agoric/orchestration'; | ||
* @import {VowTools} from '@agoric/vow'; | ||
* @import {Zone} from '@agoric/zone'; | ||
* @import {TransactionFeed} from './transaction-feed.js'; | ||
* @import {CctpTxEvidence, NobleAddress} from '../types.js'; | ||
* @import {StatusManager} from './status-manager.js'; | ||
* @import {TransactionFeed} from './transaction-feed.js'; | ||
*/ | ||
|
||
import { assertAllDefined } from '@agoric/internal'; | ||
|
||
/** | ||
* @param {Zone} zone | ||
* @param {object} caps | ||
* @param {ChainHub} caps.chainHub | ||
* @param {TransactionFeed} caps.feed | ||
* @param {StatusManager} caps.statusManager | ||
* @param {VowTools} caps.vowTools | ||
*/ | ||
export const prepareAdvancer = (zone, { feed, statusManager }) => { | ||
assertAllDefined({ feed, statusManager }); | ||
return zone.exo('Fast USDC Advancer', undefined, {}); | ||
export const prepareAdvancer = ( | ||
zone, | ||
{ chainHub, feed, statusManager, vowTools: { watch } }, | ||
) => { | ||
assertAllDefined({ feed, statusManager, watch }); | ||
|
||
const transferHandler = zone.exo( | ||
'Fast USDC Advance Transfer Handler', | ||
M.interface('TransferHandlerI', { | ||
// TODO confirm undefined, and not bigint (sequence) | ||
onFulfilled: M.call(M.undefined(), { | ||
address: M.string(), | ||
amount: M.bigint(), | ||
index: M.number(), | ||
}).returns(M.undefined()), | ||
onRejected: M.call(M.error(), { | ||
address: M.string(), | ||
amount: M.bigint(), | ||
index: M.number(), | ||
}).returns(M.undefined()), | ||
}), | ||
{ | ||
/** | ||
* @param {undefined} result | ||
* @param {{ address: NobleAddress; amount: bigint; index: number; }} ctx | ||
*/ | ||
onFulfilled(result, { address, amount, index }) { | ||
// TODO, endow with logger | ||
console.log('@@@fulfilled', { address, amount, index, result }); | ||
statusManager.advance(address, amount, index); | ||
}, | ||
onRejected(error) { | ||
// XXX retry logic? | ||
// What do we do if we fail, should we keep a Status? | ||
// TODO, endow with logger | ||
console.log('@@@rejected', { error }); | ||
}, | ||
}, | ||
); | ||
|
||
return zone.exoClass( | ||
'Fast USDC Advancer', | ||
M.interface('AdvancerI', { | ||
handleEvent: M.call(CctpTxEvidenceShape).returns(VowShape), | ||
}), | ||
/** | ||
* @param {{ | ||
* localDenom: Denom; | ||
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>; | ||
* }} config | ||
*/ | ||
config => harden(config), | ||
{ | ||
/** | ||
* TODO riff on name. for now, assume this is invoked when a new entry is | ||
* observed via the EventFeed. | ||
* | ||
* @param {CctpTxEvidence} event | ||
*/ | ||
handleEvent(event) { | ||
// observe regardless of validation checks | ||
// TODO - should we only observe after validation checks? | ||
const entryIndex = statusManager.observe(event); | ||
|
||
const { | ||
params: { EUD }, | ||
} = addressTools.getQueryParams(event.aux.recipientAddress); | ||
if (!EUD) { | ||
throw makeError( | ||
`'recipientAddress' does not contain EUD param: ${q(event.aux.recipientAddress)}`, | ||
); | ||
} | ||
|
||
// TODO validation checks: | ||
// 1. ensure there's enough $ | ||
// 2. ensure we can find chainID | ||
// 3. ~~ensure valid PFM path~~ best observable via .transfer() vow rejection | ||
|
||
const { chainId } = chainHub.getChainInfoByAddress(EUD); | ||
|
||
/** @type {ChainAddress} */ | ||
const destination = harden({ | ||
chainId, | ||
value: EUD, | ||
encoding: /** @type {const} */ ('bech32'), | ||
}); | ||
|
||
/** @type {DenomAmount} */ | ||
const amount = harden({ | ||
denom: this.state.localDenom, | ||
value: BigInt(event.tx.amount), | ||
}); | ||
|
||
const transferV = E(this.state.poolAccount).transfer( | ||
destination, | ||
amount, | ||
); | ||
|
||
// onFulfilled, update StatusManger with `SETTLED` | ||
// onRejected, TBD | ||
return watch(transferV, transferHandler, { | ||
index: entryIndex, | ||
address: event.tx.forwardingAddress, | ||
amount: amount.value, | ||
}); | ||
}, | ||
}, | ||
{ | ||
stateShape: harden({ | ||
localDenom: M.string(), | ||
poolAccount: M.remotable(), | ||
}), | ||
}, | ||
); | ||
}; | ||
harden(prepareAdvancer); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,95 @@ | ||
import { assertAllDefined } from '@agoric/internal'; | ||
import { atob } from '@endo/base64'; | ||
import { makeError, q } from '@endo/errors'; | ||
import { M } from '@endo/patterns'; | ||
|
||
import { addressTools } from '../utils/address.js'; | ||
|
||
/** | ||
* @import { FungibleTokenPacketData } from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js'; | ||
* @import {Denom} from '@agoric/orchestration'; | ||
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats'; | ||
* @import {Zone} from '@agoric/zone'; | ||
* @import { NobleAddress } from '../types.js'; | ||
* @import {StatusManager} from './status-manager.js'; | ||
*/ | ||
|
||
import { assertAllDefined } from '@agoric/internal'; | ||
|
||
/** | ||
* @param {Zone} zone | ||
* @param {object} caps | ||
* @param {StatusManager} caps.statusManager | ||
*/ | ||
export const prepareSettler = (zone, { statusManager }) => { | ||
assertAllDefined({ statusManager }); | ||
return zone.exo('Fast USDC Settler', undefined, {}); | ||
return zone.exoClass( | ||
'Fast USDC Settler', | ||
M.interface('SettlerI', { | ||
receiveUpcall: M.call(M.record()).returns(M.promise()), | ||
}), | ||
/** | ||
* | ||
* @param {{ | ||
* sourceChannel: IBCChannelID; | ||
* remoteDenom: Denom | ||
* }} config | ||
*/ | ||
config => harden(config), | ||
{ | ||
/** @param {VTransferIBCEvent} event */ | ||
async receiveUpcall(event) { | ||
if (event.packet.source_channel !== this.state.sourceChannel) { | ||
// only interested in packets from the issuing chain | ||
return; | ||
} | ||
const tx = /** @type {FungibleTokenPacketData} */ ( | ||
JSON.parse(atob(event.packet.data)) | ||
); | ||
if (tx.denom !== this.state.remoteDenom) { | ||
// only interested in uusdc | ||
return; | ||
} | ||
|
||
if (!addressTools.hasQueryParams(tx.receiver)) { | ||
// only interested in receivers with query params | ||
return; | ||
} | ||
|
||
const { params } = addressTools.getQueryParams(tx.receiver); | ||
// TODO - what's the schema address parameter schema for FUSDC? | ||
if (!params?.EUD) { | ||
// only interested in receivers with EUD parameter | ||
return; | ||
} | ||
|
||
const hasPendingSettlement = statusManager.hasPendingSettlement( | ||
tx.sender, | ||
BigInt(tx.amount), | ||
); | ||
if (!hasPendingSettlement) { | ||
// TODO FAILURE PATH -> put money in recovery account or .transfer to receiver | ||
// TODO should we have a TxStatus for this? | ||
throw makeError( | ||
`🚨 No pending settlement found for ${q(tx.sender)} ${q(tx.amount)}`, | ||
); | ||
} | ||
|
||
// TODO disperse funds | ||
// ~1. fee to contractFeeAccount | ||
// ~2. remainder in poolAccount | ||
|
||
// update status manager, marking tx `SETTLED` | ||
statusManager.settle( | ||
/** @type {NobleAddress} */ (tx.sender), | ||
BigInt(tx.amount), | ||
); | ||
}, | ||
}, | ||
{ | ||
stateShape: harden({ | ||
sourceChannel: M.string(), | ||
remoteDenom: M.string(), | ||
}), | ||
}, | ||
); | ||
}; | ||
harden(prepareSettler); |
Oops, something went wrong.