@agoric/fast-usdc 0.2.0-u19.1 → 0.2.0-u20.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.
package/src/utils/fees.js CHANGED
@@ -1,56 +1,142 @@
1
1
  import { AmountMath } from '@agoric/ertp';
2
- import { multiplyBy } from '@agoric/zoe/src/contractSupport/ratio.js';
2
+ import { multiplyBy } from '@agoric/ertp/src/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';
5
6
  import { FeeConfigShape } from '../type-guards.js';
6
7
 
7
- const { add, isGTE, subtract } = AmountMath;
8
+ const { add, isGTE, subtract, makeEmpty } = AmountMath;
8
9
 
9
10
  /**
10
11
  * @import {Amount} from '@agoric/ertp';
12
+ * @import {AccountId} from '@agoric/orchestration';
11
13
  * @import {FeeConfig} from '../types.js';
12
- * @import {RepayAmountKWR} from '../exos/liquidity-pool.js';
13
14
  */
14
15
 
15
- /** @param {FeeConfig} feeConfig */
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
+ */
16
55
  export const makeFeeTools = feeConfig => {
17
56
  mustMatch(feeConfig, FeeConfigShape, 'Must provide feeConfig');
18
- const { flat, variableRate } = feeConfig;
57
+ const emptyAmount = makeEmpty(feeConfig.flat.brand);
58
+
19
59
  const feeTools = harden({
20
60
  /**
21
- * Calculate the net amount to advance after withholding fees.
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}.
22
64
  *
23
65
  * @param {Amount<'nat'>} requested
24
- * @throws {Error} if requested does not exceed fees
66
+ * @param {AccountId} destination
67
+ * @returns {Amount<'nat'>}
25
68
  */
26
- calculateAdvance(requested) {
27
- const fee = feeTools.calculateAdvanceFee(requested);
28
- return subtract(requested, fee);
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'>}
84
+ */
85
+ calculateRelayFee(destination) {
86
+ const relay = getConfigValue(feeConfig, 'relay', destination);
87
+ return relay || emptyAmount;
29
88
  },
30
89
  /**
31
90
  * Calculate the total fee to charge for the advance.
32
91
  *
33
92
  * @param {Amount<'nat'>} requested
93
+ * @param {AccountId} destination
34
94
  * @throws {Error} if requested does not exceed fees
35
95
  */
36
- calculateAdvanceFee(requested) {
37
- const fee = add(multiplyBy(requested, variableRate), flat);
96
+ calculateAdvanceFee(requested, destination) {
97
+ const baseFee = feeTools.calculateBaseFee(requested, destination);
98
+ const relayFee = feeTools.calculateRelayFee(destination);
99
+ const fee = add(baseFee, relayFee);
38
100
  !isGTE(fee, requested) || Fail`Request must exceed fees.`;
39
101
  return fee;
40
102
  },
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
+ },
41
114
  /**
42
115
  * Calculate the split of fees between pool and contract.
43
116
  *
117
+ * The `ContractFee` includes base fees plus the relay fee.
118
+ *
44
119
  * @param {Amount<'nat'>} requested
120
+ * @param {AccountId} destination
45
121
  * @returns {RepayAmountKWR} an {@link AmountKeywordRecord}
46
122
  * @throws {Error} if requested does not exceed fees
47
123
  */
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 });
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 });
54
140
  },
55
141
  });
56
142
  return feeTools;
@@ -0,0 +1,208 @@
1
+ import { encodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
2
+ import type { Bech32Address, CosmosChainAddress } from '@agoric/orchestration';
3
+ import type { CctpTxEvidence, EvmAddress } from '../src/types.js';
4
+
5
+ const mockScenarios = [
6
+ 'AGORIC_PLUS_OSMO',
7
+ 'AGORIC_PLUS_DYDX',
8
+ 'AGORIC_PLUS_AGORIC',
9
+ 'AGORIC_NO_PARAMS',
10
+ 'AGORIC_UNKNOWN_EUD',
11
+ 'AGORIC_PLUS_ETHEREUM',
12
+ 'AGORIC_PLUS_NOBLE',
13
+ 'AGORIC_PLUS_NOBLE_B32EUD',
14
+ ] as const;
15
+
16
+ export type MockScenario = (typeof mockScenarios)[number];
17
+
18
+ export const Senders = {
19
+ default: '0xDefaultFakeEthereumAddress',
20
+ } as unknown as Record<string, EvmAddress>;
21
+
22
+ const blockTimestamp = 1632340000n;
23
+
24
+ export const MockCctpTxEvidences: Record<
25
+ MockScenario,
26
+ (receiverAddress?: Bech32Address) => CctpTxEvidence
27
+ > = {
28
+ AGORIC_PLUS_OSMO: (receiverAddress?: Bech32Address) => ({
29
+ blockHash:
30
+ '0x90d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee665',
31
+ blockNumber: 21037663n,
32
+ blockTimestamp,
33
+ txHash:
34
+ '0xc81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761702',
35
+ tx: {
36
+ amount: 150000000n,
37
+ forwardingAddress: 'noble1x0ydg69dh6fqvr27xjvp6maqmrldam6yfelqkd',
38
+ sender: Senders.default,
39
+ },
40
+ aux: {
41
+ forwardingChannel: 'channel-21',
42
+ recipientAddress:
43
+ receiverAddress ||
44
+ encodeAddressHook(settlementAddress.value, {
45
+ EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
46
+ }),
47
+ },
48
+ chainId: 1,
49
+ }),
50
+ AGORIC_PLUS_DYDX: (receiverAddress?: Bech32Address) => ({
51
+ blockHash:
52
+ '0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
53
+ blockNumber: 21037669n,
54
+ blockTimestamp,
55
+ txHash:
56
+ '0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
57
+ tx: {
58
+ amount: 300000000n,
59
+ forwardingAddress: 'noble1x0ydg69dh6fqvr27xjvp6maqmrldam6yfelktz',
60
+ sender: Senders.default,
61
+ },
62
+ aux: {
63
+ forwardingChannel: 'channel-21',
64
+ recipientAddress:
65
+ receiverAddress ||
66
+ encodeAddressHook(settlementAddress.value, {
67
+ EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
68
+ }),
69
+ },
70
+ chainId: 1,
71
+ }),
72
+ AGORIC_PLUS_AGORIC: (receiverAddress?: Bech32Address) => ({
73
+ blockHash:
74
+ '0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9',
75
+ blockNumber: 21037600n,
76
+ blockTimestamp,
77
+ txHash:
78
+ '0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9',
79
+ tx: {
80
+ amount: 250000000n,
81
+ forwardingAddress: 'noble17ww3rfusv895d92c0ncgj0fl9trntn70jz7hd5',
82
+ sender: Senders.default,
83
+ },
84
+ aux: {
85
+ forwardingChannel: 'channel-21',
86
+ recipientAddress:
87
+ receiverAddress ||
88
+ encodeAddressHook(settlementAddress.value, {
89
+ EUD: 'agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2',
90
+ }),
91
+ },
92
+ chainId: 1,
93
+ }),
94
+ AGORIC_NO_PARAMS: (receiverAddress?: Bech32Address) => ({
95
+ blockHash:
96
+ '0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
97
+ blockNumber: 21037669n,
98
+ blockTimestamp,
99
+ txHash:
100
+ '0xa81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
101
+ tx: {
102
+ amount: 200000000n,
103
+ forwardingAddress: 'noble1x0ydg69dh6fqvr27xjvp6maqmrldam6yfelyyy',
104
+ sender: Senders.default,
105
+ },
106
+ aux: {
107
+ forwardingChannel: 'channel-21',
108
+ recipientAddress: receiverAddress || settlementAddress.value,
109
+ },
110
+ chainId: 1,
111
+ }),
112
+ AGORIC_UNKNOWN_EUD: (receiverAddress?: Bech32Address) => ({
113
+ blockHash:
114
+ '0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
115
+ blockNumber: 21037669n,
116
+ blockTimestamp,
117
+ txHash:
118
+ '0xa81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff387552761799',
119
+ tx: {
120
+ amount: 200000000n,
121
+ forwardingAddress: 'noble1x0ydg69dh6fqvr27xjvp6maqmrldam6yfelyyy',
122
+ sender: Senders.default,
123
+ },
124
+ aux: {
125
+ forwardingChannel: 'channel-21',
126
+ recipientAddress:
127
+ receiverAddress ||
128
+ encodeAddressHook(settlementAddress.value, {
129
+ EUD: 'random1addr',
130
+ }),
131
+ },
132
+ chainId: 1,
133
+ }),
134
+ AGORIC_PLUS_ETHEREUM: (receiverAddress?: Bech32Address) => ({
135
+ blockHash:
136
+ '0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9',
137
+ blockNumber: 21037600n,
138
+ blockTimestamp,
139
+ txHash:
140
+ '0xe81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9',
141
+ tx: {
142
+ amount: 950000000n,
143
+ forwardingAddress: 'noble17ww3rfusv895d92c0ncgj0fl9trntn70jz7ee5',
144
+ sender: Senders.default,
145
+ },
146
+ aux: {
147
+ forwardingChannel: 'channel-21',
148
+ recipientAddress:
149
+ receiverAddress ||
150
+ encodeAddressHook(settlementAddress.value, {
151
+ EUD: 'eip155:1:0x1234567890123456789012345678901234567890',
152
+ }),
153
+ },
154
+ chainId: 8453,
155
+ }),
156
+ AGORIC_PLUS_NOBLE: (receiverAddress?: Bech32Address) => ({
157
+ blockHash:
158
+ '0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9',
159
+ blockNumber: 21037600n,
160
+ blockTimestamp,
161
+ txHash:
162
+ '0xe81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9',
163
+ tx: {
164
+ amount: 950000000n,
165
+ forwardingAddress: 'noble17ww3rfusv895d92c0ncgj0fl9trntn70jz7ee5',
166
+ sender: Senders.default,
167
+ },
168
+ aux: {
169
+ forwardingChannel: 'channel-21',
170
+ recipientAddress:
171
+ receiverAddress ||
172
+ encodeAddressHook(settlementAddress.value, {
173
+ EUD: 'cosmos:noble-1:noble1u2l9za2wa7wvffhtekgyuvyvum06lwhqxfyr5d',
174
+ }),
175
+ },
176
+ chainId: 8453,
177
+ }),
178
+ /** Identical to AGORIC_PLUS_NOBLE, but the EUD is a bare bech32 */
179
+ AGORIC_PLUS_NOBLE_B32EUD: (receiverAddress?: Bech32Address) => ({
180
+ blockHash:
181
+ '0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9',
182
+ blockNumber: 21037600n,
183
+ blockTimestamp,
184
+ txHash:
185
+ '0xe81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9',
186
+ tx: {
187
+ amount: 950000000n,
188
+ forwardingAddress: 'noble17ww3rfusv895d92c0ncgj0fl9trntn70jz7ee5',
189
+ sender: Senders.default,
190
+ },
191
+ aux: {
192
+ forwardingChannel: 'channel-21',
193
+ recipientAddress:
194
+ receiverAddress ||
195
+ encodeAddressHook(settlementAddress.value, {
196
+ EUD: 'noble1u2l9za2wa7wvffhtekgyuvyvum06lwhqxfyr5d',
197
+ }),
198
+ },
199
+ chainId: 8453,
200
+ }),
201
+ };
202
+
203
+ export const settlementAddress: CosmosChainAddress = harden({
204
+ chainId: 'agoric-3',
205
+ encoding: 'bech32' as const,
206
+ // Random value, copied from tests of address hooks
207
+ value: 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek',
208
+ });
@@ -1,63 +0,0 @@
1
- import { makeTracer } from '@agoric/internal';
2
- import { inviteOracles } from './utils/core-eval.js';
3
-
4
- /**
5
- * @import {ManifestBundleRef} from '@agoric/deploy-script-support/src/externalTypes.js'
6
- * @import {BootstrapManifest} from '@agoric/vats/src/core/lib-boot.js'
7
- * @import {LegibleCapData} from './utils/config-marshal.js'
8
- * @import {FastUSDCConfig} from './types.js'
9
- * @import {FastUSDCCorePowers, FastUSDCKit} from './start-fast-usdc.core.js';
10
- */
11
-
12
- const trace = makeTracer('FUSD-AddOperators', true);
13
-
14
- /**
15
- * @throws if oracle smart wallets are not yet provisioned
16
- *
17
- * @param {BootstrapPowers & FastUSDCCorePowers } powers
18
- * @param {{ options: LegibleCapData<FastUSDCConfig> }} config
19
- */
20
- export const addOperators = async (
21
- { consume: { namesByAddress, fastUsdcKit } },
22
- config,
23
- ) => {
24
- trace(addOperators.name);
25
-
26
- const kit = await fastUsdcKit;
27
-
28
- const { creatorFacet } = kit;
29
-
30
- trace(config);
31
-
32
- // @ts-expect-error XXX LegibleCapData typedef
33
- const { oracles } = config.options.structure;
34
-
35
- await inviteOracles({ creatorFacet, namesByAddress }, oracles);
36
- };
37
- harden(addOperators);
38
-
39
- /**
40
- * @param {{
41
- * restoreRef: (b: ERef<ManifestBundleRef>) => Promise<Installation>;
42
- * }} utils
43
- * @param {{
44
- * options: LegibleCapData<FastUSDCConfig>;
45
- * }} param1
46
- */
47
- export const getManifestForAddOperators = ({ restoreRef: _ }, { options }) => {
48
- return {
49
- /** @type {BootstrapManifest} */
50
- manifest: {
51
- [addOperators.name]: {
52
- consume: {
53
- fastUsdcKit: true,
54
-
55
- // widely shared: name services
56
- agoricNames: true,
57
- namesByAddress: true,
58
- },
59
- },
60
- },
61
- options,
62
- };
63
- };
@@ -1,93 +0,0 @@
1
- /** @file core eval module to collect fees. */
2
- import { AmountMath } from '@agoric/ertp';
3
- import { floorMultiplyBy } from '@agoric/zoe/src/contractSupport/index.js';
4
- import { E } from '@endo/far';
5
- import { makeTracer } from '@agoric/internal';
6
- import { fromExternalConfig } from './utils/config-marshal.js';
7
-
8
- /**
9
- * @import {DepositFacet} from '@agoric/ertp';
10
- * @import {FastUSDCCorePowers} from '@agoric/fast-usdc/src/start-fast-usdc.core.js';
11
- * @import {CopyRecord} from '@endo/pass-style'
12
- * @import {BootstrapManifestPermit} from '@agoric/vats/src/core/lib-boot.js'
13
- * @import {LegibleCapData} from './utils/config-marshal.js'
14
- */
15
-
16
- /**
17
- * @typedef {{ destinationAddress: string } &
18
- * ({ feePortion: Ratio} | {fixedFees: Amount<'nat'>}) &
19
- * CopyRecord
20
- * } FeeDistributionTerms
21
- */
22
-
23
- const kwUSDC = 'USDC'; // keyword in AmountKeywordRecord
24
- const issUSDC = 'USDC'; // issuer name
25
-
26
- const trace = makeTracer('FUCF', true);
27
-
28
- /**
29
- * @param {BootstrapPowers & FastUSDCCorePowers } permittedPowers
30
- * @param {{ options: LegibleCapData<{ feeTerms: FeeDistributionTerms}> }} config
31
- */
32
- export const distributeFees = async (permittedPowers, config) => {
33
- trace('distributeFees...', config.options);
34
-
35
- const { agoricNames, namesByAddress, zoe } = permittedPowers.consume;
36
- /** @type {Brand<'nat'>} */
37
- const usdcBrand = await E(agoricNames).lookup('brand', issUSDC);
38
- /** @type {{ feeTerms: FeeDistributionTerms}} */
39
- const { feeTerms: terms } = fromExternalConfig(config.options, {
40
- USDC: usdcBrand,
41
- });
42
-
43
- const { creatorFacet } = await permittedPowers.consume.fastUsdcKit;
44
- const want = {
45
- [kwUSDC]: await ('fixedFees' in terms
46
- ? terms.fixedFees
47
- : E(creatorFacet)
48
- .getContractFeeBalance()
49
- .then(balance => floorMultiplyBy(balance, terms.feePortion))),
50
- };
51
- const proposal = harden({ want });
52
-
53
- /** @type {DepositFacet} */
54
- const depositFacet = await E(namesByAddress).lookup(
55
- terms.destinationAddress,
56
- 'depositFacet',
57
- );
58
- trace('to:', terms.destinationAddress, depositFacet);
59
-
60
- const toWithdraw = await E(creatorFacet).makeWithdrawFeesInvitation();
61
- trace('invitation:', toWithdraw, 'proposal:', proposal);
62
- const seat = E(zoe).offer(toWithdraw, proposal);
63
- const result = await E(seat).getOfferResult();
64
- trace('offer result', result);
65
- const payout = await E(seat).getPayout(kwUSDC);
66
- /** @type {Amount<'nat'>} */
67
- // @ts-expect-error USDC is a nat brand
68
- const rxd = await E(depositFacet).receive(payout);
69
- trace('received', rxd);
70
- if (!AmountMath.isGTE(rxd, proposal.want[kwUSDC])) {
71
- trace('🚨 expected', proposal.want[kwUSDC], 'got', rxd);
72
- }
73
- trace('done');
74
- };
75
- harden(distributeFees);
76
-
77
- /** @satisfies {BootstrapManifestPermit} */
78
- const permit = {
79
- consume: {
80
- fastUsdcKit: true,
81
- agoricNames: true,
82
- namesByAddress: true,
83
- zoe: true,
84
- },
85
- };
86
-
87
- /**
88
- * @param {unknown} _utils
89
- * @param {Parameters<typeof distributeFees>[1]} config
90
- */
91
- export const getManifestForDistributeFees = (_utils, { options }) => {
92
- return { manifest: { [distributeFees.name]: permit }, options };
93
- };