|
| 1 | +# Publishing to chainStorage |
| 2 | + |
| 3 | +Contracts can use [notifiers and subscriptions](../js-programming/notifiers) |
| 4 | +to publish to clients. To publish data visible to [vstorage queries](../getting-started/contract-rpc#querying-vstorage), contracts should connect |
| 5 | +a subscriber to a `chainStorage` node. |
| 6 | + |
| 7 | +## Deployment Capabilities for Publishing to chainStorage |
| 8 | + |
| 9 | +In [Adding Parameter Governance to a Contract](../governance/#adding-parameter-governance-to-a-contract), |
| 10 | +`storageNode` and `marshaller` are passed to the contract in its `privateArgs` so it can publish to chainStorage. |
| 11 | + |
| 12 | +In [dapp-agoric-basics](https://github.com/Agoric/dapp-agoric-basics), the `startSwapContract` uses 2 [permitted deployment capabilities](../coreeval/permissions), `chainStorage` and `board` and uses them to make the `privateArgs`: |
| 13 | + |
| 14 | +```js |
| 15 | +const marshaller = await E(board).getPublishingMarshaller(); |
| 16 | +const storageNode = await E(chainStorage).makeChildNode(contractName); |
| 17 | +``` |
| 18 | + |
| 19 | +A `Marshaller` is parameterized by functions for mapping unforgeable object identities to plain data slot references and back. Using the [board](../integration/name-services#the-board-publishing-under-arbitrary-names) name service gives consistent slot references across contracts. |
| 20 | +As discussed in [Marshalling Amounts and Instances](../getting-started/contract-rpc#marshalling-amounts-and-instances), this lets |
| 21 | +off-chain clients use the same `@endo/marshal` API. |
| 22 | + |
| 23 | +The `chainStorage` node corresponds to the `published` key in the |
| 24 | +[vstorage hierarchy](/reference/vstorage-ref). |
| 25 | +Using `E(chainStorage).makeChildNode(contractName)` gives the contract |
| 26 | +access to write to the `published.swaparoo` key and all keys under it. |
| 27 | + |
| 28 | +The `swaparoo` contract delegates the rest of publishing governance parameters to the `@agoric/governance` package. |
| 29 | + |
| 30 | +## Publishing structured data to chainStorage |
| 31 | + |
| 32 | +Let's look at the Inter Protocol [assetReserve.js](https://github.com/Agoric/agoric-sdk/blob/agoric-upgrade-13/packages/inter-protocol/src/reserve/assetReserve.js) contract to get more of the details. It publishes to [published.reserve.metrics](https://github.com/Agoric/agoric-sdk/blob/agoric-upgrade-13/packages/inter-protocol/test/reserve/snapshots/test-reserve.js.md) data of the form |
| 33 | + |
| 34 | +```js |
| 35 | +/** |
| 36 | + * @typedef {object} MetricsNotification |
| 37 | + * @property {AmountKeywordRecord} allocations |
| 38 | + * @property {Amount<'nat'>} shortfallBalance shortfall from liquidation that |
| 39 | + * has not yet been compensated. |
| 40 | + * @property {Amount<'nat'>} totalFeeMinted total Fee tokens minted to date |
| 41 | + * @property {Amount<'nat'>} totalFeeBurned total Fee tokens burned to date |
| 42 | + */ |
| 43 | +``` |
| 44 | + |
| 45 | +For example: |
| 46 | + |
| 47 | +```js |
| 48 | + { |
| 49 | + allocations: { |
| 50 | + Fee: { |
| 51 | + brand: Object @Alleged: IST brand {}, |
| 52 | + value: 64561373455n, |
| 53 | + }, |
| 54 | + ATOM: { |
| 55 | + brand: Object @Alleged: ATOM brand {}, |
| 56 | + value: 6587020n |
| 57 | + }, |
| 58 | + }, |
| 59 | + shortfallBalance: { |
| 60 | + brand: Object @Alleged: IST brand {}, |
| 61 | + value: 5747205025n, |
| 62 | + }, |
| 63 | + totalFeeBurned: { |
| 64 | + brand: Object @Alleged: IST brand {}, |
| 65 | + value: n, |
| 66 | + }, |
| 67 | + totalFeeMinted: { |
| 68 | + brand: Object @Alleged: IST brand {}, |
| 69 | + value: 0n, |
| 70 | + }, |
| 71 | + }, |
| 72 | +``` |
| 73 | + |
| 74 | +The method that writes this data is: |
| 75 | + |
| 76 | +```js |
| 77 | + writeMetrics() { |
| 78 | + const { state } = this; |
| 79 | + const metrics = harden({ |
| 80 | + allocations: state.collateralSeat.getCurrentAllocation(), |
| 81 | + shortfallBalance: state.shortfallBalance, |
| 82 | + totalFeeMinted: state.totalFeeMinted, |
| 83 | + totalFeeBurned: state.totalFeeBurned, |
| 84 | + }); |
| 85 | + void state.metricsKit.recorder.write(metrics); |
| 86 | + }, |
| 87 | +``` |
| 88 | + |
| 89 | +The `metricsKit` is made with a `makeRecorderKit` function: |
| 90 | + |
| 91 | +```js |
| 92 | + metricsKit: makeRecorderKit( |
| 93 | + metricsNode, |
| 94 | + /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher<MetricsNotification>} */ ( |
| 95 | + M.any() |
| 96 | + ), |
| 97 | + ), |
| 98 | +``` |
| 99 | + |
| 100 | +We "prepare" (in the [exo](https://endojs.github.io/endo/modules/_endo_exo.html) sense) that function for making |
| 101 | +a `RecorderKit` using [prepareRecorderKitMakers](/reference/zoe-api/zoe-helpers#preparerecorderkitmakers-baggage-marshaller). |
| 102 | + |
| 103 | +```js |
| 104 | +const { makeRecorderKit } = prepareRecorderKitMakers( |
| 105 | + baggage, |
| 106 | + privateArgs.marshaller, |
| 107 | +); |
| 108 | +``` |
| 109 | + |
| 110 | +The contract gets `baggage`, along with `privateArgs` when it starts in |
| 111 | +[the usual way for upgradable contracts](./contract-upgrade.html#upgradable-declaration): |
| 112 | + |
| 113 | +```js |
| 114 | +/** |
| 115 | + * Asset Reserve holds onto assets for the Inter Protocol, and ... |
| 116 | + * |
| 117 | + * @param {{ |
| 118 | + * ... |
| 119 | + * marshaller: ERef<Marshaller>, |
| 120 | + * storageNode: ERef<StorageNode>, |
| 121 | + * }} privateArgs |
| 122 | + * @param {Baggage} baggage |
| 123 | + */ |
| 124 | +export const prepare = async (zcf, privateArgs, baggage) => { |
| 125 | + ... |
| 126 | +}; |
| 127 | +``` |
| 128 | + |
| 129 | +The reserve uses its `StorageNode` and makes a child to get `metricsNode`: |
| 130 | + |
| 131 | +```js |
| 132 | +const metricsNode = await E(storageNode).makeChildNode('metrics'); |
| 133 | +``` |
| 134 | + |
| 135 | +The `marshaller` is used to serialize data structures such as `MetricsNotification` above. |
| 136 | + |
| 137 | +### Deployment Capabilities for the reserve |
| 138 | + |
| 139 | +To start `assetReserve`, the [setupReserve](https://github.com/Agoric/agoric-sdk/blob/agoric-upgrade-13/packages/inter-protocol/src/proposals/econ-behaviors.js#L76) function again supplies |
| 140 | +the two relevant `privateArgs`, `marshaller` and `storageNode`: |
| 141 | + |
| 142 | +```js |
| 143 | +const STORAGE_PATH = 'reserve'; |
| 144 | +const storageNode = await E(storageNode).makeChildNode(STORAGE_PATH); |
| 145 | +const marshaller = await E(board).getReadonlyMarshaller(); |
| 146 | +``` |
| 147 | + |
| 148 | +The `setupReserve` function gets `chainStorage` and `board` deployment capabilities passed in: |
| 149 | + |
| 150 | +```js |
| 151 | +export const setupReserve = async ({ |
| 152 | + consume: { |
| 153 | + board, |
| 154 | + chainStorage, |
| 155 | + ... |
| 156 | + }, |
| 157 | +... |
| 158 | +}) => { ... }; |
| 159 | +``` |
0 commit comments