@agoric/fast-usdc 0.2.0-upgrade-20-dev-ef71cfd.0 → 0.2.0-upgrade-19-devnet-dev-5428c4d.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ import { Fail } from '@endo/errors';
2
+ import { makeMarshal } from '@endo/marshal';
3
+ import { mustMatch } from '@endo/patterns';
4
+
5
+ // TODO(#7309): move to make available beyond fast-usdc.
6
+
7
+ /**
8
+ * @import {Marshal, CapData, Passable} from '@endo/marshal';
9
+ * @import { RemotableBrand } from '@endo/eventual-send';
10
+ * @import {TypedPattern} from '@agoric/internal'
11
+ */
12
+ const { entries } = Object;
13
+
14
+ /**
15
+ * To configure amounts such as terms or ratios,
16
+ * we need to refer to objects such as brands.
17
+ *
18
+ * If parties agree on names, any party that doesn't have
19
+ * an actual presence for an object can make one up:
20
+ *
21
+ * const remotes = { USDC: Far('USDC Brand') };
22
+ *
23
+ * and use it in local computation:
24
+ *
25
+ * const terms = { fee1: AmountMath.make(remotes.USDC, 1234n) }
26
+ *
27
+ * Then we can pass references across using marshal conventions, using
28
+ * the names as slots.
29
+ *
30
+ * @param {Record<string, Passable>} slotToVal a record that gives names to stand-ins for objects in another vat
31
+ * @returns {Marshal<string>}
32
+ */
33
+ export const makeMarshalFromRecord = slotToVal => {
34
+ const convertSlotToVal = slot => {
35
+ slot in slotToVal || Fail`unknown slot ${slot}`;
36
+ return slotToVal[slot];
37
+ };
38
+ const valToSlot = new Map(entries(slotToVal).map(([k, v]) => [v, k]));
39
+ const convertValToSlot = v => {
40
+ valToSlot.has(v) || Fail`unknown value: ${v}`;
41
+ return valToSlot.get(v);
42
+ };
43
+ return makeMarshal(convertValToSlot, convertSlotToVal, {
44
+ serializeBodyFormat: 'smallcaps',
45
+ });
46
+ };
47
+
48
+ /**
49
+ * @typedef {`\$${number}${string}`} SmallCapsSlotRef
50
+ */
51
+
52
+ /**
53
+ * @template T
54
+ * @typedef {{ [KeyType in keyof T]: T[KeyType] } & {}} Simplify flatten the
55
+ * type output to improve type hints shown in editors
56
+ * https://github.com/sindresorhus/type-fest/blob/main/source/simplify.d.ts
57
+ */
58
+
59
+ /**
60
+ * @template T
61
+ * @template R
62
+ * @typedef {T extends R
63
+ * ? SmallCapsSlotRef
64
+ * : T extends {}
65
+ * ? Simplify<SmallCapsStructureOf<T, R>>
66
+ * : Awaited<T>} SmallCapsStructureOf
67
+ */
68
+
69
+ /**
70
+ * The smallCaps body is a string, which simplifies some usage.
71
+ * But it's hard to read and write.
72
+ *
73
+ * The parsed structure makes a convenient notation for configuration etc.
74
+ *
75
+ * @template {Passable} [T=Passable]
76
+ * @template [R=RemotableBrand]
77
+ * @typedef {{
78
+ * structure: SmallCapsStructureOf<T, R>;
79
+ * slots: string[];
80
+ * }} LegibleCapData
81
+ */
82
+
83
+ /**
84
+ * @template {Passable} [T=Passable]
85
+ * @template [R=RemotableBrand]
86
+ * @param {CapData<string>} capData
87
+ * @returns {LegibleCapData<T, R>}
88
+ */
89
+ export const toLegible = ({ body, slots }) =>
90
+ harden({ structure: JSON.parse(body.replace(/^#/, '')), slots });
91
+
92
+ /**
93
+ * @template {Passable} [T=Passable]
94
+ * @template [R=RemotableBrand]
95
+ * @param {LegibleCapData<T,R>} legible
96
+ * @returns {CapData<string>}
97
+ */
98
+ export const fromLegible = ({ structure, slots }) =>
99
+ harden({ body: `#${JSON.stringify(structure)}`, slots });
100
+
101
+ /**
102
+ * @template {Passable} [T=Passable]
103
+ * @template [R=RemotableBrand]
104
+ * @param {T} config
105
+ * @param {Record<string, Passable>} context
106
+ * @param {TypedPattern<T>} [shape]
107
+ * @returns {LegibleCapData<T,R>}
108
+ */
109
+ export const toExternalConfig = (config, context, shape) => {
110
+ if (shape) {
111
+ mustMatch(config, shape);
112
+ }
113
+ return toLegible(makeMarshalFromRecord(context).toCapData(config));
114
+ };
115
+
116
+ /**
117
+ * @template {Passable} [T=Passable]
118
+ * @template [R=RemotableBrand]
119
+ * @param {LegibleCapData<T,R>} repr
120
+ * @param {Record<string, Passable>} context
121
+ * @param {TypedPattern<T>} [shape]
122
+ * @returns {T}
123
+ */
124
+ export const fromExternalConfig = (repr, context, shape) => {
125
+ const config = makeMarshalFromRecord(context).fromCapData(fromLegible(repr));
126
+ if (shape) {
127
+ mustMatch(config, shape);
128
+ }
129
+ return config;
130
+ };
@@ -0,0 +1,73 @@
1
+ import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal';
2
+ import { Fail } from '@endo/errors';
3
+ import { E } from '@endo/eventual-send';
4
+ import { makeMarshal } from '@endo/marshal';
5
+
6
+ const trace = makeTracer('FUCoreEval');
7
+
8
+ /**
9
+ * @import {Brand, DepositFacet} from '@agoric/ertp';
10
+ * @import {FastUSDCKit} from '../start-fast-usdc.core.js'
11
+ * @import {FeedPolicy} from '../types.js'
12
+ */
13
+
14
+ export const FEED_POLICY = 'feedPolicy';
15
+ export const marshalData = makeMarshal(_val => Fail`data only`);
16
+
17
+ /**
18
+ * @param {ERef<StorageNode>} node
19
+ * @param {FeedPolicy} policy
20
+ */
21
+ export const publishFeedPolicy = async (node, policy) => {
22
+ const feedPolicy = E(node).makeChildNode(FEED_POLICY);
23
+ const value = marshalData.toCapData(policy);
24
+ await E(feedPolicy).setValue(JSON.stringify(value));
25
+ };
26
+
27
+ /**
28
+ * @param {object} powers
29
+ * @param {FastUSDCKit['creatorFacet']} powers.creatorFacet
30
+ * @param {BootstrapPowers['consume']['namesByAddress']} powers.namesByAddress
31
+ * @param {Record<string, string>} oracles
32
+ */
33
+ export const inviteOracles = async (
34
+ { creatorFacet, namesByAddress },
35
+ oracles,
36
+ ) => {
37
+ const oracleDepositFacets = await deeplyFulfilledObject(
38
+ objectMap(
39
+ oracles,
40
+ /** @type {(address: string) => Promise<DepositFacet>} */
41
+ address => E(namesByAddress).lookup(address, 'depositFacet'),
42
+ ),
43
+ );
44
+ await Promise.all(
45
+ Object.entries(oracleDepositFacets).map(async ([name, depositFacet]) => {
46
+ const address = oracles[name];
47
+ trace('making invitation for', name, address);
48
+ const toWatch = await E(creatorFacet).makeOperatorInvitation(address);
49
+
50
+ const amt = await E(depositFacet).receive(toWatch);
51
+ trace('sent', amt, 'to', name);
52
+ }),
53
+ );
54
+ };
55
+
56
+ const BOARD_AUX = 'boardAux';
57
+ /**
58
+ * @param {Brand} brand
59
+ * @param {Pick<BootstrapPowers['consume'], 'board' | 'chainStorage'>} powers
60
+ */
61
+ export const publishDisplayInfo = async (brand, { board, chainStorage }) => {
62
+ // chainStorage type includes undefined, which doesn't apply here.
63
+ // @ts-expect-error UNTIL https://github.com/Agoric/agoric-sdk/issues/8247
64
+ const boardAux = E(chainStorage).makeChildNode(BOARD_AUX);
65
+ const [id, displayInfo, allegedName] = await Promise.all([
66
+ E(board).getId(brand),
67
+ E(brand).getDisplayInfo(),
68
+ E(brand).getAllegedName(),
69
+ ]);
70
+ const node = E(boardAux).makeChildNode(id);
71
+ const aux = marshalData.toCapData(harden({ allegedName, displayInfo }));
72
+ await E(node).setValue(JSON.stringify(aux));
73
+ };
@@ -0,0 +1,127 @@
1
+ import { denomHash, withChainCapabilities } from '@agoric/orchestration';
2
+ import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js';
3
+ import { ChainPolicies } from './chain-policies.js';
4
+
5
+ /**
6
+ * @import {FastUSDCConfig} from '@agoric/fast-usdc';
7
+ * @import {Passable} from '@endo/marshal';
8
+ * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration';
9
+ */
10
+
11
+ /** @type {[Denom, DenomDetail & { brandKey?: string}]} */
12
+ const usdcOnAgoric = [
13
+ `ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId })}`,
14
+ {
15
+ baseName: 'noble',
16
+ chainName: 'agoric',
17
+ baseDenom: 'uusdc',
18
+ brandKey: 'USDC',
19
+ },
20
+ ];
21
+
22
+ /** @type {[Denom, DenomDetail & { brandKey?: string}][]} */
23
+ export const transferAssetInfo = [
24
+ ['uusdc', { baseName: 'noble', chainName: 'noble', baseDenom: 'uusdc' }],
25
+ usdcOnAgoric,
26
+ ];
27
+ harden(transferAssetInfo);
28
+
29
+ /** ABI for DepositForBurn event in TokenMessenger contract */
30
+ const DepositForBurnEvent =
31
+ 'DepositForBurn(uint64,address,uint256,address,bytes32,uint32,bytes32,bytes32)';
32
+
33
+ /**
34
+ * @type {Record<string, Pick<FastUSDCConfig, 'oracles' | 'feedPolicy' | 'chainInfo' | 'assetInfo' >>}
35
+ *
36
+ * meanwhile, use price oracle addresses (from updatePriceFeeds.js).
37
+ */
38
+ export const configurations = {
39
+ /**
40
+ * NOTE: The a3p-integration runtime does _not_ include
41
+ * a noble chain; this limits functionality to advancing
42
+ * to the Agoric chain.
43
+ */
44
+ A3P_INTEGRATION: {
45
+ oracles: {
46
+ gov1: 'agoric1ee9hr0jyrxhy999y755mp862ljgycmwyp4pl7q',
47
+ gov2: 'agoric1wrfh296eu2z34p6pah7q04jjuyj3mxu9v98277',
48
+ gov3: 'agoric1ydzxwh6f893jvpaslmaz6l8j2ulup9a7x8qvvq',
49
+ },
50
+ feedPolicy: {
51
+ nobleAgoricChannelId: 'channel-does-not-exist',
52
+ nobleDomainId: 4,
53
+ chainPolicies: ChainPolicies.TESTNET,
54
+ eventFilter: DepositForBurnEvent,
55
+ },
56
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
57
+ withChainCapabilities({
58
+ agoric: fetchedChainInfo.agoric,
59
+ // registering USDC-on-agoric requires registering the noble chain
60
+ noble: fetchedChainInfo.noble,
61
+ })
62
+ ),
63
+ assetInfo: [usdcOnAgoric],
64
+ },
65
+ MAINNET: {
66
+ // per JVC 12 Feb 2025
67
+ oracles: {
68
+ '01node': 'agoric1ym488t6j24x3ys3va3452ftx44lhs64rz8pu7h',
69
+ SimplyStaking: 'agoric1s5yawjgj6xcw4ea5r2x4cjrnkprmd0fcun2tyk',
70
+ DSRV: 'agoric17crpkfxarq658e9ddru2petrfr0fhjzvjfccq9',
71
+ },
72
+ feedPolicy: {
73
+ nobleAgoricChannelId: 'channel-21',
74
+ nobleDomainId: 4,
75
+ chainPolicies: ChainPolicies.MAINNET,
76
+ eventFilter: DepositForBurnEvent,
77
+ },
78
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
79
+ withChainCapabilities(fetchedChainInfo)
80
+ ),
81
+ assetInfo: transferAssetInfo,
82
+ },
83
+ DEVNET: {
84
+ oracles: {
85
+ DSRV: 'agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft',
86
+ Stakin: 'agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82',
87
+ '01node': 'agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4',
88
+ 'Simply Staking': 'agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf',
89
+ P2P: 'agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk',
90
+ },
91
+ feedPolicy: {
92
+ nobleAgoricChannelId: 'TODO',
93
+ nobleDomainId: 4,
94
+ chainPolicies: ChainPolicies.TESTNET,
95
+ eventFilter: DepositForBurnEvent,
96
+ },
97
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
98
+ withChainCapabilities(fetchedChainInfo) // TODO: use devnet values
99
+ ),
100
+ assetInfo: transferAssetInfo,
101
+ },
102
+ EMERYNET: {
103
+ oracles: {
104
+ gov1: 'agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce',
105
+ gov2: 'agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang',
106
+ },
107
+ feedPolicy: {
108
+ nobleAgoricChannelId: 'TODO',
109
+ nobleDomainId: 4,
110
+ chainPolicies: ChainPolicies.TESTNET,
111
+ eventFilter: DepositForBurnEvent,
112
+ },
113
+ chainInfo: /** @type {Record<string, CosmosChainInfo & Passable>} */ (
114
+ withChainCapabilities(fetchedChainInfo) // TODO: use emerynet values
115
+ ),
116
+ assetInfo: transferAssetInfo,
117
+ },
118
+ };
119
+ harden(configurations);
120
+
121
+ // Constraints on the configurations
122
+ const MAINNET_EXPECTED_ORACLES = 3;
123
+ assert(
124
+ new Set(Object.values(configurations.MAINNET.oracles)).size ===
125
+ MAINNET_EXPECTED_ORACLES,
126
+ `Mainnet must have exactly ${MAINNET_EXPECTED_ORACLES} oracles`,
127
+ );
package/src/utils/fees.js CHANGED
@@ -1,142 +1,56 @@
1
1
  import { AmountMath } from '@agoric/ertp';
2
- import { multiplyBy } from '@agoric/ertp/src/ratio.js';
2
+ import { multiplyBy } from '@agoric/zoe/src/contractSupport/ratio.js';
3
3
  import { Fail } from '@endo/errors';
4
4
  import { mustMatch } from '@endo/patterns';
5
- import { chainOfAccount } from '@agoric/orchestration/src/utils/address.js';
6
5
  import { FeeConfigShape } from '../type-guards.js';
7
6
 
8
- const { add, isGTE, subtract, makeEmpty } = AmountMath;
7
+ const { add, isGTE, subtract } = AmountMath;
9
8
 
10
9
  /**
11
10
  * @import {Amount} from '@agoric/ertp';
12
- * @import {AccountId} from '@agoric/orchestration';
13
11
  * @import {FeeConfig} from '../types.js';
12
+ * @import {RepayAmountKWR} from '../exos/liquidity-pool.js';
14
13
  */
15
14
 
16
- /**
17
- * @typedef {{
18
- * Principal: Amount<'nat'>;
19
- * PoolFee: Amount<'nat'>;
20
- * ContractFee: Amount<'nat'>;
21
- * RelayFee: Amount<'nat'>;
22
- * }} RepayAmountKWR
23
- */
24
-
25
- /**
26
- * @typedef {{
27
- * Principal: Payment<'nat'>;
28
- * PoolFee: Payment<'nat'>;
29
- * ContractFee: Payment<'nat'>;
30
- * RelayFee: Payment<'nat'>;
31
- * }} RepayPaymentKWR
32
- */
33
-
34
- /**
35
- * @template {keyof Omit<FeeConfig, 'destinationOverrides'>} K
36
- * @param {FeeConfig} feeConfig
37
- * @param {K} key
38
- * @param {AccountId} destination
39
- * @returns {FeeConfig[K]}
40
- */
41
- const getConfigValue = (feeConfig, key, destination) => {
42
- const chainId = chainOfAccount(destination);
43
- if (
44
- feeConfig.destinationOverrides?.[chainId] &&
45
- feeConfig.destinationOverrides[chainId][key] !== undefined
46
- ) {
47
- return feeConfig.destinationOverrides[chainId][key];
48
- }
49
- return feeConfig[key];
50
- };
51
-
52
- /**
53
- * @param {FeeConfig} feeConfig
54
- */
15
+ /** @param {FeeConfig} feeConfig */
55
16
  export const makeFeeTools = feeConfig => {
56
17
  mustMatch(feeConfig, FeeConfigShape, 'Must provide feeConfig');
57
- const emptyAmount = makeEmpty(feeConfig.flat.brand);
58
-
18
+ const { flat, variableRate } = feeConfig;
59
19
  const feeTools = harden({
60
20
  /**
61
- * Calculate the base fee to charge for the advance (variable + flat).
62
- * Will be shared between the pool and the contract based on
63
- * {@link FeeConfig.contractRate}.
21
+ * Calculate the net amount to advance after withholding fees.
64
22
  *
65
23
  * @param {Amount<'nat'>} requested
66
- * @param {AccountId} destination
67
- * @returns {Amount<'nat'>}
68
- */
69
- calculateBaseFee(requested, destination) {
70
- const flat = getConfigValue(feeConfig, 'flat', destination);
71
- const variableRate = getConfigValue(
72
- feeConfig,
73
- 'variableRate',
74
- destination,
75
- );
76
- return add(multiplyBy(requested, variableRate), flat);
77
- },
78
- /**
79
- * Calculate the optional relay fee charged for certain destinations.
80
- * Only disbursed to contract seat.
81
- *
82
- * @param {AccountId} destination
83
- * @returns {Amount<'nat'>}
24
+ * @throws {Error} if requested does not exceed fees
84
25
  */
85
- calculateRelayFee(destination) {
86
- const relay = getConfigValue(feeConfig, 'relay', destination);
87
- return relay || emptyAmount;
26
+ calculateAdvance(requested) {
27
+ const fee = feeTools.calculateAdvanceFee(requested);
28
+ return subtract(requested, fee);
88
29
  },
89
30
  /**
90
31
  * Calculate the total fee to charge for the advance.
91
32
  *
92
33
  * @param {Amount<'nat'>} requested
93
- * @param {AccountId} destination
94
34
  * @throws {Error} if requested does not exceed fees
95
35
  */
96
- calculateAdvanceFee(requested, destination) {
97
- const baseFee = feeTools.calculateBaseFee(requested, destination);
98
- const relayFee = feeTools.calculateRelayFee(destination);
99
- const fee = add(baseFee, relayFee);
36
+ calculateAdvanceFee(requested) {
37
+ const fee = add(multiplyBy(requested, variableRate), flat);
100
38
  !isGTE(fee, requested) || Fail`Request must exceed fees.`;
101
39
  return fee;
102
40
  },
103
- /**
104
- * Calculate the net amount to advance after withholding fees.
105
- *
106
- * @param {Amount<'nat'>} requested
107
- * @param {AccountId} destination
108
- * @throws {Error} if requested does not exceed fees
109
- */
110
- calculateAdvance(requested, destination) {
111
- const fee = feeTools.calculateAdvanceFee(requested, destination);
112
- return subtract(requested, fee);
113
- },
114
41
  /**
115
42
  * Calculate the split of fees between pool and contract.
116
43
  *
117
- * The `ContractFee` includes base fees plus the relay fee.
118
- *
119
44
  * @param {Amount<'nat'>} requested
120
- * @param {AccountId} destination
121
45
  * @returns {RepayAmountKWR} an {@link AmountKeywordRecord}
122
46
  * @throws {Error} if requested does not exceed fees
123
47
  */
124
- calculateSplit(requested, destination) {
125
- const baseFee = feeTools.calculateBaseFee(requested, destination);
126
- const relayFee = feeTools.calculateRelayFee(destination);
127
- const totalFee = add(baseFee, relayFee);
128
- !isGTE(totalFee, requested) || Fail`Request must exceed fees.`;
129
-
130
- const contractRate = getConfigValue(
131
- feeConfig,
132
- 'contractRate',
133
- destination,
134
- );
135
- const Principal = subtract(requested, totalFee);
136
- const ContractFee = multiplyBy(baseFee, contractRate);
137
- const PoolFee = subtract(baseFee, ContractFee);
138
-
139
- return harden({ Principal, PoolFee, ContractFee, RelayFee: relayFee });
48
+ calculateSplit(requested) {
49
+ const fee = feeTools.calculateAdvanceFee(requested);
50
+ const Principal = subtract(requested, fee);
51
+ const ContractFee = multiplyBy(fee, feeConfig.contractRate);
52
+ const PoolFee = subtract(fee, ContractFee);
53
+ return harden({ Principal, PoolFee, ContractFee });
140
54
  },
141
55
  });
142
56
  return feeTools;
@@ -0,0 +1,28 @@
1
+ import { makeTracer } from '@agoric/internal';
2
+
3
+ const trace = makeTracer('ZoeUtils');
4
+
5
+ /**
6
+ * Used for "continuing offer" invitations in which the caller does not need
7
+ * anything in return. In those cases there is no Zoe offer safety and the
8
+ * invitation making function can perform the request itself.
9
+ *
10
+ * But smart-wallet expects an invitation maker to make an invitation, so this
11
+ * function abstracts making such an inert invitation and logs consistently when
12
+ * it is used.
13
+ *
14
+ * When this is used by an invitation maker that performs the operation, receiving
15
+ * one of these invitations is evidence that the operation took place.
16
+ *
17
+ * @param {ZCF} zcf
18
+ * @param {string} description @see {@link ZCF.makeInvitation}
19
+ * @returns {() => Promise<Invitation>} an arg-less invitation maker
20
+ */
21
+ export const defineInertInvitation = (zcf, description) => {
22
+ return () =>
23
+ zcf.makeInvitation(seat => {
24
+ trace(`ℹ️ An offer was made on an inert invitation for ${description}`);
25
+ seat.exit();
26
+ return 'inert; nothing should be expected from this offer';
27
+ }, description);
28
+ };
@@ -1,29 +0,0 @@
1
- import { M } from '@endo/patterns';
2
- import { CctpTxEvidenceShape, RiskAssessmentShape } from './type-guards.js';
3
-
4
- /**
5
- * @import {Zone} from '@agoric/zone';
6
- * @import {CctpTxEvidence, RiskAssessment} from '@agoric/fast-usdc/src/types.js';
7
- */
8
-
9
- /** Name in the invitation purse (keyed also by this contract instance) */
10
- export const INVITATION_MAKERS_DESC = 'oracle operator invitation';
11
-
12
- export const OperatorKitI = {
13
- admin: M.interface('Admin', {
14
- disable: M.call().returns(),
15
- }),
16
-
17
- invitationMakers: M.interface('InvitationMakers', {
18
- SubmitEvidence: M.call(CctpTxEvidenceShape)
19
- .optional(RiskAssessmentShape)
20
- .returns(M.promise()),
21
- }),
22
-
23
- operator: M.interface('Operator', {
24
- submitEvidence: M.call(CctpTxEvidenceShape)
25
- .optional(RiskAssessmentShape)
26
- .returns(),
27
- getStatus: M.call().returns(M.record()),
28
- }),
29
- };