Skip to content

Commit ebe7d88

Browse files
authored
CAIP-10 EUDs (#11215)
refs: https://github.com/Agoric/agoric-private/issues/252 ## Description Several refactors and generalizations in support of #11174 which will add CCTP support. ### Security Considerations Some values now come from powers instead of baggage. Net improvement in POLA. ### Scaling Considerations `getAddress()` is called on the ICA instead of storing it. It's a vat-local operation so should be similarly fast to pulling from the KV. ### Documentation Considerations none ### Testing Considerations CI ### Upgrade Considerations Should be fully backwards compatible.
2 parents eeb33f0 + 3c16d47 commit ebe7d88

18 files changed

+154
-151
lines changed

packages/builders/test/snapshots/orchestration-imports.test.js.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Generated by [AVA](https://avajs.dev).
99
> Snapshot 1
1010
1111
@Module {
12-
AccountArgShape: Object @match:or {
12+
AccountIdArgShape: Object @match:or {
1313
payload: [
1414
Object @match:string {
1515
payload: [],
@@ -100,6 +100,17 @@ Generated by [AVA](https://avajs.dev).
100100
},
101101
],
102102
},
103+
Caip10RecordShape: {
104+
accountAddress: Object @match:string {
105+
payload: [],
106+
},
107+
namespace: Object @match:string {
108+
payload: [],
109+
},
110+
reference: Object @match:string {
111+
payload: [],
112+
},
113+
},
103114
ChainAddressShape: {
104115
chainId: Object @match:string {
105116
payload: [],
Binary file not shown.

packages/fast-usdc-contract/src/exos/advancer.ts

+19-14
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ import {
3535
AnyNatAmountShape,
3636
CosmosChainAddressShape,
3737
} from '@agoric/orchestration';
38+
import type { AccountId } from '@agoric/orchestration/src/orchestration-api.js';
39+
import { parseAccountIdArg } from '@agoric/orchestration/src/utils/address.js';
3840
import type { ZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
3941
import { pickFacet } from '@agoric/vat-data';
4042
import type { VowTools } from '@agoric/vow';
@@ -45,12 +47,13 @@ import { Fail, q } from '@endo/errors';
4547
import { E } from '@endo/far';
4648
import { M, mustMatch } from '@endo/patterns';
4749
import type { LiquidityPoolKit } from './liquidity-pool.js';
48-
import type { StatusManager } from './status-manager.js';
4950
import type { SettlerKit } from './settler.js';
51+
import type { StatusManager } from './status-manager.js';
5052

5153
interface AdvancerKitPowers {
5254
chainHub: ChainHub;
5355
feeConfig: FeeConfig;
56+
getNobleICA: () => OrchestrationAccount<{ chainId: 'noble-1' }>;
5457
log?: LogFn;
5558
statusManager: StatusManager;
5659
usdc: { brand: Brand<'nat'>; denom: Denom };
@@ -62,7 +65,7 @@ interface AdvancerKitPowers {
6265
interface AdvancerVowCtx {
6366
fullAmount: NatAmount;
6467
advanceAmount: NatAmount;
65-
destination: CosmosChainAddress;
68+
destination: AccountId;
6669
forwardingAddress: NobleAddress;
6770
txHash: EvmHash;
6871
}
@@ -71,7 +74,7 @@ const AdvancerVowCtxShape: TypedPattern<AdvancerVowCtx> = M.splitRecord(
7174
{
7275
fullAmount: AnyNatAmountShape,
7376
advanceAmount: AnyNatAmountShape,
74-
destination: CosmosChainAddressShape,
77+
destination: M.string(), // AccountId
7578
forwardingAddress: M.string(),
7679
txHash: EvmHashShape,
7780
},
@@ -82,7 +85,6 @@ const AdvancerVowCtxShape: TypedPattern<AdvancerVowCtx> = M.splitRecord(
8285
const AdvancerKitI = harden({
8386
advancer: M.interface('AdvancerI', {
8487
handleTransactionEvent: M.callWhen(EvidenceWithRiskShape).returns(),
85-
setIntermediateRecipient: M.call(CosmosChainAddressShape).returns(),
8688
}),
8789
depositHandler: M.interface('DepositHandlerI', {
8890
onFulfilled: M.call(M.undefined(), AdvancerVowCtxShape).returns(VowShape),
@@ -110,6 +112,7 @@ export const stateShape = harden({
110112
notifier: M.remotable(),
111113
borrower: M.remotable(),
112114
poolAccount: M.remotable(),
115+
/** @deprecated */
113116
intermediateRecipient: M.opt(CosmosChainAddressShape),
114117
settlementAddress: M.opt(CosmosChainAddressShape),
115118
});
@@ -127,6 +130,7 @@ export const prepareAdvancerKit = (
127130
{
128131
chainHub,
129132
feeConfig,
133+
getNobleICA,
130134
log = makeTracer('Advancer', true),
131135
statusManager,
132136
usdc,
@@ -159,8 +163,8 @@ export const prepareAdvancerKit = (
159163
}) =>
160164
harden({
161165
...config,
162-
// make sure the state record has this property, perhaps with an undefined value
163-
intermediateRecipient: config.intermediateRecipient,
166+
// deprecated, set key for schema compatibility
167+
intermediateRecipient: undefined,
164168
}),
165169
{
166170
advancer: {
@@ -198,7 +202,7 @@ export const prepareAdvancerKit = (
198202
log(`decoded EUD: ${EUD}`);
199203
assert.typeof(EUD, 'string');
200204
// throws if the bech32 prefix is not found
201-
const destination = chainHub.makeChainAddress(EUD);
205+
const destination = chainHub.resolveAccountId(EUD);
202206

203207
const fullAmount = toAmount(evidence.tx.amount);
204208
const { borrower, notifier, poolAccount } = this.state;
@@ -240,11 +244,8 @@ export const prepareAdvancerKit = (
240244
statusManager.skipAdvance(evidence, [(error as Error).message]);
241245
}
242246
},
243-
/** @param {CosmosChainAddress} intermediateRecipient */
244-
setIntermediateRecipient(intermediateRecipient: CosmosChainAddress) {
245-
this.state.intermediateRecipient = intermediateRecipient;
246-
},
247247
},
248+
248249
depositHandler: {
249250
/**
250251
* @param result
@@ -255,16 +256,20 @@ export const prepareAdvancerKit = (
255256
result: undefined,
256257
ctx: AdvancerVowCtx & { tmpSeat: ZCFSeat },
257258
) {
258-
const { poolAccount, intermediateRecipient, settlementAddress } =
259-
this.state;
259+
const { poolAccount, settlementAddress } = this.state;
260260
const { destination, advanceAmount, tmpSeat, ...detail } = ctx;
261261
tmpSeat.exit();
262262
const amount = harden({
263263
denom: usdc.denom,
264264
value: advanceAmount.value,
265265
});
266+
const intermediateRecipient = getNobleICA().getAddress();
267+
const accountId = parseAccountIdArg(destination);
268+
269+
assert.equal(accountId.namespace, 'cosmos');
270+
266271
const transferOrSendV =
267-
destination.chainId === settlementAddress.chainId
272+
accountId.reference === settlementAddress.chainId
268273
? E(poolAccount).send(destination, amount)
269274
: E(poolAccount).transfer(destination, amount, {
270275
forwardOpts: { intermediateRecipient },

packages/fast-usdc-contract/src/exos/settler.ts

+35-54
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,43 @@ import { E } from '@endo/far';
1515
import { M } from '@endo/patterns';
1616

1717
import { decodeAddressHook } from '@agoric/cosmic-proto/address-hooks.js';
18-
import { fromOnly } from '@agoric/zoe/src/contractSupport/index.js';
1918
import { PendingTxStatus } from '@agoric/fast-usdc/src/constants.js';
20-
import { makeFeeTools } from '@agoric/fast-usdc/src/utils/fees.js';
2119
import {
2220
CctpTxEvidenceShape,
2321
EvmHashShape,
2422
makeNatAmountShape,
2523
} from '@agoric/fast-usdc/src/type-guards.js';
24+
import { makeFeeTools } from '@agoric/fast-usdc/src/utils/fees.js';
25+
import { fromOnly } from '@agoric/zoe/src/contractSupport/index.js';
2626

27+
import type { HostInterface, HostOf } from '@agoric/async-flow';
2728
import type { FungibleTokenPacketData } from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
2829
import type { Amount, Brand, NatAmount, NatValue } from '@agoric/ertp';
30+
import type {
31+
CctpTxEvidence,
32+
EvmHash,
33+
FeeConfig,
34+
LogFn,
35+
NobleAddress,
36+
} from '@agoric/fast-usdc/src/types.js';
2937
import type {
3038
AccountId,
39+
ChainHub,
3140
Denom,
3241
OrchestrationAccount,
33-
ChainHub,
34-
CosmosChainAddress,
3542
} from '@agoric/orchestration';
43+
import { parseAccountId } from '@agoric/orchestration/src/utils/address.js';
3644
import type { WithdrawToSeat } from '@agoric/orchestration/src/utils/zoe-tools.js';
45+
import type { MapStore } from '@agoric/store';
3746
import type { IBCChannelID, IBCPacket, VTransferIBCEvent } from '@agoric/vats';
38-
import type { Zone } from '@agoric/zone';
39-
import type { HostOf, HostInterface } from '@agoric/async-flow';
4047
import type { TargetRegistration } from '@agoric/vats/src/bridge-target.js';
41-
import type {
42-
NobleAddress,
43-
FeeConfig,
44-
EvmHash,
45-
LogFn,
46-
CctpTxEvidence,
47-
} from '@agoric/fast-usdc/src/types.js';
48-
import type { ZCF, ZCFSeat } from '@agoric/zoe/src/zoeService/zoe.js';
49-
import type { MapStore } from '@agoric/store';
5048
import type { VowTools } from '@agoric/vow';
51-
import { parseAccountId } from '@agoric/orchestration/src/utils/address.js';
52-
import type { Baggage } from '@agoric/vat-data';
53-
import { Fail } from '@endo/errors';
49+
import type { ZCF } from '@agoric/zoe/src/zoeService/zoe.js';
50+
import type { Zone } from '@agoric/zone';
51+
import { makeSupportsCctp } from '../utils/cctp.ts';
52+
import { asMultiset } from '../utils/store.ts';
5453
import type { LiquidityPoolKit } from './liquidity-pool.js';
5554
import type { StatusManager } from './status-manager.js';
56-
import { asMultiset } from '../utils/store.ts';
57-
import { NOBLE_ICA_BAGGAGE_KEY } from '../fast-usdc.contract.ts';
58-
import { makeSupportsCctp } from '../utils/cctp.ts';
5955

6056
const decodeEventPacket = (
6157
{ data }: IBCPacket,
@@ -115,7 +111,7 @@ const makeMintedEarlyKey = (addr: NobleAddress, amount: bigint): string =>
115111
/** @param {Brand<'nat'>} USDC */
116112
export const makeAdvanceDetailsShape = (USDC: Brand<'nat'>) =>
117113
harden({
118-
destination: CosmosChainAddressShape,
114+
destination: M.string(),
119115
forwardingAddress: M.string(),
120116
fullAmount: makeNatAmountShape(USDC),
121117
txHash: EvmHashShape,
@@ -128,6 +124,7 @@ export const stateShape = harden({
128124
sourceChannel: M.string(),
129125
remoteDenom: M.string(),
130126
mintedEarly: M.remotable('mintedEarly'),
127+
/** @deprecated */
131128
intermediateRecipient: M.opt(CosmosChainAddressShape),
132129
});
133130

@@ -144,22 +141,22 @@ export const stateShape = harden({
144141
export const prepareSettler = (
145142
zone: Zone,
146143
{
147-
baggage,
148144
currentChainReference,
149145
chainHub,
150146
feeConfig,
147+
getNobleICA,
151148
log = makeTracer('Settler', true),
152149
statusManager,
153150
USDC,
154151
vowTools,
155152
withdrawToSeat,
156153
zcf,
157154
}: {
158-
baggage: Baggage;
159155
/** e.g., `agoric-3` */
160156
currentChainReference: string;
161157
chainHub: ChainHub;
162158
feeConfig: FeeConfig;
159+
getNobleICA: () => OrchestrationAccount<{ chainId: 'noble-1' }>;
163160
log?: LogFn;
164161
statusManager: StatusManager;
165162
USDC: Brand<'nat'>;
@@ -172,20 +169,11 @@ export const prepareSettler = (
172169

173170
const supportsCctp = makeSupportsCctp(chainHub);
174171

175-
/**
176-
* aka `IntermediateRecipientAccount`. A contract-controlled ICA on Noble that can send `MsgDepositForBurn`.
177-
* retrieve from baggage, until:
178-
* [Allow state shape with new optional fields #10200](https://github.com/Agoric/agoric-sdk/issues/10200)
179-
*/
180-
const getNobleICA = (): OrchestrationAccount<{ chainId: 'noble-1' }> =>
181-
baggage.get(NOBLE_ICA_BAGGAGE_KEY);
182-
183172
return zone.exoClassKit(
184173
'Fast USDC Settler',
185174
{
186175
creator: M.interface('SettlerCreatorI', {
187176
monitorMintingDeposits: M.callWhen().returns(M.any()),
188-
setIntermediateRecipient: M.call(CosmosChainAddressShape).returns(),
189177
}),
190178
tap: M.interface('SettlerTapI', {
191179
receiveUpcall: M.call(M.record()).returns(M.promise()),
@@ -195,10 +183,9 @@ export const prepareSettler = (
195183
makeAdvanceDetailsShape(USDC),
196184
M.boolean(),
197185
).returns(),
198-
checkMintedEarly: M.call(
199-
CctpTxEvidenceShape,
200-
CosmosChainAddressShape,
201-
).returns(M.boolean()),
186+
checkMintedEarly: M.call(CctpTxEvidenceShape, M.string()).returns(
187+
M.boolean(),
188+
),
202189
}),
203190
self: M.interface('SettlerSelfI', {
204191
addMintedEarly: M.call(M.string(), M.nat()).returns(),
@@ -226,13 +213,12 @@ export const prepareSettler = (
226213
settlementAccount: HostInterface<
227214
OrchestrationAccount<{ chainId: 'agoric-any' }>
228215
>;
229-
intermediateRecipient?: CosmosChainAddress;
230216
}) => {
231217
log('config', config);
232218
return {
233219
...config,
234-
// make sure the state record has this property, perhaps with an undefined value
235-
intermediateRecipient: config.intermediateRecipient,
220+
// This comes from a power now but the schema requires this key to be set
221+
intermediateRecipient: undefined,
236222
registration: undefined as
237223
| HostInterface<TargetRegistration>
238224
| undefined,
@@ -252,9 +238,6 @@ export const prepareSettler = (
252238
assert.typeof(registration, 'object');
253239
this.state.registration = registration;
254240
},
255-
setIntermediateRecipient(intermediateRecipient: CosmosChainAddress) {
256-
this.state.intermediateRecipient = intermediateRecipient;
257-
},
258241
},
259242
tap: {
260243
async receiveUpcall(event: VTransferIBCEvent) {
@@ -310,24 +293,21 @@ export const prepareSettler = (
310293
txHash: EvmHash;
311294
forwardingAddress: NobleAddress;
312295
fullAmount: Amount<'nat'>;
313-
destination: CosmosChainAddress;
296+
destination: AccountId;
314297
},
315298
success: boolean,
316299
): void {
317300
const { mintedEarly } = this.state;
318301
const { value: fullValue } = fullAmount;
319302
const key = makeMintedEarlyKey(forwardingAddress, fullValue);
303+
320304
if (mintedEarly.has(key)) {
321305
asMultiset(mintedEarly).remove(key);
322306
statusManager.advanceOutcomeForMintedEarly(txHash, success);
323307
if (success) {
324308
void this.facets.self.disburse(txHash, fullValue);
325309
} else {
326-
void this.facets.self.forward(
327-
txHash,
328-
fullValue,
329-
destination.value,
330-
);
310+
void this.facets.self.forward(txHash, fullValue, destination);
331311
}
332312
} else {
333313
statusManager.advanceOutcome(forwardingAddress, fullValue, success);
@@ -338,12 +318,12 @@ export const prepareSettler = (
338318
* funds to the pool.
339319
*
340320
* @param {CctpTxEvidence} evidence
341-
* @param {CosmosChainAddress} destination
321+
* @param {AccountId} destination
342322
* @returns {boolean} whether the EUD received funds without an advance
343323
*/
344324
checkMintedEarly(
345325
evidence: CctpTxEvidence,
346-
destination: CosmosChainAddress,
326+
destination: AccountId,
347327
): boolean {
348328
const {
349329
tx: { forwardingAddress, amount },
@@ -359,7 +339,7 @@ export const prepareSettler = (
359339
);
360340
asMultiset(mintedEarly).remove(key);
361341
statusManager.advanceOutcomeForUnknownMint(evidence);
362-
void this.facets.self.forward(txHash, amount, destination.value);
342+
void this.facets.self.forward(txHash, amount, destination);
363343
return true;
364344
}
365345
return false;
@@ -418,9 +398,8 @@ export const prepareSettler = (
418398
* @param {string} EUD
419399
*/
420400
forward(txHash: EvmHash, fullValue: NatValue, EUD: string) {
421-
const { settlementAccount, intermediateRecipient } = this.state;
401+
const { settlementAccount } = this.state;
422402
log('forwarding', fullValue, 'to', EUD, 'for', txHash);
423-
if (!intermediateRecipient) throw Fail`No intermediate recipient`;
424403

425404
const dest: AccountId | null = (() => {
426405
try {
@@ -436,6 +415,8 @@ export const prepareSettler = (
436415
const { namespace, reference } = parseAccountId(dest);
437416
const amt = AmountMath.make(USDC, fullValue);
438417

418+
const intermediateRecipient = getNobleICA().getAddress();
419+
439420
if (namespace === 'cosmos') {
440421
const transferOrSendV =
441422
reference === currentChainReference

0 commit comments

Comments
 (0)