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

feat: implement .transfer on CosmosOrchestrationAccount #9882

Merged
merged 14 commits into from
Aug 16, 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
101 changes: 97 additions & 4 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,12 +341,105 @@ test('basic-flows', async t => {
wd.getCurrentWalletRecord().offerToPublicSubscriberPaths,
);
t.deepEqual(publicSubscriberPaths['request-loa'], {
account: 'published.basicFlows.agoric1mockVlocalchainAddress',
account: 'published.basicFlows.agoric1fakeLCAAddress1',
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-loa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.basicFlows.agoric1mockVlocalchainAddress'), '');
t.is(readLatest('published.basicFlows.agoric1fakeLCAAddress'), '');

await wd.sendOffer({
id: 'transfer-to-noble-from-cosmos',
invitationSpec: {
source: 'continuing',
previousOffer: 'request-coa',
invitationMakerName: 'Transfer',
},
proposal: {},
offerArgs: {
amount: { denom: 'ibc/uusdchash', value: 10n },
destination: {
chainId: 'noble-1',
value: 'noble1test',
encoding: 'bech32,',
},
},
});
t.like(wd.getLatestUpdateRecord(), {
status: {
id: 'transfer-to-noble-from-cosmos',
error: undefined,
},
});

await wd.sendOffer({
id: 'transfer-to-noble-from-cosmos-timeout',
invitationSpec: {
source: 'continuing',
previousOffer: 'request-coa',
invitationMakerName: 'Transfer',
},
proposal: {},
offerArgs: {
amount: { denom: 'ibc/uusdchash', value: SIMULATED_ERRORS.TIMEOUT },
destination: {
chainId: 'noble-1',
value: 'noble1test',
encoding: 'bech32,',
},
},
});
t.like(wd.getLatestUpdateRecord(), {
status: {
id: 'transfer-to-noble-from-cosmos-timeout',
error:
'Error: ABCI code: 5: error handling packet: see events for details',
},
});

await wd.sendOffer({
id: 'transfer-to-noble-from-agoric',
invitationSpec: {
source: 'continuing',
previousOffer: 'request-loa',
invitationMakerName: 'Transfer',
},
proposal: {},
offerArgs: {
amount: { denom: 'ibc/uusdchash', value: 10n },
destination: {
chainId: 'noble-1',
value: 'noble1test',
encoding: 'bech32,',
},
},
});
t.like(wd.getLatestUpdateRecord(), {
status: {
id: 'transfer-to-noble-from-agoric',
error: undefined,
},
});

await t.throwsAsync(
wd.executeOffer({
id: 'transfer-to-noble-from-agoric-timeout',
invitationSpec: {
source: 'continuing',
previousOffer: 'request-loa',
invitationMakerName: 'Transfer',
},
proposal: {},
offerArgs: {
amount: { denom: 'ibc/uusdchash', value: SIMULATED_ERRORS.TIMEOUT },
destination: {
chainId: 'noble-1',
value: 'noble1test',
encoding: 'bech32,',
},
},
}),
);
});

test.serial('auto-stake-it - proposal', async t => {
Expand Down Expand Up @@ -401,7 +494,7 @@ test.serial('basic-flows - portfolio holder', async t => {
[
'request-portfolio-acct',
{
agoric: 'published.basicFlows.agoric1mockVlocalchainAddress',
agoric: 'published.basicFlows.agoric1fakeLCAAddress',
cosmoshub: 'published.basicFlows.cosmos1test',
// XXX support multiple chain addresses in ibc mocks
osmosis: 'published.basicFlows.cosmos1test',
Expand All @@ -415,7 +508,7 @@ test.serial('basic-flows - portfolio holder', async t => {
// XXX this overrides a previous account, since mocks only provide one address
t.is(readLatest('published.basicFlows.cosmos1test'), '');
// XXX this overrides a previous account, since mocks only provide one address
t.is(readLatest('published.basicFlows.agoric1mockVlocalchainAddress'), '');
t.is(readLatest('published.basicFlows.agoric1fakeLCAAddress'), '');

const { BLD } = agoricNamesRemotes.brand;
BLD || Fail`BLD missing from agoricNames`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const test: TestFn<WalletFactoryTestContext> = anyTest;
/**
* To update, pass the message into `makeTxPacket` or `makeQueryPacket` from
* `@agoric/orchestration`, and paste the resulting `data` key into `protoMsgMocks`
* in [mocks.js](../../tools/ibc/mocks.js).
* in [mocks.ts](../../tools/ibc/mocks.ts).
* If adding a new msg, reference the mock in the `sendPacket` switch statement
* in [supports.ts](../../tools/supports.ts).
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-check
import { createMockAckMap } from '@agoric/orchestration/tools/ibc-mocks.js';

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

Expand All @@ -16,6 +17,9 @@ const responses = {
// eyJkYXRhIjoiQ2hzeUdRb1hDaEp6YjIxbExXbHVkbUZzYVdRdFpHVnViMjBTQVRBPSJ9
queryBalanceUnknownDenom:
'eyJyZXN1bHQiOiJleUprWVhSaElqb2lRMmh6ZVVkUmIxaERhRXA2WWpJeGJFeFhiSFZrYlVaellWZFJkRnBIVm5WaU1qQlRRVlJCUFNKOSJ9',
// /ibc.applications.transfer.v1.MsgTransferResponse - sequence 0n
ibcTransfer:
'eyJyZXN1bHQiOiJFak1LTVM5cFltTXVZWEJ3YkdsallYUnBiMjV6TG5SeVlXNXpabVZ5TG5ZeExrMXpaMVJ5WVc1elptVnlVbVZ6Y0c5dWMyVT0ifQ==',
// {"error":"ABCI code: 4: error handling packet: see events for details"}
error4:
'eyJlcnJvciI6IkFCQ0kgY29kZTogNDogZXJyb3IgaGFuZGxpbmcgcGFja2V0OiBzZWUgZXZlbnRzIGZvciBkZXRhaWxzIn0=',
Expand Down Expand Up @@ -56,11 +60,19 @@ export const protoMsgMocks = {
msg: 'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xVS0l5OWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxelowUmxiR1ZuWVhSbEVpNEtDMk52YzIxdmN6RjBaWE4wRWhKamIzTnRiM04yWVd4dmNHVnlNWFJsYzNRYUN3b0ZkV0YwYjIwU0FqRXdFZ2RVUlZOVVNVNUhHSUNVNjl3RCIsIm1lbW8iOiIifQ==',
ack: responses.delegate,
},
// MsgTransfer 10 ibc/uusdchash from cosmos1test to noble1test through channel-536
ibcTransfer: {
msg: 'eyJ0eXBlIjoxLCJkYXRhIjoiQ25zS0tTOXBZbU11WVhCd2JHbGpZWFJwYjI1ekxuUnlZVzV6Wm1WeUxuWXhMazF6WjFSeVlXNXpabVZ5RWs0S0NIUnlZVzV6Wm1WeUVndGphR0Z1Ym1Wc0xUVXpOaG9UQ2cxcFltTXZkWFZ6WkdOb1lYTm9FZ0l4TUNJTFkyOXpiVzl6TVhSbGMzUXFDbTV2WW14bE1YUmxjM1F5QURpQThKTEwzUWc9IiwibWVtbyI6IiJ9',
ack: responses.ibcTransfer,
},
error: {
msg: '',
ack: responses.error5,
},
};

export const protoMsgMockMap = createMockAckMap(protoMsgMocks);

/**
* Adds parameters to IBC version string if it's JSON
* @param {string} version version or JSON version string
Expand Down
47 changes: 15 additions & 32 deletions packages/boot/tools/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ import { loadSwingsetConfigFile } from '@agoric/swingset-vat';
import { makeSlogSender } from '@agoric/telemetry';
import { TimeMath, Timestamp } from '@agoric/time';
import { Fail } from '@endo/errors';
import { fakeLocalChainBridgeTxMsgHandler } from '@agoric/vats/tools/fake-bridge.js';
import {
fakeLocalChainBridgeTxMsgHandler,
LOCALCHAIN_DEFAULT_ADDRESS,
} from '@agoric/vats/tools/fake-bridge.js';

import {
makeRunUtils,
Expand All @@ -37,15 +40,13 @@ import {

import type { ExecutionContext as AvaT } from 'ava';

import type { JsonSafe } from '@agoric/cosmic-proto';
import type { MsgDelegateResponse } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js';
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, IBCPacket } from '@agoric/vats';
import type { BootstrapRootObject } from '@agoric/vats/src/core/lib-boot.js';
import type { EProxy } from '@endo/eventual-send';
import { icaMocks, protoMsgMocks } from './ibc/mocks.js';
import { icaMocks, protoMsgMockMap, protoMsgMocks } from './ibc/mocks.js';

const trace = makeTracer('BSTSupport', false);

Expand Down Expand Up @@ -325,6 +326,7 @@ export const makeSwingsetTestKit = async (
let lastBankNonce = 0n;
let ibcSequenceNonce = 0;
let lcaSequenceNonce = 0;
let lcaAccountsCreated = 0;

const outboundMessages = new Map();

Expand Down Expand Up @@ -449,34 +451,13 @@ export const makeSwingsetTestKit = async (
case 'startChannelOpenInit':
pushInbound(BridgeId.DIBC, icaMocks.channelOpenAck(obj));
return undefined;
case 'sendPacket':
switch (obj.packet.data) {
case protoMsgMocks.delegate.msg: {
return ackLater(obj, protoMsgMocks.delegate.ack);
}
case protoMsgMocks.delegateWithOpts.msg: {
return ackLater(obj, protoMsgMocks.delegateWithOpts.ack);
}
case protoMsgMocks.queryBalance.msg: {
return ackLater(obj, protoMsgMocks.queryBalance.ack);
}
case protoMsgMocks.queryUnknownPath.msg: {
return ackLater(obj, protoMsgMocks.queryUnknownPath.ack);
}
case protoMsgMocks.queryBalanceMulti.msg: {
return ackLater(obj, protoMsgMocks.queryBalanceMulti.ack);
}
case protoMsgMocks.queryBalanceUnknownDenom.msg: {
return ackLater(
obj,
protoMsgMocks.queryBalanceUnknownDenom.ack,
);
}
default: {
// An error that would be triggered before reception on another chain
return ackImmediately(obj, protoMsgMocks.error.ack);
}
case 'sendPacket': {
if (protoMsgMockMap[obj.packet.data]) {
return ackLater(obj, protoMsgMockMap[obj.packet.data]);
}
// An error that would be triggered before reception on another chain
return ackImmediately(obj, protoMsgMocks.error.ack);
Copy link
Member Author

Choose a reason for hiding this comment

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

// An error that would be triggered before reception on another chain

Thinking on this more, I suspect the relayer could also be delayed. Any harm in making this ackLater?

Copy link
Member

@turadg turadg Aug 13, 2024

Choose a reason for hiding this comment

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

Seems like a trade-off. IIRC:

  1. a sendPacket message could have an immediate failure before leaving the local node (i.e. just over the bridge) or after a reply from a relayer.
  2. we can't reliably distinguish between those cases here

If (2) is false, then let's distinguish.

If (2) is true then we have to pick the better way to handle both. The pro of ackLater is that it let's us test doing other work before the ack arrives. The con is that it's something someone could forget in a test, and even when they don't it's more work.

The other immediate con is that it may break some tests and need fixing.

I don't have a strong stance on which is better. Whatever infidelity you choose, please leave a comment explaining the decision.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thinking on this more, I also don't have a strong stance. It seems the main benefit of ackLater is to allow us to control timing in the test context. ackImmediately seems like a sensible default, as someone can always choose to supply a mock to the bridge that will use ackLater.

Nonetheless, sharing some thoughts/findings to your question -

  1. a sendPacket message could have an immediate failure before leaving the local node (i.e. just over the bridge) or after a reply from a relayer.

Correct, the sendPacket handler can fail via ReceiveSendPacket before leaving agoric golang and going out to a relayer.

It seems this might only fail if an invalid sourcePort and sourceChannel are provided. I'm not sure when ibc.js or network.js would allow this since .downcall() is heavily guarded with ocaps + closures - the only thing coming to mind is bad state where a channel is closed but ibc/network are unaware.

}
default:
return undefined;
}
Expand All @@ -490,7 +471,9 @@ export const makeSwingsetTestKit = async (
return undefined;
}
case `${BridgeId.VLOCALCHAIN}:VLOCALCHAIN_ALLOCATE_ADDRESS`: {
return 'agoric1mockVlocalchainAddress';
const address = `${LOCALCHAIN_DEFAULT_ADDRESS}${lcaAccountsCreated || ''}`;
lcaAccountsCreated += 1;
return address;
}
case `${BridgeId.VLOCALCHAIN}:VLOCALCHAIN_EXECUTE_TX`: {
lcaSequenceNonce += 1;
Expand Down
5 changes: 3 additions & 2 deletions packages/builders/scripts/testing/restart-stakeAtom.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ const trace = makeTracer('RestartSA', true);
*/
export const restartStakeAtom = async ({
consume: {
agoricNames,
board,
chainStorage,
chainTimerService,
cosmosInterchainService,

turadg marked this conversation as resolved.
Show resolved Hide resolved
contractKits,
},
instance: instances,
Expand All @@ -40,6 +40,7 @@ export const restartStakeAtom = async ({

const privateArgs = await deeplyFulfilledObject(
harden({
agoricNames,
cosmosInterchainService,
storageNode: makeStorageNodeChild(chainStorage, 'stakeAtom'),
marshaller,
Expand All @@ -57,11 +58,11 @@ export const getManifest = () => {
manifest: {
[restartStakeAtom.name]: {
consume: {
agoricNames: true,
board: true,
chainStorage: true,
chainTimerService: true,
cosmosInterchainService: true,

contractKits: true,
},
instance: {
Expand Down
4 changes: 4 additions & 0 deletions packages/cosmic-proto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
"types": "./dist/codegen/ibc/applications/interchain_accounts/v1/packet.d.ts",
"default": "./dist/codegen/ibc/applications/interchain_accounts/v1/packet.js"
},
"./ibc/applications/transfer/v1/tx.js": {
"types": "./dist/codegen/ibc/applications/transfer/v1/tx.d.ts",
"default": "./dist/codegen/ibc/applications/transfer/v1/tx.js"
},
"./ibc/core/channel/v1/channel.js": {
"types": "./dist/codegen/ibc/core/channel/v1/channel.d.ts",
"default": "./dist/codegen/ibc/core/channel/v1/channel.js"
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/examples/stakeBld.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export const start = async (zcf, privateArgs, baggage) => {
address: harden({
value: address,
encoding: 'bech32',
chainId: 'local',
chainId: 'agoriclocal',
}),
storageNode: privateArgs.storageNode,
});
Expand Down
21 changes: 16 additions & 5 deletions packages/orchestration/src/examples/stakeIca.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { M } from '@endo/patterns';
import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js';
import { makeChainHub } from '../exos/chain-hub.js';

const trace = makeTracer('StakeIca');
/**
* @import {Baggage} from '@agoric/vat-data';
* @import {IBCConnectionID} from '@agoric/vats';
* @import {Remote} from '@agoric/internal';
* @import {IBCConnectionID, NameHub} from '@agoric/vats';
* @import {TimerService} from '@agoric/time';
* @import {ResolvedContinuingOfferResult} from '../utils/zoe-tools.js';
* @import {ICQConnection, CosmosInterchainService} from '../types.js';
* @import {ICQConnection, CosmosInterchainService, ChainHub} from '../types.js';
*/

/** @type {ContractMeta<typeof start>} */
Expand All @@ -31,6 +33,7 @@ export const meta = harden({
icqEnabled: M.boolean(),
},
privateArgsShape: {
agoricNames: M.remotable('agoricNames NameHub'),
cosmosInterchainService: M.remotable('cosmosInterchainService'),
storageNode: StorageNodeShape,
marshaller: M.remotable('marshaller'),
Expand All @@ -54,6 +57,7 @@ harden(privateArgsShape);
/**
* @param {ZCF<StakeIcaTerms>} zcf
* @param {{
* agoricNames: Remote<NameHub>;
* cosmosInterchainService: CosmosInterchainService;
* storageNode: StorageNode;
* marshaller: Marshaller;
Expand All @@ -70,6 +74,7 @@ export const start = async (zcf, privateArgs, baggage) => {
icqEnabled,
} = zcf.getTerms();
const {
agoricNames,
cosmosInterchainService: orchestration,
marshaller,
storageNode,
Expand All @@ -86,11 +91,17 @@ export const start = async (zcf, privateArgs, baggage) => {

const vowTools = prepareVowTools(zone.subZone('vows'));

const chainHub = makeChainHub(agoricNames, vowTools);

const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount(
zone,
makeRecorderKit,
vowTools,
zcf,
{
chainHub,
makeRecorderKit,
timerService: timer,
vowTools,
zcf,
},
);

async function makeAccountKit() {
Expand Down
1 change: 1 addition & 0 deletions packages/orchestration/src/exos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ classDiagram
redelegate()
send()
sendAll()
transfer()
undelegate()
withdrawReward()
}
Expand Down
Loading
Loading