From 0a5a46455489a6ac719f4d1cfc32a8a65d65b942 Mon Sep 17 00:00:00 2001 From: 99adarsh Date: Tue, 11 Feb 2025 22:05:02 +0530 Subject: [PATCH] add ibc transfer rejection callbacks in elys contract --- .../src/examples/elys-contract-tap-kit.js | 357 +++++++++++++++--- .../src/examples/elys-contract.flow.js | 18 +- .../src/examples/elys.contract.js | 29 +- 3 files changed, 334 insertions(+), 70 deletions(-) diff --git a/packages/orchestration/src/examples/elys-contract-tap-kit.js b/packages/orchestration/src/examples/elys-contract-tap-kit.js index 3d72ea84394..685dc65f7f1 100644 --- a/packages/orchestration/src/examples/elys-contract-tap-kit.js +++ b/packages/orchestration/src/examples/elys-contract-tap-kit.js @@ -34,9 +34,9 @@ const trace = makeTracer('StrideStakingTap'); * hostToAgoricChannel: IBCChannelID; * nativeDenom: string; * ibcDenomOnAgoric: string; + * ibcDenomOnStride: string; * hostICAAccount: ERef>; * hostICAAccountAddress: ChainAddress; - * chainId: string; * bech32Prefix: string; * }} SupportedHostChainShape */ @@ -44,18 +44,18 @@ const SupportedHostChainShape = { hostToAgoricChannel: M.string(), nativeDenom: M.string(), ibcDenomOnAgoric: M.string(), + ibcDenomOnStride: M.string(), hostICAAccount: M.remotable('HostAccountICA'), hostICAAccountAddress: ChainAddressShape, - chainId: M.string(), bech32Prefix: M.string(), }; harden(SupportedHostChainShape); /** * @typedef {{ - * localAccount: ERef>; + * localAccount: ERef>; * localAccountAddress: ChainAddress; - * strideICAAccount: ERef>; + * strideICAAccount: ERef>; * strideICAAddress: ChainAddress; * elysICAAccount: ERef>; * elysICAAddress: ChainAddress; @@ -63,7 +63,9 @@ harden(SupportedHostChainShape); * elysToAgoricChannel: IBCChannelID; * AgoricToElysChannel: IBCChannelID; * stDenomOnElysTohostToAgoricChannelMap: MapStore; - * elysChainId: string; + * agoricBech32Prefix: string; + * strideBech32Prefix: string; + * elysBech32Prefix: string; * }} StakingTapState */ /** @type {TypedPattern} */ @@ -80,7 +82,9 @@ const StakingTapStateShape = { stDenomOnElysTohostToAgoricChannelMap: M.remotable( 'stDenomOnElysToHostChannelMap', ), - elysChainId: M.string(), + agoricBech32Prefix: M.string(), + strideBech32Prefix: M.string(), + elysBech32Prefix: M.string(), }; harden(StakingTapStateShape); @@ -105,38 +109,83 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { M.bigint(), M.string(), M.string(), - M.any(), - ).returns(M.or(VowShape, M.undefined())), + SupportedHostChainShape, + ).returns(VowShape), + // On rejected, move funds to use address on agoric chain + onRejected: M.call( + M.bigint(), + M.string(), + M.string(), + SupportedHostChainShape, + ).returns(VowShape), }, ), watchAndLiquidStakeOnStride: M.interface('WatchAndLiquidStakeOnStride', { // move from host to stride - onFulfilled: M.call(M.string(), M.string(), M.string()).returns( - M.or(VowShape, M.undefined()), - ), + onFulfilled: M.call( + M.string(), + M.string(), + M.string(), + SupportedHostChainShape, + ).returns(VowShape), + // On rejected, move funds to user address on host chain + onRejected: M.call( + M.string(), + M.string(), + M.string(), + SupportedHostChainShape, + ).returns(VowShape), }), watchAndSendSTtokensToUsersElysAccount: M.interface( 'WatchAndSendSTtokensToUsersElysAccount', { // move from host to stride - onFulfilled: M.call(M.string()).returns(VowShape), + onFulfilled: M.call( + M.bigint(), + M.string(), + SupportedHostChainShape, + ).returns(VowShape), + // On rejected, move funds to user address on stride chain + onRejected: M.call( + M.bigint(), + M.string(), + SupportedHostChainShape, + ).returns(VowShape), }, ), watchAndMoveFromElysToStride: M.interface( 'WatchAndMoveFromElysToStride', { - onFulfilled: M.call(M.bigint(), M.string(), M.string()).returns( - M.or(VowShape, M.undefined()), - ), + onFulfilled: M.call( + M.bigint(), + M.string(), + M.string(), + M.string(), + SupportedHostChainShape, + ).returns(M.or(VowShape, M.undefined())), + // On rejected, move funds to user address on elys chain + onRejected: M.call( + M.bigint(), + M.string(), + M.string(), + M.string(), + SupportedHostChainShape, + ).returns(M.or(VowShape, M.undefined())), }, ), watchAndRedeemOnStride: M.interface('WatchAndRedeemOnStride', { // move from host to stride onFulfilled: M.call( M.string(), + M.string(), + M.string(), + SupportedHostChainShape, + ).returns(M.or(VowShape, M.undefined())), + onRejected: M.call( M.string(), M.string(), M.string(), + SupportedHostChainShape, ).returns(M.or(VowShape, M.undefined())), }), }, @@ -154,11 +203,9 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { trace('receiveUpcall', event); // Receiving native from host chain - + if ( - !this.state.supportedHostChains.has( - event.packet.source_channel, - ) && + !this.state.supportedHostChains.has(event.packet.source_channel) && event.packet.source_channel !== this.state.elysToAgoricChannel ) { return; @@ -190,7 +237,17 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { if (tx.denom !== hostChainInfo.nativeDenom) { return; } - trace('LiquidStake: Moving funds to host-chain') + trace('LiquidStake: Moving funds to host-chain'); + trace( + 'hostChainInfo.hostICAAccountAddress ', + hostChainInfo.hostICAAccountAddress, + ); + trace( + 'hostChainInfo.ibcDenomOnAgoric ', + hostChainInfo.ibcDenomOnAgoric, + ); + trace('BigInt(tx.amount) ', BigInt(tx.amount)); + return watch( E(localAccount).transfer(hostChainInfo.hostICAAccountAddress, { denom: hostChainInfo.ibcDenomOnAgoric, @@ -198,9 +255,9 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { }), this.facets.watchAndMoveFromHostToStride, BigInt(tx.amount), - hostChainInfo.nativeDenom, // tx.denom == hostChainInfo.nativeDenom + hostChainInfo.nativeDenom, // tx.denom == hostChainInfo.nativeDenom tx.sender, - hostChainInfo.hostICAAccount, + hostChainInfo, ); } else { // Retrieve Native token from stTokens on elys @@ -214,10 +271,10 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { if (hostChainInfo === undefined) { return; } - + const { hostICAAccountAddress } = hostChainInfo; const ibcDenomOnAgoricFromElys = `ibc/${denomHash({ denom: `st${tx.denom}`, channelId: AgoricToElysChannel })}`; - trace('LiquidStakeRedeem: Moving funds to elys') + trace('LiquidStakeRedeem: Moving funds to elys'); // Transfer to elys ICA account return watch( E(localAccount).transfer(elysICAAddress, { @@ -228,8 +285,8 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { tx.amount, tx.denom, tx.sender, - hostChainInfo.bech32Prefix, - hostChainInfo.chainId, + ibcDenomOnAgoricFromElys, + hostChainInfo, ); } }, @@ -241,13 +298,14 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { * @param {bigint} amount * @param {string} denom * @param {string} senderAddress - * @param {ERef>} fromRemoteAccount + * @param {SupportedHostChainShape} hostChainInfo */ - onFulfilled(_result, amount, denom, senderAddress, fromRemoteAccount) { + onFulfilled(_result, amount, denom, senderAddress, hostChainInfo) { const { strideICAAddress } = this.state; - trace('LiquidStake: Moving funds to stride from host') + const { hostICAAccount } = hostChainInfo; + trace('LiquidStake: Moving funds to stride from host'); return watch( - E(fromRemoteAccount).transfer(strideICAAddress, { + E(hostICAAccount).transfer(strideICAAddress, { denom, value: amount, }), @@ -255,39 +313,108 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { amount.toString(), denom, senderAddress, + hostChainInfo, + ); + }, + /** + * @param {Error} _result + * @param {bigint} amount + * @param {string} _denom + * @param {string} senderAddress + * @param {SupportedHostChainShape} hostChainInfo + */ + // move funds to users agoric address + onRejected(_result, amount, _denom, senderAddress, hostChainInfo) { + const { localAccount, localAccountAddress, agoricBech32Prefix } = + this.state; + const { ibcDenomOnAgoric } = hostChainInfo; + + const senderAgoricAddress = deriveAddress( + senderAddress, + agoricBech32Prefix, + ); + const senderAgoricChainAddress = { + chainId: localAccountAddress.chainId, + encoding: localAccountAddress.encoding, + value: senderAgoricAddress, + }; + + return watch( + E(localAccount).send(senderAgoricChainAddress, { + denom: ibcDenomOnAgoric, + value: amount, + }), ); }, }, + // Move from elys to stride chainAddress watchAndMoveFromElysToStride: { /** * @param {void} _result * @param {string} amount - * @param {string} denom + * @param {string} ibcDenomOnElys + * @param {string} _ibcDenomOnAgoricFromElys * @param {string} senderAddress - * @param {string} hostChainPrefix - * @param {string} hostChainId + * @param {SupportedHostChainShape} hostChainInfo */ onFulfilled( _result, amount, - denom, + ibcDenomOnElys, + _ibcDenomOnAgoricFromElys, senderAddress, - hostChainPrefix, - hostChainId, + hostChainInfo, ) { const { strideICAAddress, elysICAAccount } = this.state; - trace('LiquidStakeRedeem: Moving funds to stride from elys') + trace('LiquidStakeRedeem: Moving funds to stride from elys'); return watch( E(elysICAAccount).transfer(strideICAAddress, { - denom, + denom: ibcDenomOnElys, value: BigInt(amount), }), this.facets.watchAndRedeemOnStride, amount, + ibcDenomOnElys, senderAddress, - hostChainPrefix, - hostChainId, + hostChainInfo, + ); + }, + /** + * @param {void} _result + * @param {string} amount + * @param {string} _ibcDenomOnElys + * @param {string} ibcDenomOnAgoricFromElys + * @param {string} senderAddress + * @param {SupportedHostChainShape} _hostChainInfo + */ + // move funds to users agoric address + onRejected( + _result, + amount, + _ibcDenomOnElys, + ibcDenomOnAgoricFromElys, + senderAddress, + _hostChainInfo, + ) { + const { localAccount, localAccountAddress, agoricBech32Prefix } = + this.state; + + const senderAgoricAddress = deriveAddress( + senderAddress, + agoricBech32Prefix, + ); + const senderAgoricChainAddress = { + chainId: localAccountAddress.chainId, + encoding: localAccountAddress.encoding, + value: senderAgoricAddress, + }; + + return watch( + E(localAccount).send(senderAgoricChainAddress, { + denom: ibcDenomOnAgoricFromElys, + value: BigInt(amount), + }), ); }, }, @@ -296,41 +423,82 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { /** * @param {void} _result * @param {string} amount + * @param {string} ibcDenomOnElys * @param {string} senderAddress - * @param {string} hostChainPrefix, - * @param {string} hostChainId, + * @param {SupportedHostChainShape} hostChainInfo, */ onFulfilled( _result, amount, + ibcDenomOnElys, senderAddress, - hostChainPrefix, - hostChainId, + hostChainInfo, ) { const { strideICAAddress, strideICAAccount } = this.state; - + const { bech32Prefix, hostICAAccountAddress } = hostChainInfo; // TODO: verify address derivation const senderNativeAddress = deriveAddress( senderAddress, - hostChainPrefix, + bech32Prefix, + ); + trace( + `Derived Native address from elys address ${senderAddress} is ${senderNativeAddress}`, ); - trace(`Derived Native address from elys address ${senderAddress} is ${senderNativeAddress}`) // UnStake Tokens and get stTokens on strideICA wallet const strideRedeemStakeMsg = Any.toJSON( MsgRedeemStake.toProtoMsg({ creator: strideICAAddress.value, amount: amount, - hostZone: hostChainId, + hostZone: hostICAAccountAddress.chainId, receiver: senderNativeAddress, }), ); - trace('LiquidStakeRedeem: unstaking now') + trace('LiquidStakeRedeem: unstaking now'); return watch( E(strideICAAccount).executeEncodedTx([strideRedeemStakeMsg]), ); }, + /** + * @param {void} _result + * @param {string} amount + * @param {string} ibcDenomOnElys + * @param {string} senderAddress + * @param {SupportedHostChainShape} _hostChainInfo, + */ + // move funds to users elys address + onRejected( + _result, + amount, + ibcDenomOnElys, + senderAddress, + _hostChainInfo, + ) { + const { elysICAAccount, elysICAAddress, elysBech32Prefix } = + this.state; + + const senderElysAddress = deriveAddress( + senderAddress, + elysBech32Prefix, + ); + trace( + `Derived Native address from elys address ${senderAddress} is ${senderElysAddress}`, + ); + + const senderElysChainAddress = { + chainId: elysICAAddress.chainId, + encoding: elysICAAddress.encoding, + value: senderElysAddress, + }; + + return watch( + E(elysICAAccount).send(senderElysChainAddress, { + denom: ibcDenomOnElys, + value: BigInt(amount), + }), + ); + }, }, // Liquid Stake on stride chain watchAndLiquidStakeOnStride: { @@ -339,8 +507,15 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { * @param {string} amount * @param {string} nativeDenom * @param {string} senderAddress + * @param {SupportedHostChainShape} hostChainInfo */ - onFulfilled(_result, amount, nativeDenom, senderAddress) { + onFulfilled( + _result, + amount, + nativeDenom, + senderAddress, + hostChainInfo, + ) { const { strideICAAccount, strideICAAddress } = this.state; const strideLiquidStakeMsg = Any.toJSON( @@ -351,11 +526,39 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { }), ); - trace('LiquidStake: Calling liquid stake') + trace('LiquidStake: Calling liquid stake'); return watch( E(strideICAAccount).executeEncodedTx([strideLiquidStakeMsg]), this.facets.watchAndSendSTtokensToUsersElysAccount, + BigInt(amount), senderAddress, + hostChainInfo, + ); + }, + /** + * @param {Error} _result + * @param {string} amount + * @param {string} nativeDenom + * @param {string} senderAddress + * @param {SupportedHostChainShape} hostChainInfo + */ + // move funds to users host address + onRejected(_result, amount, nativeDenom, senderAddress, hostChainInfo) { + const { hostICAAccount, hostICAAccountAddress, bech32Prefix } = + hostChainInfo; + + const senderHostAddress = deriveAddress(senderAddress, bech32Prefix); + const senderHostChainAddress = { + chainId: hostICAAccountAddress.chainId, + encoding: hostICAAccountAddress.encoding, + value: senderHostAddress, + }; + + return watch( + E(hostICAAccount).send(senderHostChainAddress, { + denom: nativeDenom, + value: BigInt(amount), + }), ); }, }, @@ -363,10 +566,13 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { watchAndSendSTtokensToUsersElysAccount: { /** * @param {string} result + * @param {bigint} amount * @param {string} senderAddress + * @param {SupportedHostChainShape} hostChainInfo */ - onFulfilled(result, senderAddress) { - const { strideICAAccount, elysChainId } = this.state; + onFulfilled(result, amount, senderAddress, hostChainInfo) { + const { strideICAAccount, elysBech32Prefix, elysICAAddress } = + this.state; const strideLSDResponse = tryDecodeResponse( result, @@ -378,17 +584,22 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { ); // TODO: verify the address derivation - const senderElysAddress = deriveAddress(senderAddress, 'elys'); - trace(`Derived Elys address from ${senderAddress} is ${senderElysAddress}`) + const senderElysAddress = deriveAddress( + senderAddress, + elysBech32Prefix, + ); + trace( + `Derived Elys address from ${senderAddress} is ${senderElysAddress}`, + ); /** @type {ChainAddress} */ const senderChainAddress = { - chainId: elysChainId, - encoding: 'bech32', + chainId: elysICAAddress.chainId, + encoding: elysICAAddress.encoding, value: senderElysAddress.toString(), }; - trace('LiquidStake: Moving funds to elys from stride') + trace('LiquidStake: Moving funds to elys from stride'); // Move stTokens from stride ICA to user's elys address return watch( E(strideICAAccount).transfer(senderChainAddress, { @@ -397,6 +608,36 @@ const prepareStrideStakingTapKit = (zone, { watch }) => { }), ); }, + /** + * @param {Error} _result + * @param {bigint} amount + * @param {string} senderAddress + * @param {SupportedHostChainShape} hostChainInfo + */ + // move funds to user address on stride chain + onRejected(_result, amount, senderAddress, hostChainInfo) { + const { ibcDenomOnStride } = hostChainInfo; + const { strideICAAccount, strideICAAddress, strideBech32Prefix } = + this.state; + + const senderStrideAddress = deriveAddress( + senderAddress, + strideBech32Prefix, + ); + + const senderStrideChainAddress = { + chainId: strideICAAddress.chainId, + encoding: strideICAAddress.encoding, + value: senderStrideAddress, + }; + + return watch( + E(strideICAAccount).send(senderStrideChainAddress, { + denom: ibcDenomOnStride, + value: BigInt(amount), + }), + ); + }, }, }, ); diff --git a/packages/orchestration/src/examples/elys-contract.flow.js b/packages/orchestration/src/examples/elys-contract.flow.js index 5b83ef2389e..803d210ab5a 100644 --- a/packages/orchestration/src/examples/elys-contract.flow.js +++ b/packages/orchestration/src/examples/elys-contract.flow.js @@ -27,7 +27,7 @@ const trace = makeTracer('StrideStakingFlow'); export const makeICAHookAccounts = async ( orch, { makeStrideStakingTap, chainHub }, - { chainNames, supportedHostChains,stDenomOnElysTohostToAgoricChannelMap }, + { chainNames, supportedHostChains, stDenomOnElysTohostToAgoricChannelMap }, ) => { const allRemoteChains = await Promise.all( chainNames.map(n => orch.getChain(n)), @@ -35,19 +35,22 @@ export const makeICAHookAccounts = async ( // Agoric local account const agoric = await orch.getChain('agoric'); - const { chainId: agoricChainId } = await agoric.getChainInfo(); + const { chainId: agoricChainId, bech32Prefix: agoricBech32Prefix } = + await agoric.getChainInfo(); const localAccount = await agoric.makeAccount(); const localAccountAddress = localAccount.getAddress(); // stride ICA account const stride = await orch.getChain('stride'); - const { chainId: strideChainId } = await stride.getChainInfo(); + const { chainId: strideChainId, bech32Prefix: strideBech32Prefix } = + await stride.getChainInfo(); const strideICAAccount = await stride.makeAccount(); const strideICAAddress = strideICAAccount.getAddress(); // Elys ICA account const elys = await orch.getChain('elys'); - const { chainId: elysChainId } = await elys.getChainInfo(); + const { chainId: elysChainId, bech32Prefix: elysBech32Prefix } = + await elys.getChainInfo(); const elysICAAccount = await elys.makeAccount(); const elysICAAddress = elysICAAccount.getAddress(); @@ -79,6 +82,7 @@ export const makeICAHookAccounts = async ( await chainHub.getConnectionInfo(chainId, strideChainId); const ibcDenomOnAgoric = `ibc/${denomHash({ denom: nativeDenom, channelId: transferChannel.channelId })}`; + const ibcDenomOnStride = `ibc/${denomHash({ denom: nativeDenom, channelId: transferChannelhostStride.channelId })}`; // Required in retrieving native token back from stTokens on elys chain const stTokenDenomOnElys = `ibc/${denomHash({ denom: `st${nativeDenom}`, channelId: transferChannelStrideElys.channelId })}`; @@ -94,9 +98,9 @@ export const makeICAHookAccounts = async ( hostToAgoricChannel: transferChannel.counterPartyChannelId, nativeDenom, ibcDenomOnAgoric, + ibcDenomOnStride, hostICAAccount: ICAAccount, hostICAAccountAddress: ICAAddress, - chainId, bech32Prefix, }; supportedHostChains.init( @@ -116,7 +120,9 @@ export const makeICAHookAccounts = async ( elysToAgoricChannel: transferChannelAgoricElys.counterPartyChannelId, AgoricToElysChannel: transferChannelAgoricElys.channelId, stDenomOnElysTohostToAgoricChannelMap, - elysChainId, + agoricBech32Prefix, + strideBech32Prefix, + elysBech32Prefix, }); // @ts-expect-error tap.receiveUpcall: 'Vow | undefined' not assignable to 'Promise' diff --git a/packages/orchestration/src/examples/elys.contract.js b/packages/orchestration/src/examples/elys.contract.js index 8a648f867db..eb28909979b 100644 --- a/packages/orchestration/src/examples/elys.contract.js +++ b/packages/orchestration/src/examples/elys.contract.js @@ -1,8 +1,12 @@ +import { makeTracer } from '@agoric/internal'; +import { registerChainsAndAssets } from '../utils/chain-hub-helper.js'; import { withOrchestration } from '../utils/start-helper.js'; import { prepareStrideStakingTap } from './elys-contract-tap-kit.js'; import * as flows from './elys-contract.flow.js'; import { E } from '@endo/far'; +const trace = makeTracer('ContractInstantiation'); + const interfaceTODO = undefined; /** * @import {Zone} from '@agoric/zone'; @@ -13,18 +17,18 @@ const interfaceTODO = undefined; /** * To be wrapped with `withOrchestration`. * - * @param {ZCF} _zcf + * @param {ZCF} zcf * @param {OrchestrationPowers & { * marshaller: Marshaller; * chainInfo?: Record; * assetInfo?: [Denom, DenomDetail & { brandKey?: string }][]; - * }} _privateArgs + * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools */ const contract = async ( - _zcf, - _privateArgs, + zcf, + privateArgs, zone, { chainHub, orchestrateAll, vowTools }, // orchestration tools ) => { @@ -38,8 +42,22 @@ const contract = async ( chainHub, }); + trace( + 'Registered chains and assets :', + privateArgs.chainInfo, + privateArgs.assetInfo, + ); + registerChainsAndAssets( + chainHub, + zcf.getTerms().brands, + privateArgs.chainInfo, + privateArgs.assetInfo, + ); + const passablesupportedHostChains = zone.mapStore('supportedHostChains'); - const stDenomOnElysTohostToAgoricChannelMap = zone.mapStore('stDenomOnElysToHostChannelMap'); + const stDenomOnElysTohostToAgoricChannelMap = zone.mapStore( + 'stDenomOnElysToHostChannelMap', + ); const icaAndLocalAccount = zone.makeOnce('icaAndLocalAccount', _key => makeICAHookAccounts({ @@ -62,7 +80,6 @@ const contract = async ( export const start = withOrchestration(contract); harden(start); - // TODO: Send these params during initialisation of the contract const allowedChains = ['cosmoshub']; harden(allowedChains);