diff --git a/multichain-testing/test/fast-usdc/config.ts b/multichain-testing/test/fast-usdc/config.ts index c7b1833ec6da..a53c5e5f66cb 100644 --- a/multichain-testing/test/fast-usdc/config.ts +++ b/multichain-testing/test/fast-usdc/config.ts @@ -1,4 +1,5 @@ import type { IBCChannelID } from '@agoric/vats'; +import type { FeedPolicy } from '@agoric/fast-usdc/src/types.js'; export const oracleMnemonics = { oracle1: @@ -10,19 +11,12 @@ export const oracleMnemonics = { }; harden(oracleMnemonics); -export const makeFeedPolicy = (nobleAgoricChannelId: IBCChannelID) => { +export const makeFeedPolicy = ( + nobleAgoricChannelId: IBCChannelID, +): Omit => { return { nobleAgoricChannelId, nobleDomainId: 4, - chainPolicies: { - Arbitrum: { - attenuatedCttpBridgeAddress: - '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', - cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', - chainId: 42161, - confirmations: 2, - }, - }, }; }; harden(makeFeedPolicy); diff --git a/packages/boot/test/fast-usdc/fast-usdc.test.ts b/packages/boot/test/fast-usdc/fast-usdc.test.ts index f0692aba0fd1..796827714ac8 100644 --- a/packages/boot/test/fast-usdc/fast-usdc.test.ts +++ b/packages/boot/test/fast-usdc/fast-usdc.test.ts @@ -153,12 +153,12 @@ test.serial( }, ); -test.serial('writes feed policy to vstorage', async t => { +test.serial('writes chain policy to vstorage', async t => { const { storage } = t.context; const opts = { node: 'fastUsdc.feedPolicy', owner: 'the general and chain-specific policies for the Fast USDC feed', - showValue: JSON.parse, + showValue: defaultSerializer.parse, }; await documentStorageSchema(t, storage, opts); }); diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md index 982534c91763..80d6379e2c87 100644 --- a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md +++ b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.md @@ -4,7 +4,7 @@ The actual snapshot is saved in `fast-usdc.test.ts.snap`. Generated by [AVA](https://avajs.dev). -## writes feed policy to vstorage +## writes chain policy to vstorage > Under "published", the "fastUsdc.feedPolicy" node is delegated to the general and chain-specific policies for the Fast USDC feed. > The example below illustrates the schema of the data published there. @@ -18,33 +18,43 @@ Generated by [AVA](https://avajs.dev). chainPolicies: { Arbitrum: { attenuatedCttpBridgeAddress: '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', chainId: 42161, confirmations: 2, + maxAmountPerBlockWindow: 20000000000n, }, Base: { attenuatedCttpBridgeAddress: '0xB6615B2662b35fc3533F8479002e62D0523341De', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x1682Ae6375C4E4A97e4B583BC394c861A46D8962', chainId: 8453, confirmations: 2, + maxAmountPerBlockWindow: 20000000000n, }, Ethereum: { attenuatedCttpBridgeAddress: '0xBC8552339dA68EB65C8b88B414B5854E0E366cFc', + blockWindowSize: 10, cctpTokenMessengerAddress: '0xBd3fa81B58Ba92a82136038B25aDec7066af3155', chainId: 1, confirmations: 2, + maxAmountPerBlockWindow: 20000000000n, }, Optimism: { attenuatedCttpBridgeAddress: '0x48C5417ED570928eC85D5e3AD4e7E0EeD7dB1E2A', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x2B4069517957735bE00ceE0fadAE88a26365528f', chainId: 10, confirmations: 2, + maxAmountPerBlockWindow: 20000000000n, }, Polygon: { attenuatedCttpBridgeAddress: '0x32cb9574650AFF312c80edc4B4343Ff5500767cA', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9daF8c91AEFAE50b9c0E69629D3F6Ca40cA3B3FE', chainId: 137, confirmations: 2, + maxAmountPerBlockWindow: 20000000000n, }, }, nobleAgoricChannelId: 'channel-21', diff --git a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap index 3b97e5ce3fdd..631c44c151fa 100644 Binary files a/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap and b/packages/boot/test/fast-usdc/snapshots/fast-usdc.test.ts.snap differ diff --git a/packages/builders/scripts/fast-usdc/init-fast-usdc.js b/packages/builders/scripts/fast-usdc/init-fast-usdc.js index fade01cea83f..28f7bb21df07 100644 --- a/packages/builders/scripts/fast-usdc/init-fast-usdc.js +++ b/packages/builders/scripts/fast-usdc/init-fast-usdc.js @@ -122,7 +122,15 @@ export default async (homeP, endowments) => { return configurations[net].feedPolicy; } if (!feedPolicy) throw Error(feedPolicyUsage); - return JSON.parse(feedPolicy); + const parsed = JSON.parse(feedPolicy); + if (!parsed.chainPolicies) { + // chainPolicies contain bigints which can't be stringified + return { + // @ts-expect-error spreading CopyRecord & Passable + ...configurations.MAINNET.feedPolicy, + ...parsed, + }; + } }; const parseOracleArgs = () => { diff --git a/packages/fast-usdc/src/fast-usdc-policy.core.js b/packages/fast-usdc/src/fast-usdc-policy.core.js index 1a8af06d5ac1..a92d9fdb2b89 100644 --- a/packages/fast-usdc/src/fast-usdc-policy.core.js +++ b/packages/fast-usdc/src/fast-usdc-policy.core.js @@ -3,6 +3,7 @@ import { E } from '@endo/far'; import { fromExternalConfig } from './utils/config-marshal.js'; import { FeedPolicyShape } from './type-guards.js'; +import { makePublishingStorageKit } from './fast-usdc.start.js'; /** * @import {Passable} from '@endo/pass-style' @@ -18,11 +19,13 @@ const FEED_POLICY = 'feedPolicy'; * XXX copied from fast-usdc.start.js * * @param {ERef} node - * @param {FeedPolicy} policy + * @param {ERef} marshaller + * @param {FeedPolicy & Passable} policy */ -const publishFeedPolicy = async (node, policy) => { +const publishFeedPolicy = async (node, marshaller, policy) => { const feedPolicy = E(node).makeChildNode(FEED_POLICY); - await E(feedPolicy).setValue(JSON.stringify(policy)); + const value = await E(marshaller).toCapData(policy); + await E(feedPolicy).setValue(JSON.stringify(value)); }; /** @@ -32,7 +35,7 @@ const publishFeedPolicy = async (node, policy) => { * @param {{ options: LegibleCapData<{feedPolicy: FeedPolicy & Passable}> }} config */ export const updateFastUsdcPolicy = async ( - { consume: { agoricNames, chainStorage } }, + { consume: { agoricNames, board, chainStorage } }, config, ) => { /** @type {Issuer<'nat'>} */ @@ -46,9 +49,12 @@ export const updateFastUsdcPolicy = async ( harden({ feedPolicy: FeedPolicyShape }), ); - const storageNode = await E(chainStorage).makeChildNode(contractName); + const { storageNode, marshaller } = await makePublishingStorageKit( + contractName, + { board, chainStorage }, + ); - await publishFeedPolicy(storageNode, feedPolicy); + await publishFeedPolicy(storageNode, marshaller, feedPolicy); }; /** @@ -67,6 +73,7 @@ export const getManifestForUpdateFastUsdcPolicy = (_utils, { options }) => { // widely shared: name services agoricNames: true, + board: true, }, }, }, diff --git a/packages/fast-usdc/src/fast-usdc.start.js b/packages/fast-usdc/src/fast-usdc.start.js index 44a2838a1307..8e38f8a4fc62 100644 --- a/packages/fast-usdc/src/fast-usdc.start.js +++ b/packages/fast-usdc/src/fast-usdc.start.js @@ -22,6 +22,7 @@ import { fromExternalConfig } from './utils/config-marshal.js'; * @import {Board} from '@agoric/vats' * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js' * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js' + * @import {Passable} from '@endo/pass-style' * @import {LegibleCapData} from './utils/config-marshal.js' * @import {FastUsdcSF} from './fast-usdc.contract.js' * @import {FeedPolicy, FastUSDCConfig} from './types.js' @@ -57,7 +58,10 @@ export const FastUSDCConfigShape = M.splitRecord({ * board: ERef; * }} io */ -const makePublishingStorageKit = async (path, { chainStorage, board }) => { +export const makePublishingStorageKit = async ( + path, + { chainStorage, board }, +) => { const storageNode = await E(chainStorage).makeChildNode(path); const marshaller = await E(board).getPublishingMarshaller(); @@ -89,11 +93,13 @@ const POOL_METRICS = 'poolMetrics'; /** * @param {ERef} node - * @param {FeedPolicy} policy + * @param {ERef} marshaller + * @param {FeedPolicy & Passable} policy */ -const publishFeedPolicy = async (node, policy) => { +const publishFeedPolicy = async (node, marshaller, policy) => { const feedPolicy = E(node).makeChildNode(FEED_POLICY); - await E(feedPolicy).setValue(JSON.stringify(policy)); + const value = await E(marshaller).toCapData(policy); + await E(feedPolicy).setValue(JSON.stringify(value)); }; /** @@ -211,7 +217,7 @@ export const startFastUSDC = async ( fastUsdcKit.resolve(harden({ ...kit, privateArgs })); const { instance, creatorFacet } = kit; - await publishFeedPolicy(storageNode, feedPolicy); + await publishFeedPolicy(storageNode, marshaller, feedPolicy); const { issuers: fastUsdcIssuers, diff --git a/packages/fast-usdc/src/type-guards.js b/packages/fast-usdc/src/type-guards.js index ccbb596ae327..e6b801b65c7a 100644 --- a/packages/fast-usdc/src/type-guards.js +++ b/packages/fast-usdc/src/type-guards.js @@ -122,9 +122,11 @@ harden(PoolMetricsShape); /** @type {TypedPattern} */ export const ChainPolicyShape = { attenuatedCttpBridgeAddress: EvmHashShape, + blockWindowSize: M.number(), cctpTokenMessengerAddress: EvmHashShape, confirmations: M.number(), chainId: M.number(), + maxAmountPerBlockWindow: M.bigint(), }; harden(ChainPolicyShape); diff --git a/packages/fast-usdc/src/types.ts b/packages/fast-usdc/src/types.ts index 433ea135d978..cce645843aaf 100644 --- a/packages/fast-usdc/src/types.ts +++ b/packages/fast-usdc/src/types.ts @@ -83,12 +83,16 @@ export interface PoolMetrics extends PoolStats { export interface ChainPolicy { /** `msg.sender` of DepositAndBurn to TokenMessenger must be an attenuated wrapper contract that does not contain `replaceDepositForBurn` */ attenuatedCttpBridgeAddress: EvmHash; + /** the number of blocks to consider for `maxAmountPerBlockWindow` */ + blockWindowSize: number; /** @see {@link https://developers.circle.com/stablecoins/evm-smart-contracts} */ cctpTokenMessengerAddress: EvmHash; /** e.g., `1` for ETH mainnet 42161 for Arbitrum One. @see {@link https://chainlist.org/} */ chainId: EvmChainID; /** the number of block confirmations to observe before reporting */ confirmations: number; + /** do not advance more than this amount per block window */ + maxAmountPerBlockWindow: bigint; } export interface FeedPolicy { diff --git a/packages/fast-usdc/src/utils/chain-policies.js b/packages/fast-usdc/src/utils/chain-policies.js index a9020615ed84..7ce3098ad14f 100644 --- a/packages/fast-usdc/src/utils/chain-policies.js +++ b/packages/fast-usdc/src/utils/chain-policies.js @@ -5,70 +5,90 @@ export const ChainPolicies = /** @type {const} */ ({ MAINNET: { Arbitrum: { attenuatedCttpBridgeAddress: '0xe298b93ffB5eA1FB628e0C0D55A43aeaC268e347', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x19330d10D9Cc8751218eaf51E8885D058642E08A', chainId: 42161, - confirmations: 2, + confirmations: 2, // TODO mocked + maxAmountPerBlockWindow: 20_000_000_000n, // TODO mocked }, Base: { attenuatedCttpBridgeAddress: '0xB6615B2662b35fc3533F8479002e62D0523341De', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x1682Ae6375C4E4A97e4B583BC394c861A46D8962', chainId: 8453, - confirmations: 2, + confirmations: 2, // TODO mocked + maxAmountPerBlockWindow: 20_000_000_000n, // TODO mocked }, Ethereum: { attenuatedCttpBridgeAddress: '0xBC8552339dA68EB65C8b88B414B5854E0E366cFc', + blockWindowSize: 10, cctpTokenMessengerAddress: '0xBd3fa81B58Ba92a82136038B25aDec7066af3155', chainId: 1, - confirmations: 2, + confirmations: 2, // TODO mocked + maxAmountPerBlockWindow: 20_000_000_000n, // TODO mocked }, Optimism: { attenuatedCttpBridgeAddress: '0x48C5417ED570928eC85D5e3AD4e7E0EeD7dB1E2A', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x2B4069517957735bE00ceE0fadAE88a26365528f', chainId: 10, - confirmations: 2, + confirmations: 2, // TODO mocked + maxAmountPerBlockWindow: 20_000_000_000n, // TODO mocked }, Polygon: { attenuatedCttpBridgeAddress: '0x32cb9574650AFF312c80edc4B4343Ff5500767cA', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9daF8c91AEFAE50b9c0E69629D3F6Ca40cA3B3FE', chainId: 137, - confirmations: 2, + confirmations: 2, // TODO mocked + maxAmountPerBlockWindow: 20_000_000_000n, // TODO mocked }, }, TESTNET: { // Arbitrum Sepolia Arbitrum: { attenuatedCttpBridgeAddress: '0xTODO', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5', chainId: 421614, confirmations: 2, + maxAmountPerBlockWindow: 20_000_000_000n, }, // Base Sepolia Base: { attenuatedCttpBridgeAddress: '0xTODO', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5', chainId: 84532, confirmations: 2, + maxAmountPerBlockWindow: 20_000_000_000n, }, // Ethereum Sepolia Ethereum: { attenuatedCttpBridgeAddress: '0xTODO', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5', chainId: 11155111, confirmations: 2, + maxAmountPerBlockWindow: 20_000_000_000n, }, // OP Sepolia Optimism: { attenuatedCttpBridgeAddress: '0xTODO', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5', chainId: 11155420, confirmations: 2, + maxAmountPerBlockWindow: 20_000_000_000n, }, // Polygon PoS Amoy Polygon: { attenuatedCttpBridgeAddress: '0xTODO', + blockWindowSize: 10, cctpTokenMessengerAddress: '0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5', chainId: 80002, confirmations: 2, + maxAmountPerBlockWindow: 20_000_000_000n, }, }, });