-
Notifications
You must be signed in to change notification settings - Fork 215
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` MapStore states updates (`OBSERVED`, `ADVANCED`) via `Advancer` and `Settler` stubs
- Loading branch information
1 parent
631deab
commit 1d2679d
Showing
12 changed files
with
894 additions
and
15 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 |
---|---|---|
@@ -0,0 +1,26 @@ | ||
## **StatusManager** state diagram, showing different transitions | ||
|
||
|
||
### Contract state diagram | ||
|
||
*Transactions are qualified by the OCW and EventFeed before arriving to the Advancer.* | ||
|
||
```mermaid | ||
stateDiagram-v2 | ||
[*] --> Advanced: Advancer .advance() | ||
Advanced --> Settled: Settler .settle() after fees | ||
[*] --> Observed: Advancer .observed() | ||
Observed --> Settled: Settler .settle() sans fees | ||
Settled --> [*] | ||
``` | ||
|
||
### Complete state diagram (starting from OCW) | ||
|
||
```mermaid | ||
stateDiagram-v2 | ||
Observed --> Qualified | ||
Observed --> Unqualified | ||
Qualified --> Advanced | ||
Advanced --> Settled | ||
Qualified --> Settled | ||
``` |
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,125 @@ | ||
import { assertAllDefined } from '@agoric/internal'; | ||
import { ChainAddressShape } from '@agoric/orchestration'; | ||
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, LogFn} 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 {LogFn} caps.log | ||
* @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, log, 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(), { | ||
amount: M.bigint(), | ||
destination: ChainAddressShape, | ||
}).returns(M.undefined()), | ||
onRejected: M.call(M.error(), { | ||
amount: M.bigint(), | ||
destination: ChainAddressShape, | ||
}).returns(M.undefined()), | ||
}), | ||
{ | ||
/** | ||
* @param {undefined} result TODO confirm this is not a bigint (sequence) | ||
* @param {{ destination: ChainAddress; amount: bigint; }} ctx | ||
*/ | ||
onFulfilled(result, { destination, amount }) { | ||
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? | ||
log('Advance transfer rejected', q(error).toString()); | ||
}, | ||
}, | ||
); | ||
|
||
return zone.exoClass( | ||
'Fast USDC Advancer', | ||
M.interface('AdvancerI', { | ||
handleTransactionEvent: M.call(CctpTxEvidenceShape).returns(VowShape), | ||
}), | ||
/** | ||
* @param {{ | ||
* localDenom: Denom; | ||
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>; | ||
* }} 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)}`, | ||
); | ||
} | ||
|
||
// TODO #10391 this can throw, and should make a status update in the catch | ||
const destination = chainHub.makeChainAddress(EUD); | ||
|
||
/** @type {DenomAmount} */ | ||
const requestedAmount = harden({ | ||
denom: this.state.localDenom, | ||
value: BigInt(evidence.tx.amount), | ||
}); | ||
|
||
// TODO #10391 ensure there's enough funds in poolAccount | ||
|
||
const transferV = E(this.state.poolAccount).transfer( | ||
destination, | ||
requestedAmount, | ||
); | ||
|
||
// mark as Advanced since `transferV` initiates the advance | ||
statusManager.advance(evidence); | ||
|
||
return watch(transferV, transferHandler, { | ||
destination, | ||
amount: requestedAmount.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,98 @@ | ||
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) { | ||
// TODO #10390 log all early returns | ||
// 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; | ||
} | ||
|
||
// TODO discern between SETTLED and OBSERVED; each has different fees/destinations | ||
const hasPendingSettlement = statusManager.hasPendingSettlement( | ||
// given the sourceChannel check, we can be certain of this cast | ||
/** @type {NobleAddress} */ (tx.sender), | ||
BigInt(tx.amount), | ||
); | ||
if (!hasPendingSettlement) { | ||
// TODO FAILURE PATH -> put money in recovery account or .transfer to receiver | ||
// TODO should we have an ORPHANED 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.