Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: fusdc multichain support #10626

Merged
merged 8 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 11 additions & 13 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,19 +378,19 @@ test.serial('basic-flows', async t => {
[
'request-coa',
{
account: 'published.basicFlows.cosmos1test',
account: 'published.basicFlows.cosmos1test1',
},
],
],
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-coa', numWantsSatisfied: 1 },
});
t.deepEqual(readPublished('basicFlows.cosmos1test'), {
t.deepEqual(readPublished('basicFlows.cosmos1test1'), {
localAddress:
'/ibc-port/icacontroller-2/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-2',
'/ibc-port/icacontroller-2/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test1","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-2',
remoteAddress:
'/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-2',
'/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test1","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-2',
});

// create a local orchestration account
Expand Down Expand Up @@ -599,25 +599,23 @@ test.serial('basic-flows - portfolio holder', async t => {
'request-portfolio-acct',
{
agoric: 'published.basicFlows.agoric1fakeLCAAddress1',
cosmoshub: 'published.basicFlows.cosmos1test',
// XXX support multiple chain addresses in ibc mocks
osmosis: 'published.basicFlows.cosmos1test',
cosmoshub: 'published.basicFlows.cosmos1test2',
osmosis: 'published.basicFlows.cosmos1test3',
},
],
],
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-portfolio-acct', numWantsSatisfied: 1 },
});
// XXX this overrides a previous account, since mocks only provide one address
t.deepEqual(readPublished('basicFlows.cosmos1test'), {

t.deepEqual(readPublished('basicFlows.cosmos1test3'), {
localAddress:
'/ibc-port/icacontroller-4/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4',
'/ibc-port/icacontroller-4/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test3","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4',
remoteAddress:
'/ibc-hop/connection-1/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4',
'/ibc-hop/connection-1/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test3","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4',
});
// XXX this overrides a previous account, since mocks only provide one address
t.is(readPublished('basicFlows.agoric1fakeLCAAddress'), '');
t.is(readPublished('basicFlows.agoric1fakeLCAAddress1'), '');

const { BLD } = agoricNamesRemotes.brand;
BLD || Fail`BLD missing from agoricNames`;
Expand Down
14 changes: 13 additions & 1 deletion packages/boot/test/fast-usdc/fast-usdc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js';
import { makeMarshal } from '@endo/marshal';
import { defaultMarshaller } from '@agoric/internal/src/storage-test-utils.js';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import { BridgeId } from '@agoric/internal';
import {
makeWalletFactoryContext,
type WalletFactoryTestContext,
} from '../bootstrapTests/walletFactory.js';
import {
makeSwingsetHarness,
insistManagerType,
AckBehavior,
} from '../../tools/supports.js';

const test: TestFn<
Expand Down Expand Up @@ -54,8 +56,9 @@ test.serial(
async t => {
const {
agoricNamesRemotes,
evalProposal,
bridgeUtils,
buildProposal,
evalProposal,
refreshAgoricNamesRemotes,
storage,
walletFactoryDriver: wd,
Expand All @@ -67,6 +70,15 @@ test.serial(
wd.provideSmartWallet('agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj'),
]);

// inbound `startChannelOpenInit` responses immediately.
// needed since the Fusdc StartFn relies on an ICA being created
bridgeUtils.setAckBehavior(
BridgeId.DIBC,
'startChannelOpenInit',
AckBehavior.Immediate,
);
bridgeUtils.setBech32Prefix('noble');

const materials = buildProposal(
'@agoric/builders/scripts/fast-usdc/init-fast-usdc.js',
['--net', 'MAINNET'],
Expand Down
25 changes: 16 additions & 9 deletions packages/boot/tools/ibc/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @ts-check
import { createMockAckMap } from '@agoric/orchestration/tools/ibc-mocks.js';
import type { IBCChannelID, IBCEvent, IBCMethod } from '@agoric/vats';

/** @import { IBCChannelID, IBCMethod, IBCEvent } from '@agoric/vats'; */

Expand Down Expand Up @@ -65,6 +66,11 @@ export const protoMsgMocks = {
msg: 'eyJ0eXBlIjoxLCJkYXRhIjoiQ25zS0tTOXBZbU11WVhCd2JHbGpZWFJwYjI1ekxuUnlZVzV6Wm1WeUxuWXhMazF6WjFSeVlXNXpabVZ5RWs0S0NIUnlZVzV6Wm1WeUVndGphR0Z1Ym1Wc0xUVXpOaG9UQ2cxcFltTXZkWFZ6WkdOb1lYTm9FZ0l4TUNJTFkyOXpiVzl6TVhSbGMzUXFDbTV2WW14bE1YUmxjM1F5QURpQThKTEwzUWc9IiwibWVtbyI6IiJ9',
ack: responses.ibcTransfer,
},
// MsgTransfer 10 ibc/uusdchash from cosmos1test1 to noble1test through channel-536
ibcTransfer2: {
msg: 'eyJ0eXBlIjoxLCJkYXRhIjoiQ253S0tTOXBZbU11WVhCd2JHbGpZWFJwYjI1ekxuUnlZVzV6Wm1WeUxuWXhMazF6WjFSeVlXNXpabVZ5RWs4S0NIUnlZVzV6Wm1WeUVndGphR0Z1Ym1Wc0xUVXpOaG9UQ2cxcFltTXZkWFZ6WkdOb1lYTm9FZ0l4TUNJTVkyOXpiVzl6TVhSbGMzUXhLZ3B1YjJKc1pURjBaWE4wTWdBNGdQQ1N5OTBJIiwibWVtbyI6IiJ9',
ack: responses.ibcTransfer,
},
error: {
msg: '',
ack: responses.error5,
Expand Down Expand Up @@ -101,17 +107,18 @@ export const addParamsIfJsonVersion = (version, params) => {
export const icaMocks = {
/**
* ICA Channel Creation
* @param {IBCMethod<'startChannelOpenInit'>} obj
* @returns {IBCEvent<'channelOpenAck'>}
* @param obj
* @param bech32Prefix
*/
channelOpenAck: obj => {
channelOpenAck: (
obj: IBCMethod<'startChannelOpenInit'>,
bech32Prefix: string = 'cosmos',
): IBCEvent<'channelOpenAck'> => {
// Fake a channel IDs from port suffixes. _Ports have no relation to channels, and hosts
// and controllers will likely have different channel IDs for the same channel._
const mocklID = Number(obj.packet.source_port.split('-').at(-1));
/** @type {IBCChannelID} */
const mockLocalChannelID = `channel-${mocklID}`;
/** @type {IBCChannelID} */
const mockRemoteChannelID = `channel-${mocklID}`;
const mockLocalChannelID: IBCChannelID = `channel-${mocklID}`;
const mockRemoteChannelID: IBCChannelID = `channel-${mocklID}`;

return {
type: 'IBC_EVENT',
Expand All @@ -125,8 +132,8 @@ export const icaMocks = {
channel_id: mockRemoteChannelID,
},
counterpartyVersion: addParamsIfJsonVersion(obj.version, {
// TODO, parameterize
address: 'cosmos1test',
// mockID expected to increase monotonically since icacontroller ports are sequential
address: `${bech32Prefix}1test${mocklID < 2 ? '' : mocklID - 1}`,
}),
connectionHops: obj.hops,
order: obj.order,
Expand Down
68 changes: 64 additions & 4 deletions packages/boot/tools/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import type { ExecutionContext as AvaT } from 'ava';
import type { CoreEvalSDKType } from '@agoric/cosmic-proto/swingset/swingset.js';
import type { EconomyBootstrapPowers } from '@agoric/inter-protocol/src/proposals/econ-behaviors.js';
import type { SwingsetController } from '@agoric/swingset-vat/src/controller/controller.js';
import type { BridgeHandler, IBCMethod } from '@agoric/vats';
import type { BridgeHandler, IBCDowncallMethod, IBCMethod } from '@agoric/vats';
import type { BootstrapRootObject } from '@agoric/vats/src/core/lib-boot.js';
import type { EProxy } from '@endo/eventual-send';
import type { FastUSDCCorePowers } from '@agoric/fast-usdc/src/fast-usdc.start.js';
Expand Down Expand Up @@ -289,6 +289,14 @@ export const matchIter = (t: AvaT, iter, valueRef) => {
matchValue(t, iter.value, valueRef);
};

export const AckBehavior = {
/** inbound responses are queued. use `flushInboundQueue()` to simulate the remote response */
Queued: 'QUEUED',
/** inbound messages are delivered immediately */
Immediate: 'IMMEDIATE',
} as const;
type AckBehaviorType = (typeof AckBehavior)[keyof typeof AckBehavior];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a way to make the type name be the same as the value name in JSDoc. In .ts one would usually do enum. I'm not sure how to do it in .ts without enum.


/**
* Start a SwingSet kernel to be used by tests and benchmarks.
*
Expand Down Expand Up @@ -365,7 +373,30 @@ export const makeSwingsetTestKit = async (
console.log('inbound', ...args);
bridgeInbound!(...args);
};
/**
* Config DIBC bridge behavior.
* Defaults to `Queued` unless specified.
* Current only configured for `channelOpenInit` but can be
* extended to support `sendPacket`.
*/
const ackBehaviors: Partial<
Record<BridgeId, Partial<Record<IBCDowncallMethod, AckBehaviorType>>>
> = {
[BridgeId.DIBC]: {
startChannelOpenInit: AckBehavior.Queued,
},
};

const shouldAckImmediately = (
bridgeId: BridgeId,
method: IBCDowncallMethod,
) => ackBehaviors?.[bridgeId]?.[method] === AckBehavior.Immediate;

/**
* configurable `bech32Prefix` for DIBC bridge
* messages that involve creating an ICA.
*/
let bech32Prefix = 'cosmos';
/**
* Adds the sequence so the bridge knows what response to connect it to.
* Then queue it send it over the bridge over this returns.
Expand Down Expand Up @@ -404,7 +435,7 @@ export const makeSwingsetTestKit = async (
* Mock the bridge outbound handler. The real one is implemented in Golang so
* changes there will sometimes require changes here.
*/
const bridgeOutbound = (bridgeId: string, obj: any) => {
const bridgeOutbound = (bridgeId: BridgeId, obj: any) => {
// store all messages for querying by tests
if (!outboundMessages.has(bridgeId)) {
outboundMessages.set(bridgeId, []);
Expand Down Expand Up @@ -476,9 +507,17 @@ export const makeSwingsetTestKit = async (
case `${BridgeId.DIBC}:IBC_METHOD`:
case `${BridgeId.VTRANSFER}:IBC_METHOD`: {
switch (obj.method) {
case 'startChannelOpenInit':
pushInbound(BridgeId.DIBC, icaMocks.channelOpenAck(obj));
turadg marked this conversation as resolved.
Show resolved Hide resolved
case 'startChannelOpenInit': {
const message = icaMocks.channelOpenAck(obj, bech32Prefix);
const handle = shouldAckImmediately(
bridgeId,
'startChannelOpenInit',
)
? inbound
: pushInbound;
handle(BridgeId.DIBC, message);
return undefined;
}
case 'sendPacket': {
if (protoMsgMockMap[obj.packet.data]) {
return ackLater(obj, protoMsgMockMap[obj.packet.data]);
Expand Down Expand Up @@ -629,6 +668,27 @@ export const makeSwingsetTestKit = async (
getOutboundMessages: (bridgeId: string) =>
harden([...outboundMessages.get(bridgeId)]),
getInboundQueueLength: () => inboundQueue.length,
setAckBehavior(
bridgeId: BridgeId,
method: IBCDowncallMethod,
behavior: AckBehaviorType,
): void {
if (!ackBehaviors?.[bridgeId]?.[method])
throw Fail`ack behavior not yet configurable for ${bridgeId} ${method}`;
console.log('setting', bridgeId, method, 'ack behavior to', behavior);
ackBehaviors[bridgeId][method] = behavior;
},
lookupAckBehavior(
bridgeId: BridgeId,
method: IBCDowncallMethod,
): AckBehaviorType {
if (!ackBehaviors?.[bridgeId]?.[method])
throw Fail`ack behavior not yet configurable for ${bridgeId} ${method}`;
return ackBehaviors[bridgeId][method];
},
setBech32Prefix(prefix: string): void {
bech32Prefix = prefix;
},
/**
* @param {number} max the max number of messages to flush
* @returns {Promise<number>} the number of messages flushed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,17 @@ Generated by [AVA](https://avajs.dev).
payload: [
{},
{
intermediateRecipient: {
chainId: Object @match:string {
payload: [],
},
encoding: Object @match:string {
payload: [],
},
value: Object @match:string {
payload: [],
},
},
retries: Object @match:kind {
payload: 'number',
},
Expand Down Expand Up @@ -423,6 +434,17 @@ Generated by [AVA](https://avajs.dev).
payload: [
{},
{
intermediateRecipient: {
chainId: Object @match:string {
payload: [],
},
encoding: Object @match:string {
payload: [],
},
value: Object @match:string {
payload: [],
},
},
retries: Object @match:kind {
payload: 'number',
},
Expand Down
Binary file not shown.
1 change: 0 additions & 1 deletion packages/fast-usdc/src/cli/operator-commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export const addOperatorCommands = (
.requiredOption('--recipientAddress <string>', 'bech32 address', String)
.requiredOption('--blockHash <0xhex>', 'hex hash', parseHex)
.requiredOption('--blockNumber <number>', 'number', parseNat)
.requiredOption('--blockTimestamp <number>', 'number', parseNat)
.requiredOption('--chainId <string>', 'chain id', Number)
.requiredOption('--amount <number>', 'number', parseNat)
.requiredOption('--forwardingAddress <string>', 'bech32 address', String)
Expand Down
20 changes: 15 additions & 5 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export const prepareAdvancerKit = (
* notifyFacet: import('./settler.js').SettlerKit['notify'];
* borrowerFacet: LiquidityPoolKit['borrower'];
* poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
* intermediateRecipient: ChainAddress;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we seem to throw away all but the .value without checking it. Just use a string?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to check it, but even without checking it there's utility in knowing it's not just a string but a ChainAddress

* }} config
*/
config => harden(config),
Expand Down Expand Up @@ -187,12 +188,20 @@ export const prepareAdvancerKit = (
* @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
*/
onFulfilled(result, ctx) {
const { poolAccount } = this.state;
const { poolAccount, intermediateRecipient } = this.state;
const { destination, advanceAmount, ...detail } = ctx;
const transferV = E(poolAccount).transfer(destination, {
denom: usdc.denom,
value: advanceAmount.value,
});
const transferV = E(poolAccount).transfer(
destination,
{
denom: usdc.denom,
value: advanceAmount.value,
},
{
forwardOpts: {
intermediateRecipient,
},
},
);
return watch(transferV, this.facets.transferHandler, {
destination,
advanceAmount,
Expand Down Expand Up @@ -250,6 +259,7 @@ export const prepareAdvancerKit = (
notifyFacet: M.remotable(),
borrowerFacet: M.remotable(),
poolAccount: M.remotable(),
intermediateRecipient: ChainAddressShape,
}),
},
);
Expand Down
10 changes: 9 additions & 1 deletion packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AmountMath } from '@agoric/ertp';
import { assertAllDefined, makeTracer } from '@agoric/internal';
import { ChainAddressShape } from '@agoric/orchestration';
import { atob } from '@endo/base64';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
Expand Down Expand Up @@ -93,6 +94,7 @@ export const prepareSettler = (
* remoteDenom: Denom;
* repayer: LiquidityPoolKit['repayer'];
* settlementAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>
* intermediateRecipient: ChainAddress;
* }} config
*/
config => {
Expand Down Expand Up @@ -255,14 +257,19 @@ export const prepareSettler = (
* @param {string} EUD
*/
forward(txHash, sender, fullValue, EUD) {
const { settlementAccount } = this.state;
const { settlementAccount, intermediateRecipient } = this.state;

const dest = chainHub.makeChainAddress(EUD);

// TODO? statusManager.forwarding(txHash, sender, amount);
const txfrV = E(settlementAccount).transfer(
dest,
AmountMath.make(USDC, fullValue),
{
forwardOpts: {
intermediateRecipient,
},
},
);
void vowTools.watch(txfrV, this.facets.transferHandler, {
txHash,
Expand Down Expand Up @@ -305,6 +312,7 @@ export const prepareSettler = (
sourceChannel: M.string(),
remoteDenom: M.string(),
mintedEarly: M.remotable('mintedEarly'),
intermediateRecipient: ChainAddressShape,
}),
},
);
Expand Down
Loading
Loading