From 70a6e18e0e8b3429a2d879313507d7910d63fc72 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 19 Oct 2022 17:54:34 +0800 Subject: [PATCH 01/72] create a clean copy to for PR --- spec/app/ics-101-interchain-swap/README.md | 874 +++++++++++++++++++++ 1 file changed, 874 insertions(+) create mode 100644 spec/app/ics-101-interchain-swap/README.md diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md new file mode 100644 index 000000000..98a8f22cb --- /dev/null +++ b/spec/app/ics-101-interchain-swap/README.md @@ -0,0 +1,874 @@ +--- +ics: 101 +title: Interchain Swap +stage: draft +category: IBC/APP +kind: instantiation +author: Ping , Edward Gunawan +created: 2022-10-09 +modified: 2022-10-11 +requires: 24, 25 +--- + +## Synopsis + +This standard document specifies the packet data structure, state machine handling logic, and encoding details for token exchange through single-sided liquidity pools over an IBC channel between separate chains. + +### Motivation + +ICS-101 Interchain Swaps enables chains their own token pricing mechanism and exchange protocol via IBC transactions. Each chain can thus play a role in a fully decentralised exchange network. + +Users might also prefer single asset pools over dual assets pools as it removes the risk of impermanent loss. + +### Definitions + +`Interchain swap`: a IBC token swap protocol, built on top of an automated marketing making system, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. + +`Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. + +`Weighted pools`: liquidity pools characterized by the percentage weight of each token denomination maintained within. + +`Single-sided liquidity pools`: a liquidity pool that does not require users to deposit both token denominations -- one is enough. + +`Left-side swap`: a token exchange that specifies the desired quantity to be sold. + +`Right-side swap`: a token exchange that specifies the desired quantity to be purchased. + +`Pool state`: the entire state of a liquidity pool including its invariant value which is derived from its token balances and weights inside. + +### Desired Properties + +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralization`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. + + +## Technical Specification + +### Algorithms + +#### Invariant + +A constant invariant is maintained after trades which takes into consideration token weights and balance. The value function $V$ is defined as: + +$$V = {Π_tB_t^{W_t}}$$ + +Where + +- $t$ ranges over the tokens in the pool +- $B_t$ is the balance of the token in the pool +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. + +#### Spot Price + +Spot prices of tokens are defined entirely by the weights and balances of the token pair. The spot price between any two tokens, $SpotPrice_i^{o}$, or in short $SP_i^o$, is the ratio of the token balances normalized by their weights: + +$$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ + +- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool +- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool +- $W_i$ is the weight of token $i$ +- $W_o$ is the weight of token $o$ + +#### Fees + +Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.0001% and a maximum value of 10%. + +The fees go to liquidity providers in exchange for depositing their tokens in the pool to facilitate trades. Trade fees are collected at the time of a swap, and goes directly into the pool, increasing the pool balance. For a trade with a given $inputToken$ and $outputToken$, the amount collected by the pool as a fee is + +$$Amount_{fee} = Amount_{inputToken} * swapFee$$ + +As the pool collects fees, liquidity providers automatically collect fees through their proportional ownership of the pool balance. + +### Data Structures + +Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. + +```ts +enum MessageType { + Create, + Deposit, + Withdraw, + LeftSwap, + RightSwap, +} + +// IBCSwapDataPacket is used to wrap message for relayer. +interface IBCSwapDataPacket { + msgType: MessageType, + data: Uint8Array, // Bytes +} +``` + +### Sub-protocols + +Traditional liquidity pools typically maintain its pool state in one location. + +A liquidity pool in the interchain swap protocol maintains its pool state on both its source chain and destination chain. The pool states mirror each other and are synced through IBC packet relays, which we elaborate on in the following sub-protocols. + +IBCSwap implements the following sub-protocols: + +```protobuf + rpc DelegateCreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); + rpc DelegateSingleDeposit(MsgSingleDepositRequest) returns (MsgSingleDepositResponse); + rpc DelegateWithdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); + rpc DelegateLeftSwap(MsgLeftSwapRequest) returns (MsgSwapResponse); + rpc DelegateRightSwap(MsgRightSwapRequest) returns (MsgSwapResponse); +``` + +#### Interfaces for sub-protocols + +``` ts +interface MsgCreatePoolRequest { + sender: string, + denoms: string[], + decimals: [], + weight: string, +} + +interface MsgCreatePoolResponse {} +``` +```ts +interface MsgDepositRequest { + sender: string, + tokens: Coin[], +} +interface MsgSingleDepositResponse { + pool_token: Coin[]; +} +``` +```ts +interface MsgWithdrawRequest { + sender: string, + poolCoin: Coin, + denomOut: string, // optional, if not set, withdraw native coin to sender. +} +interface MsgWithdrawResponse { + tokens: Coin[]; +} +``` + ```ts + interface MsgLeftSwapRequest { + sender: string, + tokenIn: Coin, + denomOut: string, + slippage: number; // max tolerated slippage + recipient: string, +} +interface MsgSwapResponse { + tokens: Coin[]; +} +``` + ```ts +interface MsgRightSwapRequest { + sender: string, + denomIn: string, + tokenOut: Coin, + slippage: number; // max tolerated slippage + recipient: string, +} +interface MsgSwapResponse { + tokens: Coin[]; +} +``` + +### Control Flow And Life Scope + +To implement interchain swap, we introduce the `Message Delegator` and `Relay Listener`. The `Message Delegator` will pre-process the request (validate msgs, lock assets, etc), and then forward the transactions to the relayer. + +```go +func (k Keeper) DelegateCreatePool(goctx context.Context, msg *types.MsgCreatePoolRequest) (*types.MsgCreatePoolResponse, error) { + ctx := sdk.UnwrapSDKContext(goctx) + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + _, err1 := sdk.AccAddressFromBech32(msg.Sender) + if err1 != nil { + return nil, err1 + } + + pool := types.NewBalancerLiquidityPool(msg.Denoms, msg.Decimals, msg.Weight) + if err := pool.Validate(); err != nil { + return nil, err + } + + // count native tokens + count := 0 + for _, denom := range msg.Denoms { + if k.bankKeeper.HasSupply(ctx, denom) { + count += 1 + pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_NATIVE_ASSET) + } else { + pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_REMOTE_ASSET) + } + } + if count == 0 { + return nil, types.ErrNoNativeTokenInPool + } + + msgByte, err0 := types.ModuleCdc.Marshal(msg) + if err0 != nil { + return nil, err0 + } + + packet := types.NewIBCSwapPacketData(types.CREATE_POOL, msgByte, nil) + if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { + return nil, err + } + + ctx.EventManager().EmitTypedEvents(msg) + + return &types.MsgCreatePoolResponse{}, nil +} + +func (k Keeper) DelegateSingleDeposit(goctx context.Context, msg *types.MsgSingleDepositRequest) (*types.MsgSingleDepositResponse, error) { + ctx := sdk.UnwrapSDKContext(goctx) + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + sender, err1 := sdk.AccAddressFromBech32(msg.Sender) + if err1 != nil { + return nil, err1 + } + + // deposit assets to the swap module + length := len(msg.Tokens) + var coins = make([]sdk.Coin, length) + for i := 0; i < length; i++ { + coins[i] = *msg.Tokens[i] + } + k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(coins...)) + + msgByte, err0 := types.ModuleCdc.Marshal(msg) + if err0 != nil { + return nil, err0 + } + + packet := types.NewIBCSwapPacketData(types.SINGLE_DEPOSIT, msgByte, nil) + if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { + return nil, err + } + + ctx.EventManager().EmitTypedEvents(msg) + + return &types.MsgSingleDepositResponse{}, nil +} + +func (k Keeper) DelegateWithdraw(ctx2 context.Context, msg *types.MsgWithdrawRequest) (*types.MsgWithdrawResponse, error) { + ctx := sdk.UnwrapSDKContext(ctx2) + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + sender, err1 := sdk.AccAddressFromBech32(msg.Sender) + if err1 != nil { + return nil, err1 + } + + // deposit assets to the swap module + k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(*msg.PoolToken)) + + msgByte, err0 := types.ModuleCdc.Marshal(msg) + if err0 != nil { + return nil, err0 + } + + packet := types.NewIBCSwapPacketData(types.WITHDRAW, msgByte, nil) + if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { + return nil, err + } + + ctx.EventManager().EmitTypedEvents(msg) + + return &types.MsgWithdrawResponse{}, nil +} + +func (k Keeper) DelegateLeftSwap(goctx context.Context, msg *types.MsgLeftSwapRequest) (*types.MsgSwapResponse, error) { + ctx := sdk.UnwrapSDKContext(goctx) + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + sender, err1 := sdk.AccAddressFromBech32(msg.Sender) + if err1 != nil { + return nil, err1 + } + + // deposit assets to the swap module + k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(*msg.TokenIn)) + + msgByte, err0 := types.ModuleCdc.Marshal(msg) + if err0 != nil { + return nil, err0 + } + + packet := types.NewIBCSwapPacketData(types.LEFT_SWAP, msgByte, nil) + if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { + return nil, err + } + + ctx.EventManager().EmitTypedEvents(msg) + + return &types.MsgSwapResponse{}, nil +} + +func (k Keeper) DelegateRightSwap(goctx context.Context, msg *types.MsgRightSwapRequest) (*types.MsgSwapResponse, error) { + ctx := sdk.UnwrapSDKContext(goctx) + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + sender, err1 := sdk.AccAddressFromBech32(msg.Sender) + if err1 != nil { + return nil, err1 + } + + k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(*msg.TokenIn)) + + msgByte, err0 := types.ModuleCdc.Marshal(msg) + if err0 != nil { + return nil, err0 + } + + packet := types.NewIBCSwapPacketData(types.RIGHT_SWAP, msgByte, nil) + if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { + return nil, err + } + + ctx.EventManager().EmitTypedEvents(msg) + + return &types.MsgSwapResponse{}, nil +} +``` + +The `Relay Listener` handle all transactions, execute transactions when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. + +```go +func (k Keeper) OnCreatePoolReceived(ctx sdk.Context, msg *types.MsgCreatePoolRequest) (*types.MsgCreatePoolResponse, error) { + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + _, err1 := sdk.AccAddressFromBech32(msg.Sender) + if err1 != nil { + return nil, err1 + } + + pool := types.NewBalancerLiquidityPool(msg.Denoms, msg.Decimals, msg.Weight) + if err := pool.Validate(); err != nil { + return nil, err + } + + // count native tokens + count := 0 + for _, denom := range msg.Denoms { + if k.bankKeeper.HasSupply(ctx, denom) { + count += 1 + pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_NATIVE_ASSET) + } else { + pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_REMOTE_ASSET) + } + } + if count == 0 { + return nil, types.ErrNoNativeTokenInPool + } + + k.SetBalancerPool(ctx, pool) + + return &types.MsgCreatePoolResponse{ + PoolId: pool.Id, + }, nil + +} + +func (k Keeper) OnSingleDepositReceived(ctx sdk.Context, msg *types.MsgSingleDepositRequest) (*types.MsgSingleDepositResponse, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + amm, err := k.CreateIBCSwapAMM(ctx, msg.PoolId) + if err != nil { + return nil, err + } + + poolToken, err := amm.Deposit(msg.Tokens) + if err != nil { + return nil, err + } + + k.SetBalancerPool(ctx, *amm.Pool) // update pool states + + return &types.MsgSingleDepositResponse{ + PoolToken: &poolToken, + }, nil +} + +func (k Keeper) OnWithdrawReceived(ctx sdk.Context, msg *types.MsgWithdrawRequest) (*types.MsgWithdrawResponse, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + amm, err := k.CreateIBCSwapAMM(ctx, msg.PoolToken.Denom) // Pool Token denomination is the pool Id + if err != nil { + return nil, err + } + + outToken, err := amm.Withdraw(msg.PoolToken, msg.DenomOut) + if err != nil { + return nil, err + } + + k.SetBalancerPool(ctx, *amm.Pool) // update pool states + + // only output one asset in the pool + return &types.MsgWithdrawResponse{ + Tokens: []*sdk.Coin{ + &outToken, + }, + }, nil +} + +func (k Keeper) OnLeftSwapReceived(ctx sdk.Context, msg *types.MsgLeftSwapRequest) (*types.MsgSwapResponse, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + poolId := types.GeneratePoolId([]string{msg.TokenIn.Denom, msg.DenomOut}) + + amm, err := k.CreateIBCSwapAMM(ctx, poolId) // Pool Token denomination is the pool Id + if err != nil { + return nil, err + } + + outToken, err := amm.LeftSwap(msg.TokenIn, msg.DenomOut) + if err != nil { + return nil, err + } + + k.SetBalancerPool(ctx, *amm.Pool) // update pool states + + // only output one asset in the pool + return &types.MsgSwapResponse{ + TokenOut: &outToken, + }, nil +} + +func (k Keeper) OnRightSwapReceived(ctx sdk.Context, msg *types.MsgRightSwapRequest) (*types.MsgSwapResponse, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + poolId := types.GeneratePoolId([]string{msg.TokenIn.Denom, msg.TokenOut.Denom}) + + amm, err := k.CreateIBCSwapAMM(ctx, poolId) // Pool Token denomination is the pool Id + if err != nil { + return nil, err + } + + outToken, err := amm.RightSwap(msg.TokenIn.Denom, msg.TokenOut) + if err != nil { + return nil, err + } + + k.SetBalancerPool(ctx, *amm.Pool) // update pool states + + // only output one asset in the pool + return &types.MsgSwapResponse{ + TokenOut: &outToken, + }, nil +} + +func (k Keeper) OnCreatePoolAcknowledged(ctx sdk.Context, request *types.MsgCreatePoolRequest, response *types.MsgCreatePoolResponse) error { + +} + +func (k Keeper) OnSingleDepositAcknowledged(ctx sdk.Context, request *types.MsgSingleDepositRequest, response *types.MsgSingleDepositResponse) error { + +} + +func (k Keeper) OnWithdrawAcknowledged(ctx sdk.Context, request *types.MsgWithdrawRequest, response *types.MsgWithdrawResponse) error { + +} + +func (k Keeper) OnLeftSwapAcknowledged(ctx sdk.Context, request *types.MsgLeftSwapRequest, response *types.MsgSwapResponse) error { + +} + +func (k Keeper) OnRightSwapAcknowledged(ctx sdk.Context, request *types.MsgRightSwapRequest, response *types.MsgSwapResponse) error { + +} +``` + +#### Port & channel setup + +The fungible token swap module on a chain must always bind to a port with the id `interchainswap` + +The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module). + +```typescript +function setup() { + capability = routingModule.bindPort("interchainswap", ModuleCallbacks{ + onChanOpenInit, + onChanOpenTry, + onChanOpenAck, + onChanOpenConfirm, + onChanCloseInit, + onChanCloseConfirm, + onRecvPacket, + onTimeoutPacket, + onAcknowledgePacket, + onTimeoutPacketClose + }) + claimCapability("port", capability) +} +``` + +Once the setup function has been called, channels can be created via the IBC routing module. + +#### Channel lifecycle management + +An interchain swap module will accept new channels from any module on another machine, if and only if: + +- The channel being created is unordered. +- The version string is `ics101-1`. + +```typescript +function onChanOpenInit( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + version: string) => (version: string, err: Error) { + // only ordered channels allowed + abortTransactionUnless(order === ORDERED) + // assert that version is "ics20-1" or empty + // if empty, we return the default transfer version to core IBC + // as the version for this channel + abortTransactionUnless(version === "ics101-1" || version === "") + return "ics101-1", nil +} +``` + +```typescript +function onChanOpenTry( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string) => (version: string, err: Error) { + // only ordered channels allowed + abortTransactionUnless(order === ORDERED) + // assert that version is "ics101-1" + abortTransactionUnless(counterpartyVersion === "ics101-1") + // return version that this chain will use given the + // counterparty version + return "ics101-1", nil +} +``` + +```typescript +function onChanOpenAck( + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string) { + abortTransactionUnless(counterpartyVersion === "ics101-1") +} +``` + +#### Packet relay + +`SendIBCSwapDelegationDataPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. + +```ts +function SendIBCSwapDelegationDataPacket( + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64) { + + // send packet using the interface defined in ICS4 + handler.sendPacket( + getCapability("port"), + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + swapPacket + ) +} + +``` + +`onRecvPacket` is called by the routing module when a packet addressed to this module has been received. + +```go +func (im IBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) ibcexported.Acknowledgement { + ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) + + var data types.IBCSwapPacketData + var ackErr error + if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap packet data") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + + // only attempt the application logic if the packet data + // was successfully decoded + if ack.Success() { + + switch data.Type { + case types.CREATE_POOL: + var msg types.MsgCreatePoolRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + if res, err := im.keeper.OnCreatePoolReceived(ctx, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { + ack = channeltypes.NewErrorAcknowledgement(errEncode) + } else { + ack = channeltypes.NewResultAcknowledgement(result) + } + break + case types.SINGLE_DEPOSIT: + var msg types.MsgSingleDepositRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + if res, err := im.keeper.OnSingleDepositReceived(ctx, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { + ack = channeltypes.NewErrorAcknowledgement(errEncode) + } else { + ack = channeltypes.NewResultAcknowledgement(result) + } + break + case types.WITHDRAW: + var msg types.MsgWithdrawRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + if res, err := im.keeper.OnWithdrawReceived(ctx, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { + ack = channeltypes.NewErrorAcknowledgement(errEncode) + } else { + ack = channeltypes.NewResultAcknowledgement(result) + } + break + case types.LEFT_SWAP: + var msg types.MsgLeftSwapRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + if res, err := im.keeper.OnLeftSwapReceived(ctx, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { + ack = channeltypes.NewErrorAcknowledgement(errEncode) + } else { + ack = channeltypes.NewResultAcknowledgement(result) + } + break + case types.RIGHT_SWAP: + var msg types.MsgRightSwapRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } + if res, err := im.keeper.OnRightSwapReceived(ctx, &msg); err != nil { + ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") + ack = channeltypes.NewErrorAcknowledgement(ackErr) + } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { + ack = channeltypes.NewErrorAcknowledgement(errEncode) + } else { + ack = channeltypes.NewResultAcknowledgement(result) + } + break + } + + } + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack +} +``` + +`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. + +```go + +// OnAcknowledgementPacket implements the IBCModule interface +func (im IBCModule) OnAcknowledgementPacket( + ctx sdk.Context, + packet channeltypes.Packet, + acknowledgement []byte, + relayer sdk.AccAddress, +) error { + var ack channeltypes.Acknowledgement + if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-101 ibcswap packet acknowledgement: %v", err) + } + var data types.IBCSwapPacketData + if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-101 ibcswap packet data: %s", err.Error()) + } + + switch data.Type { + case types.CREATE_POOL: + var request types.MsgCreatePoolRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { + return err + } + var response types.MsgCreatePoolResponse + if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { + return err + } + if err := im.keeper.OnCreatePoolAcknowledged(ctx, &request, &response); err != nil { + return err + } + break + case types.SINGLE_DEPOSIT: + var request types.MsgSingleDepositRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { + return err + } + var response types.MsgSingleDepositResponse + if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { + return err + } + if err := im.keeper.OnSingleDepositAcknowledged(ctx, &request, &response); err != nil { + return err + } + break + case types.WITHDRAW: + var request types.MsgWithdrawRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { + return err + } + var response types.MsgWithdrawResponse + if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { + return err + } + if err := im.keeper.OnWithdrawAcknowledged(ctx, &request, &response); err != nil { + return err + } + break + case types.LEFT_SWAP: + var request types.MsgLeftSwapRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { + return err + } + var response types.MsgSwapResponse + if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { + return err + } + if err := im.keeper.OnLeftSwapAcknowledged(ctx, &request, &response); err != nil { + return err + } + break + case types.RIGHT_SWAP: + var request types.MsgRightSwapRequest + if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { + return err + } + var response types.MsgSwapResponse + if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { + return err + } + if err := im.keeper.OnRightSwapAcknowledged(ctx, &request, &response); err != nil { + return err + } + break + } + + return nil +} +``` + +`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that the tokens will be refunded). Tokens are also refunded on failure. + +```ts +function onTimeoutPacket(packet: Packet) { + // the packet timed-out, so refund the tokens + refundTokens(packet) +} + +``` + +```ts + +function refundToken(packet: Packet) { + let token + switch packet.type { + case LeftSwap: + case RightSwap: + token = packet.tokenIn + break; + case Deposit: + token = packet.tokens + break; + case Withdraw: + token = packet.pool_token + } + escrowAccount = channelEscrowAddresses[packet.srcChannel] + bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) +} +``` + +## Backwards Compatibility + +Not applicable. + +## Forwards Compatibility + +Coming soon. + +## Example Implementation + +https://github.com/ibcswap/ibcswap + +## Other Implementations + +Coming soon. + +## History + +Oct 9, 2022 - Draft written + +Oct 11, 2022 - Draft revised + +## References + +https://dev.balancer.fi/resources/pool-math/weighted-math#spot-price + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From 6c20bbb0ba23c34cb393b4c557478cf90e3dfdd7 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Thu, 15 Dec 2022 15:39:53 +0800 Subject: [PATCH 02/72] add pool and amm definition --- spec/app/ics-101-interchain-swap/README.md | 268 ++++++++++++++++----- 1 file changed, 209 insertions(+), 59 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 98a8f22cb..ff019d37b 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -84,6 +84,173 @@ As the pool collects fees, liquidity providers automatically collect fees throug ### Data Structures +#### Pool Structure +```ts +interface Coin { + amount: int32 + denom: string +} +``` +```ts +enum PoolSide { + POOL_SIDE_PENDING = 0; + POOL_SIDE_NATIVE_ASSET = 1; + POOL_SIDE_REMOTE_ASSET = 2; +} +``` +```ts +// PoolStatus defines if the pool is ready for trading +enum PoolStatus { + POOL_STATUS_READY = 0; + POOL_STATUS_INITIAL = 1; +} +``` +```ts +enum PoolType { + POOL_TYPE_WEIGHTED_CROSS_CHAIN = 0; + POOL_TYPE_WEIGHTED_INTERN_CHAIN = 1; +} +``` +```ts +interface PoolAsset { + side: PoolSide; + balance: Coin; + // percentage + weight: int32; + decimals: int32; +} +``` +```ts +interface InterchainLiquidityPool { + id: string; + assets []PoolAsset; + // the issued amount of pool token in the pool. the denom is a prefix ("pool") + pool id + supply: Coin; +} +``` + +#### IBC Market Maker +```ts +class InterchainMarketMaker { + pool :InterchainLiquidityPool + // basis point + feeRate: int32 + constructor(pool: InterchainLiquidityPool, feeRate: int32) { + this.pool = pool; + this.feeRate = feeRate + } + + // MarketPrice Bi / Wi / (Bo / Wo) + function marketPrice(denomIn, denomOut string): float64 { + const tokenIn = this.Pool.findAssetByDenom(denomIn) + const tokenOut = this.Pool.findAssetByDenom(denomOut) + const balanceIn = tokenIn.balance.amount + const balanceOut = tokenOut.balance.amount + const weightIn := tokenIn.weight + const weightOut := tokenOut.weight + + return balanceIn / weightIn / (balanceOut / weightOut) + } + + // P_issued = P_supply * ((1 + At/Bt) ** Wt -1) + function depositSingleAsset(token: Coin): Coin { + const asset = this.pool.findAssetByDenom(token.denom) + const amount = token.amount + const supply = this.pool.supply.amount + const weight = asset.weight / 100 + const issueAmount = supply * (math.pow(1+amount/asset.balance, weight) - 1) + + asset.balance.amount += token.amount // update balance of the asset + + return { + amount: issueAmount, + denom: this.pool.supply.denom + } + } + + // input the supply token, output the expected token. + // At = Bt * (1 - (1 - P_redeemed / P_supply) ** 1/Wt) + function withdraw(redeem: Coin, denomOut: string): Coin { + + const asset = this.pool.findAssetByDenom(denomOut) + + abortTransactionUnless(asset != null) + abortTransactionUnless(redeem.amount <= asset.balance.amount) + abortTransactionUnless(redeem.denom == this.pool.supply.denom) + + const balance = asset.balance.amount + const supply = this.pool.supply.amount + const weight = asset.weight / 100 // convert int to percent + + const amountOut = balance * (1 - ( 1 - redeem.amount / supply) ** (1/weight)) + return { + amount: amountOut, + denom: denomOut, + } + } + + // LeftSwap implements OutGivenIn + // Input how many coins you want to sell, output an amount you will receive + // Ao = Bo * ((1 - Bi / (Bi + Ai)) ** Wi/Wo) + function leftSwap(amountIn: Coin, denomOut: string): Coin { + + const assetIn = this.pool.findAssetByDenom(amountIn.denom) + abortTransactionUnless(assetIn != null) + + const assetOut = this.pool.findAssetByDenom(denomOut) + abortTransactionUnless(assetOut != null) + + // redeem.weight is percentage + const balanceOut = assetOut.balance.amount + const balanceIn = assetIn.balance.amount + const weightIn = assetIn.weight / 100 + const weightOut = assetOut.weight / 100 + const amount = this.minusFees(amountIn.amount) + + const amountOut := balanceOut * ((1- balanceIn / (balanceIn + amount) ** (weightIn/weightOut)) + + return { + amount: amountOut, + denom:denomOut + } + } + + // RightSwap implements InGivenOut + // Input how many coins you want to buy, output an amount you need to pay + // Ai = Bi * ((Bo/(Bo - Ao)) ** Wo/Wi -1) + function rightSwap(amountIn: Coin, amountOut: Coin) Coin { + + const assetIn = this.pool.findAssetByDenom(amountIn.denom) + abortTransactionUnless(assetIn != null) + const AssetOut = this.pool.findAssetByDenom(amountOut.denom) + abortTransactionUnless(assetOut != null) + + const balanceIn = assetIn.balance.amount + const balanceOut = assetOut.balance.amount + const weightIn = assetIn.weight / 100 + const weightOut = assetOut.weight / 100 + + const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1) + + abortTransactionUnless(amountIn.amount > amount) + + return { + amount, + denom: amountIn.denom + } + } + + // amount - amount * feeRate / 10000 + function minusFees(amount sdk.Int) sdk.Int { + return amount * (1 - this.pool.feeRate / 10000)) + } + + +} +``` + +#### Data packets + Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. ```ts @@ -94,11 +261,12 @@ enum MessageType { LeftSwap, RightSwap, } - +``` +```ts // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { msgType: MessageType, - data: Uint8Array, // Bytes + data: []byte, // Bytes } ``` @@ -122,6 +290,8 @@ IBCSwap implements the following sub-protocols: ``` ts interface MsgCreatePoolRequest { + sourcePort: string, + sourceChannel: string, sender: string, denoms: string[], decimals: [], @@ -136,7 +306,7 @@ interface MsgDepositRequest { tokens: Coin[], } interface MsgSingleDepositResponse { - pool_token: Coin[]; + poolToken: Coin; } ``` ```ts @@ -150,7 +320,7 @@ interface MsgWithdrawResponse { } ``` ```ts - interface MsgLeftSwapRequest { +interface MsgLeftSwapRequest { sender: string, tokenIn: Coin, denomOut: string, @@ -179,63 +349,43 @@ interface MsgSwapResponse { To implement interchain swap, we introduce the `Message Delegator` and `Relay Listener`. The `Message Delegator` will pre-process the request (validate msgs, lock assets, etc), and then forward the transactions to the relayer. ```go -func (k Keeper) DelegateCreatePool(goctx context.Context, msg *types.MsgCreatePoolRequest) (*types.MsgCreatePoolResponse, error) { - ctx := sdk.UnwrapSDKContext(goctx) - - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - _, err1 := sdk.AccAddressFromBech32(msg.Sender) - if err1 != nil { - return nil, err1 - } - - pool := types.NewBalancerLiquidityPool(msg.Denoms, msg.Decimals, msg.Weight) - if err := pool.Validate(); err != nil { - return nil, err - } - - // count native tokens - count := 0 - for _, denom := range msg.Denoms { - if k.bankKeeper.HasSupply(ctx, denom) { - count += 1 - pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_NATIVE_ASSET) - } else { - pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_REMOTE_ASSET) - } - } - if count == 0 { - return nil, types.ErrNoNativeTokenInPool - } - - msgByte, err0 := types.ModuleCdc.Marshal(msg) - if err0 != nil { - return nil, err0 - } - - packet := types.NewIBCSwapPacketData(types.CREATE_POOL, msgByte, nil) - if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { - return nil, err - } - - ctx.EventManager().EmitTypedEvents(msg) - - return &types.MsgCreatePoolResponse{}, nil +function delegateCreatePool(msg: MsgCreatePoolRequest) : MsgCreatePoolResponse { + + // ICS 24 host check if both port and channel are validate + abortTransactionUnless(host.portIdentifierValidator(msg.sourcePort)) + abortTransactionUnless(host.channelIdentifierValidator(msg.sourceChannel)); + + // Only two assets in a pool + abortTransactionUnless(msg.denoms.length != 2) + abortTransactionUnless(msg.decimals.length != 2) + abortTransactionUnless(msg.weight.split(':').length != 2) // weight: "50:50" + + cosnt pool = newBalancerLiquidityPool(msg.denoms, msg.decimals, msg.weight) + + const localAssetCount = 0 + for(var denom in msg.denoms) { + if (bank.hasSupply(denom)) { + localAssetCount += 1 + } + } + // should have 1 native asset on the chain + abortTransactionUnless(localAssetCount == 1) + + // contructs the IBC data packet + const packet = { + type: MessageType.Create, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + memo: "", + } + sendAtomicSwapPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) + + return new MsgCreatePoolResponse() } -func (k Keeper) DelegateSingleDeposit(goctx context.Context, msg *types.MsgSingleDepositRequest) (*types.MsgSingleDepositResponse, error) { - ctx := sdk.UnwrapSDKContext(goctx) - - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - sender, err1 := sdk.AccAddressFromBech32(msg.Sender) - if err1 != nil { - return nil, err1 - } +function delegateSingleDeposit(msg MsgSingleDepositRequest) : MsgSingleDepositResponse { + + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.token.lenght > 0) // deposit assets to the swap module length := len(msg.Tokens) From 0720c3ffa8c17127c0a372214d3a98d8667fcd35 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 16 Dec 2022 10:41:55 +0800 Subject: [PATCH 03/72] replace golang code with typescript. --- spec/app/ics-101-interchain-swap/README.md | 826 ++++++++++----------- 1 file changed, 380 insertions(+), 446 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index ff019d37b..e6088356b 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -93,52 +93,83 @@ interface Coin { ``` ```ts enum PoolSide { - POOL_SIDE_PENDING = 0; - POOL_SIDE_NATIVE_ASSET = 1; - POOL_SIDE_REMOTE_ASSET = 2; + Native = 1; + Remote = 2; } ``` ```ts // PoolStatus defines if the pool is ready for trading enum PoolStatus { - POOL_STATUS_READY = 0; - POOL_STATUS_INITIAL = 1; -} -``` -```ts -enum PoolType { - POOL_TYPE_WEIGHTED_CROSS_CHAIN = 0; - POOL_TYPE_WEIGHTED_INTERN_CHAIN = 1; + POOL_STATUS_INITIAL = 0; + POOL_STATUS_READY = 1; } ``` + ```ts interface PoolAsset { side: PoolSide; balance: Coin; // percentage weight: int32; - decimals: int32; + decimal: int32; } ``` ```ts interface InterchainLiquidityPool { id: string; assets []PoolAsset; - // the issued amount of pool token in the pool. the denom is a prefix ("pool") + pool id + // the issued amount of pool token in the pool. the denom is pool id supply: Coin; + status: PoolStatus; + encounterPartyPort: string; + encounterPartyChannel: string; + constructor(denoms: []string, decimals: []number, weight: string, portId string, channelId string) { + + this.id = generatePoolId(denoms) + this.supply = { + amount: 0, + denom: this.id + } + this.status = PoolStatus.POOL_STATUS_INITIAL + this.encounterPartyPort = portId + this.encounterPartyChannel = channelId + + // construct assets + const weights = weight.split(':').length + if(denoms.lenght === decimals.lenght && denoms.lenght === weight.split(':').length) { + for(let i=0; i < denoms.lenght; i++) { + this.assets.push({ + side: store.hasSupply(denom[i]) ? PoolSide.Native: PoolSide.Remote, + balance: { + amount: 0, + denom: denom[i], + }, + weight: number(weights[i]) + decimal: decimals[i] + }) + } + } + } +} +``` +```ts +function generatePoolId(denoms: []string) { + return "pool" + sha256(denoms.sort().join('')) } ``` - #### IBC Market Maker ```ts class InterchainMarketMaker { - pool :InterchainLiquidityPool - // basis point - feeRate: int32 - constructor(pool: InterchainLiquidityPool, feeRate: int32) { - this.pool = pool; - this.feeRate = feeRate - } + pool :InterchainLiquidityPool + // basis point + feeRate: int32 + + static initialize(pool: InterchainLiquidityPool, feeRate: int32) : InterchainMarketMaker { + return { + pool: pool, + feeRate: feeRate, + } + } // MarketPrice Bi / Wi / (Bo / Wo) function marketPrice(denomIn, denomOut string): float64 { @@ -175,6 +206,7 @@ class InterchainMarketMaker { const asset = this.pool.findAssetByDenom(denomOut) abortTransactionUnless(asset != null) + abortTransactionUnless(this.pool.status === PoolStatus.POOL_STATUS_READY) abortTransactionUnless(redeem.amount <= asset.balance.amount) abortTransactionUnless(redeem.denom == this.pool.supply.denom) @@ -193,7 +225,7 @@ class InterchainMarketMaker { // Input how many coins you want to sell, output an amount you will receive // Ao = Bo * ((1 - Bi / (Bi + Ai)) ** Wi/Wo) function leftSwap(amountIn: Coin, denomOut: string): Coin { - + const assetIn = this.pool.findAssetByDenom(amountIn.denom) abortTransactionUnless(assetIn != null) @@ -219,7 +251,7 @@ class InterchainMarketMaker { // Input how many coins you want to buy, output an amount you need to pay // Ai = Bi * ((Bo/(Bo - Ao)) ** Wo/Wi -1) function rightSwap(amountIn: Coin, amountOut: Coin) Coin { - + const assetIn = this.pool.findAssetByDenom(amountIn.denom) abortTransactionUnless(assetIn != null) const AssetOut = this.pool.findAssetByDenom(amountOut.denom) @@ -244,8 +276,6 @@ class InterchainMarketMaker { function minusFees(amount sdk.Int) sdk.Int { return amount * (1 - this.pool.feeRate / 10000)) } - - } ``` @@ -265,11 +295,24 @@ enum MessageType { ```ts // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { - msgType: MessageType, + type: MessageType, data: []byte, // Bytes } ``` +```typescript +type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; + +interface IBCSwapDataPacketSuccess { + // This is binary 0x01 base64 encoded + result: "AQ==" +} + +interface IBCSwapDataPacketError { + error: string +} +``` + ### Sub-protocols Traditional liquidity pools typically maintain its pool state in one location. @@ -293,8 +336,8 @@ interface MsgCreatePoolRequest { sourcePort: string, sourceChannel: string, sender: string, - denoms: string[], - decimals: [], + denoms: []string, + decimals: []int32, weight: string, } @@ -302,8 +345,9 @@ interface MsgCreatePoolResponse {} ``` ```ts interface MsgDepositRequest { + poolId: string sender: string, - tokens: Coin[], + tokens: Coin[], // only one element for now, might have two in the feature } interface MsgSingleDepositResponse { poolToken: Coin; @@ -313,28 +357,28 @@ interface MsgSingleDepositResponse { interface MsgWithdrawRequest { sender: string, poolCoin: Coin, - denomOut: string, // optional, if not set, withdraw native coin to sender. + denomOut: string, } interface MsgWithdrawResponse { - tokens: Coin[]; + tokens: []Coin; } ``` ```ts interface MsgLeftSwapRequest { sender: string, tokenIn: Coin, - denomOut: string, + tokenOut: Coin, slippage: number; // max tolerated slippage recipient: string, } interface MsgSwapResponse { - tokens: Coin[]; + tokens: []Coin; } ``` ```ts interface MsgRightSwapRequest { sender: string, - denomIn: string, + tokenIn: Coin, tokenOut: Coin, slippage: number; // max tolerated slippage recipient: string, @@ -349,18 +393,19 @@ interface MsgSwapResponse { To implement interchain swap, we introduce the `Message Delegator` and `Relay Listener`. The `Message Delegator` will pre-process the request (validate msgs, lock assets, etc), and then forward the transactions to the relayer. ```go -function delegateCreatePool(msg: MsgCreatePoolRequest) : MsgCreatePoolResponse { +function delegateCreatePool(msg: MsgCreatePoolRequest) { // ICS 24 host check if both port and channel are validate abortTransactionUnless(host.portIdentifierValidator(msg.sourcePort)) - abortTransactionUnless(host.channelIdentifierValidator(msg.sourceChannel)); + abortTransactionUnless(host.channelIdentifierValidator(msg.sourceChannel)); // Only two assets in a pool abortTransactionUnless(msg.denoms.length != 2) abortTransactionUnless(msg.decimals.length != 2) abortTransactionUnless(msg.weight.split(':').length != 2) // weight: "50:50" + abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) - cosnt pool = newBalancerLiquidityPool(msg.denoms, msg.decimals, msg.weight) + cosnt pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, msg.sourcePort, msg.sourceChannel) const localAssetCount = 0 for(var denom in msg.denoms) { @@ -369,293 +414,302 @@ function delegateCreatePool(msg: MsgCreatePoolRequest) : MsgCreatePoolResponse { } } // should have 1 native asset on the chain - abortTransactionUnless(localAssetCount == 1) + abortTransactionUnless(localAssetCount >= 1) - // contructs the IBC data packet + // constructs the IBC data packet const packet = { type: MessageType.Create, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - memo: "", } - sendAtomicSwapPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) - return new MsgCreatePoolResponse() } -function delegateSingleDeposit(msg MsgSingleDepositRequest) : MsgSingleDepositResponse { +function delegateSingleDeposit(msg MsgSingleDepositRequest) { abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.token.lenght > 0) - - // deposit assets to the swap module - length := len(msg.Tokens) - var coins = make([]sdk.Coin, length) - for i := 0; i < length; i++ { - coins[i] = *msg.Tokens[i] - } - k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(coins...)) - - msgByte, err0 := types.ModuleCdc.Marshal(msg) - if err0 != nil { - return nil, err0 - } - - packet := types.NewIBCSwapPacketData(types.SINGLE_DEPOSIT, msgByte, nil) - if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { - return nil, err - } + abortTransactionUnless(msg.tokens.lenght > 0) + + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + + for(var token in msg.tokens) { + const balance = bank.queryBalance(sender, token.denom) + // should have enough balance + abortTransactionUnless(balance.amount >= token.amount) + } - ctx.EventManager().EmitTypedEvents(msg) + // deposit assets to the escrowed account + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokens) - return &types.MsgSingleDepositResponse{}, nil + // constructs the IBC data packet + const packet = { + type: MessageType.Deposit, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + } + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } -func (k Keeper) DelegateWithdraw(ctx2 context.Context, msg *types.MsgWithdrawRequest) (*types.MsgWithdrawResponse, error) { - ctx := sdk.UnwrapSDKContext(ctx2) - - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - sender, err1 := sdk.AccAddressFromBech32(msg.Sender) - if err1 != nil { - return nil, err1 - } +function delegateWithdraw(msg MsgWithdrawRequest) { - // deposit assets to the swap module - k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(*msg.PoolToken)) - - msgByte, err0 := types.ModuleCdc.Marshal(msg) - if err0 != nil { - return nil, err0 - } + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.token.lenght > 0) + + const pool = store.findPoolById(msg.poolToken.denom) + abortTransactionUnless(pool != null) + abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) + + const outToken = this.pool.findAssetByDenom(msg.denomOut) + abortTransactionUnless(outToken != null) + abortTransactionUnless(outToken.poolSide == PoolSide.Native) - packet := types.NewIBCSwapPacketData(types.WITHDRAW, msgByte, nil) - if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { - return nil, err - } + // lock pool token to the swap module + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) - ctx.EventManager().EmitTypedEvents(msg) + // constructs the IBC data packet + const packet = { + type: MessageType.Withdraw, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + } + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) - return &types.MsgWithdrawResponse{}, nil } -func (k Keeper) DelegateLeftSwap(goctx context.Context, msg *types.MsgLeftSwapRequest) (*types.MsgSwapResponse, error) { - ctx := sdk.UnwrapSDKContext(goctx) - - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - sender, err1 := sdk.AccAddressFromBech32(msg.Sender) - if err1 != nil { - return nil, err1 - } - - // deposit assets to the swap module - k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(*msg.TokenIn)) - - msgByte, err0 := types.ModuleCdc.Marshal(msg) - if err0 != nil { - return nil, err0 - } +function delegateLeftSwap(msg MsgLeftSwapRequest) { + + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) + abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) + abortTransactionUnless(msg.slippage > 0) + abortTransactionUnless(msg.recipient != null) + + const pool = store.findPoolById([tokenIn.denom, denomOut]) + abortTransactionUnless(pool != null) + abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - packet := types.NewIBCSwapPacketData(types.LEFT_SWAP, msgByte, nil) - if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { - return nil, err - } + // lock swap-in token to the swap module + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - ctx.EventManager().EmitTypedEvents(msg) + // contructs the IBC data packet + const packet = { + type: MessageType.Leftswap, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + } + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) - return &types.MsgSwapResponse{}, nil } -func (k Keeper) DelegateRightSwap(goctx context.Context, msg *types.MsgRightSwapRequest) (*types.MsgSwapResponse, error) { - ctx := sdk.UnwrapSDKContext(goctx) - - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - sender, err1 := sdk.AccAddressFromBech32(msg.Sender) - if err1 != nil { - return nil, err1 - } - - k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(*msg.TokenIn)) - - msgByte, err0 := types.ModuleCdc.Marshal(msg) - if err0 != nil { - return nil, err0 - } - - packet := types.NewIBCSwapPacketData(types.RIGHT_SWAP, msgByte, nil) - if err := k.SendIBCSwapDelegationDataPacket(ctx, msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp, packet); err != nil { - return nil, err - } +function delegateRightSwap(msg MsgRightSwapRequest) { + + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) + abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) + abortTransactionUnless(msg.slippage > 0) + abortTransactionUnless(msg.recipient != null) + + const pool = store.findPoolById([tokenIn.denom, tokenOut.denom]) + abortTransactionUnless(pool != null) + abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) + + // lock swap-in token to the swap module + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - ctx.EventManager().EmitTypedEvents(msg) + // contructs the IBC data packet + const packet = { + type: MessageType.Rightswap, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + } + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) - return &types.MsgSwapResponse{}, nil } ``` The `Relay Listener` handle all transactions, execute transactions when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. ```go -func (k Keeper) OnCreatePoolReceived(ctx sdk.Context, msg *types.MsgCreatePoolRequest) (*types.MsgCreatePoolResponse, error) { - - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - _, err1 := sdk.AccAddressFromBech32(msg.Sender) - if err1 != nil { - return nil, err1 - } - - pool := types.NewBalancerLiquidityPool(msg.Denoms, msg.Decimals, msg.Weight) - if err := pool.Validate(); err != nil { - return nil, err - } - - // count native tokens - count := 0 - for _, denom := range msg.Denoms { - if k.bankKeeper.HasSupply(ctx, denom) { - count += 1 - pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_NATIVE_ASSET) - } else { - pool.UpdateAssetPoolSide(denom, types.PoolSide_POOL_SIDE_REMOTE_ASSET) - } - } - if count == 0 { - return nil, types.ErrNoNativeTokenInPool - } - - k.SetBalancerPool(ctx, pool) - - return &types.MsgCreatePoolResponse{ - PoolId: pool.Id, - }, nil +function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { -} - -func (k Keeper) OnSingleDepositReceived(ctx sdk.Context, msg *types.MsgSingleDepositRequest) (*types.MsgSingleDepositResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - amm, err := k.CreateIBCSwapAMM(ctx, msg.PoolId) - if err != nil { - return nil, err - } - - poolToken, err := amm.Deposit(msg.Tokens) - if err != nil { - return nil, err - } - - k.SetBalancerPool(ctx, *amm.Pool) // update pool states + // Only two assets in a pool + abortTransactionUnless(msg.denoms.length != 2) + abortTransactionUnless(msg.decimals.length != 2) + abortTransactionUnless(msg.weight.split(':').length != 2) // weight format: "50:50" + abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) + + // construct mirror pool on destination chain + cosnt pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, destPort, destChannel) + + // count native tokens + const count = 0 + for(var denom in msg.denoms { + if bank.hasSupply(ctx, denom) { + count += 1 + pool.updateAssetPoolSide(denom, PoolSide.Native) + } else { + pool.updateAssetPoolSide(denom, PoolSide.Remote) + } + } + // only one token (could be either native or IBC token) is validate + abortTransactionUnless(count == 1) - return &types.MsgSingleDepositResponse{ - PoolToken: &poolToken, - }, nil -} + store.savePool(pool) -func (k Keeper) OnWithdrawReceived(ctx sdk.Context, msg *types.MsgWithdrawRequest) (*types.MsgWithdrawResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - amm, err := k.CreateIBCSwapAMM(ctx, msg.PoolToken.Denom) // Pool Token denomination is the pool Id - if err != nil { - return nil, err - } - - outToken, err := amm.Withdraw(msg.PoolToken, msg.DenomOut) - if err != nil { - return nil, err - } - - k.SetBalancerPool(ctx, *amm.Pool) // update pool states - - // only output one asset in the pool - return &types.MsgWithdrawResponse{ - Tokens: []*sdk.Coin{ - &outToken, - }, - }, nil + return { + poolId: pool.id, + } } -func (k Keeper) OnLeftSwapReceived(ctx sdk.Context, msg *types.MsgLeftSwapRequest) (*types.MsgSwapResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - poolId := types.GeneratePoolId([]string{msg.TokenIn.Denom, msg.DenomOut}) - - amm, err := k.CreateIBCSwapAMM(ctx, poolId) // Pool Token denomination is the pool Id - if err != nil { - return nil, err - } - - outToken, err := amm.LeftSwap(msg.TokenIn, msg.DenomOut) - if err != nil { - return nil, err - } +function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { + + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.tokens.lenght > 0) + + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + + // fetch fee rate from the params module, maintained by goverance + const feeRate = params.getPoolFeeRate() - k.SetBalancerPool(ctx, *amm.Pool) // update pool states + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const poolToken = amm.depositSingleAsset(msg.tokens[0]) + + store.savePool(amm.pool) // update pool states - // only output one asset in the pool - return &types.MsgSwapResponse{ - TokenOut: &outToken, - }, nil + return { poolToken } } -func (k Keeper) OnRightSwapReceived(ctx sdk.Context, msg *types.MsgRightSwapRequest) (*types.MsgSwapResponse, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err - } - - poolId := types.GeneratePoolId([]string{msg.TokenIn.Denom, msg.TokenOut.Denom}) - - amm, err := k.CreateIBCSwapAMM(ctx, poolId) // Pool Token denomination is the pool Id - if err != nil { - return nil, err - } - - outToken, err := amm.RightSwap(msg.TokenIn.Denom, msg.TokenOut) - if err != nil { - return nil, err - } +function onWithdrawReceived(msg: MsgWithdrawRequest) MsgWithdrawResponse { + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.denomOut != null) + abortTransactionUnless(msg.poolCoin.amount > 0) + + const pool = store.findPoolById(msg.poolCoin.denom) + abortTransactionUnless(pool != null) + + // fetch fee rate from the params module, maintained by goverance + const feeRate = params.getPoolFeeRate() - k.SetBalancerPool(ctx, *amm.Pool) // update pool states + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const outToken = amm.withdraw(msg.poolCoin, msg.denomOut) + store.savePool(amm.pool) // update pool states + + // the outToken will sent to msg's sender in `onAcknowledgement()` - // only output one asset in the pool - return &types.MsgSwapResponse{ - TokenOut: &outToken, - }, nil + return { tokens: outToken } } -func (k Keeper) OnCreatePoolAcknowledged(ctx sdk.Context, request *types.MsgCreatePoolRequest, response *types.MsgCreatePoolResponse) error { +function onLeftSwapReceived(msg: MsgLeftSwapRequest) MsgSwapResponse { + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) + abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) + abortTransactionUnless(msg.slippage > 0) + abortTransactionUnless(msg.recipient != null) + + const pool = store.findPoolById(generatePoolId([tokenIn.denom, denomOut])) + abortTransactionUnless(pool != null) + // fetch fee rate from the params module, maintained by goverance + const feeRate = params.getPoolFeeRate() + + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom) + + const expected = msg.tokenOut.amount + + // tolerance check + abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) + + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(escrowAddr, msg.recipient, outToken) + + store.savePool(amm.pool) // update pool states + + return { tokens: outToken } } -func (k Keeper) OnSingleDepositAcknowledged(ctx sdk.Context, request *types.MsgSingleDepositRequest, response *types.MsgSingleDepositResponse) error { +function onRightSwapReceived(msg MsgRightSwapRequest) MsgSwapResponse { + abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) + abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) + abortTransactionUnless(msg.slippage > 0) + abortTransactionUnless(msg.recipient != null) + + const pool = store.findPoolById([tokenIn.denom, tokenOut.denom]) + abortTransactionUnless(pool != null) + abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) + // fetch fee rate from the params module, maintained by goverance + const feeRate = params.getPoolFeeRate() + + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const minTokenIn = amm.rightSwap(msg.tokenIn, msg.tokenOut) + + // tolerance check + abortTransactionUnless(tokenIn.amount > minTokenIn.amount) + abortTransactionUnless((tokenIn.amount - minTokenIn.amount)/minTokenIn.amount > msg.slippage / 10000)) + + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(escrowAddr, msg.recipient, msg.tokenOut) + + store.savePool(amm.pool) // update pool states + + return { tokens: minTokenIn } } -func (k Keeper) OnWithdrawAcknowledged(ctx sdk.Context, request *types.MsgWithdrawRequest, response *types.MsgWithdrawResponse) error { - +function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { + // do nothing } -func (k Keeper) OnLeftSwapAcknowledged(ctx sdk.Context, request *types.MsgLeftSwapRequest, response *types.MsgSwapResponse) error { +function onSingleDepositAcknowledged(request: MsgSingleDepositRequest, response: MsgSingleDepositResponse) { + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + pool.supply.amount += response.tokens.amount + store.savePool(pool) + + bank.mintCoin(MODULE_NAME, reponse.token) + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) +} +function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + abortTransactionUnless(pool.supply.amount >= response.tokens.amount) + pool.supply.amount -= response.tokens.amount + store.savePool(pool) + + bank.sendCoinsFromAccountToModule(msg.sender, MODULE_NAME, response.tokens) + bank.burnCoin(MODULE_NAME, reponse.token) } -func (k Keeper) OnRightSwapAcknowledged(ctx sdk.Context, request *types.MsgRightSwapRequest, response *types.MsgSwapResponse) error { +function onLeftSwapAcknowledged(request: MsgLeftSwapRequest, response: MsgSwapResponse) { + const pool = store.findPoolById([request.tokenIn.denom, request.tokenOut.denom]) + abortTransactionUnless(pool != null) + + const assetOut = pool.findAssetByDenom(request.tokenOut.denom) + abortTransactionUnless(assetOut.balance.amount >= response.tokens.amount) + assetOut.balance.amount -= response.tokens.amount + + const assetIn = pool.findAssetByDenom(request.tokenIn.denom) + assetIn.balance.amount += request.tokenIn.amount + + store.savePool(pool) +} +function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) { + const pool = store.findPoolById([request.tokenIn.denom, request.tokenOut.denom]) + abortTransactionUnless(pool != null) + + const assetOut = pool.findAssetByDenom(request.tokenOut.denom) + abortTransactionUnless(assetOut.balance.amount >= response.tokens.amount) + assetOut.balance.amount -= request.tokenOut.amount + + const assetIn = pool.findAssetByDenom(request.tokenIn.denom) + assetIn.balance.amount += request.tokenIn.amount + + store.savePool(pool) } ``` @@ -742,10 +796,10 @@ function onChanOpenAck( #### Packet relay -`SendIBCSwapDelegationDataPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. +`sendInterchainIBCSwapDataPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. ```ts -function SendIBCSwapDelegationDataPacket( +function sendInterchainIBCSwapDataPacket( swapPacket: IBCSwapPacketData, sourcePort: string, sourceChannel: string, @@ -768,103 +822,38 @@ function SendIBCSwapDelegationDataPacket( `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. ```go -func (im IBCModule) OnRecvPacket( - ctx sdk.Context, - packet channeltypes.Packet, - relayer sdk.AccAddress, -) ibcexported.Acknowledgement { - ack := channeltypes.NewResultAcknowledgement([]byte{byte(1)}) - - var data types.IBCSwapPacketData - var ackErr error - if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap packet data") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - - // only attempt the application logic if the packet data - // was successfully decoded - if ack.Success() { - - switch data.Type { - case types.CREATE_POOL: - var msg types.MsgCreatePoolRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - if res, err := im.keeper.OnCreatePoolReceived(ctx, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { - ack = channeltypes.NewErrorAcknowledgement(errEncode) - } else { - ack = channeltypes.NewResultAcknowledgement(result) - } - break - case types.SINGLE_DEPOSIT: - var msg types.MsgSingleDepositRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - if res, err := im.keeper.OnSingleDepositReceived(ctx, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { - ack = channeltypes.NewErrorAcknowledgement(errEncode) - } else { - ack = channeltypes.NewResultAcknowledgement(result) - } - break - case types.WITHDRAW: - var msg types.MsgWithdrawRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - if res, err := im.keeper.OnWithdrawReceived(ctx, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { - ack = channeltypes.NewErrorAcknowledgement(errEncode) - } else { - ack = channeltypes.NewResultAcknowledgement(result) - } - break - case types.LEFT_SWAP: - var msg types.MsgLeftSwapRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - if res, err := im.keeper.OnLeftSwapReceived(ctx, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { - ack = channeltypes.NewErrorAcknowledgement(errEncode) - } else { - ack = channeltypes.NewResultAcknowledgement(result) - } - break - case types.RIGHT_SWAP: - var msg types.MsgRightSwapRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } - if res, err := im.keeper.OnRightSwapReceived(ctx, &msg); err != nil { - ackErr = sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "cannot unmarshal ICS-101 interchain swap message") - ack = channeltypes.NewErrorAcknowledgement(ackErr) - } else if result, errEncode := types.ModuleCdc.Marshal(res); errEncode != nil { - ack = channeltypes.NewErrorAcknowledgement(errEncode) - } else { - ack = channeltypes.NewResultAcknowledgement(result) - } - break - } - - } +function onRecvPacket(packet: Packet) { + + IBCSwapPacketData swapPacket = packet.data + // construct default acknowledgement of success + const ack: IBCSwapDataAcknowledgement = new IBCSwapDataPacketSuccess() + + try{ + switch swapPacket.type { + case CREATE_POOL: + var msg: MsgCreatePoolRequest = protobuf.decode(swapPacket.data) + onCreatePoolReceived(msg, packet.destPortId, packet.destChannelId) + break + case SINGLE_DEPOSIT: + var msg: MsgSingleDepositRequest = protobuf.decode(swapPacket.data) + onSingleDepositReceived(msg) + break + case WITHDRAW: + var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) + onWithdrawReceived(msg) + break + case LEFT_SWAP: + var msg: MsgLeftSwapRequest = protobuf.decode(swapPacket.data) + onLeftswapReceived(msg) + break + case RIGHT_SWAP: + var msg: MsgRightSwapRequest = protobuf.decode(swapPacket.data) + onRightReceived(msg) + break + } + } catch { + ack = new IBCSwapDataPacketError() + } // NOTE: acknowledgement will be written synchronously during IBC handler execution. return ack @@ -876,88 +865,33 @@ func (im IBCModule) OnRecvPacket( ```go // OnAcknowledgementPacket implements the IBCModule interface -func (im IBCModule) OnAcknowledgementPacket( - ctx sdk.Context, - packet channeltypes.Packet, - acknowledgement []byte, - relayer sdk.AccAddress, -) error { +function OnAcknowledgementPacket( + packet: channeltypes.Packet, + ack channeltypes.Acknowledgement, +) { var ack channeltypes.Acknowledgement - if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-101 ibcswap packet acknowledgement: %v", err) - } - var data types.IBCSwapPacketData - if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-101 ibcswap packet data: %s", err.Error()) - } - - switch data.Type { - case types.CREATE_POOL: - var request types.MsgCreatePoolRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { - return err - } - var response types.MsgCreatePoolResponse - if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { - return err - } - if err := im.keeper.OnCreatePoolAcknowledged(ctx, &request, &response); err != nil { - return err - } - break - case types.SINGLE_DEPOSIT: - var request types.MsgSingleDepositRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { - return err - } - var response types.MsgSingleDepositResponse - if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { - return err - } - if err := im.keeper.OnSingleDepositAcknowledged(ctx, &request, &response); err != nil { - return err - } - break - case types.WITHDRAW: - var request types.MsgWithdrawRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { - return err - } - var response types.MsgWithdrawResponse - if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { - return err - } - if err := im.keeper.OnWithdrawAcknowledged(ctx, &request, &response); err != nil { - return err - } - break - case types.LEFT_SWAP: - var request types.MsgLeftSwapRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { - return err - } - var response types.MsgSwapResponse - if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { - return err - } - if err := im.keeper.OnLeftSwapAcknowledged(ctx, &request, &response); err != nil { - return err - } - break - case types.RIGHT_SWAP: - var request types.MsgRightSwapRequest - if err := types.ModuleCdc.Unmarshal(data.Data, &request); err != nil { - return err - } - var response types.MsgSwapResponse - if err := types.ModuleCdc.Unmarshal(ack.GetResult(), &response); err != nil { - return err - } - if err := im.keeper.OnRightSwapAcknowledged(ctx, &request, &response); err != nil { - return err - } - break - } + + if (!ack.success()) { + refund(packet) + } else { + const swapPacket = protobuf.decode(packet.data) + switch swapPacket.type { + case CREATE_POOL: + onCreatePoolAcknowledged(msg) + break; + case SINGLE_DEPOSIT: + onSingleDepositAcknowledged(msg) + break; + case WITHDRAW: + onWithdrawAcknowledged(msg) + break; + case LEFT_SWAP: + onLeftSwapAcknowledged(msg) + break; + case RIGHT_SWAP: + onRightSwapAcknowledged(msg) + } + } return nil } From 5e80da1219602f1771538e07ea780b71a00ce3f5 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 16 Dec 2022 12:25:20 +0800 Subject: [PATCH 04/72] add generatePoolid() --- spec/app/ics-101-interchain-swap/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index e6088356b..51a232050 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -510,7 +510,7 @@ function delegateRightSwap(msg MsgRightSwapRequest) { abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - const pool = store.findPoolById([tokenIn.denom, tokenOut.denom]) + const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) @@ -638,7 +638,7 @@ function onRightSwapReceived(msg MsgRightSwapRequest) MsgSwapResponse { abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - const pool = store.findPoolById([tokenIn.denom, tokenOut.denom]) + const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) // fetch fee rate from the params module, maintained by goverance @@ -685,7 +685,7 @@ function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdr } function onLeftSwapAcknowledged(request: MsgLeftSwapRequest, response: MsgSwapResponse) { - const pool = store.findPoolById([request.tokenIn.denom, request.tokenOut.denom]) + const pool = store.findPoolById(generatePoolId[request.tokenIn.denom, request.tokenOut.denom])) abortTransactionUnless(pool != null) const assetOut = pool.findAssetByDenom(request.tokenOut.denom) @@ -699,7 +699,7 @@ function onLeftSwapAcknowledged(request: MsgLeftSwapRequest, response: MsgSwapRe } function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) { - const pool = store.findPoolById([request.tokenIn.denom, request.tokenOut.denom]) + const pool = store.findPoolById(generatePoolId([request.tokenIn.denom, request.tokenOut.denom])) abortTransactionUnless(pool != null) const assetOut = pool.findAssetByDenom(request.tokenOut.denom) From 76948757bc506f3209b4296975208d221b6312b0 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 16 Dec 2022 12:31:35 +0800 Subject: [PATCH 05/72] format code --- spec/app/ics-101-interchain-swap/README.md | 90 +++++++++++----------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 51a232050..b3c045641 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -392,7 +392,7 @@ interface MsgSwapResponse { To implement interchain swap, we introduce the `Message Delegator` and `Relay Listener`. The `Message Delegator` will pre-process the request (validate msgs, lock assets, etc), and then forward the transactions to the relayer. -```go +```ts function delegateCreatePool(msg: MsgCreatePoolRequest) { // ICS 24 host check if both port and channel are validate @@ -439,11 +439,11 @@ function delegateSingleDeposit(msg MsgSingleDepositRequest) { abortTransactionUnless(balance.amount >= token.amount) } - // deposit assets to the escrowed account - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + // deposit assets to the escrowed account + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokens) - // constructs the IBC data packet + // constructs the IBC data packet const packet = { type: MessageType.Deposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. @@ -464,11 +464,11 @@ function delegateWithdraw(msg MsgWithdrawRequest) { abortTransactionUnless(outToken != null) abortTransactionUnless(outToken.poolSide == PoolSide.Native) - // lock pool token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) + // lock pool token to the swap module + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) - // constructs the IBC data packet + // constructs the IBC data packet const packet = { type: MessageType.Withdraw, data: protobuf.encode(msg), // encode the request message to protobuf bytes. @@ -513,12 +513,12 @@ function delegateRightSwap(msg MsgRightSwapRequest) { const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - - // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) + + // lock swap-in token to the swap module + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - // contructs the IBC data packet + // contructs the IBC data packet const packet = { type: MessageType.Rightswap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. @@ -530,7 +530,7 @@ function delegateRightSwap(msg MsgRightSwapRequest) { The `Relay Listener` handle all transactions, execute transactions when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. -```go +```ts function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { // Only two assets in a pool @@ -564,7 +564,7 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { - abortTransactionUnless(msg.sender != null) + abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokens.lenght > 0) const pool = store.findPoolById(msg.poolId) @@ -573,12 +573,12 @@ function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDeposit // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - const amm = InterchainMarketMaker.initialize(pool, feeRate) - const poolToken = amm.depositSingleAsset(msg.tokens[0]) - - store.savePool(amm.pool) // update pool states - - return { poolToken } + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const poolToken = amm.depositSingleAsset(msg.tokens[0]) + + store.savePool(amm.pool) // update pool states + + return { poolToken } } function onWithdrawReceived(msg: MsgWithdrawRequest) MsgWithdrawResponse { @@ -591,14 +591,14 @@ function onWithdrawReceived(msg: MsgWithdrawRequest) MsgWithdrawResponse { // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - - const amm = InterchainMarketMaker.initialize(pool, feeRate) - const outToken = amm.withdraw(msg.poolCoin, msg.denomOut) - store.savePool(amm.pool) // update pool states - - // the outToken will sent to msg's sender in `onAcknowledgement()` - - return { tokens: outToken } + + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const outToken = amm.withdraw(msg.poolCoin, msg.denomOut) + store.savePool(amm.pool) // update pool states + + // the outToken will sent to msg's sender in `onAcknowledgement()` + + return { tokens: outToken } } function onLeftSwapReceived(msg: MsgLeftSwapRequest) MsgSwapResponse { @@ -613,21 +613,21 @@ function onLeftSwapReceived(msg: MsgLeftSwapRequest) MsgSwapResponse { abortTransactionUnless(pool != null) // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - - const amm = InterchainMarketMaker.initialize(pool, feeRate) - const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom) - - const expected = msg.tokenOut.amount - - // tolerance check - abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) - - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) - bank.sendCoins(escrowAddr, msg.recipient, outToken) - - store.savePool(amm.pool) // update pool states - - return { tokens: outToken } + + const amm = InterchainMarketMaker.initialize(pool, feeRate) + const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom) + + const expected = msg.tokenOut.amount + + // tolerance check + abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) + + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + bank.sendCoins(escrowAddr, msg.recipient, outToken) + + store.savePool(amm.pool) // update pool states + + return { tokens: outToken } } function onRightSwapReceived(msg MsgRightSwapRequest) MsgSwapResponse { From 36b3b0bf7b61640515ad1872b2c4afa3038a17c7 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 16 Dec 2022 12:38:47 +0800 Subject: [PATCH 06/72] format code, change language tags --- spec/app/ics-101-interchain-swap/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index b3c045641..e610681b1 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -821,7 +821,7 @@ function sendInterchainIBCSwapDataPacket( `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. -```go +```ts function onRecvPacket(packet: Packet) { IBCSwapPacketData swapPacket = packet.data @@ -855,22 +855,22 @@ function onRecvPacket(packet: Packet) { ack = new IBCSwapDataPacketError() } - // NOTE: acknowledgement will be written synchronously during IBC handler execution. - return ack + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack } ``` `onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. -```go +```ts // OnAcknowledgementPacket implements the IBCModule interface function OnAcknowledgementPacket( packet: channeltypes.Packet, ack channeltypes.Acknowledgement, ) { - var ack channeltypes.Acknowledgement + var ack channeltypes.Acknowledgement if (!ack.success()) { refund(packet) } else { @@ -893,7 +893,7 @@ function OnAcknowledgementPacket( } } - return nil + return nil } ``` From aece0edbe26ae8227dcda7be458701aa13a9ef8e Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 6 Apr 2023 15:25:31 +0300 Subject: [PATCH 07/72] fix spec wrong formula(withdraw,leftswap) --- spec/app/ics-101-interchain-swap/README.md | 279 +++++++++++---------- 1 file changed, 141 insertions(+), 138 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index e610681b1..1c87a1820 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -4,9 +4,9 @@ title: Interchain Swap stage: draft category: IBC/APP kind: instantiation -author: Ping , Edward Gunawan +author: Ping , Edward Gunawan , Marian created: 2022-10-09 -modified: 2022-10-11 +modified: 2023-04-06 requires: 24, 25 --- @@ -16,15 +16,15 @@ This standard document specifies the packet data structure, state machine handli ### Motivation -ICS-101 Interchain Swaps enables chains their own token pricing mechanism and exchange protocol via IBC transactions. Each chain can thus play a role in a fully decentralised exchange network. +ICS-101 Interchain Swaps enables chains their own token pricing mechanism and exchange protocol via IBC transactions. Each chain can thus play a role in a fully decentralised exchange network. Users might also prefer single asset pools over dual assets pools as it removes the risk of impermanent loss. ### Definitions -`Interchain swap`: a IBC token swap protocol, built on top of an automated marketing making system, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. +`Interchain swap`: a IBC token swap protocol, built on top of an automated marketing making system, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. -`Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. +`Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. `Weighted pools`: liquidity pools characterized by the percentage weight of each token denomination maintained within. @@ -38,34 +38,33 @@ Users might also prefer single asset pools over dual assets pools as it removes ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralization`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralization`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. - `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. - `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. - `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. - ## Technical Specification ### Algorithms #### Invariant -A constant invariant is maintained after trades which takes into consideration token weights and balance. The value function $V$ is defined as: +A constant invariant is maintained after trades which takes into consideration token weights and balance. The value function $V$ is defined as: -$$V = {Π_tB_t^{W_t}}$$ +$$V = {Π_tB_t^{W_t}}$$ Where - $t$ ranges over the tokens in the pool - $B_t$ is the balance of the token in the pool -- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. #### Spot Price -Spot prices of tokens are defined entirely by the weights and balances of the token pair. The spot price between any two tokens, $SpotPrice_i^{o}$, or in short $SP_i^o$, is the ratio of the token balances normalized by their weights: +Spot prices of tokens are defined entirely by the weights and balances of the token pair. The spot price between any two tokens, $SpotPrice_i^{o}$, or in short $SP_i^o$, is the ratio of the token balances normalized by their weights: -$$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ +$$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ - $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool - $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool @@ -74,29 +73,32 @@ $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ #### Fees -Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.0001% and a maximum value of 10%. +Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.0001% and a maximum value of 10%. -The fees go to liquidity providers in exchange for depositing their tokens in the pool to facilitate trades. Trade fees are collected at the time of a swap, and goes directly into the pool, increasing the pool balance. For a trade with a given $inputToken$ and $outputToken$, the amount collected by the pool as a fee is +The fees go to liquidity providers in exchange for depositing their tokens in the pool to facilitate trades. Trade fees are collected at the time of a swap, and goes directly into the pool, increasing the pool balance. For a trade with a given $inputToken$ and $outputToken$, the amount collected by the pool as a fee is -$$Amount_{fee} = Amount_{inputToken} * swapFee$$ +$$Amount_{fee} = Amount_{inputToken} * swapFee$$ As the pool collects fees, liquidity providers automatically collect fees through their proportional ownership of the pool balance. ### Data Structures #### Pool Structure + ```ts interface Coin { - amount: int32 - denom: string + amount: int32; + denom: string; } ``` + ```ts enum PoolSide { Native = 1; Remote = 2; } ``` + ```ts // PoolStatus defines if the pool is ready for trading enum PoolStatus { @@ -114,6 +116,7 @@ interface PoolAsset { decimal: int32; } ``` + ```ts interface InterchainLiquidityPool { id: string; @@ -124,7 +127,7 @@ interface InterchainLiquidityPool { encounterPartyPort: string; encounterPartyChannel: string; constructor(denoms: []string, decimals: []number, weight: string, portId string, channelId string) { - + this.id = generatePoolId(denoms) this.supply = { amount: 0, @@ -133,7 +136,7 @@ interface InterchainLiquidityPool { this.status = PoolStatus.POOL_STATUS_INITIAL this.encounterPartyPort = portId this.encounterPartyChannel = channelId - + // construct assets const weights = weight.split(':').length if(denoms.lenght === decimals.lenght && denoms.lenght === weight.split(':').length) { @@ -152,25 +155,28 @@ interface InterchainLiquidityPool { } } ``` + ```ts function generatePoolId(denoms: []string) { return "pool" + sha256(denoms.sort().join('')) } ``` + #### IBC Market Maker + ```ts class InterchainMarketMaker { pool :InterchainLiquidityPool // basis point feeRate: int32 - + static initialize(pool: InterchainLiquidityPool, feeRate: int32) : InterchainMarketMaker { return { pool: pool, feeRate: feeRate, } } - + // MarketPrice Bi / Wi / (Bo / Wo) function marketPrice(denomIn, denomOut string): float64 { const tokenIn = this.Pool.findAssetByDenom(denomIn) @@ -179,10 +185,10 @@ class InterchainMarketMaker { const balanceOut = tokenOut.balance.amount const weightIn := tokenIn.weight const weightOut := tokenOut.weight - + return balanceIn / weightIn / (balanceOut / weightOut) } - + // P_issued = P_supply * ((1 + At/Bt) ** Wt -1) function depositSingleAsset(token: Coin): Coin { const asset = this.pool.findAssetByDenom(token.denom) @@ -192,24 +198,24 @@ class InterchainMarketMaker { const issueAmount = supply * (math.pow(1+amount/asset.balance, weight) - 1) asset.balance.amount += token.amount // update balance of the asset - + return { amount: issueAmount, denom: this.pool.supply.denom } } - + // input the supply token, output the expected token. // At = Bt * (1 - (1 - P_redeemed / P_supply) ** 1/Wt) function withdraw(redeem: Coin, denomOut: string): Coin { - + const asset = this.pool.findAssetByDenom(denomOut) abortTransactionUnless(asset != null) abortTransactionUnless(this.pool.status === PoolStatus.POOL_STATUS_READY) - abortTransactionUnless(redeem.amount <= asset.balance.amount) + abortTransactionUnless(redeem.amount <= this.pool.supply.amount) abortTransactionUnless(redeem.denom == this.pool.supply.denom) - + const balance = asset.balance.amount const supply = this.pool.supply.amount const weight = asset.weight / 100 // convert int to percent @@ -220,58 +226,58 @@ class InterchainMarketMaker { denom: denomOut, } } - + // LeftSwap implements OutGivenIn // Input how many coins you want to sell, output an amount you will receive - // Ao = Bo * ((1 - Bi / (Bi + Ai)) ** Wi/Wo) + // Ao = Bo * (1 -(Bi / (Bi + Ai)) ** Wi/Wo) function leftSwap(amountIn: Coin, denomOut: string): Coin { - + const assetIn = this.pool.findAssetByDenom(amountIn.denom) abortTransactionUnless(assetIn != null) - + const assetOut = this.pool.findAssetByDenom(denomOut) abortTransactionUnless(assetOut != null) - + // redeem.weight is percentage const balanceOut = assetOut.balance.amount const balanceIn = assetIn.balance.amount const weightIn = assetIn.weight / 100 const weightOut = assetOut.weight / 100 const amount = this.minusFees(amountIn.amount) - - const amountOut := balanceOut * ((1- balanceIn / (balanceIn + amount) ** (weightIn/weightOut)) - + + const amountOut := balanceOut * (1- (balanceIn / (balanceIn + amount)) ** (weightIn/weightOut)) + return { amount: amountOut, denom:denomOut } } - + // RightSwap implements InGivenOut // Input how many coins you want to buy, output an amount you need to pay // Ai = Bi * ((Bo/(Bo - Ao)) ** Wo/Wi -1) function rightSwap(amountIn: Coin, amountOut: Coin) Coin { - + const assetIn = this.pool.findAssetByDenom(amountIn.denom) abortTransactionUnless(assetIn != null) const AssetOut = this.pool.findAssetByDenom(amountOut.denom) abortTransactionUnless(assetOut != null) - + const balanceIn = assetIn.balance.amount const balanceOut = assetOut.balance.amount const weightIn = assetIn.weight / 100 const weightOut = assetOut.weight / 100 - + const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1) - + abortTransactionUnless(amountIn.amount > amount) - + return { amount, denom: amountIn.denom } } - + // amount - amount * feeRate / 10000 function minusFees(amount sdk.Int) sdk.Int { return amount * (1 - this.pool.feeRate / 10000)) @@ -281,17 +287,18 @@ class InterchainMarketMaker { #### Data packets -Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. +Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. ```ts enum MessageType { - Create, - Deposit, - Withdraw, - LeftSwap, - RightSwap, + Create, + Deposit, + Withdraw, + LeftSwap, + RightSwap, } ``` + ```ts // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { @@ -305,19 +312,19 @@ type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketEr interface IBCSwapDataPacketSuccess { // This is binary 0x01 base64 encoded - result: "AQ==" + result: "AQ=="; } interface IBCSwapDataPacketError { - error: string + error: string; } ``` ### Sub-protocols -Traditional liquidity pools typically maintain its pool state in one location. +Traditional liquidity pools typically maintain its pool state in one location. -A liquidity pool in the interchain swap protocol maintains its pool state on both its source chain and destination chain. The pool states mirror each other and are synced through IBC packet relays, which we elaborate on in the following sub-protocols. +A liquidity pool in the interchain swap protocol maintains its pool state on both its source chain and destination chain. The pool states mirror each other and are synced through IBC packet relays, which we elaborate on in the following sub-protocols. IBCSwap implements the following sub-protocols: @@ -331,7 +338,7 @@ IBCSwap implements the following sub-protocols: #### Interfaces for sub-protocols -``` ts +```ts interface MsgCreatePoolRequest { sourcePort: string, sourceChannel: string, @@ -343,16 +350,18 @@ interface MsgCreatePoolRequest { interface MsgCreatePoolResponse {} ``` + ```ts interface MsgDepositRequest { - poolId: string - sender: string, - tokens: Coin[], // only one element for now, might have two in the feature + poolId: string; + sender: string; + tokens: Coin[]; // only one element for now, might have two in the feature } interface MsgSingleDepositResponse { - poolToken: Coin; + poolToken: Coin; } ``` + ```ts interface MsgWithdrawRequest { sender: string, @@ -363,28 +372,30 @@ interface MsgWithdrawResponse { tokens: []Coin; } ``` - ```ts + +```ts interface MsgLeftSwapRequest { - sender: string, - tokenIn: Coin, - tokenOut: Coin, - slippage: number; // max tolerated slippage - recipient: string, + sender: string, + tokenIn: Coin, + tokenOut: Coin, + slippage: number; // max tolerated slippage + recipient: string, } interface MsgSwapResponse { - tokens: []Coin; + tokens: []Coin; } ``` - ```ts + +```ts interface MsgRightSwapRequest { - sender: string, - tokenIn: Coin, - tokenOut: Coin, - slippage: number; // max tolerated slippage - recipient: string, + sender: string; + tokenIn: Coin; + tokenOut: Coin; + slippage: number; // max tolerated slippage + recipient: string; } interface MsgSwapResponse { - tokens: Coin[]; + tokens: Coin[]; } ``` @@ -404,9 +415,9 @@ function delegateCreatePool(msg: MsgCreatePoolRequest) { abortTransactionUnless(msg.decimals.length != 2) abortTransactionUnless(msg.weight.split(':').length != 2) // weight: "50:50" abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) - + cosnt pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, msg.sourcePort, msg.sourceChannel) - + const localAssetCount = 0 for(var denom in msg.denoms) { if (bank.hasSupply(denom)) { @@ -415,24 +426,24 @@ function delegateCreatePool(msg: MsgCreatePoolRequest) { } // should have 1 native asset on the chain abortTransactionUnless(localAssetCount >= 1) - + // constructs the IBC data packet const packet = { type: MessageType.Create, data: protobuf.encode(msg), // encode the request message to protobuf bytes. } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) - + } function delegateSingleDeposit(msg MsgSingleDepositRequest) { - + abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokens.lenght > 0) - + const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - + for(var token in msg.tokens) { const balance = bank.queryBalance(sender, token.denom) // should have enough balance @@ -455,11 +466,11 @@ function delegateWithdraw(msg MsgWithdrawRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.token.lenght > 0) - + const pool = store.findPoolById(msg.poolToken.denom) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - + const outToken = this.pool.findAssetByDenom(msg.denomOut) abortTransactionUnless(outToken != null) abortTransactionUnless(outToken.poolSide == PoolSide.Native) @@ -478,13 +489,13 @@ function delegateWithdraw(msg MsgWithdrawRequest) { } function delegateLeftSwap(msg MsgLeftSwapRequest) { - + abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - + const pool = store.findPoolById([tokenIn.denom, denomOut]) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) @@ -503,17 +514,17 @@ function delegateLeftSwap(msg MsgLeftSwapRequest) { } function delegateRightSwap(msg MsgRightSwapRequest) { - + abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - + const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - + // lock swap-in token to the swap module const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) @@ -552,7 +563,7 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC pool.updateAssetPoolSide(denom, PoolSide.Remote) } } - // only one token (could be either native or IBC token) is validate + // only one token (could be either native or IBC token) is validate abortTransactionUnless(count == 1) store.savePool(pool) @@ -563,21 +574,21 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC } function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { - + abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokens.lenght > 0) - + const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - + // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() const amm = InterchainMarketMaker.initialize(pool, feeRate) const poolToken = amm.depositSingleAsset(msg.tokens[0]) - + store.savePool(amm.pool) // update pool states - + return { poolToken } } @@ -585,19 +596,19 @@ function onWithdrawReceived(msg: MsgWithdrawRequest) MsgWithdrawResponse { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.denomOut != null) abortTransactionUnless(msg.poolCoin.amount > 0) - + const pool = store.findPoolById(msg.poolCoin.denom) abortTransactionUnless(pool != null) - + // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - + const amm = InterchainMarketMaker.initialize(pool, feeRate) const outToken = amm.withdraw(msg.poolCoin, msg.denomOut) store.savePool(amm.pool) // update pool states - + // the outToken will sent to msg's sender in `onAcknowledgement()` - + return { tokens: outToken } } @@ -609,24 +620,24 @@ function onLeftSwapReceived(msg: MsgLeftSwapRequest) MsgSwapResponse { abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - const pool = store.findPoolById(generatePoolId([tokenIn.denom, denomOut])) + const pool = store.findPoolById(generatePoolId([tokenIn.denom, denomOut])) abortTransactionUnless(pool != null) // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - + const amm = InterchainMarketMaker.initialize(pool, feeRate) const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom) - + const expected = msg.tokenOut.amount - + // tolerance check abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) - + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) bank.sendCoins(escrowAddr, msg.recipient, outToken) - + store.savePool(amm.pool) // update pool states - + return { tokens: outToken } } @@ -637,7 +648,7 @@ function onRightSwapReceived(msg MsgRightSwapRequest) MsgSwapResponse { abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - + const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) @@ -646,16 +657,16 @@ function onRightSwapReceived(msg MsgRightSwapRequest) MsgSwapResponse { const amm = InterchainMarketMaker.initialize(pool, feeRate) const minTokenIn = amm.rightSwap(msg.tokenIn, msg.tokenOut) - + // tolerance check abortTransactionUnless(tokenIn.amount > minTokenIn.amount) abortTransactionUnless((tokenIn.amount - minTokenIn.amount)/minTokenIn.amount > msg.slippage / 10000)) - + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) bank.sendCoins(escrowAddr, msg.recipient, msg.tokenOut) - + store.savePool(amm.pool) // update pool states - + return { tokens: minTokenIn } } @@ -668,7 +679,7 @@ function onSingleDepositAcknowledged(request: MsgSingleDepositRequest, response: abortTransactionUnless(pool != null) pool.supply.amount += response.tokens.amount store.savePool(pool) - + bank.mintCoin(MODULE_NAME, reponse.token) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) } @@ -679,7 +690,7 @@ function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdr abortTransactionUnless(pool.supply.amount >= response.tokens.amount) pool.supply.amount -= response.tokens.amount store.savePool(pool) - + bank.sendCoinsFromAccountToModule(msg.sender, MODULE_NAME, response.tokens) bank.burnCoin(MODULE_NAME, reponse.token) } @@ -687,28 +698,28 @@ function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdr function onLeftSwapAcknowledged(request: MsgLeftSwapRequest, response: MsgSwapResponse) { const pool = store.findPoolById(generatePoolId[request.tokenIn.denom, request.tokenOut.denom])) abortTransactionUnless(pool != null) - + const assetOut = pool.findAssetByDenom(request.tokenOut.denom) abortTransactionUnless(assetOut.balance.amount >= response.tokens.amount) assetOut.balance.amount -= response.tokens.amount - + const assetIn = pool.findAssetByDenom(request.tokenIn.denom) assetIn.balance.amount += request.tokenIn.amount - + store.savePool(pool) } function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) { const pool = store.findPoolById(generatePoolId([request.tokenIn.denom, request.tokenOut.denom])) abortTransactionUnless(pool != null) - + const assetOut = pool.findAssetByDenom(request.tokenOut.denom) abortTransactionUnless(assetOut.balance.amount >= response.tokens.amount) assetOut.balance.amount -= request.tokenOut.amount - + const assetIn = pool.findAssetByDenom(request.tokenIn.denom) assetIn.balance.amount += request.tokenIn.amount - + store.savePool(pool) } ``` @@ -789,8 +800,9 @@ function onChanOpenAck( portIdentifier: Identifier, channelIdentifier: Identifier, counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) { - abortTransactionUnless(counterpartyVersion === "ics101-1") + counterpartyVersion: string +) { + abortTransactionUnless(counterpartyVersion === "ics101-1"); } ``` @@ -804,19 +816,11 @@ function sendInterchainIBCSwapDataPacket( sourcePort: string, sourceChannel: string, timeoutHeight: Height, - timeoutTimestamp: uint64) { - - // send packet using the interface defined in ICS4 - handler.sendPacket( - getCapability("port"), - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - swapPacket - ) + timeoutTimestamp: uint64 +) { + // send packet using the interface defined in ICS4 + handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); } - ``` `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. @@ -827,7 +831,7 @@ function onRecvPacket(packet: Packet) { IBCSwapPacketData swapPacket = packet.data // construct default acknowledgement of success const ack: IBCSwapDataAcknowledgement = new IBCSwapDataPacketSuccess() - + try{ switch swapPacket.type { case CREATE_POOL: @@ -842,11 +846,11 @@ function onRecvPacket(packet: Packet) { var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) onWithdrawReceived(msg) break - case LEFT_SWAP: + case LEFT_SWAP: var msg: MsgLeftSwapRequest = protobuf.decode(swapPacket.data) onLeftswapReceived(msg) break - case RIGHT_SWAP: + case RIGHT_SWAP: var msg: MsgRightSwapRequest = protobuf.decode(swapPacket.data) onRightReceived(msg) break @@ -897,14 +901,13 @@ function OnAcknowledgementPacket( } ``` -`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that the tokens will be refunded). Tokens are also refunded on failure. +`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that the tokens will be refunded). Tokens are also refunded on failure. ```ts function onTimeoutPacket(packet: Packet) { // the packet timed-out, so refund the tokens - refundTokens(packet) + refundTokens(packet); } - ``` ```ts From 7b1a503cdb90d5170cdcc82707350693ad112fdc Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 20 Apr 2023 14:01:13 +0300 Subject: [PATCH 08/72] Update interchain swap README and implementation with support for double-sided liquidity pools and double deposits --- spec/app/ics-101-interchain-swap/README.md | 161 +++++++++++++++++++-- 1 file changed, 152 insertions(+), 9 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 1c87a1820..49f421006 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -4,9 +4,9 @@ title: Interchain Swap stage: draft category: IBC/APP kind: instantiation -author: Ping , Edward Gunawan , Marian +author: Ping , Edward Gunawan , Jafferey Hu ,Marian created: 2022-10-09 -modified: 2023-04-06 +modified: 2023-04-20 requires: 24, 25 --- @@ -30,6 +30,8 @@ Users might also prefer single asset pools over dual assets pools as it removes `Single-sided liquidity pools`: a liquidity pool that does not require users to deposit both token denominations -- one is enough. +`Double-sided liquidity pools`: a liquidity pool that does require users to deposit both token denominations -- require token pair. + `Left-side swap`: a token exchange that specifies the desired quantity to be sold. `Right-side swap`: a token exchange that specifies the desired quantity to be purchased. @@ -205,6 +207,24 @@ class InterchainMarketMaker { } } + // P_issued = P_supply * (1 + At/Bt) + function depositDoubleAsset(tokens: Coin[]): Coin[] { + const lpTokens = []; + for (const token in tokens) { + const asset = this.pool.findAssetByDenom(token.denom) + const amount = token.amount + const supply = this.pool.supply.amount + const weight = asset.weight / 100 + const issueAmount = supply * (1+amount/asset.balance) + asset.balance.amount += token.amount // update balance of the asset + lpTokens.push({ + amount: issueAmount, + denom: this.pool.supply.denom + }); + } + return lpTokens + } + // input the supply token, output the expected token. // At = Bt * (1 - (1 - P_redeemed / P_supply) ** 1/Wt) function withdraw(redeem: Coin, denomOut: string): Coin { @@ -293,6 +313,7 @@ Only one packet data type is required: `IBCSwapDataPacket`, which specifies the enum MessageType { Create, Deposit, + DoubleDeposit, Withdraw, LeftSwap, RightSwap, @@ -331,6 +352,7 @@ IBCSwap implements the following sub-protocols: ```protobuf rpc DelegateCreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); rpc DelegateSingleDeposit(MsgSingleDepositRequest) returns (MsgSingleDepositResponse); + rpc DelegateDoubleDeposit(MsgDoubleDepositRequest) returns (MsgDoubleDepositResponse); rpc DelegateWithdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); rpc DelegateLeftSwap(MsgLeftSwapRequest) returns (MsgSwapResponse); rpc DelegateRightSwap(MsgRightSwapRequest) returns (MsgSwapResponse); @@ -362,6 +384,28 @@ interface MsgSingleDepositResponse { } ``` +```ts +interface LocalDeposit { + sender: string; + token: Coin; +} +interface RemoteDeposit { + sender: string; + sequence: int; // account transaction sequence + token: Coin; + signature: Uint8Array; +} + +interface MsgDoubleDepositRequest { + poolId: string; + localDeposit: LocalDeposit; + remoteDeposit: RemoteDeposit; +} +interface MsgDoubleDepositResponse { + poolTokens: Coin[]; +} +``` + ```ts interface MsgWithdrawRequest { sender: string, @@ -462,6 +506,36 @@ function delegateSingleDeposit(msg MsgSingleDepositRequest) { sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } + +function delegateDoubleDeposit(msg MsgDoubleDepositRequest) { + + abortTransactionUnless(msg.localDeposit.sender != null) + abortTransactionUnless(msg.localDeposit.token != null) + abortTransactionUnless(msg.remoteDeposit.sender != null) + abortTransactionUnless(msg.remoteDeposit.token != null) + abortTransactionUnless(msg.remoteDeposit.signature != null) + abortTransactionUnless(msg.remoteDeposit.sequence != null) + + + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + + const balance = bank.queryBalance(sender, msg.localDeposit.token.denom) + // should have enough balance + abortTransactionUnless(balance.amount >= msg.localDeposit.token.amount) + + // deposit assets to the escrowed account + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + + // constructs the IBC data packet + const packet = { + type: MessageType.DoubleDeposit, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + } + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) +} + function delegateWithdraw(msg MsgWithdrawRequest) { abortTransactionUnless(msg.sender != null) @@ -575,18 +649,61 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokens.lenght > 0) + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + + const amm = store.findAmmById(msg.poolId) + + if(amm !== null) { + // fetch fee rate from the params module, maintained by goverance + const feeRate = params.getPoolFeeRate() + const amm = InterchainMarketMaker.initialize(pool, feeRate) + } + + const poolToken = amm.singleDeposit(msg.tokens[0]) + store.savePool(amm.pool) // update pool states + + return { poolToken } +} + + +function onDoubleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { + + abortTransactionUnless(msg.remoteDeposit.sender != null) + abortTransactionUnless(msg.remoteDeposit.token != null) const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - // fetch fee rate from the params module, maintained by goverance - const feeRate = params.getPoolFeeRate() + const amm = store.findAmmById(msg.poolId) + if(amm !== null) { + // fetch fee rate from the params module, maintained by goverance + const feeRate = params.getPoolFeeRate() + const amm = InterchainMarketMaker.initialize(pool, feeRate) + } - const amm = InterchainMarketMaker.initialize(pool, feeRate) - const poolToken = amm.depositSingleAsset(msg.tokens[0]) + // verify signature + const sender = account.GetAccount(msg.remoteDeposit.sender) + abortTransactionUnless(sender != null) + abortTransactionUnless(msg.remoteDeposit.sequence != senderGetSequence()) + const remoteDeposit = { + sender: sender.GetAddress(); + sequence: sender.GetSequence(); + token: msg.remoteDeposit.Token; + } + const encoder = new TextEncoder(); + const rawRemoteDepositTx = encoder.encode(JSON.stringify(remoteDeposit)); + const pubKey = account.GetPubKey() + const isValid = pubKey.VerifySignature(rawRemoteDepositTx, msg.remoteDeposit.signature) + abortTransactionUnless(isValid != false) + + // deposit remote token + const poolTokens = amm.doubleSingleAsset([msg.localDeposit.token, msg.remoteDeposit.token]) + + // mint voucher token + bank.mintCoin(MODULE_NAME, poolTokens[1]) + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.remoteDeposit.sender, poolTokens[1]) store.savePool(amm.pool) // update pool states return { poolToken } @@ -680,10 +797,24 @@ function onSingleDepositAcknowledged(request: MsgSingleDepositRequest, response: pool.supply.amount += response.tokens.amount store.savePool(pool) - bank.mintCoin(MODULE_NAME, reponse.token) + bank.mintCoin(MODULE_NAME,request.sender,reponse.token) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) } +function onDoubleDepositAcknowledged(request: MsgDoubleDepositRequest, response: MsgDoubleDepositResponse) { + const pool = store.findPoolById(msg.poolId) + abortTransactionUnless(pool != null) + + for(const poolToken in response.PoolTokens) { + pool.supply.amount += poolToken.amount + } + + store.savePool(pool) + + bank.mintCoin(MODULE_NAME,reponse.tokens[0]) + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]) +} + function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) @@ -842,6 +973,12 @@ function onRecvPacket(packet: Packet) { var msg: MsgSingleDepositRequest = protobuf.decode(swapPacket.data) onSingleDepositReceived(msg) break + + case Double_DEPOSIT: + var msg: MsgDoubleDepositRequest = protobuf.decode(swapPacket.data) + onDoubleDepositReceived(msg) + break + case WITHDRAW: var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) onWithdrawReceived(msg) @@ -886,6 +1023,9 @@ function OnAcknowledgementPacket( case SINGLE_DEPOSIT: onSingleDepositAcknowledged(msg) break; + case Double_DEPOSIT: + onDoubleDepositAcknowledged(msg) + break; case WITHDRAW: onWithdrawAcknowledged(msg) break; @@ -922,6 +1062,9 @@ function refundToken(packet: Packet) { case Deposit: token = packet.tokens break; + case DoubleDeposit: + token = packet.tokens + break; case Withdraw: token = packet.pool_token } From e501d23c9f691b624281e7c80ac14db48109aa01 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 20 Apr 2023 14:49:10 +0300 Subject: [PATCH 09/72] Update author list in ICS-101 Interchain Swap README file --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 49f421006..4b4fece7e 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -4,7 +4,7 @@ title: Interchain Swap stage: draft category: IBC/APP kind: instantiation -author: Ping , Edward Gunawan , Jafferey Hu ,Marian +author: Ping , Edward Gunawan , Jafferey Hu < huzhiwei@outlook.com> , Marian created: 2022-10-09 modified: 2023-04-20 requires: 24, 25 From f5d23af41b7df4408bbffe8ebee257606f8b7c74 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Mon, 24 Apr 2023 17:37:35 +0800 Subject: [PATCH 10/72] add diagram --- spec/app/ics-101-interchain-swap/README.md | 9 +++++++-- spec/app/ics-101-interchain-swap/interchain swap.svg | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 spec/app/ics-101-interchain-swap/interchain swap.svg diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index e610681b1..25ebbd256 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -28,7 +28,7 @@ Users might also prefer single asset pools over dual assets pools as it removes `Weighted pools`: liquidity pools characterized by the percentage weight of each token denomination maintained within. -`Single-sided liquidity pools`: a liquidity pool that does not require users to deposit both token denominations -- one is enough. +`Single-sided liquidity pools`: a liquidity pool that does not require users to deposit both token denominations -- one is enough. But a single deposit will affect the price of the liquidity pool, increasing the opportunity for arbitrageurs `Left-side swap`: a token exchange that specifies the desired quantity to be sold. @@ -47,6 +47,10 @@ Users might also prefer single asset pools over dual assets pools as it removes ## Technical Specification +This is an overview of how Interchain Swap works + +![Interchain Swap Diagram](interchain swap.svg) + ### Algorithms #### Invariant @@ -285,7 +289,8 @@ Only one packet data type is required: `IBCSwapDataPacket`, which specifies the ```ts enum MessageType { - Create, + RequestPoolCreation, + ResponsePoolCreation, Deposit, Withdraw, LeftSwap, diff --git a/spec/app/ics-101-interchain-swap/interchain swap.svg b/spec/app/ics-101-interchain-swap/interchain swap.svg new file mode 100644 index 000000000..336c7f39e --- /dev/null +++ b/spec/app/ics-101-interchain-swap/interchain swap.svg @@ -0,0 +1 @@ +Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap Packet3 Execute Swap(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool StateOver slippage tolerance1. Calculate Swap Output Amount2. Check slippage tolerance3. Send Token4. Update pool states1. Create Swap Order2. Lock  Swap In Assets2. Delegate Swap RequestMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well. \ No newline at end of file From 25d26459d9e8be9c613194620ae234818efecc71 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Mon, 24 Apr 2023 18:32:52 +0800 Subject: [PATCH 11/72] rename diagram --- spec/app/ics-101-interchain-swap/README.md | 2 +- spec/app/ics-101-interchain-swap/interchain swap.svg | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 spec/app/ics-101-interchain-swap/interchain swap.svg diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index b0690a89f..d20170a2e 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -50,7 +50,7 @@ Users might also prefer single asset pools over dual assets pools as it removes This is an overview of how Interchain Swap works -![Interchain Swap Diagram](interchain swap.svg) +![Interchain Swap Diagram](interchain-swap.svg) ### Algorithms diff --git a/spec/app/ics-101-interchain-swap/interchain swap.svg b/spec/app/ics-101-interchain-swap/interchain swap.svg deleted file mode 100644 index 336c7f39e..000000000 --- a/spec/app/ics-101-interchain-swap/interchain swap.svg +++ /dev/null @@ -1 +0,0 @@ -Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap Packet3 Execute Swap(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool StateOver slippage tolerance1. Calculate Swap Output Amount2. Check slippage tolerance3. Send Token4. Update pool states1. Create Swap Order2. Lock  Swap In Assets2. Delegate Swap RequestMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well. \ No newline at end of file From 9f5b7f882e1d1531e92f49708394983e11aa34e6 Mon Sep 17 00:00:00 2001 From: HuLaTown Date: Tue, 25 Apr 2023 12:38:19 +0800 Subject: [PATCH 12/72] ICS-101: fix name & typo --- spec/app/ics-101-interchain-swap/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index b0690a89f..bc6b8299f 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -4,7 +4,7 @@ title: Interchain Swap stage: draft category: IBC/APP kind: instantiation -author: Ping , Edward Gunawan , Jafferey Hu < huzhiwei@outlook.com> , Marian +author: Ping , Edward Gunawan , Jeffrey Hu , Marian , Jay Tipirneni created: 2022-10-09 modified: 2023-04-20 requires: 24, 25 @@ -145,8 +145,8 @@ interface InterchainLiquidityPool { // construct assets const weights = weight.split(':').length - if(denoms.lenght === decimals.lenght && denoms.lenght === weight.split(':').length) { - for(let i=0; i < denoms.lenght; i++) { + if(denoms.length === decimals.length && denoms.length === weight.split(':').length) { + for(let i=0; i < denoms.length; i++) { this.assets.push({ side: store.hasSupply(denom[i]) ? PoolSide.Native: PoolSide.Remote, balance: { @@ -487,7 +487,7 @@ function delegateCreatePool(msg: MsgCreatePoolRequest) { function delegateSingleDeposit(msg MsgSingleDepositRequest) { abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokens.lenght > 0) + abortTransactionUnless(msg.tokens.length > 0) const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) @@ -543,7 +543,7 @@ function delegateDoubleDeposit(msg MsgDoubleDepositRequest) { function delegateWithdraw(msg MsgWithdrawRequest) { abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.token.lenght > 0) + abortTransactionUnless(msg.token.length > 0) const pool = store.findPoolById(msg.poolToken.denom) abortTransactionUnless(pool != null) From b459474a6992b352bc6bcb19edbdc7bb04db4f29 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Tue, 25 Apr 2023 14:57:46 +0800 Subject: [PATCH 13/72] rename diagram --- spec/app/ics-101-interchain-swap/README.md | 2 ++ spec/app/ics-101-interchain-swap/interchain-swap.svg | 1 + 2 files changed, 3 insertions(+) create mode 100644 spec/app/ics-101-interchain-swap/interchain-swap.svg diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index d20170a2e..e7d20d691 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -48,6 +48,8 @@ Users might also prefer single asset pools over dual assets pools as it removes ## Technical Specification +Unlike Other Swaps. Interchain swap have two copies of pool state, each pool state are mirrored to the other. it's not always same. it will keep dynamic consistent through various IBC transactions: such as deposit, swap, withdraw. + This is an overview of how Interchain Swap works ![Interchain Swap Diagram](interchain-swap.svg) diff --git a/spec/app/ics-101-interchain-swap/interchain-swap.svg b/spec/app/ics-101-interchain-swap/interchain-swap.svg new file mode 100644 index 000000000..336c7f39e --- /dev/null +++ b/spec/app/ics-101-interchain-swap/interchain-swap.svg @@ -0,0 +1 @@ +Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap Packet3 Execute Swap(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool StateOver slippage tolerance1. Calculate Swap Output Amount2. Check slippage tolerance3. Send Token4. Update pool states1. Create Swap Order2. Lock  Swap In Assets2. Delegate Swap RequestMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well. \ No newline at end of file From 80da056d647e8b600d34bb2122537700692876da Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 26 Apr 2023 10:08:51 +0800 Subject: [PATCH 14/72] change role name to `Swap Initiator` and `State Updater` --- spec/app/ics-101-interchain-swap/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index e7d20d691..a1ee667b6 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -50,6 +50,10 @@ Users might also prefer single asset pools over dual assets pools as it removes Unlike Other Swaps. Interchain swap have two copies of pool state, each pool state are mirrored to the other. it's not always same. it will keep dynamic consistent through various IBC transactions: such as deposit, swap, withdraw. +To implement interchain swap, we introduce the `Swap Initiator` and `State Updater`. The `Swap Initiator` will pre-process the request and execute the swap (validate msgs, lock assets, etc), and then forward the transactions to the relayer. The `State Updater` just simply update the states sent from `Swap Initiator`(keep the pool state consistency). + +Each chain could be either `Swap Initiator` or `State Updater`, it's depend on where the swap is created. + This is an overview of how Interchain Swap works ![Interchain Swap Diagram](interchain-swap.svg) @@ -81,7 +85,7 @@ $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ #### Fees -Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.0001% and a maximum value of 10%. +Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.01% and a maximum value of 10%. To avoid use decimal, we use basis point for `feeRate`, 1 = 0.01%, 100 = 1% The fees go to liquidity providers in exchange for depositing their tokens in the pool to facilitate trades. Trade fees are collected at the time of a swap, and goes directly into the pool, increasing the pool balance. For a trade with a given $inputToken$ and $outputToken$, the amount collected by the pool as a fee is @@ -172,6 +176,8 @@ function generatePoolId(denoms: []string) { #### IBC Market Maker +Market Maker is stateless, which is initialized with few parameters: such liquidity pool and fee rates. Note, For a trading pair, the `feeRate` on each chain could be different. which can be updated by governance. + ```ts class InterchainMarketMaker { pool :InterchainLiquidityPool @@ -213,7 +219,7 @@ class InterchainMarketMaker { } } - // P_issued = P_supply * (1 + At/Bt) + // P_issued = P_supply * (1 + At/Bt) function depositDoubleAsset(tokens: Coin[]): Coin[] { const lpTokens = []; for (const token in tokens) { @@ -451,8 +457,6 @@ interface MsgSwapResponse { ### Control Flow And Life Scope -To implement interchain swap, we introduce the `Message Delegator` and `Relay Listener`. The `Message Delegator` will pre-process the request (validate msgs, lock assets, etc), and then forward the transactions to the relayer. - ```ts function delegateCreatePool(msg: MsgCreatePoolRequest) { From 63cb7aea22e228f2990ae23d1345df5330ac99fb Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 26 Apr 2023 10:15:55 +0800 Subject: [PATCH 15/72] Add Risk evaluation --- spec/app/ics-101-interchain-swap/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index a1ee667b6..ff887fefe 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -1082,6 +1082,12 @@ function refundToken(packet: Packet) { bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) } ``` +## RISKS + +### Pool State Inconsistency + +### Price Impact Of Single Asset Deposit + ## Backwards Compatibility From 8f164ed48cb3b8f8dfa6e68616e5f11c35bfe7ce Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 26 Apr 2023 10:53:22 +0800 Subject: [PATCH 16/72] add risk details --- spec/app/ics-101-interchain-swap/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index ff887fefe..3f66f59c1 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -1085,9 +1085,23 @@ function refundToken(packet: Packet) { ## RISKS ### Pool State Inconsistency +To maintain pool state synchronization is extreme important for Interchain Swap, since we have two mirrored pool across the two chain. +However, Pool state synchronization could be delayed due to the relayer halt or network issues. the delay could affect swap price. + +Solutions: + - Timeout: Swap order need to be confirmed on the counterparty chain. it would be canceled and refund if packets not arrival the counterparty on time. + - Slippage Tolerance can be a way to protect loss caused by in-consistency. + - Single side trade: Each Token can only be trade its native chain. in inconsistency state, the backlog swap would sell lower prices than consistency state. which could help to maintain consistency. ### Price Impact Of Single Asset Deposit +Single side deposit is convenient for user to deposit asset. +But single side deposit could break the balance of constant invariant. which means the current pool price would go higher or lower. which increase opportunity for arbitrageur + +Solution: + - set upper limit for single side deposit. The ratio of profits taken away by arbitrageurs is directly proportional to the ratio of single-sided deposits and the quantity of that asset in the liquidity pool. + + ## Backwards Compatibility From e6aa8bfe74be7a704da265c5aaca481c27edb3fd Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 26 Apr 2023 12:31:58 +0800 Subject: [PATCH 17/72] add state change in relay packet --- spec/app/ics-101-interchain-swap/README.md | 50 ++++++++++++++-------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 3f66f59c1..24ec89ac1 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -99,7 +99,7 @@ As the pool collects fees, liquidity providers automatically collect fees throug ```ts interface Coin { - amount: int32; + amount: int64; denom: string; } ``` @@ -333,11 +333,21 @@ enum MessageType { ``` ```ts +interface StateChange { + in: Coin[], + out: Coin[], + poolToken: Coin, // could be negtive +} + // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { type: MessageType, data: []byte, // Bytes + stateChange: StateChange } + + + ``` ```typescript @@ -362,12 +372,12 @@ A liquidity pool in the interchain swap protocol maintains its pool state on bot IBCSwap implements the following sub-protocols: ```protobuf - rpc DelegateCreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); - rpc DelegateSingleDeposit(MsgSingleDepositRequest) returns (MsgSingleDepositResponse); - rpc DelegateDoubleDeposit(MsgDoubleDepositRequest) returns (MsgDoubleDepositResponse); - rpc DelegateWithdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); - rpc DelegateLeftSwap(MsgLeftSwapRequest) returns (MsgSwapResponse); - rpc DelegateRightSwap(MsgRightSwapRequest) returns (MsgSwapResponse); + rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); + rpc SingleDeposit(MsgSingleDepositRequest) returns (MsgSingleDepositResponse); + rpc DoubleDeposit(MsgDoubleDepositRequest) returns (MsgDoubleDepositResponse); + rpc Withdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); + rpc LeftSwap(MsgLeftSwapRequest) returns (MsgSwapResponse); + rpc RightSwap(MsgRightSwapRequest) returns (MsgSwapResponse); ``` #### Interfaces for sub-protocols @@ -457,8 +467,10 @@ interface MsgSwapResponse { ### Control Flow And Life Scope +These are methods of `Swap Initiator`, which execute main logic and output a state change, state change is need to be executed on both local and remote + ```ts -function delegateCreatePool(msg: MsgCreatePoolRequest) { +function createPool(msg: MsgCreatePoolRequest) { // ICS 24 host check if both port and channel are validate abortTransactionUnless(host.portIdentifierValidator(msg.sourcePort)) @@ -490,7 +502,7 @@ function delegateCreatePool(msg: MsgCreatePoolRequest) { } -function delegateSingleDeposit(msg MsgSingleDepositRequest) { +function singleDeposit(msg MsgSingleDepositRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokens.lenght > 0) @@ -517,7 +529,7 @@ function delegateSingleDeposit(msg MsgSingleDepositRequest) { } -function delegateDoubleDeposit(msg MsgDoubleDepositRequest) { +function doubleDeposit(msg MsgDoubleDepositRequest) { abortTransactionUnless(msg.localDeposit.sender != null) abortTransactionUnless(msg.localDeposit.token != null) @@ -546,7 +558,7 @@ function delegateDoubleDeposit(msg MsgDoubleDepositRequest) { sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } -function delegateWithdraw(msg MsgWithdrawRequest) { +function withdraw(msg MsgWithdrawRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.token.lenght > 0) @@ -572,7 +584,7 @@ function delegateWithdraw(msg MsgWithdrawRequest) { } -function delegateLeftSwap(msg MsgLeftSwapRequest) { +function leftSwap(msg MsgLeftSwapRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) @@ -597,7 +609,7 @@ function delegateLeftSwap(msg MsgLeftSwapRequest) { } -function delegateRightSwap(msg MsgRightSwapRequest) { +function rightSwap(msg MsgRightSwapRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) @@ -623,7 +635,7 @@ function delegateRightSwap(msg MsgRightSwapRequest) { } ``` -The `Relay Listener` handle all transactions, execute transactions when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. +The `State Updater` handle all transactions, update states and sent tokens when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. ```ts function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { @@ -657,7 +669,7 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC } } -function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { +function onSingleDepositReceived(msg: MsgSingleDepositRequest, state: StateChange): MsgSingleDepositResponse { const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) @@ -677,7 +689,7 @@ function onSingleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDeposit } -function onDoubleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDepositResponse { +function onDoubleDepositReceived(msg: MsgSingleDepositRequest, state: StateChange): MsgSingleDepositResponse { abortTransactionUnless(msg.remoteDeposit.sender != null) abortTransactionUnless(msg.remoteDeposit.token != null) @@ -719,7 +731,7 @@ function onDoubleDepositReceived(msg: MsgSingleDepositRequest): MsgSingleDeposit return { poolToken } } -function onWithdrawReceived(msg: MsgWithdrawRequest) MsgWithdrawResponse { +function onWithdrawReceived(msg: MsgWithdrawRequest, state: StateChange) MsgWithdrawResponse { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.denomOut != null) abortTransactionUnless(msg.poolCoin.amount > 0) @@ -739,7 +751,7 @@ function onWithdrawReceived(msg: MsgWithdrawRequest) MsgWithdrawResponse { return { tokens: outToken } } -function onLeftSwapReceived(msg: MsgLeftSwapRequest) MsgSwapResponse { +function onLeftSwapReceived(msg: MsgLeftSwapRequest, state: StateChange) MsgSwapResponse { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) @@ -768,7 +780,7 @@ function onLeftSwapReceived(msg: MsgLeftSwapRequest) MsgSwapResponse { return { tokens: outToken } } -function onRightSwapReceived(msg MsgRightSwapRequest) MsgSwapResponse { +function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwapResponse { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) From fedd2e3454bea639fbd52ffa561274a49dc0b72a Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 26 Apr 2023 16:05:56 +0800 Subject: [PATCH 18/72] add list for sub-protocol --- spec/app/ics-101-interchain-swap/README.md | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 0521e0b3a..c4385c4f9 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -50,9 +50,11 @@ Users might also prefer single asset pools over dual assets pools as it removes Unlike Other Swaps. Interchain swap have two copies of pool state, each pool state are mirrored to the other. it's not always same. it will keep dynamic consistent through various IBC transactions: such as deposit, swap, withdraw. +Each pool is only used for assets on the chain where the liquidity pool is located + To implement interchain swap, we introduce the `Swap Initiator` and `State Updater`. The `Swap Initiator` will pre-process the request and execute the swap (validate msgs, lock assets, etc), and then forward the transactions to the relayer. The `State Updater` just simply update the states sent from `Swap Initiator`(keep the pool state consistency). -Each chain could be either `Swap Initiator` or `State Updater`, it's depend on where the swap is created. +Each chain could be either `Swap Initiator` or `State Updater`, it depend on where the swap is created. This is an overview of how Interchain Swap works @@ -174,9 +176,9 @@ function generatePoolId(denoms: []string) { } ``` -#### IBC Market Maker +#### Interchain Market Maker -Market Maker is stateless, which is initialized with few parameters: such liquidity pool and fee rates. Note, For a trading pair, the `feeRate` on each chain could be different. which can be updated by governance. +Interchain Market Maker is a core component for swap calculation, which is stateless and initialized with few parameters: such liquidity pool and fee rates. Note, For a trading pair, the `feeRate` on each chain could be different. which can be updated by governance. ```ts class InterchainMarketMaker { @@ -321,6 +323,7 @@ class InterchainMarketMaker { Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. +- Payloads in packet ```ts enum MessageType { RequestPoolCreation, @@ -330,26 +333,24 @@ enum MessageType { LeftSwap, RightSwap, } -``` -```ts interface StateChange { in: Coin[], out: Coin[], poolToken: Coin, // could be negtive } - +``` +- Packet structure +```ts // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { type: MessageType, data: []byte, // Bytes stateChange: StateChange } - - - ``` +- Packet of Acknowledgement ```typescript type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; @@ -369,7 +370,7 @@ Traditional liquidity pools typically maintain its pool state in one location. A liquidity pool in the interchain swap protocol maintains its pool state on both its source chain and destination chain. The pool states mirror each other and are synced through IBC packet relays, which we elaborate on in the following sub-protocols. -IBCSwap implements the following sub-protocols: +Interchain Swap implements the following sub-protocols: ```protobuf rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); @@ -382,6 +383,8 @@ IBCSwap implements the following sub-protocols: #### Interfaces for sub-protocols +- Create cross chain liquidity pool + ```ts interface MsgCreatePoolRequest { sourcePort: string, @@ -394,7 +397,7 @@ interface MsgCreatePoolRequest { interface MsgCreatePoolResponse {} ``` - +- Single Side Deposit ```ts interface MsgDepositRequest { poolId: string; @@ -406,6 +409,7 @@ interface MsgSingleDepositResponse { } ``` +- Two sides Deposit ```ts interface LocalDeposit { sender: string; @@ -427,7 +431,7 @@ interface MsgDoubleDepositResponse { poolTokens: Coin[]; } ``` - +- Withdraw ```ts interface MsgWithdrawRequest { sender: string, @@ -438,6 +442,7 @@ interface MsgWithdrawResponse { tokens: []Coin; } ``` +- Left Swap ```ts interface MsgLeftSwapRequest { @@ -451,6 +456,7 @@ interface MsgSwapResponse { tokens: []Coin; } ``` +- Right Swap ```ts interface MsgRightSwapRequest { From 4cbec041099c1a9b6a96fa4c4f88378d977d8ace Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 26 Apr 2023 17:07:26 +0800 Subject: [PATCH 19/72] re-implement the singe deposit --- spec/app/ics-101-interchain-swap/README.md | 77 +++++++++++++--------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index c4385c4f9..563b0d032 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -184,13 +184,11 @@ Interchain Market Maker is a core component for swap calculation, which is state class InterchainMarketMaker { pool :InterchainLiquidityPool // basis point - feeRate: int32 + feeRate: number // int32 - static initialize(pool: InterchainLiquidityPool, feeRate: int32) : InterchainMarketMaker { - return { - pool: pool, - feeRate: feeRate, - } + construct(pool: InterchainLiquidityPool, feeRate: number) : InterchainMarketMaker { + this.pool = pool, + this.feeRate = feeRate, } // MarketPrice Bi / Wi / (Bo / Wo) @@ -402,7 +400,7 @@ interface MsgCreatePoolResponse {} interface MsgDepositRequest { poolId: string; sender: string; - tokens: Coin[]; // only one element for now, might have two in the feature + token: Coin; } interface MsgSingleDepositResponse { poolToken: Coin; @@ -516,20 +514,22 @@ function singleDeposit(msg MsgSingleDepositRequest) { const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - for(var token in msg.tokens) { - const balance = bank.queryBalance(sender, token.denom) - // should have enough balance - abortTransactionUnless(balance.amount >= token.amount) - } + const balance = bank.queryBalance(msg.sender, msg.token.denom) + // should have enough balance + abortTransactionUnless(balance.amount >= token.amount) // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + cosnt poolToken = amm.depositSingleAsset(msg.token) + // constructs the IBC data packet const packet = { type: MessageType.Deposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. + stateChange: { poolToken } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } @@ -555,11 +555,15 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + + cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + cosnt poolToken = amm.depositSingleAsset(msg.token) // constructs the IBC data packet const packet = { type: MessageType.DoubleDeposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. + stateChange: { poolToken }, } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } @@ -580,11 +584,18 @@ function withdraw(msg MsgWithdrawRequest) { // lock pool token to the swap module const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) + + cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + cosnt outAmount = amm.withdraw(msg.poolToken) // constructs the IBC data packet const packet = { type: MessageType.Withdraw, data: protobuf.encode(msg), // encode the request message to protobuf bytes. + stateChange: { + poolToken: msg.poolToken, + out: outAmount, + } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -605,11 +616,15 @@ function leftSwap(msg MsgLeftSwapRequest) { // lock swap-in token to the swap module const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) + + cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + cosnt outAmount = amm.leftSwap(msg.tokenIn, denomOut) // contructs the IBC data packet const packet = { type: MessageType.Leftswap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. + stateChange: { in: msg.tokenIn, out: outAmount } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -630,11 +645,16 @@ function rightSwap(msg MsgRightSwapRequest) { // lock swap-in token to the swap module const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) + + cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + cosnt inAmount = amm.rightSwap(msg.tokenIn, msg.tokenOut) + abortTransactionUnless(msg.tokenIn > inAmount) // contructs the IBC data packet const packet = { type: MessageType.Rightswap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. + stateChange: {in: msg.TokenIn, out: msg.TokenOut } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -676,22 +696,19 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC } function onSingleDepositReceived(msg: MsgSingleDepositRequest, state: StateChange): MsgSingleDepositResponse { - + const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - - const amm = store.findAmmById(msg.poolId) - - if(amm !== null) { - // fetch fee rate from the params module, maintained by goverance - const feeRate = params.getPoolFeeRate() - const amm = InterchainMarketMaker.initialize(pool, feeRate) - } - - const poolToken = amm.singleDeposit(msg.tokens[0]) - store.savePool(amm.pool) // update pool states - - return { poolToken } + + // add deposit asset + const assetIn = pool.findAssetByDenom(state.in.denom) + assetIn.balance.amount += state.in.amount + + // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. + pool.supply.amount += state.poolToken.amount + store.savePool(pool) + + return { poolToken: state.poolToken } } @@ -707,7 +724,7 @@ function onDoubleDepositReceived(msg: MsgSingleDepositRequest, state: StateChang if(amm !== null) { // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - const amm = InterchainMarketMaker.initialize(pool, feeRate) + const amm = new InterchainMarketMaker(pool, feeRate) } // verify signature @@ -748,7 +765,7 @@ function onWithdrawReceived(msg: MsgWithdrawRequest, state: StateChange) MsgWith // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - const amm = InterchainMarketMaker.initialize(pool, feeRate) + const amm = new InterchainMarketMaker(pool, feeRate) const outToken = amm.withdraw(msg.poolCoin, msg.denomOut) store.savePool(amm.pool) // update pool states @@ -770,7 +787,7 @@ function onLeftSwapReceived(msg: MsgLeftSwapRequest, state: StateChange) MsgSwap // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - const amm = InterchainMarketMaker.initialize(pool, feeRate) + const amm = new InterchainMarketMaker(pool, feeRate) const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom) const expected = msg.tokenOut.amount @@ -800,7 +817,7 @@ function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwa // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() - const amm = InterchainMarketMaker.initialize(pool, feeRate) + const amm = new InterchainMarketMaker(pool, feeRate) const minTokenIn = amm.rightSwap(msg.tokenIn, msg.tokenOut) // tolerance check From 6d994e444b00fe36f514078d0dc89198c8274f0c Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Tue, 2 May 2023 06:15:25 +0300 Subject: [PATCH 20/72] change packet message type and fix misspellings --- spec/app/ics-101-interchain-swap/README.md | 157 ++++++++++++--------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 563b0d032..b16725e9e 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -56,7 +56,7 @@ To implement interchain swap, we introduce the `Swap Initiator` and `State Updat Each chain could be either `Swap Initiator` or `State Updater`, it depend on where the swap is created. -This is an overview of how Interchain Swap works +This is an overview of how Interchain Swap works ![Interchain Swap Diagram](interchain-swap.svg) @@ -322,23 +322,30 @@ class InterchainMarketMaker { Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. - Payloads in packet + ```ts enum MessageType { - RequestPoolCreation, - ResponsePoolCreation, - Deposit, - Withdraw, - LeftSwap, - RightSwap, + CreatePool, + SingleDeposit, + DoubleDeposit, + Withdraw, + Swap +} + +enum SwapType { + Right + Left } interface StateChange { - in: Coin[], - out: Coin[], - poolToken: Coin, // could be negtive + in: Coin[]; + out: Coin[]; + poolToken: Coin; // could be negtive } ``` + - Packet structure + ```ts // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { @@ -349,6 +356,7 @@ interface IBCSwapDataPacket { ``` - Packet of Acknowledgement + ```typescript type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; @@ -395,7 +403,9 @@ interface MsgCreatePoolRequest { interface MsgCreatePoolResponse {} ``` + - Single Side Deposit + ```ts interface MsgDepositRequest { poolId: string; @@ -408,6 +418,7 @@ interface MsgSingleDepositResponse { ``` - Two sides Deposit + ```ts interface LocalDeposit { sender: string; @@ -429,7 +440,9 @@ interface MsgDoubleDepositResponse { poolTokens: Coin[]; } ``` + - Withdraw + ```ts interface MsgWithdrawRequest { sender: string, @@ -440,11 +453,13 @@ interface MsgWithdrawResponse { tokens: []Coin; } ``` + - Left Swap ```ts -interface MsgLeftSwapRequest { +interface MsgSwapRequest { sender: string, + swapType: SwapType, tokenIn: Coin, tokenOut: Coin, slippage: number; // max tolerated slippage @@ -454,6 +469,7 @@ interface MsgSwapResponse { tokens: []Coin; } ``` + - Right Swap ```ts @@ -471,7 +487,7 @@ interface MsgSwapResponse { ### Control Flow And Life Scope -These are methods of `Swap Initiator`, which execute main logic and output a state change, state change is need to be executed on both local and remote +These are methods of `Swap Initiator`, which execute main logic and output a state change, state change is need to be executed on both local and remote ```ts function createPool(msg: MsgCreatePoolRequest) { @@ -486,7 +502,7 @@ function createPool(msg: MsgCreatePoolRequest) { abortTransactionUnless(msg.weight.split(':').length != 2) // weight: "50:50" abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) - cosnt pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, msg.sourcePort, msg.sourceChannel) + const pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, msg.sourcePort, msg.sourceChannel) const localAssetCount = 0 for(var denom in msg.denoms) { @@ -499,7 +515,7 @@ function createPool(msg: MsgCreatePoolRequest) { // constructs the IBC data packet const packet = { - type: MessageType.Create, + type: MessageType.CreatePool, data: protobuf.encode(msg), // encode the request message to protobuf bytes. } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -522,9 +538,9 @@ function singleDeposit(msg MsgSingleDepositRequest) { const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) - cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - cosnt poolToken = amm.depositSingleAsset(msg.token) - + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const poolToken = amm.depositSingleAsset(msg.token) + // constructs the IBC data packet const packet = { type: MessageType.Deposit, @@ -555,9 +571,9 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) - - cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - cosnt poolToken = amm.depositSingleAsset(msg.token) + + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const poolToken = amm.depositSingleAsset(msg.token) // constructs the IBC data packet const packet = { @@ -582,11 +598,11 @@ function withdraw(msg MsgWithdrawRequest) { abortTransactionUnless(outToken.poolSide == PoolSide.Native) // lock pool token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) - - cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - cosnt outAmount = amm.withdraw(msg.poolToken) + + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const outAmount = amm.withdraw(msg.poolToken) // constructs the IBC data packet const packet = { @@ -601,7 +617,7 @@ function withdraw(msg MsgWithdrawRequest) { } -function leftSwap(msg MsgLeftSwapRequest) { +function leftSwap(msg MsgSwapRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) @@ -614,15 +630,15 @@ function leftSwap(msg MsgLeftSwapRequest) { abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - - cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - cosnt outAmount = amm.leftSwap(msg.tokenIn, denomOut) + + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const outAmount = amm.leftSwap(msg.tokenIn, denomOut) // contructs the IBC data packet const packet = { - type: MessageType.Leftswap, + type: MessageType.Swap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. stateChange: { in: msg.tokenIn, out: outAmount } } @@ -638,16 +654,16 @@ function rightSwap(msg MsgRightSwapRequest) { abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) + const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom]) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - - cosnt amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - cosnt inAmount = amm.rightSwap(msg.tokenIn, msg.tokenOut) + + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const inAmount = amm.rightSwap(msg.tokenIn, msg.tokenOut) abortTransactionUnless(msg.tokenIn > inAmount) // contructs the IBC data packet @@ -673,11 +689,11 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) // construct mirror pool on destination chain - cosnt pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, destPort, destChannel) + const pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, destPort, destChannel) // count native tokens const count = 0 - for(var denom in msg.denoms { + for(var denom in msg.denoms) { if bank.hasSupply(ctx, denom) { count += 1 pool.updateAssetPoolSide(denom, PoolSide.Native) @@ -696,18 +712,18 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC } function onSingleDepositReceived(msg: MsgSingleDepositRequest, state: StateChange): MsgSingleDepositResponse { - + const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - + // add deposit asset const assetIn = pool.findAssetByDenom(state.in.denom) assetIn.balance.amount += state.in.amount - + // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. pool.supply.amount += state.poolToken.amount store.savePool(pool) - + return { poolToken: state.poolToken } } @@ -774,7 +790,7 @@ function onWithdrawReceived(msg: MsgWithdrawRequest, state: StateChange) MsgWith return { tokens: outToken } } -function onLeftSwapReceived(msg: MsgLeftSwapRequest, state: StateChange) MsgSwapResponse { +function onLeftSwapReceived(msg: MsgSwapRequest, state: StateChange) MsgSwapResponse { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) @@ -795,7 +811,7 @@ function onLeftSwapReceived(msg: MsgLeftSwapRequest, state: StateChange) MsgSwap // tolerance check abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(escrowAddr, msg.recipient, outToken) store.savePool(amm.pool) // update pool states @@ -824,7 +840,7 @@ function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwa abortTransactionUnless(tokenIn.amount > minTokenIn.amount) abortTransactionUnless((tokenIn.amount - minTokenIn.amount)/minTokenIn.amount > msg.slippage / 10000)) - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encouterPartyChannel) + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(escrowAddr, msg.recipient, msg.tokenOut) store.savePool(amm.pool) // update pool states @@ -842,7 +858,7 @@ function onSingleDepositAcknowledged(request: MsgSingleDepositRequest, response: pool.supply.amount += response.tokens.amount store.savePool(pool) - bank.mintCoin(MODULE_NAME,request.sender,reponse.token) + bank.mintCoin(MODULE_NAME,request.sender,response.token) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) } @@ -856,7 +872,7 @@ function onDoubleDepositAcknowledged(request: MsgDoubleDepositRequest, response: store.savePool(pool) - bank.mintCoin(MODULE_NAME,reponse.tokens[0]) + bank.mintCoin(MODULE_NAME,response.tokens[0]) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]) } @@ -868,10 +884,10 @@ function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdr store.savePool(pool) bank.sendCoinsFromAccountToModule(msg.sender, MODULE_NAME, response.tokens) - bank.burnCoin(MODULE_NAME, reponse.token) + bank.burnCoin(MODULE_NAME, response.token) } -function onLeftSwapAcknowledged(request: MsgLeftSwapRequest, response: MsgSwapResponse) { +function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) { const pool = store.findPoolById(generatePoolId[request.tokenIn.denom, request.tokenOut.denom])) abortTransactionUnless(pool != null) @@ -1028,13 +1044,13 @@ function onRecvPacket(packet: Packet) { var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) onWithdrawReceived(msg) break - case LEFT_SWAP: - var msg: MsgLeftSwapRequest = protobuf.decode(swapPacket.data) - onLeftswapReceived(msg) - break - case RIGHT_SWAP: - var msg: MsgRightSwapRequest = protobuf.decode(swapPacket.data) - onRightReceived(msg) + case SWAP: + var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) + if(msg.SwapType === SwapType.Left) { + onLeftSwapReceived(msg) + }else{ + onRightSwapReceived(msg) + } break } } catch { @@ -1074,11 +1090,15 @@ function OnAcknowledgementPacket( case WITHDRAW: onWithdrawAcknowledged(msg) break; - case LEFT_SWAP: + case SWAP: + var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) + if(msg.SwapType === SwapType.Left) { + onLeftSwapAcknowledged(msg) + }else{ + onRightSwapAcknowledged(msg) + } onLeftSwapAcknowledged(msg) break; - case RIGHT_SWAP: - onRightSwapAcknowledged(msg) } } @@ -1100,8 +1120,7 @@ function onTimeoutPacket(packet: Packet) { function refundToken(packet: Packet) { let token switch packet.type { - case LeftSwap: - case RightSwap: + case Swap: token = packet.tokenIn break; case Deposit: @@ -1117,26 +1136,28 @@ function refundToken(packet: Packet) { bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) } ``` + ## RISKS ### Pool State Inconsistency + To maintain pool state synchronization is extreme important for Interchain Swap, since we have two mirrored pool across the two chain. However, Pool state synchronization could be delayed due to the relayer halt or network issues. the delay could affect swap price. -Solutions: - - Timeout: Swap order need to be confirmed on the counterparty chain. it would be canceled and refund if packets not arrival the counterparty on time. - - Slippage Tolerance can be a way to protect loss caused by in-consistency. - - Single side trade: Each Token can only be trade its native chain. in inconsistency state, the backlog swap would sell lower prices than consistency state. which could help to maintain consistency. +Solutions: + +- Timeout: Swap order need to be confirmed on the counterparty chain. it would be canceled and refund if packets not arrival the counterparty on time. +- Slippage Tolerance can be a way to protect loss caused by in-consistency. +- Single side trade: Each Token can only be trade its native chain. in inconsistency state, the backlog swap would sell lower prices than consistency state. which could help to maintain consistency. ### Price Impact Of Single Asset Deposit -Single side deposit is convenient for user to deposit asset. -But single side deposit could break the balance of constant invariant. which means the current pool price would go higher or lower. which increase opportunity for arbitrageur +Single side deposit is convenient for user to deposit asset. +But single side deposit could break the balance of constant invariant. which means the current pool price would go higher or lower. which increase opportunity for arbitrageur Solution: - - set upper limit for single side deposit. The ratio of profits taken away by arbitrageurs is directly proportional to the ratio of single-sided deposits and the quantity of that asset in the liquidity pool. - +- set upper limit for single side deposit. The ratio of profits taken away by arbitrageurs is directly proportional to the ratio of single-sided deposits and the quantity of that asset in the liquidity pool. ## Backwards Compatibility From 7529c655f59f8f2efe66c2e09fd672a77861eb7f Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Thu, 27 Apr 2023 15:27:14 +0800 Subject: [PATCH 21/72] Update README.md --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index b16725e9e..be8bc09f6 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -677,7 +677,7 @@ function rightSwap(msg MsgRightSwapRequest) { } ``` -The `State Updater` handle all transactions, update states and sent tokens when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. +The `State Updater` handle all packets relayed from the source chain, update pool states and sent tokens when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. ```ts function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { From 69fcf8912209991b4776fd17307c65b6a62f7f29 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Thu, 27 Apr 2023 17:36:58 +0800 Subject: [PATCH 22/72] add pool state update on Swap Initiator --- spec/app/ics-101-interchain-swap/README.md | 69 +++++++++++++++++----- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index be8bc09f6..2233a218f 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -525,27 +525,36 @@ function createPool(msg: MsgCreatePoolRequest) { function singleDeposit(msg MsgSingleDepositRequest) { abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokens.length > 0) + abortTransactionUnless(msg.token.amount > 0) const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) const balance = bank.queryBalance(msg.sender, msg.token.denom) // should have enough balance - abortTransactionUnless(balance.amount >= token.amount) + abortTransactionUnless(balance.amount >= msg.token.amount) // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + // calculation const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const poolToken = amm.depositSingleAsset(msg.token) + + // update local pool state, + const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) + assetIn.balance.amount += msg.token.amount + pool.supply.amount += poolToken.amount + store.savePool(pool) + + // pool token should be minted and sent onAcknowledgement. // constructs the IBC data packet const packet = { type: MessageType.Deposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { poolToken } + stateChange: { in: [msg.token], poolToken } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } @@ -560,26 +569,36 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { abortTransactionUnless(msg.remoteDeposit.signature != null) abortTransactionUnless(msg.remoteDeposit.sequence != null) - const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) const balance = bank.queryBalance(sender, msg.localDeposit.token.denom) // should have enough balance abortTransactionUnless(balance.amount >= msg.localDeposit.token.amount) + + // TODO Marian: check the ratio of local amount and remote amount // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + // calculation const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const poolToken = amm.depositSingleAsset(msg.token) + const poolToken = amm.depositSingleAsset(msg.token) // should replace with doubleDeposit() ? + + // update local pool state, + const assetIn = pool.findAssetByDenom(msg.localDeposit.token.denom) + assetIn.balance.amount += msg.localDeposit.token.amount + const assetIn2 = pool.findAssetByDenom(msg.remoteDeposit.token.denom) + assetIn2.balance.amount += msg.remoteDeposit.token.amount + pool.supply.amount += poolToken.amount + store.savePool(pool) // constructs the IBC data packet const packet = { type: MessageType.DoubleDeposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { poolToken }, + stateChange: { in: [msg.localDeposit, msg.remoteDeposit], poolToken }, } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } @@ -602,7 +621,13 @@ function withdraw(msg MsgWithdrawRequest) { bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const outAmount = amm.withdraw(msg.poolToken) + const outToken = amm.withdraw(msg.poolToken) + + // update local pool state, + const assetOut = pool.findAssetByDenom(msg.denomOut) + assetOut.balance.amount -= outToken.amount + pool.supply.amount -= poolToken.amount + store.savePool(pool) // constructs the IBC data packet const packet = { @@ -610,7 +635,7 @@ function withdraw(msg MsgWithdrawRequest) { data: protobuf.encode(msg), // encode the request message to protobuf bytes. stateChange: { poolToken: msg.poolToken, - out: outAmount, + out: [outToken], } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -625,22 +650,30 @@ function leftSwap(msg MsgSwapRequest) { abortTransactionUnless(msg.slippage > 0) abortTransactionUnless(msg.recipient != null) - const pool = store.findPoolById([tokenIn.denom, denomOut]) + const pool = store.findPoolById([msg.tokenIn.denom, msg.tokenOut.denom]) abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) // lock swap-in token to the swap module const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) + bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn.denom) const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const outAmount = amm.leftSwap(msg.tokenIn, denomOut) + const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut) + // TODO add slippage check here. + + // update local pool state, + const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) + assetIn.balance.amount += msg.tokenIn.amount + const assetOut = pool.findAssetByDenom(msg.tokenOut.denom) + assetOut.balance.amount -= outToken.amount + store.savePool(pool) // contructs the IBC data packet const packet = { type: MessageType.Swap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { in: msg.tokenIn, out: outAmount } + stateChange: { in: [msg.tokenIn], out: [outToken] } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -665,12 +698,20 @@ function rightSwap(msg MsgRightSwapRequest) { const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const inAmount = amm.rightSwap(msg.tokenIn, msg.tokenOut) abortTransactionUnless(msg.tokenIn > inAmount) + // TODO add slippage check here. + + // update local pool state, + const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) + assetIn.balance.amount += msg.tokenIn.amount + const assetOut = pool.findAssetByDenom(msg.denomOut) + assetOut.balance.amount -= outToken.amount + store.savePool(pool) // contructs the IBC data packet const packet = { type: MessageType.Rightswap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: {in: msg.TokenIn, out: msg.TokenOut } + stateChange: {in: [msg.TokenIn], out: [msg.TokenOut] } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -960,7 +1001,7 @@ function onChanOpenInit( version: string) => (version: string, err: Error) { // only ordered channels allowed abortTransactionUnless(order === ORDERED) - // assert that version is "ics20-1" or empty + // assert that version is "ics101-1" or empty // if empty, we return the default transfer version to core IBC // as the version for this channel abortTransactionUnless(version === "ics101-1" || version === "") From 5a5095eaac3d1e8c10e7fd0715010a5724699f57 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 27 Apr 2023 14:27:33 +0300 Subject: [PATCH 23/72] add initial deposit logic --- spec/app/ics-101-interchain-swap/README.md | 27 ++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index be8bc09f6..6e037dae6 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -95,6 +95,15 @@ $$Amount_{fee} = Amount_{inputToken} * swapFee$$ As the pool collects fees, liquidity providers automatically collect fees through their proportional ownership of the pool balance. +### Pool Initialization + +In InterchainSwap, the liquidity pool has two possible states: + +- `INITIAL`: The pool is newly created and not yet ready for swaps. This means that all pool parameters have been registered in the state machine, but it doesn't possess the necessary assets for the swap pair. + +- `READY`: The pool has acquired all required assets to facilitate swaps. + The pool's status can be updated through an initial deposit. If a user provides the full amount of assets that match the pool's initial parameters using either a `single deposit` or double deposit, the pool's status will automatically change to `READY`. With the initial deposit, the user receives sufficient pool tokens to withdraw their assets at any time. + ### Data Structures #### Pool Structure @@ -534,6 +543,11 @@ function singleDeposit(msg MsgSingleDepositRequest) { // should have enough balance abortTransactionUnless(balance.amount >= token.amount) + if(pool.status == POOL_STATUS_INITIAL) { + const asset = pool.findAssetByDenom(msg.token.denom) + abortTransactionUnless(balance.amount !== asset.amount) + } + // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) @@ -716,10 +730,10 @@ function onSingleDepositReceived(msg: MsgSingleDepositRequest, state: StateChang const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) - // add deposit asset - const assetIn = pool.findAssetByDenom(state.in.denom) - assetIn.balance.amount += state.in.amount - + if (pool.Status == PoolStatus_POOL_STATUS_INIT) { + // switch pool status to 'READY' + pool.Status = PoolStatus_POOL_STATUS_READY + } // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. pool.supply.amount += state.poolToken.amount store.savePool(pool) @@ -759,6 +773,11 @@ function onDoubleDepositReceived(msg: MsgSingleDepositRequest, state: StateChang const isValid = pubKey.VerifySignature(rawRemoteDepositTx, msg.remoteDeposit.signature) abortTransactionUnless(isValid != false) + if (pool.Status == PoolStatus_POOL_STATUS_INIT) { + // switch pool status to 'READY' + pool.Status = PoolStatus_POOL_STATUS_READY + } + // deposit remote token const poolTokens = amm.doubleSingleAsset([msg.localDeposit.token, msg.remoteDeposit.token]) From 65f9d4adf9f3f931a1f4faefe34a91b73094c48e Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 00:20:00 +0800 Subject: [PATCH 24/72] update synopsis, motivation, definitions & properties --- spec/app/ics-101-interchain-swap/README.md | 207 +++++++++++---------- 1 file changed, 109 insertions(+), 98 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 5460c5e76..0fda0342a 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -12,25 +12,27 @@ requires: 24, 25 ## Synopsis -This standard document specifies the packet data structure, state machine handling logic, and encoding details for token exchange through single-sided liquidity pools over an IBC channel between separate chains. +This standard document specifies the packet data structure, state machine handling logic, and encoding details for token exchange through single-asset liquidity pools over an IBC channel between separate chains. ### Motivation ICS-101 Interchain Swaps enables chains their own token pricing mechanism and exchange protocol via IBC transactions. Each chain can thus play a role in a fully decentralised exchange network. -Users might also prefer single asset pools over dual assets pools as it removes the risk of impermanent loss. +Features include an option to provide liquidity with a single asset instead of a pair, which users might prefer as it removes the risk of impermanent loss. ### Definitions `Interchain swap`: a IBC token swap protocol, built on top of an automated marketing making system, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. +`Interchain liquidity pool`: a single-asset liquidity pool held in the asset's native chain, and has a corresponding single-asset liquidity pool on a separate chain. This comprises an interchain liquidity pool and can execute interchain swaps to exchange tokens. + `Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. `Weighted pools`: liquidity pools characterized by the percentage weight of each token denomination maintained within. -`Single-sided liquidity pools`: a liquidity pool that does not require users to deposit both token denominations -- one is enough. But a single deposit will affect the price of the liquidity pool, increasing the opportunity for arbitrageurs +`Single-asset deposit`: a deposit into a liquidity pool that does not require users to deposit both token denominations -- one is enough. While this deposit method will impact the exchange price of the pool, it also provide an opportunity for arbitrage. -`Double-sided liquidity pools`: a liquidity pool that does require users to deposit both token denominations -- require token pair. +`Multi-asset deposit`: a deposit into a liquidity pool that require users to deposit both token denominations. This deposit method will not impact the exchange price of the pool. `Left-side swap`: a token exchange that specifies the desired quantity to be sold. @@ -40,11 +42,11 @@ Users might also prefer single asset pools over dual assets pools as it removes ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralization`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. -- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. -- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. ## Technical Specification @@ -70,9 +72,9 @@ $$V = {Π_tB_t^{W_t}}$$ Where -- $t$ ranges over the tokens in the pool -- $B_t$ is the balance of the token in the pool -- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. +- $t$ ranges over the tokens in the pool +- $B_t$ is the balance of the token in the pool +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. #### Spot Price @@ -80,10 +82,10 @@ Spot prices of tokens are defined entirely by the weights and balances of the to $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ -- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool -- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool -- $W_i$ is the weight of token $i$ -- $W_o$ is the weight of token $o$ +- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool +- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool +- $W_i$ is the weight of token $i$ +- $W_o$ is the weight of token $o$ #### Fees @@ -99,10 +101,10 @@ As the pool collects fees, liquidity providers automatically collect fees throug In InterchainSwap, the liquidity pool has two possible states: -- `INITIAL`: The pool is newly created and not yet ready for swaps. This means that all pool parameters have been registered in the state machine, but it doesn't possess the necessary assets for the swap pair. +- `INITIAL`: The pool is newly created and not yet ready for swaps. This means that all pool parameters have been registered in the state machine, but it doesn't possess the necessary assets for the swap pair. -- `READY`: The pool has acquired all required assets to facilitate swaps. - The pool's status can be updated through an initial deposit. If a user provides the full amount of assets that match the pool's initial parameters using either a `single deposit` or double deposit, the pool's status will automatically change to `READY`. With the initial deposit, the user receives sufficient pool tokens to withdraw their assets at any time. +- `READY`: The pool has acquired all required assets to facilitate swaps. + The pool's status can be updated through an initial deposit. If a user provides the full amount of assets that match the pool's initial parameters using either a `single deposit` or double deposit, the pool's status will automatically change to `READY`. With the initial deposit, the user receives sufficient pool tokens to withdraw their assets at any time. ### Data Structures @@ -110,8 +112,8 @@ In InterchainSwap, the liquidity pool has two possible states: ```ts interface Coin { - amount: int64; - denom: string; + amount: int64; + denom: string; } ``` @@ -132,11 +134,11 @@ enum PoolStatus { ```ts interface PoolAsset { - side: PoolSide; - balance: Coin; - // percentage - weight: int32; - decimal: int32; + side: PoolSide; + balance: Coin; + // percentage + weight: int32; + decimal: int32; } ``` @@ -330,7 +332,7 @@ class InterchainMarketMaker { Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. -- Payloads in packet +- Payloads in packet ```ts enum MessageType { @@ -353,7 +355,7 @@ interface StateChange { } ``` -- Packet structure +- Packet structure ```ts // IBCSwapDataPacket is used to wrap message for relayer. @@ -364,18 +366,20 @@ interface IBCSwapDataPacket { } ``` -- Packet of Acknowledgement +- Packet of Acknowledgement ```typescript -type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; +type IBCSwapDataAcknowledgement = + | IBCSwapDataPacketSuccess + | IBCSwapDataPacketError; interface IBCSwapDataPacketSuccess { - // This is binary 0x01 base64 encoded - result: "AQ=="; + // This is binary 0x01 base64 encoded + result: "AQ=="; } interface IBCSwapDataPacketError { - error: string; + error: string; } ``` @@ -398,7 +402,7 @@ Interchain Swap implements the following sub-protocols: #### Interfaces for sub-protocols -- Create cross chain liquidity pool +- Create cross chain liquidity pool ```ts interface MsgCreatePoolRequest { @@ -413,44 +417,44 @@ interface MsgCreatePoolRequest { interface MsgCreatePoolResponse {} ``` -- Single Side Deposit +- Single Side Deposit ```ts interface MsgDepositRequest { - poolId: string; - sender: string; - token: Coin; + poolId: string; + sender: string; + token: Coin; } interface MsgSingleDepositResponse { - poolToken: Coin; + poolToken: Coin; } ``` -- Two sides Deposit +- Two sides Deposit ```ts interface LocalDeposit { - sender: string; - token: Coin; + sender: string; + token: Coin; } interface RemoteDeposit { - sender: string; - sequence: int; // account transaction sequence - token: Coin; - signature: Uint8Array; + sender: string; + sequence: int; // account transaction sequence + token: Coin; + signature: Uint8Array; } interface MsgDoubleDepositRequest { - poolId: string; - localDeposit: LocalDeposit; - remoteDeposit: RemoteDeposit; + poolId: string; + localDeposit: LocalDeposit; + remoteDeposit: RemoteDeposit; } interface MsgDoubleDepositResponse { - poolTokens: Coin[]; + poolTokens: Coin[]; } ``` -- Withdraw +- Withdraw ```ts interface MsgWithdrawRequest { @@ -463,7 +467,7 @@ interface MsgWithdrawResponse { } ``` -- Left Swap +- Left Swap ```ts interface MsgSwapRequest { @@ -479,18 +483,18 @@ interface MsgSwapResponse { } ``` -- Right Swap +- Right Swap ```ts interface MsgRightSwapRequest { - sender: string; - tokenIn: Coin; - tokenOut: Coin; - slippage: number; // max tolerated slippage - recipient: string; + sender: string; + tokenIn: Coin; + tokenOut: Coin; + slippage: number; // max tolerated slippage + recipient: string; } interface MsgSwapResponse { - tokens: Coin[]; + tokens: Coin[]; } ``` @@ -555,13 +559,13 @@ function singleDeposit(msg MsgSingleDepositRequest) { // calculation const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const poolToken = amm.depositSingleAsset(msg.token) - + // update local pool state, const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) assetIn.balance.amount += msg.token.amount pool.supply.amount += poolToken.amount store.savePool(pool) - + // pool token should be minted and sent onAcknowledgement. // constructs the IBC data packet @@ -589,7 +593,7 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { const balance = bank.queryBalance(sender, msg.localDeposit.token.denom) // should have enough balance abortTransactionUnless(balance.amount >= msg.localDeposit.token.amount) - + // TODO Marian: check the ratio of local amount and remote amount // deposit assets to the escrowed account @@ -599,7 +603,7 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { // calculation const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const poolToken = amm.depositSingleAsset(msg.token) // should replace with doubleDeposit() ? - + // update local pool state, const assetIn = pool.findAssetByDenom(msg.localDeposit.token.denom) assetIn.balance.amount += msg.localDeposit.token.amount @@ -635,13 +639,13 @@ function withdraw(msg MsgWithdrawRequest) { bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const outToken = amm.withdraw(msg.poolToken) - + const outToken = amm.withdraw(msg.poolToken) + // update local pool state, const assetOut = pool.findAssetByDenom(msg.denomOut) assetOut.balance.amount -= outToken.amount pool.supply.amount -= poolToken.amount - store.savePool(pool) + store.savePool(pool) // constructs the IBC data packet const packet = { @@ -668,22 +672,22 @@ function leftSwap(msg MsgSwapRequest) { abortTransactionUnless(pool != null) abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn.denom) + // lock swap-in token to the swap module + const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn.denom) - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut) // TODO add slippage check here. - + // update local pool state, const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) assetIn.balance.amount += msg.tokenIn.amount const assetOut = pool.findAssetByDenom(msg.tokenOut.denom) assetOut.balance.amount -= outToken.amount - store.savePool(pool) + store.savePool(pool) - // contructs the IBC data packet + // contructs the IBC data packet const packet = { type: MessageType.Swap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. @@ -709,17 +713,17 @@ function rightSwap(msg MsgRightSwapRequest) { const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const inAmount = amm.rightSwap(msg.tokenIn, msg.tokenOut) abortTransactionUnless(msg.tokenIn > inAmount) // TODO add slippage check here. - + // update local pool state, const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) assetIn.balance.amount += msg.tokenIn.amount const assetOut = pool.findAssetByDenom(msg.denomOut) assetOut.balance.amount -= outToken.amount - store.savePool(pool) + store.savePool(pool) // contructs the IBC data packet const packet = { @@ -1006,8 +1010,8 @@ Once the setup function has been called, channels can be created via the IBC rou An interchain swap module will accept new channels from any module on another machine, if and only if: -- The channel being created is unordered. -- The version string is `ics101-1`. +- The channel being created is unordered. +- The version string is `ics101-1`. ```typescript function onChanOpenInit( @@ -1049,12 +1053,12 @@ function onChanOpenTry( ```typescript function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string ) { - abortTransactionUnless(counterpartyVersion === "ics101-1"); + abortTransactionUnless(counterpartyVersion === "ics101-1"); } ``` @@ -1064,14 +1068,21 @@ function onChanOpenAck( ```ts function sendInterchainIBCSwapDataPacket( - swapPacket: IBCSwapPacketData, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64 + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64 ) { - // send packet using the interface defined in ICS4 - handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); + // send packet using the interface defined in ICS4 + handler.sendPacket( + getCapability("port"), + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + swapPacket + ); } ``` @@ -1128,8 +1139,8 @@ function onRecvPacket(packet: Packet) { // OnAcknowledgementPacket implements the IBCModule interface function OnAcknowledgementPacket( - packet: channeltypes.Packet, - ack channeltypes.Acknowledgement, + packet: channeltypes.Packet, + ack channeltypes.Acknowledgement, ) { var ack channeltypes.Acknowledgement @@ -1170,8 +1181,8 @@ function OnAcknowledgementPacket( ```ts function onTimeoutPacket(packet: Packet) { - // the packet timed-out, so refund the tokens - refundTokens(packet); + // the packet timed-out, so refund the tokens + refundTokens(packet); } ``` @@ -1206,9 +1217,9 @@ However, Pool state synchronization could be delayed due to the relayer halt or Solutions: -- Timeout: Swap order need to be confirmed on the counterparty chain. it would be canceled and refund if packets not arrival the counterparty on time. -- Slippage Tolerance can be a way to protect loss caused by in-consistency. -- Single side trade: Each Token can only be trade its native chain. in inconsistency state, the backlog swap would sell lower prices than consistency state. which could help to maintain consistency. +- Timeout: Swap order need to be confirmed on the counterparty chain. it would be canceled and refund if packets not arrival the counterparty on time. +- Slippage Tolerance can be a way to protect loss caused by in-consistency. +- Single side trade: Each Token can only be trade its native chain. in inconsistency state, the backlog swap would sell lower prices than consistency state. which could help to maintain consistency. ### Price Impact Of Single Asset Deposit @@ -1217,7 +1228,7 @@ But single side deposit could break the balance of constant invariant. which mea Solution: -- set upper limit for single side deposit. The ratio of profits taken away by arbitrageurs is directly proportional to the ratio of single-sided deposits and the quantity of that asset in the liquidity pool. +- set upper limit for single side deposit. The ratio of profits taken away by arbitrageurs is directly proportional to the ratio of single-sided deposits and the quantity of that asset in the liquidity pool. ## Backwards Compatibility From 56b7784ddba784f32f6808f64f2b5e0a8a05f5b0 Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 01:08:45 +0800 Subject: [PATCH 25/72] update technical specifications --- spec/app/ics-101-interchain-swap/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 0fda0342a..a53fc09a5 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -24,7 +24,7 @@ Features include an option to provide liquidity with a single asset instead of a `Interchain swap`: a IBC token swap protocol, built on top of an automated marketing making system, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. -`Interchain liquidity pool`: a single-asset liquidity pool held in the asset's native chain, and has a corresponding single-asset liquidity pool on a separate chain. This comprises an interchain liquidity pool and can execute interchain swaps to exchange tokens. +`Interchain liquidity pool`: a single-asset liquidity pool on a chain, with a corresponding single-asset liquidity pool on a separate chain. This comprises an interchain liquidity pool and can execute interchain swaps to exchange between the assets. `Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. @@ -50,9 +50,9 @@ Features include an option to provide liquidity with a single asset instead of a ## Technical Specification -Unlike Other Swaps. Interchain swap have two copies of pool state, each pool state are mirrored to the other. it's not always same. it will keep dynamic consistent through various IBC transactions: such as deposit, swap, withdraw. +An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain during a deposit, withdrawal or swap will update its corresponding pool state on the other chain through the transaction's packet relay. -Each pool is only used for assets on the chain where the liquidity pool is located +The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each single-asset liquidity pool can only execute sell orders of the token it holds. To implement interchain swap, we introduce the `Swap Initiator` and `State Updater`. The `Swap Initiator` will pre-process the request and execute the swap (validate msgs, lock assets, etc), and then forward the transactions to the relayer. The `State Updater` just simply update the states sent from `Swap Initiator`(keep the pool state consistency). From ecf615860eb929a0cd2f295593b8fd17daebce07 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 27 Apr 2023 20:30:31 +0300 Subject: [PATCH 26/72] remove unessary part in packet --- spec/app/ics-101-interchain-swap/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 5460c5e76..1ca5fdb06 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -349,7 +349,7 @@ enum SwapType { interface StateChange { in: Coin[]; out: Coin[]; - poolToken: Coin; // could be negtive + poolTokens: Coin[]; // could be negtive } ``` @@ -568,7 +568,7 @@ function singleDeposit(msg MsgSingleDepositRequest) { const packet = { type: MessageType.Deposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { in: [msg.token], poolToken } + stateChange: { in: [msg.token], [poolToken] } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } @@ -592,13 +592,14 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { // TODO Marian: check the ratio of local amount and remote amount + // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) // calculation const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const poolToken = amm.depositSingleAsset(msg.token) // should replace with doubleDeposit() ? + const poolTokens = amm.doubleDeposit([msg.localDeposit.token, msg.remoteDeposit.token]) // should replace with doubleDeposit() ? // update local pool state, const assetIn = pool.findAssetByDenom(msg.localDeposit.token.denom) @@ -612,7 +613,7 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { const packet = { type: MessageType.DoubleDeposit, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { in: [msg.localDeposit, msg.remoteDeposit], poolToken }, + stateChange: { in: [msg.localDeposit, msg.remoteDeposit], poolTokens }, } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } @@ -687,7 +688,7 @@ function leftSwap(msg MsgSwapRequest) { const packet = { type: MessageType.Swap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { in: [msg.tokenIn], out: [outToken] } + stateChange: { out: [outToken] } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) @@ -725,7 +726,7 @@ function rightSwap(msg MsgRightSwapRequest) { const packet = { type: MessageType.Rightswap, data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: {in: [msg.TokenIn], out: [msg.TokenOut] } + stateChange: {out: [msg.TokenOut] } } sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) From 449234931118e942ebfb1d606eed57b6a173358c Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 02:40:22 +0800 Subject: [PATCH 27/72] update technical specifications --- spec/app/ics-101-interchain-swap/README.md | 45 +++++++++++----------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index a53fc09a5..dd1117812 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -50,15 +50,15 @@ Features include an option to provide liquidity with a single asset instead of a ## Technical Specification -An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain during a deposit, withdrawal or swap will update its corresponding pool state on the other chain through the transaction's packet relay. +### General Design -The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each single-asset liquidity pool can only execute sell orders of the token it holds. +An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain during a deposit, withdrawal or swap will update its corresponding pool state on the other chain through the transaction's packet relay. -To implement interchain swap, we introduce the `Swap Initiator` and `State Updater`. The `Swap Initiator` will pre-process the request and execute the swap (validate msgs, lock assets, etc), and then forward the transactions to the relayer. The `State Updater` just simply update the states sent from `Swap Initiator`(keep the pool state consistency). +The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each chain can only execute sell orders of the token its single-asset liquidity pool holds. -Each chain could be either `Swap Initiator` or `State Updater`, it depend on where the swap is created. +Both chains can initiate a swap, and both chains can subsequently update its pool state. -This is an overview of how Interchain Swap works +This is an overview of how Interchain Swap works: ![Interchain Swap Diagram](interchain-swap.svg) @@ -89,7 +89,7 @@ $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ #### Fees -Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.01% and a maximum value of 10%. To avoid use decimal, we use basis point for `feeRate`, 1 = 0.01%, 100 = 1% +Traders pay swap fees when they trade with a pool. These fees can be customized with a minimum value of 0.01% and a maximum value of 10%. Basis points for `feeRate` is used, where the integer 1 is 0.01% and integer 100 is 1.00%. The fees go to liquidity providers in exchange for depositing their tokens in the pool to facilitate trades. Trade fees are collected at the time of a swap, and goes directly into the pool, increasing the pool balance. For a trade with a given $inputToken$ and $outputToken$, the amount collected by the pool as a fee is @@ -99,12 +99,13 @@ As the pool collects fees, liquidity providers automatically collect fees throug ### Pool Initialization -In InterchainSwap, the liquidity pool has two possible states: +An interchain liquidity pool has two possible states: + +`POOL_STATUS_INITIAL`: The interchain liquidity pool is created yet not ready to execute swaps. Pool parameters have been registered but assets for the swap pair have not been deposited. -- `INITIAL`: The pool is newly created and not yet ready for swaps. This means that all pool parameters have been registered in the state machine, but it doesn't possess the necessary assets for the swap pair. +`POOL_STATUS_READY`: The interchain liquidity pool is ready to execute swaps. Required assets have been deposited. -- `READY`: The pool has acquired all required assets to facilitate swaps. - The pool's status can be updated through an initial deposit. If a user provides the full amount of assets that match the pool's initial parameters using either a `single deposit` or double deposit, the pool's status will automatically change to `READY`. With the initial deposit, the user receives sufficient pool tokens to withdraw their assets at any time. +The pool can be fully funded through the initial deposit or through subsequent deposits. Deposits can be single-asset deposits or multi-asset deposits. ### Data Structures @@ -149,8 +150,8 @@ interface InterchainLiquidityPool { // the issued amount of pool token in the pool. the denom is pool id supply: Coin; status: PoolStatus; - encounterPartyPort: string; - encounterPartyChannel: string; + counterpartyPort: string; + counterpartyChannel: string; constructor(denoms: []string, decimals: []number, weight: string, portId string, channelId string) { this.id = generatePoolId(denoms) @@ -159,8 +160,8 @@ interface InterchainLiquidityPool { denom: this.id } this.status = PoolStatus.POOL_STATUS_INITIAL - this.encounterPartyPort = portId - this.encounterPartyChannel = channelId + this.counterpartyPort = portId + this.counterpartyChannel = channelId // construct assets const weights = weight.split(':').length @@ -189,7 +190,7 @@ function generatePoolId(denoms: []string) { #### Interchain Market Maker -Interchain Market Maker is a core component for swap calculation, which is stateless and initialized with few parameters: such liquidity pool and fee rates. Note, For a trading pair, the `feeRate` on each chain could be different. which can be updated by governance. +The `InterchainMarketMaker` is a core component for swap calculations, and is initialized with an `InterchainLiquidityPool` and `feeRate`. Since the `feeRate` for a trading pair can be updated through governance, it is possible to have a different rate on each chain. ```ts class InterchainMarketMaker { @@ -553,7 +554,7 @@ function singleDeposit(msg MsgSingleDepositRequest) { } // deposit assets to the escrowed account - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) // calculation @@ -597,7 +598,7 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { // TODO Marian: check the ratio of local amount and remote amount // deposit assets to the escrowed account - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokens) // calculation @@ -635,7 +636,7 @@ function withdraw(msg MsgWithdrawRequest) { abortTransactionUnless(outToken.poolSide == PoolSide.Native) // lock pool token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) @@ -673,7 +674,7 @@ function leftSwap(msg MsgSwapRequest) { abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn.denom) const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) @@ -710,7 +711,7 @@ function rightSwap(msg MsgRightSwapRequest) { abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) @@ -875,7 +876,7 @@ function onLeftSwapReceived(msg: MsgSwapRequest, state: StateChange) MsgSwapResp // tolerance check abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(escrowAddr, msg.recipient, outToken) store.savePool(amm.pool) // update pool states @@ -904,7 +905,7 @@ function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwa abortTransactionUnless(tokenIn.amount > minTokenIn.amount) abortTransactionUnless((tokenIn.amount - minTokenIn.amount)/minTokenIn.amount > msg.slippage / 10000)) - const escrowAddr = escrowAddress(pool.encounterPartyPort, pool.encounterPartyChannel) + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) bank.sendCoins(escrowAddr, msg.recipient, msg.tokenOut) store.savePool(amm.pool) // update pool states From 8cd9a149bdf4b43848ec1ee54233cd9c1128ad11 Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 02:55:34 +0800 Subject: [PATCH 28/72] update technical specifications --- spec/app/ics-101-interchain-swap/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index dd1117812..032084f58 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -52,9 +52,9 @@ Features include an option to provide liquidity with a single asset instead of a ### General Design -An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain during a deposit, withdrawal or swap will update its corresponding pool state on the other chain through the transaction's packet relay. +An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain will update its corresponding pool state on the other chain through the IBC packet relay mechanism, which occurs during a deposit, withdrawal or swap. -The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each chain can only execute sell orders of the token its single-asset liquidity pool holds. +The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each chain can only execute sell orders of the token held by its single-asset liquidity pool. Both chains can initiate a swap, and both chains can subsequently update its pool state. From 09e2e3291364c20954dd81dc212f1d2b93d91972 Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 03:49:56 +0800 Subject: [PATCH 29/72] update technical specifications and risk --- spec/app/ics-101-interchain-swap/README.md | 53 +++++++--------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 032084f58..249d45d49 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -40,6 +40,10 @@ Features include an option to provide liquidity with a single asset instead of a `Pool state`: the entire state of a liquidity pool including its invariant value which is derived from its token balances and weights inside. +`Source chain`: a chain that begins a IBC transaction. + +`Destination chain`: a chain that receives an IBC transaction from a source chain. + ### Desired Properties - `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. @@ -52,7 +56,7 @@ Features include an option to provide liquidity with a single asset instead of a ### General Design -An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain will update its corresponding pool state on the other chain through the IBC packet relay mechanism, which occurs during a deposit, withdrawal or swap. +An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain will update its corresponding pool state on the other chain through IBC packet relays, which occurs during a deposit, withdrawal or swap. The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each chain can only execute sell orders of the token held by its single-asset liquidity pool. @@ -190,7 +194,7 @@ function generatePoolId(denoms: []string) { #### Interchain Market Maker -The `InterchainMarketMaker` is a core component for swap calculations, and is initialized with an `InterchainLiquidityPool` and `feeRate`. Since the `feeRate` for a trading pair can be updated through governance, it is possible to have a different rate on each chain. +The `InterchainMarketMaker` is a core component for swap calculations, and is initialized with an `InterchainLiquidityPool` and `feeRate`. Since the `feeRate` for a trading pair can be updated through governance, it is possible to have different rates on each chain. ```ts class InterchainMarketMaker { @@ -333,8 +337,6 @@ class InterchainMarketMaker { Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. -- Payloads in packet - ```ts enum MessageType { CreatePool, @@ -356,8 +358,6 @@ interface StateChange { } ``` -- Packet structure - ```ts // IBCSwapDataPacket is used to wrap message for relayer. interface IBCSwapDataPacket { @@ -367,8 +367,6 @@ interface IBCSwapDataPacket { } ``` -- Packet of Acknowledgement - ```typescript type IBCSwapDataAcknowledgement = | IBCSwapDataPacketSuccess @@ -386,10 +384,6 @@ interface IBCSwapDataPacketError { ### Sub-protocols -Traditional liquidity pools typically maintain its pool state in one location. - -A liquidity pool in the interchain swap protocol maintains its pool state on both its source chain and destination chain. The pool states mirror each other and are synced through IBC packet relays, which we elaborate on in the following sub-protocols. - Interchain Swap implements the following sub-protocols: ```protobuf @@ -403,8 +397,6 @@ Interchain Swap implements the following sub-protocols: #### Interfaces for sub-protocols -- Create cross chain liquidity pool - ```ts interface MsgCreatePoolRequest { sourcePort: string, @@ -418,8 +410,6 @@ interface MsgCreatePoolRequest { interface MsgCreatePoolResponse {} ``` -- Single Side Deposit - ```ts interface MsgDepositRequest { poolId: string; @@ -431,8 +421,6 @@ interface MsgSingleDepositResponse { } ``` -- Two sides Deposit - ```ts interface LocalDeposit { sender: string; @@ -455,8 +443,6 @@ interface MsgDoubleDepositResponse { } ``` -- Withdraw - ```ts interface MsgWithdrawRequest { sender: string, @@ -468,8 +454,6 @@ interface MsgWithdrawResponse { } ``` -- Left Swap - ```ts interface MsgSwapRequest { sender: string, @@ -484,8 +468,6 @@ interface MsgSwapResponse { } ``` -- Right Swap - ```ts interface MsgRightSwapRequest { sender: string; @@ -501,7 +483,7 @@ interface MsgSwapResponse { ### Control Flow And Life Scope -These are methods of `Swap Initiator`, which execute main logic and output a state change, state change is need to be executed on both local and remote +These are methods that output a state change on the source chain, which will be synced by the destination chain. ```ts function createPool(msg: MsgCreatePoolRequest) { @@ -737,7 +719,7 @@ function rightSwap(msg MsgRightSwapRequest) { } ``` -The `State Updater` handle all packets relayed from the source chain, update pool states and sent tokens when received, and send the result as an acknowledgement. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. +These are methods that handle packets relayed from a source chain, and includes pool state updates and token transfers. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. ```ts function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { @@ -1213,23 +1195,22 @@ function refundToken(packet: Packet) { ### Pool State Inconsistency -To maintain pool state synchronization is extreme important for Interchain Swap, since we have two mirrored pool across the two chain. -However, Pool state synchronization could be delayed due to the relayer halt or network issues. the delay could affect swap price. +Pool state synchronization is extremely important for Interchain Swap, since there are two mirrored pool across the two chain. +However, pool state synchronization could be delayed due to relayer halts or network issues. This can create swap price differences. -Solutions: +Solutions and mitigations: -- Timeout: Swap order need to be confirmed on the counterparty chain. it would be canceled and refund if packets not arrival the counterparty on time. -- Slippage Tolerance can be a way to protect loss caused by in-consistency. -- Single side trade: Each Token can only be trade its native chain. in inconsistency state, the backlog swap would sell lower prices than consistency state. which could help to maintain consistency. +- Timeout: Packets timeout when packet acknowledgements are delayed for extended period of times. This limits the impact of inconsistent pool states on mispriced swap orders. +- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. +- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. ### Price Impact Of Single Asset Deposit -Single side deposit is convenient for user to deposit asset. -But single side deposit could break the balance of constant invariant. which means the current pool price would go higher or lower. which increase opportunity for arbitrageur +Single-asset deposits are convenient for users and remove impermanence loss, but can have a significant impact on the swap prices. -Solution: +Solution and mitigations: -- set upper limit for single side deposit. The ratio of profits taken away by arbitrageurs is directly proportional to the ratio of single-sided deposits and the quantity of that asset in the liquidity pool. +- Set an upper limit for single-asset deposits. This would be proportional to the amount deposited and the balance of the asset in the liquidity pool. ## Backwards Compatibility From 3380d446982c5d22bbf600eb34ed664a42f0f0c8 Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 03:52:40 +0800 Subject: [PATCH 30/72] update technical specifications and risk --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 249d45d49..7c597df00 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -483,7 +483,7 @@ interface MsgSwapResponse { ### Control Flow And Life Scope -These are methods that output a state change on the source chain, which will be synced by the destination chain. +These are methods that output a state change on the source chain, which will be subsequently synced to the destination chain. ```ts function createPool(msg: MsgCreatePoolRequest) { From 0e0ef38ce6d137c6311c8a3979bc7679e33fc38d Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 03:55:05 +0800 Subject: [PATCH 31/72] update technical specifications and risk --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 7c597df00..eec72265d 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -719,7 +719,7 @@ function rightSwap(msg MsgRightSwapRequest) { } ``` -These are methods that handle packets relayed from a source chain, and includes pool state updates and token transfers. In this way, packets relayed on the source chain update pool states on the destination chain according to results in the acknowledgement. +These are methods that handle packets relayed from a source chain, and includes pool state updates and token transfers. In this way, packets relayed on the source chain update pool states on the destination chain. ```ts function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { From fd04018ff1e8aa174d1bc29b8432202cf0299d7a Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 04:03:21 +0800 Subject: [PATCH 32/72] update technical specifications and risk --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index eec72265d..e6c4d4ec9 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -1206,7 +1206,7 @@ Solutions and mitigations: ### Price Impact Of Single Asset Deposit -Single-asset deposits are convenient for users and remove impermanence loss, but can have a significant impact on the swap prices. +Single-asset deposits are convenient for users and remove impermanent loss, but can have a significant impact on the swap prices. Solution and mitigations: From abb11f26c713ebc2d0bbe9b825ce6a898b5085ab Mon Sep 17 00:00:00 2001 From: EddyG Date: Fri, 28 Apr 2023 11:00:39 +0800 Subject: [PATCH 33/72] incorporate comments --- spec/app/ics-101-interchain-swap/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 1f9c4e9d8..c9e70a2be 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -18,7 +18,7 @@ This standard document specifies the packet data structure, state machine handli ICS-101 Interchain Swaps enables chains their own token pricing mechanism and exchange protocol via IBC transactions. Each chain can thus play a role in a fully decentralised exchange network. -Features include an option to provide liquidity with a single asset instead of a pair, which users might prefer as it removes the risk of impermanent loss. +Features include an option to provide liquidity with a single asset instead of a pair, which users might prefer as it reduces the risk of impermanent loss. ### Definitions @@ -1201,13 +1201,13 @@ However, pool state synchronization could be delayed due to relayer halts or net Solutions and mitigations: -- Timeout: Packets timeout when packet acknowledgements are delayed for extended period of times. This limits the impact of inconsistent pool states on mispriced swap orders. +- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. - Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. - Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. ### Price Impact Of Single Asset Deposit -Single-asset deposits are convenient for users and remove impermanent loss, but can have a significant impact on the swap prices. +Single-asset deposits are convenient for users and reduces the risk of impermanent loss, but can have a significant impact on the swap prices. Solution and mitigations: From 26834c9d24f6e27b378853fb5ee26e33e23c5646 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 28 Apr 2023 16:42:07 +0800 Subject: [PATCH 34/72] rewrite onReceive and onAck --- spec/app/ics-101-interchain-swap/README.md | 126 ++++++++------------- 1 file changed, 46 insertions(+), 80 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 1f9c4e9d8..c56693186 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -236,7 +236,7 @@ class InterchainMarketMaker { } // P_issued = P_supply * (1 + At/Bt) - function depositDoubleAsset(tokens: Coin[]): Coin[] { + function depositMultiAsset(tokens: Coin[]): Coin[] { const lpTokens = []; for (const token in tokens) { const asset = this.pool.findAssetByDenom(token.denom) @@ -354,7 +354,7 @@ enum SwapType { interface StateChange { in: Coin[]; out: Coin[]; - poolTokens: Coin[]; // could be negtive + poolTokens: Coin[]; } ``` @@ -388,11 +388,10 @@ Interchain Swap implements the following sub-protocols: ```protobuf rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); - rpc SingleDeposit(MsgSingleDepositRequest) returns (MsgSingleDepositResponse); - rpc DoubleDeposit(MsgDoubleDepositRequest) returns (MsgDoubleDepositResponse); + rpc SingleAssetDeposit(MsgSingleAssetDepositRequest) returns (MsgSingleAssetDepositResponse); + rpc MultiAssetDeposit(MsgMultiAssetDepositRequest) returns (MsgMultiAssetDepositResponse); rpc Withdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); - rpc LeftSwap(MsgLeftSwapRequest) returns (MsgSwapResponse); - rpc RightSwap(MsgRightSwapRequest) returns (MsgSwapResponse); + rpc Swap(MsgSwapRequest) returns (MsgSwapResponse); ``` #### Interfaces for sub-protocols @@ -411,12 +410,12 @@ interface MsgCreatePoolResponse {} ``` ```ts -interface MsgDepositRequest { +interface MsgDepositAssetRequest { poolId: string; sender: string; token: Coin; } -interface MsgSingleDepositResponse { +interface MsgSingleAssetDepositResponse { poolToken: Coin; } ``` @@ -433,12 +432,12 @@ interface RemoteDeposit { signature: Uint8Array; } -interface MsgDoubleDepositRequest { +interface MsgMultiAssetDepositRequest { poolId: string; localDeposit: LocalDeposit; remoteDeposit: RemoteDeposit; } -interface MsgDoubleDepositResponse { +interface MsgMultiAssetDepositResponse { poolTokens: Coin[]; } ``` @@ -468,19 +467,6 @@ interface MsgSwapResponse { } ``` -```ts -interface MsgRightSwapRequest { - sender: string; - tokenIn: Coin; - tokenOut: Coin; - slippage: number; // max tolerated slippage - recipient: string; -} -interface MsgSwapResponse { - tokens: Coin[]; -} -``` - ### Control Flow And Life Scope These are methods that output a state change on the source chain, which will be subsequently synced to the destination chain. @@ -518,7 +504,7 @@ function createPool(msg: MsgCreatePoolRequest) { } -function singleDeposit(msg MsgSingleDepositRequest) { +function singleAssetDeposit(msg MsgSingleDepositRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.token.amount > 0) @@ -530,6 +516,7 @@ function singleDeposit(msg MsgSingleDepositRequest) { // should have enough balance abortTransactionUnless(balance.amount >= msg.token.amount) + // the first initial if(pool.status == POOL_STATUS_INITIAL) { const asset = pool.findAssetByDenom(msg.token.denom) abortTransactionUnless(balance.amount !== asset.amount) @@ -561,7 +548,7 @@ function singleDeposit(msg MsgSingleDepositRequest) { } -function doubleDeposit(msg MsgDoubleDepositRequest) { +function multiAssetDeposit(msg MsgMultiAssetDepositRequest) { abortTransactionUnless(msg.localDeposit.sender != null) abortTransactionUnless(msg.localDeposit.token != null) @@ -586,7 +573,7 @@ function doubleDeposit(msg MsgDoubleDepositRequest) { // calculation const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const poolTokens = amm.doubleDeposit([msg.localDeposit.token, msg.remoteDeposit.token]) // should replace with doubleDeposit() ? + const poolTokens = amm.depositMultiAsset([msg.localDeposit.token, msg.remoteDeposit.token]) // should replace with doubleDeposit() ? // update local pool state, const assetIn = pool.findAssetByDenom(msg.localDeposit.token.denom) @@ -754,7 +741,7 @@ function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destC } } -function onSingleDepositReceived(msg: MsgSingleDepositRequest, state: StateChange): MsgSingleDepositResponse { +function onSingleAssetDepositReceived(msg: MsgSingleAssetDepositRequest, state: StateChange): MsgSingleAssetDepositResponse { const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) @@ -763,15 +750,18 @@ function onSingleDepositReceived(msg: MsgSingleDepositRequest, state: StateChang // switch pool status to 'READY' pool.Status = PoolStatus_POOL_STATUS_READY } + // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. - pool.supply.amount += state.poolToken.amount + const assetIn = pool.findAssetByDenom(state.in[0].denom) + assetIn.balance.amount += state.in[0].amount + pool.supply.amount += state.poolToken[0].amount store.savePool(pool) return { poolToken: state.poolToken } } -function onDoubleDepositReceived(msg: MsgSingleDepositRequest, state: StateChange): MsgSingleDepositResponse { +function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: StateChange): MsgMultiAssetDepositResponse { abortTransactionUnless(msg.remoteDeposit.sender != null) abortTransactionUnless(msg.remoteDeposit.token != null) @@ -779,12 +769,14 @@ function onDoubleDepositReceived(msg: MsgSingleDepositRequest, state: StateChang const pool = store.findPoolById(msg.poolId) abortTransactionUnless(pool != null) + // Remove it , since calulation move to source chain const amm = store.findAmmById(msg.poolId) if(amm !== null) { // fetch fee rate from the params module, maintained by goverance const feeRate = params.getPoolFeeRate() const amm = new InterchainMarketMaker(pool, feeRate) } + // */ // verify signature const sender = account.GetAccount(msg.remoteDeposit.sender) @@ -807,13 +799,23 @@ function onDoubleDepositReceived(msg: MsgSingleDepositRequest, state: StateChang pool.Status = PoolStatus_POOL_STATUS_READY } + // TODO: remove it // deposit remote token const poolTokens = amm.doubleSingleAsset([msg.localDeposit.token, msg.remoteDeposit.token]) - + + // update counterparty state + state.in.forEech(in => { + const assetIn = pool.findAssetByDenom(in.denom) + assetIn.balance.amount += in.amount + }) + state.poolTokens.forEech(lp => { + pool.supply.amount += lp.amount + }) + store.savePool(amm.pool) // update pool states + // mint voucher token - bank.mintCoin(MODULE_NAME, poolTokens[1]) + bank.mintCoin(MODULE_NAME, state.poolTokens[1]) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.remoteDeposit.sender, poolTokens[1]) - store.savePool(amm.pool) // update pool states return { poolToken } } @@ -826,11 +828,12 @@ function onWithdrawReceived(msg: MsgWithdrawRequest, state: StateChange) MsgWith const pool = store.findPoolById(msg.poolCoin.denom) abortTransactionUnless(pool != null) - // fetch fee rate from the params module, maintained by goverance - const feeRate = params.getPoolFeeRate() - - const amm = new InterchainMarketMaker(pool, feeRate) - const outToken = amm.withdraw(msg.poolCoin, msg.denomOut) + // update counterparty state + state.out.forEech(out => { + const assetOut = pool.findAssetByDenom(out.denom) + assetOut.balance.amount += out.amount + }) + pool.supply.amount -= state.poolToken[0].amount store.savePool(amm.pool) // update pool states // the outToken will sent to msg's sender in `onAcknowledgement()` @@ -895,72 +898,35 @@ function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwa return { tokens: minTokenIn } } +``` +Acknowledgement use for source chain to check if the transaction is succeeded or not. + +```ts function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { // do nothing } -function onSingleDepositAcknowledged(request: MsgSingleDepositRequest, response: MsgSingleDepositResponse) { - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) - pool.supply.amount += response.tokens.amount - store.savePool(pool) - +function onSingleAssetDepositAcknowledged(request: MsgSingleAssetDepositRequest, response: MsgSingleAssetDepositResponse) { bank.mintCoin(MODULE_NAME,request.sender,response.token) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) } -function onDoubleDepositAcknowledged(request: MsgDoubleDepositRequest, response: MsgDoubleDepositResponse) { - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) - - for(const poolToken in response.PoolTokens) { - pool.supply.amount += poolToken.amount - } - - store.savePool(pool) - +function onMultiAssetDepositAcknowledged(request: MsgMultiAssetDepositRequest, response: MsgMultiAssetDepositResponse) { bank.mintCoin(MODULE_NAME,response.tokens[0]) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]) } function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) - abortTransactionUnless(pool.supply.amount >= response.tokens.amount) - pool.supply.amount -= response.tokens.amount - store.savePool(pool) - - bank.sendCoinsFromAccountToModule(msg.sender, MODULE_NAME, response.tokens) bank.burnCoin(MODULE_NAME, response.token) } function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) { - const pool = store.findPoolById(generatePoolId[request.tokenIn.denom, request.tokenOut.denom])) - abortTransactionUnless(pool != null) - - const assetOut = pool.findAssetByDenom(request.tokenOut.denom) - abortTransactionUnless(assetOut.balance.amount >= response.tokens.amount) - assetOut.balance.amount -= response.tokens.amount - - const assetIn = pool.findAssetByDenom(request.tokenIn.denom) - assetIn.balance.amount += request.tokenIn.amount - store.savePool(pool) } function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) { - const pool = store.findPoolById(generatePoolId([request.tokenIn.denom, request.tokenOut.denom])) - abortTransactionUnless(pool != null) - - const assetOut = pool.findAssetByDenom(request.tokenOut.denom) - abortTransactionUnless(assetOut.balance.amount >= response.tokens.amount) - assetOut.balance.amount -= request.tokenOut.amount - const assetIn = pool.findAssetByDenom(request.tokenIn.denom) - assetIn.balance.amount += request.tokenIn.amount - - store.savePool(pool) } ``` From 1b7c38201f1beaa439013b06674b60d6e3798e1c Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 28 Apr 2023 17:01:01 +0800 Subject: [PATCH 35/72] re-order chapters --- spec/app/ics-101-interchain-swap/README.md | 456 ++++++++++----------- 1 file changed, 228 insertions(+), 228 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 6a4810411..fbae55281 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -382,6 +382,234 @@ interface IBCSwapDataPacketError { } ``` +#### Port & channel setup + +The fungible token swap module on a chain must always bind to a port with the id `interchainswap` + +The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module). + +```typescript +function setup() { + capability = routingModule.bindPort("interchainswap", ModuleCallbacks{ + onChanOpenInit, + onChanOpenTry, + onChanOpenAck, + onChanOpenConfirm, + onChanCloseInit, + onChanCloseConfirm, + onRecvPacket, + onTimeoutPacket, + onAcknowledgePacket, + onTimeoutPacketClose + }) + claimCapability("port", capability) +} +``` + +Once the setup function has been called, channels can be created via the IBC routing module. + +#### Channel lifecycle management + +An interchain swap module will accept new channels from any module on another machine, if and only if: + +- The channel being created is unordered. +- The version string is `ics101-1`. + +```typescript +function onChanOpenInit( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + version: string) => (version: string, err: Error) { + // only ordered channels allowed + abortTransactionUnless(order === ORDERED) + // assert that version is "ics101-1" or empty + // if empty, we return the default transfer version to core IBC + // as the version for this channel + abortTransactionUnless(version === "ics101-1" || version === "") + return "ics101-1", nil +} +``` + +```typescript +function onChanOpenTry( + order: ChannelOrder, + connectionHops: [Identifier], + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyPortIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string) => (version: string, err: Error) { + // only ordered channels allowed + abortTransactionUnless(order === ORDERED) + // assert that version is "ics101-1" + abortTransactionUnless(counterpartyVersion === "ics101-1") + // return version that this chain will use given the + // counterparty version + return "ics101-1", nil +} +``` + +```typescript +function onChanOpenAck( + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string +) { + abortTransactionUnless(counterpartyVersion === "ics101-1"); +} +``` + +#### Packet relay + +`sendInterchainIBCSwapDataPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. + +```ts +function sendInterchainIBCSwapDataPacket( + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64 +) { + // send packet using the interface defined in ICS4 + handler.sendPacket( + getCapability("port"), + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + swapPacket + ); +} +``` + +`onRecvPacket` is called by the routing module when a packet addressed to this module has been received. + +```ts +function onRecvPacket(packet: Packet) { + + IBCSwapPacketData swapPacket = packet.data + // construct default acknowledgement of success + const ack: IBCSwapDataAcknowledgement = new IBCSwapDataPacketSuccess() + + try{ + switch swapPacket.type { + case CREATE_POOL: + var msg: MsgCreatePoolRequest = protobuf.decode(swapPacket.data) + onCreatePoolReceived(msg, packet.destPortId, packet.destChannelId) + break + case SINGLE_DEPOSIT: + var msg: MsgSingleDepositRequest = protobuf.decode(swapPacket.data) + onSingleDepositReceived(msg) + break + + case Double_DEPOSIT: + var msg: MsgDoubleDepositRequest = protobuf.decode(swapPacket.data) + onDoubleDepositReceived(msg) + break + + case WITHDRAW: + var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) + onWithdrawReceived(msg) + break + case SWAP: + var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) + if(msg.SwapType === SwapType.Left) { + onLeftSwapReceived(msg) + }else{ + onRightSwapReceived(msg) + } + break + } + } catch { + ack = new IBCSwapDataPacketError() + } + + // NOTE: acknowledgement will be written synchronously during IBC handler execution. + return ack +} +``` + +`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. + +```ts + +// OnAcknowledgementPacket implements the IBCModule interface +function OnAcknowledgementPacket( + packet: channeltypes.Packet, + ack channeltypes.Acknowledgement, +) { + + var ack channeltypes.Acknowledgement + if (!ack.success()) { + refund(packet) + } else { + const swapPacket = protobuf.decode(packet.data) + switch swapPacket.type { + case CREATE_POOL: + onCreatePoolAcknowledged(msg) + break; + case SINGLE_DEPOSIT: + onSingleDepositAcknowledged(msg) + break; + case Double_DEPOSIT: + onDoubleDepositAcknowledged(msg) + break; + case WITHDRAW: + onWithdrawAcknowledged(msg) + break; + case SWAP: + var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) + if(msg.SwapType === SwapType.Left) { + onLeftSwapAcknowledged(msg) + }else{ + onRightSwapAcknowledged(msg) + } + onLeftSwapAcknowledged(msg) + break; + } + } + + return nil +} +``` + +`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that the tokens will be refunded). Tokens are also refunded on failure. + +```ts +function onTimeoutPacket(packet: Packet) { + // the packet timed-out, so refund the tokens + refundTokens(packet); +} +``` + +```ts + +function refundToken(packet: Packet) { + let token + switch packet.type { + case Swap: + token = packet.tokenIn + break; + case Deposit: + token = packet.tokens + break; + case DoubleDeposit: + token = packet.tokens + break; + case Withdraw: + token = packet.pool_token + } + escrowAccount = channelEscrowAddresses[packet.srcChannel] + bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) +} +``` + ### Sub-protocols Interchain Swap implements the following sub-protocols: @@ -930,234 +1158,6 @@ function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwap } ``` -#### Port & channel setup - -The fungible token swap module on a chain must always bind to a port with the id `interchainswap` - -The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module). - -```typescript -function setup() { - capability = routingModule.bindPort("interchainswap", ModuleCallbacks{ - onChanOpenInit, - onChanOpenTry, - onChanOpenAck, - onChanOpenConfirm, - onChanCloseInit, - onChanCloseConfirm, - onRecvPacket, - onTimeoutPacket, - onAcknowledgePacket, - onTimeoutPacketClose - }) - claimCapability("port", capability) -} -``` - -Once the setup function has been called, channels can be created via the IBC routing module. - -#### Channel lifecycle management - -An interchain swap module will accept new channels from any module on another machine, if and only if: - -- The channel being created is unordered. -- The version string is `ics101-1`. - -```typescript -function onChanOpenInit( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - version: string) => (version: string, err: Error) { - // only ordered channels allowed - abortTransactionUnless(order === ORDERED) - // assert that version is "ics101-1" or empty - // if empty, we return the default transfer version to core IBC - // as the version for this channel - abortTransactionUnless(version === "ics101-1" || version === "") - return "ics101-1", nil -} -``` - -```typescript -function onChanOpenTry( - order: ChannelOrder, - connectionHops: [Identifier], - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyPortIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string) => (version: string, err: Error) { - // only ordered channels allowed - abortTransactionUnless(order === ORDERED) - // assert that version is "ics101-1" - abortTransactionUnless(counterpartyVersion === "ics101-1") - // return version that this chain will use given the - // counterparty version - return "ics101-1", nil -} -``` - -```typescript -function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string -) { - abortTransactionUnless(counterpartyVersion === "ics101-1"); -} -``` - -#### Packet relay - -`sendInterchainIBCSwapDataPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. - -```ts -function sendInterchainIBCSwapDataPacket( - swapPacket: IBCSwapPacketData, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64 -) { - // send packet using the interface defined in ICS4 - handler.sendPacket( - getCapability("port"), - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - swapPacket - ); -} -``` - -`onRecvPacket` is called by the routing module when a packet addressed to this module has been received. - -```ts -function onRecvPacket(packet: Packet) { - - IBCSwapPacketData swapPacket = packet.data - // construct default acknowledgement of success - const ack: IBCSwapDataAcknowledgement = new IBCSwapDataPacketSuccess() - - try{ - switch swapPacket.type { - case CREATE_POOL: - var msg: MsgCreatePoolRequest = protobuf.decode(swapPacket.data) - onCreatePoolReceived(msg, packet.destPortId, packet.destChannelId) - break - case SINGLE_DEPOSIT: - var msg: MsgSingleDepositRequest = protobuf.decode(swapPacket.data) - onSingleDepositReceived(msg) - break - - case Double_DEPOSIT: - var msg: MsgDoubleDepositRequest = protobuf.decode(swapPacket.data) - onDoubleDepositReceived(msg) - break - - case WITHDRAW: - var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) - onWithdrawReceived(msg) - break - case SWAP: - var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) - if(msg.SwapType === SwapType.Left) { - onLeftSwapReceived(msg) - }else{ - onRightSwapReceived(msg) - } - break - } - } catch { - ack = new IBCSwapDataPacketError() - } - - // NOTE: acknowledgement will be written synchronously during IBC handler execution. - return ack -} -``` - -`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. - -```ts - -// OnAcknowledgementPacket implements the IBCModule interface -function OnAcknowledgementPacket( - packet: channeltypes.Packet, - ack channeltypes.Acknowledgement, -) { - - var ack channeltypes.Acknowledgement - if (!ack.success()) { - refund(packet) - } else { - const swapPacket = protobuf.decode(packet.data) - switch swapPacket.type { - case CREATE_POOL: - onCreatePoolAcknowledged(msg) - break; - case SINGLE_DEPOSIT: - onSingleDepositAcknowledged(msg) - break; - case Double_DEPOSIT: - onDoubleDepositAcknowledged(msg) - break; - case WITHDRAW: - onWithdrawAcknowledged(msg) - break; - case SWAP: - var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) - if(msg.SwapType === SwapType.Left) { - onLeftSwapAcknowledged(msg) - }else{ - onRightSwapAcknowledged(msg) - } - onLeftSwapAcknowledged(msg) - break; - } - } - - return nil -} -``` - -`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that the tokens will be refunded). Tokens are also refunded on failure. - -```ts -function onTimeoutPacket(packet: Packet) { - // the packet timed-out, so refund the tokens - refundTokens(packet); -} -``` - -```ts - -function refundToken(packet: Packet) { - let token - switch packet.type { - case Swap: - token = packet.tokenIn - break; - case Deposit: - token = packet.tokens - break; - case DoubleDeposit: - token = packet.tokens - break; - case Withdraw: - token = packet.pool_token - } - escrowAccount = channelEscrowAddresses[packet.srcChannel] - bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) -} -``` - ## RISKS ### Pool State Inconsistency From 412f773c923faf6f65aad463fa9c66d66da3ce43 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 28 Apr 2023 17:28:21 +0800 Subject: [PATCH 36/72] fix message type --- spec/app/ics-101-interchain-swap/README.md | 37 +++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index fbae55281..88a4542ef 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -340,8 +340,8 @@ Only one packet data type is required: `IBCSwapDataPacket`, which specifies the ```ts enum MessageType { CreatePool, - SingleDeposit, - DoubleDeposit, + SingleAssetDeposit, + MultiAssetDeposit, Withdraw, Swap } @@ -499,25 +499,25 @@ function onRecvPacket(packet: Packet) { try{ switch swapPacket.type { - case CREATE_POOL: + case CreatePool: var msg: MsgCreatePoolRequest = protobuf.decode(swapPacket.data) onCreatePoolReceived(msg, packet.destPortId, packet.destChannelId) break - case SINGLE_DEPOSIT: + case SingleAssetDeposit: var msg: MsgSingleDepositRequest = protobuf.decode(swapPacket.data) onSingleDepositReceived(msg) break - case Double_DEPOSIT: + case MultiAssetDeposit: var msg: MsgDoubleDepositRequest = protobuf.decode(swapPacket.data) onDoubleDepositReceived(msg) break - case WITHDRAW: + case Withdraw: var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) onWithdrawReceived(msg) break - case SWAP: + case Swap: var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) if(msg.SwapType === SwapType.Left) { onLeftSwapReceived(msg) @@ -551,26 +551,25 @@ function OnAcknowledgementPacket( } else { const swapPacket = protobuf.decode(packet.data) switch swapPacket.type { - case CREATE_POOL: + case CreatePool: onCreatePoolAcknowledged(msg) break; - case SINGLE_DEPOSIT: - onSingleDepositAcknowledged(msg) + case SingleAssetDeposit: + onSingleAssetDepositAcknowledged(msg) break; - case Double_DEPOSIT: - onDoubleDepositAcknowledged(msg) + case MultiAssetDeposit: + onMultiAssetDepositAcknowledged(msg) break; - case WITHDRAW: + case Withdraw: onWithdrawAcknowledged(msg) break; - case SWAP: + case Swap: var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) if(msg.SwapType === SwapType.Left) { onLeftSwapAcknowledged(msg) }else{ - onRightSwapAcknowledged(msg) + onRightSwapAcknowledged(msg) } - onLeftSwapAcknowledged(msg) break; } } @@ -589,17 +588,17 @@ function onTimeoutPacket(packet: Packet) { ``` ```ts - +// TODO: need to decode the subpacket from packet function refundToken(packet: Packet) { let token switch packet.type { case Swap: token = packet.tokenIn break; - case Deposit: + case SingleAssetDeposit: token = packet.tokens break; - case DoubleDeposit: + case MultiAssetDeposit: token = packet.tokens break; case Withdraw: From 82ff72cfff93a70b79335d95b5fa0e9ebe911f36 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Sat, 29 Apr 2023 01:50:05 +0300 Subject: [PATCH 37/72] make refund logic more specific --- spec/app/ics-101-interchain-swap/README.md | 224 +++++++++++---------- 1 file changed, 117 insertions(+), 107 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 88a4542ef..79d757970 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -46,11 +46,11 @@ Features include an option to provide liquidity with a single asset instead of a ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. -- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. -- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. ## Technical Specification @@ -76,9 +76,9 @@ $$V = {Π_tB_t^{W_t}}$$ Where -- $t$ ranges over the tokens in the pool -- $B_t$ is the balance of the token in the pool -- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. +- $t$ ranges over the tokens in the pool +- $B_t$ is the balance of the token in the pool +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. #### Spot Price @@ -86,10 +86,10 @@ Spot prices of tokens are defined entirely by the weights and balances of the to $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ -- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool -- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool -- $W_i$ is the weight of token $i$ -- $W_o$ is the weight of token $o$ +- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool +- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool +- $W_i$ is the weight of token $i$ +- $W_o$ is the weight of token $o$ #### Fees @@ -117,8 +117,8 @@ The pool can be fully funded through the initial deposit or through subsequent d ```ts interface Coin { - amount: int64; - denom: string; + amount: int64; + denom: string; } ``` @@ -139,11 +139,11 @@ enum PoolStatus { ```ts interface PoolAsset { - side: PoolSide; - balance: Coin; - // percentage - weight: int32; - decimal: int32; + side: PoolSide; + balance: Coin; + // percentage + weight: int32; + decimal: int32; } ``` @@ -268,7 +268,7 @@ class InterchainMarketMaker { const supply = this.pool.supply.amount const weight = asset.weight / 100 // convert int to percent - const amountOut = balance * (1 - ( 1 - redeem.amount / supply) ** (1/weight)) + const amountOut = balance * redeem.amount / supply return { amount: amountOut, denom: denomOut, @@ -368,17 +368,15 @@ interface IBCSwapDataPacket { ``` ```typescript -type IBCSwapDataAcknowledgement = - | IBCSwapDataPacketSuccess - | IBCSwapDataPacketError; +type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; interface IBCSwapDataPacketSuccess { - // This is binary 0x01 base64 encoded - result: "AQ=="; + // This is binary 0x01 base64 encoded + result: "AQ=="; } interface IBCSwapDataPacketError { - error: string; + error: string; } ``` @@ -412,8 +410,8 @@ Once the setup function has been called, channels can be created via the IBC rou An interchain swap module will accept new channels from any module on another machine, if and only if: -- The channel being created is unordered. -- The version string is `ics101-1`. +- The channel being created is unordered. +- The version string is `ics101-1`. ```typescript function onChanOpenInit( @@ -455,12 +453,12 @@ function onChanOpenTry( ```typescript function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string ) { - abortTransactionUnless(counterpartyVersion === "ics101-1"); + abortTransactionUnless(counterpartyVersion === "ics101-1"); } ``` @@ -470,21 +468,14 @@ function onChanOpenAck( ```ts function sendInterchainIBCSwapDataPacket( - swapPacket: IBCSwapPacketData, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64 + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64 ) { - // send packet using the interface defined in ICS4 - handler.sendPacket( - getCapability("port"), - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - swapPacket - ); + // send packet using the interface defined in ICS4 + handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); } ``` @@ -582,30 +573,38 @@ function OnAcknowledgementPacket( ```ts function onTimeoutPacket(packet: Packet) { - // the packet timed-out, so refund the tokens - refundTokens(packet); + // the packet timed-out, so refund the tokens + refundTokens(packet, data); } ``` ```ts -// TODO: need to decode the subpacket from packet +// TODO: need to decode the subpacket from packet function refundToken(packet: Packet) { + const msg = packet.data.Data.toJSON() let token + let sender switch packet.type { + case Create: + token = msg.tokens[0] case Swap: - token = packet.tokenIn + token = msg.tokenIn + sender = msg.sender break; case SingleAssetDeposit: - token = packet.tokens + token = msg.token + sender = msg.sender break; case MultiAssetDeposit: - token = packet.tokens + token = msg.localDeposit.token + sender = msg.localDeposit.sender break; case Withdraw: - token = packet.pool_token + token = msg.poolCoin + sender = msg.sender } escrowAccount = channelEscrowAddresses[packet.srcChannel] - bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) + bank.TransferCoins(escrowAccount, sender, token.denom, token.amount) } ``` @@ -617,7 +616,7 @@ Interchain Swap implements the following sub-protocols: rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); rpc SingleAssetDeposit(MsgSingleAssetDepositRequest) returns (MsgSingleAssetDepositResponse); rpc MultiAssetDeposit(MsgMultiAssetDepositRequest) returns (MsgMultiAssetDepositResponse); - rpc Withdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); + rpc Withdraw(MsgMultiAssetWithdrawRequest) returns (MsgMultiAssetWithdrawResponse); rpc Swap(MsgSwapRequest) returns (MsgSwapResponse); ``` @@ -638,44 +637,49 @@ interface MsgCreatePoolResponse {} ```ts interface MsgDepositAssetRequest { - poolId: string; - sender: string; - token: Coin; + poolId: string; + sender: string; + token: Coin; } interface MsgSingleAssetDepositResponse { - poolToken: Coin; + poolToken: Coin; } ``` ```ts interface LocalDeposit { - sender: string; - token: Coin; + sender: string; + token: Coin; } interface RemoteDeposit { - sender: string; - sequence: int; // account transaction sequence - token: Coin; - signature: Uint8Array; + sender: string; + sequence: int; // account transaction sequence + token: Coin; + signature: Uint8Array; } interface MsgMultiAssetDepositRequest { - poolId: string; - localDeposit: LocalDeposit; - remoteDeposit: RemoteDeposit; + poolId: string; + localDeposit: LocalDeposit; + remoteDeposit: RemoteDeposit; } interface MsgMultiAssetDepositResponse { - poolTokens: Coin[]; + poolTokens: Coin[]; } ``` ```ts -interface MsgWithdrawRequest { - sender: string, - poolCoin: Coin, - denomOut: string, +interface MsgMultiAssetWithdrawRequest { + localWithdraw: WithdrawRequest + remoteWithdraw: WithdrawRequest } -interface MsgWithdrawResponse { + +interface WithdrawRequest { + sender: string + denomOut: string + poolCoin: Coin +} +interface MsgMultiAssetWithdrawResponse { tokens: []Coin; } ``` @@ -743,7 +747,7 @@ function singleAssetDeposit(msg MsgSingleDepositRequest) { // should have enough balance abortTransactionUnless(balance.amount >= msg.token.amount) - // the first initial + // the first initial if(pool.status == POOL_STATUS_INITIAL) { const asset = pool.findAssetByDenom(msg.token.denom) abortTransactionUnless(balance.amount !== asset.amount) @@ -791,8 +795,10 @@ function multiAssetDeposit(msg MsgMultiAssetDepositRequest) { // should have enough balance abortTransactionUnless(balance.amount >= msg.localDeposit.token.amount) - // TODO Marian: check the ratio of local amount and remote amount - + // check the ratio of local amount and remote amount + const localAssetInPool := pool.findAssetByDenom(msg.localDeposit.token.denom) + const remoteAssetInPool := pool.findAssetByDenom(msg.remoteDeposit.token.denom) + abortTransactionUnless(msg.localDeposit.token.amount/msg.remoteDeposit.token.amount !== localAssetInPool.amount/remoteAssetInPool.amount) // deposit assets to the escrowed account const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) @@ -819,10 +825,14 @@ function multiAssetDeposit(msg MsgMultiAssetDepositRequest) { sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } -function withdraw(msg MsgWithdrawRequest) { +function multiAssetWithdraw(msg MsgWithdrawRequest) { + + abortTransactionUnless(msg.localDeposit.sender != null) + abortTransactionUnless(msg.remoteDeposit.sender != null) + abortTransactionUnless(msg.localDeposit.token != null) + abortTransactionUnless(msg.remoteDeposit.token != null) + abortTransactionUnless(!bank.hasSupply(msg.localDeposit.denomOut)) - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.token.length > 0) const pool = store.findPoolById(msg.poolToken.denom) abortTransactionUnless(pool != null) @@ -839,11 +849,6 @@ function withdraw(msg MsgWithdrawRequest) { const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) const outToken = amm.withdraw(msg.poolToken) - // update local pool state, - const assetOut = pool.findAssetByDenom(msg.denomOut) - assetOut.balance.amount -= outToken.amount - pool.supply.amount -= poolToken.amount - store.savePool(pool) // constructs the IBC data packet const packet = { @@ -977,7 +982,7 @@ function onSingleAssetDepositReceived(msg: MsgSingleAssetDepositRequest, state: // switch pool status to 'READY' pool.Status = PoolStatus_POOL_STATUS_READY } - + // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. const assetIn = pool.findAssetByDenom(state.in[0].denom) assetIn.balance.amount += state.in[0].amount @@ -1003,7 +1008,7 @@ function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: St const feeRate = params.getPoolFeeRate() const amm = new InterchainMarketMaker(pool, feeRate) } - // */ + // */ // verify signature const sender = account.GetAccount(msg.remoteDeposit.sender) @@ -1029,7 +1034,7 @@ function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: St // TODO: remove it // deposit remote token const poolTokens = amm.doubleSingleAsset([msg.localDeposit.token, msg.remoteDeposit.token]) - + // update counterparty state state.in.forEech(in => { const assetIn = pool.findAssetByDenom(in.denom) @@ -1039,7 +1044,7 @@ function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: St pool.supply.amount += lp.amount }) store.savePool(amm.pool) // update pool states - + // mint voucher token bank.mintCoin(MODULE_NAME, state.poolTokens[1]) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.remoteDeposit.sender, poolTokens[1]) @@ -1131,30 +1136,35 @@ Acknowledgement use for source chain to check if the transaction is succeeded or ```ts function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { - // do nothing + // do nothing } -function onSingleAssetDepositAcknowledged(request: MsgSingleAssetDepositRequest, response: MsgSingleAssetDepositResponse) { - bank.mintCoin(MODULE_NAME,request.sender,response.token) - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) +function onSingleAssetDepositAcknowledged( + request: MsgSingleAssetDepositRequest, + response: MsgSingleAssetDepositResponse +) { + bank.mintCoin(MODULE_NAME, request.sender, response.token); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); } function onMultiAssetDepositAcknowledged(request: MsgMultiAssetDepositRequest, response: MsgMultiAssetDepositResponse) { - bank.mintCoin(MODULE_NAME,response.tokens[0]) - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]) + bank.mintCoin(MODULE_NAME, response.tokens[0]); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]); + + // update local pool state, + const assetOut = pool.findAssetByDenom(req.denomOut); + assetOut.balance.amount -= outToken.amount; + pool.supply.amount -= poolToken.amount; + store.savePool(pool); } function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { - bank.burnCoin(MODULE_NAME, response.token) + bank.burnCoin(MODULE_NAME, response.token); } -function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) { +function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) {} -} - -function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) { - -} +function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) {} ``` ## RISKS @@ -1166,9 +1176,9 @@ However, pool state synchronization could be delayed due to relayer halts or net Solutions and mitigations: -- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. -- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. -- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. +- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. +- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. +- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. ### Price Impact Of Single Asset Deposit @@ -1176,7 +1186,7 @@ Single-asset deposits are convenient for users and reduces the risk of impermane Solution and mitigations: -- Set an upper limit for single-asset deposits. This would be proportional to the amount deposited and the balance of the asset in the liquidity pool. +- Set an upper limit for single-asset deposits. This would be proportional to the amount deposited and the balance of the asset in the liquidity pool. ## Backwards Compatibility From 8a77ed2d2bb7be53e52c3323fd578e36a31ffb71 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Sun, 30 Apr 2023 20:39:28 +0300 Subject: [PATCH 38/72] add single deposit --- spec/app/ics-101-interchain-swap/README.md | 249 +++++++++++++-------- 1 file changed, 150 insertions(+), 99 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 88a4542ef..a1aeddf6b 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -46,11 +46,11 @@ Features include an option to provide liquidity with a single asset instead of a ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. -- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. -- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. ## Technical Specification @@ -76,9 +76,9 @@ $$V = {Π_tB_t^{W_t}}$$ Where -- $t$ ranges over the tokens in the pool -- $B_t$ is the balance of the token in the pool -- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. +- $t$ ranges over the tokens in the pool +- $B_t$ is the balance of the token in the pool +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. #### Spot Price @@ -86,10 +86,10 @@ Spot prices of tokens are defined entirely by the weights and balances of the to $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ -- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool -- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool -- $W_i$ is the weight of token $i$ -- $W_o$ is the weight of token $o$ +- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool +- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool +- $W_i$ is the weight of token $i$ +- $W_o$ is the weight of token $o$ #### Fees @@ -117,8 +117,8 @@ The pool can be fully funded through the initial deposit or through subsequent d ```ts interface Coin { - amount: int64; - denom: string; + amount: int64; + denom: string; } ``` @@ -139,11 +139,11 @@ enum PoolStatus { ```ts interface PoolAsset { - side: PoolSide; - balance: Coin; - // percentage - weight: int32; - decimal: int32; + side: PoolSide; + balance: Coin; + // percentage + weight: int32; + decimal: int32; } ``` @@ -342,7 +342,8 @@ enum MessageType { CreatePool, SingleAssetDeposit, MultiAssetDeposit, - Withdraw, + SingleAssetWithdraw, + MultiAssetWithdraw, Swap } @@ -368,17 +369,15 @@ interface IBCSwapDataPacket { ``` ```typescript -type IBCSwapDataAcknowledgement = - | IBCSwapDataPacketSuccess - | IBCSwapDataPacketError; +type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; interface IBCSwapDataPacketSuccess { - // This is binary 0x01 base64 encoded - result: "AQ=="; + // This is binary 0x01 base64 encoded + result: "AQ=="; } interface IBCSwapDataPacketError { - error: string; + error: string; } ``` @@ -412,8 +411,8 @@ Once the setup function has been called, channels can be created via the IBC rou An interchain swap module will accept new channels from any module on another machine, if and only if: -- The channel being created is unordered. -- The version string is `ics101-1`. +- The channel being created is unordered. +- The version string is `ics101-1`. ```typescript function onChanOpenInit( @@ -455,12 +454,12 @@ function onChanOpenTry( ```typescript function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string ) { - abortTransactionUnless(counterpartyVersion === "ics101-1"); + abortTransactionUnless(counterpartyVersion === "ics101-1"); } ``` @@ -470,21 +469,14 @@ function onChanOpenAck( ```ts function sendInterchainIBCSwapDataPacket( - swapPacket: IBCSwapPacketData, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64 + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64 ) { - // send packet using the interface defined in ICS4 - handler.sendPacket( - getCapability("port"), - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - swapPacket - ); + // send packet using the interface defined in ICS4 + handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); } ``` @@ -513,9 +505,13 @@ function onRecvPacket(packet: Packet) { onDoubleDepositReceived(msg) break - case Withdraw: - var msg: MsgWithdrawRequest = protobuf.decode(swapPacket.data) - onWithdrawReceived(msg) + case SingleAssetWithdraw: + var msg: MsgSingleAssetWithdrawRequest = protobuf.decode(swapPacket.data) + onSingleAssetWithdrawReceived(msg) + break + case MultiAssetWithdraw: + var msg: MsgMultiAssetWithdrawRequest = protobuf.decode(swapPacket.data) + onMultiAssetWithdrawReceived(msg) break case Swap: var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) @@ -560,8 +556,11 @@ function OnAcknowledgementPacket( case MultiAssetDeposit: onMultiAssetDepositAcknowledged(msg) break; - case Withdraw: - onWithdrawAcknowledged(msg) + case SingleAssetWithdraw: + onSingleAssetWithdrawAcknowledged(msg) + break; + case MultiAssetWithdraw: + onMultiAssetWithdrawAcknowledged(msg) break; case Swap: var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) @@ -582,13 +581,13 @@ function OnAcknowledgementPacket( ```ts function onTimeoutPacket(packet: Packet) { - // the packet timed-out, so refund the tokens - refundTokens(packet); + // the packet timed-out, so refund the tokens + refundTokens(packet); } ``` ```ts -// TODO: need to decode the subpacket from packet +// TODO: need to decode the subpacket from packet function refundToken(packet: Packet) { let token switch packet.type { @@ -601,8 +600,10 @@ function refundToken(packet: Packet) { case MultiAssetDeposit: token = packet.tokens break; - case Withdraw: + case SingleAssetWithdraw: token = packet.pool_token + case MultiAssetWithdraw: + token = packet.localWithdraw.pool_token } escrowAccount = channelEscrowAddresses[packet.srcChannel] bank.TransferCoins(escrowAccount, packet.sender, token.denom, token.amount) @@ -617,7 +618,8 @@ Interchain Swap implements the following sub-protocols: rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); rpc SingleAssetDeposit(MsgSingleAssetDepositRequest) returns (MsgSingleAssetDepositResponse); rpc MultiAssetDeposit(MsgMultiAssetDepositRequest) returns (MsgMultiAssetDepositResponse); - rpc Withdraw(MsgWithdrawRequest) returns (MsgWithdrawResponse); + rpc SingleAssetWithdraw(MsgSingleAssetWithdrawRequest) returns (MsgSingleAssetWithdrawResponse); + rpc MultiAssetWithdraw(MsgMultiAssetWithdrawRequest) returns (MsgMultiAssetWithdrawResponse); rpc Swap(MsgSwapRequest) returns (MsgSwapResponse); ``` @@ -638,44 +640,53 @@ interface MsgCreatePoolResponse {} ```ts interface MsgDepositAssetRequest { - poolId: string; - sender: string; - token: Coin; + poolId: string; + sender: string; + token: Coin; } interface MsgSingleAssetDepositResponse { - poolToken: Coin; + poolToken: Coin; } ``` ```ts interface LocalDeposit { - sender: string; - token: Coin; + sender: string; + token: Coin; } interface RemoteDeposit { - sender: string; - sequence: int; // account transaction sequence - token: Coin; - signature: Uint8Array; + sender: string; + sequence: int; // account transaction sequence + token: Coin; + signature: Uint8Array; } interface MsgMultiAssetDepositRequest { - poolId: string; - localDeposit: LocalDeposit; - remoteDeposit: RemoteDeposit; + poolId: string; + localDeposit: LocalDeposit; + remoteDeposit: RemoteDeposit; } interface MsgMultiAssetDepositResponse { - poolTokens: Coin[]; + poolTokens: Coin[]; } ``` ```ts -interface MsgWithdrawRequest { +interface MsgSingleAssetWithdrawRequest { sender: string, poolCoin: Coin, denomOut: string, } -interface MsgWithdrawResponse { +interface MsgSingleAssetWithdrawResponse { + token: Coin; +} + +interface MsgMultiAssetWithdrawRequest { + localWithdraw: MsgSingleAssetWithdrawRequest + remoteWithdraw: MsgSingleAssetWithdrawRequest +} + +interface MsgMultiAssetWithdrawResponse { tokens: []Coin; } ``` @@ -743,7 +754,7 @@ function singleAssetDeposit(msg MsgSingleDepositRequest) { // should have enough balance abortTransactionUnless(balance.amount >= msg.token.amount) - // the first initial + // the first initial if(pool.status == POOL_STATUS_INITIAL) { const asset = pool.findAssetByDenom(msg.token.denom) abortTransactionUnless(balance.amount !== asset.amount) @@ -819,7 +830,7 @@ function multiAssetDeposit(msg MsgMultiAssetDepositRequest) { sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) } -function withdraw(msg MsgWithdrawRequest) { +function singleAssetWithdraw(msg MsgWithdrawRequest) { abortTransactionUnless(msg.sender != null) abortTransactionUnless(msg.token.length > 0) @@ -858,6 +869,47 @@ function withdraw(msg MsgWithdrawRequest) { } +function multiAssetWithdraw(msg MsgMultiAssetWithdrawRequest) { + + abortTransactionUnless(msg.localWithdraw.sender != null) + abortTransactionUnless(msg.remoteWithdraw.sender != null) + abortTransactionUnless(msg.localWithdraw.poolToken != null) + abortTransactionUnless(msg.remoteWithdraw.poolToken != null) + + const pool = store.findPoolById(msg.localWithdraw.poolToken.denom) + abortTransactionUnless(pool != null) + abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) + + const outToken = this.pool.findAssetByDenom(msg.denomOut) + abortTransactionUnless(outToken != null) + abortTransactionUnless(outToken.poolSide == PoolSide.Native) + + // lock pool token to the swap module + const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) + bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) + + const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) + const outTokens = amm.multiAssetWithdraw([msg.localDeposit.poolToken, msg.remoteDeposit.poolToken]) + + // update local pool state, + const assetOut = pool.findAssetByDenom(msg.denomOut) + assetOut.balance.amount -= outToken.amount + pool.supply.amount -= poolToken.amount + store.savePool(pool) + + // constructs the IBC data packet + const packet = { + type: MessageType.Withdraw, + data: protobuf.encode(msg), // encode the request message to protobuf bytes. + stateChange: { + poolTokens: [msg.localDeposit.poolToken, msg.remoteDeposit.poolToken] , + out: outTokens, + } + } + sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) + +} + function leftSwap(msg MsgSwapRequest) { abortTransactionUnless(msg.sender != null) @@ -977,7 +1029,7 @@ function onSingleAssetDepositReceived(msg: MsgSingleAssetDepositRequest, state: // switch pool status to 'READY' pool.Status = PoolStatus_POOL_STATUS_READY } - + // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. const assetIn = pool.findAssetByDenom(state.in[0].denom) assetIn.balance.amount += state.in[0].amount @@ -1003,7 +1055,7 @@ function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: St const feeRate = params.getPoolFeeRate() const amm = new InterchainMarketMaker(pool, feeRate) } - // */ + // */ // verify signature const sender = account.GetAccount(msg.remoteDeposit.sender) @@ -1028,18 +1080,18 @@ function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: St // TODO: remove it // deposit remote token - const poolTokens = amm.doubleSingleAsset([msg.localDeposit.token, msg.remoteDeposit.token]) - + const poolTokens = amm.multiAssetDeposit([msg.localDeposit.token, msg.remoteDeposit.token]) + // update counterparty state - state.in.forEech(in => { + state.in.forEach(in => { const assetIn = pool.findAssetByDenom(in.denom) assetIn.balance.amount += in.amount }) - state.poolTokens.forEech(lp => { + state.poolTokens.forEach(lp => { pool.supply.amount += lp.amount }) store.savePool(amm.pool) // update pool states - + // mint voucher token bank.mintCoin(MODULE_NAME, state.poolTokens[1]) bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.remoteDeposit.sender, poolTokens[1]) @@ -1131,30 +1183,29 @@ Acknowledgement use for source chain to check if the transaction is succeeded or ```ts function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { - // do nothing + // do nothing } -function onSingleAssetDepositAcknowledged(request: MsgSingleAssetDepositRequest, response: MsgSingleAssetDepositResponse) { - bank.mintCoin(MODULE_NAME,request.sender,response.token) - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens) +function onSingleAssetDepositAcknowledged( + request: MsgSingleAssetDepositRequest, + response: MsgSingleAssetDepositResponse +) { + bank.mintCoin(MODULE_NAME, request.sender, response.token); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); } function onMultiAssetDepositAcknowledged(request: MsgMultiAssetDepositRequest, response: MsgMultiAssetDepositResponse) { - bank.mintCoin(MODULE_NAME,response.tokens[0]) - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]) + bank.mintCoin(MODULE_NAME, response.tokens[0]); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]); } function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { - bank.burnCoin(MODULE_NAME, response.token) -} - -function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) { - + bank.burnCoin(MODULE_NAME, response.token); } -function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) { +function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) {} -} +function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) {} ``` ## RISKS @@ -1166,9 +1217,9 @@ However, pool state synchronization could be delayed due to relayer halts or net Solutions and mitigations: -- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. -- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. -- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. +- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. +- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. +- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. ### Price Impact Of Single Asset Deposit @@ -1176,7 +1227,7 @@ Single-asset deposits are convenient for users and reduces the risk of impermane Solution and mitigations: -- Set an upper limit for single-asset deposits. This would be proportional to the amount deposited and the balance of the asset in the liquidity pool. +- Set an upper limit for single-asset deposits. This would be proportional to the amount deposited and the balance of the asset in the liquidity pool. ## Backwards Compatibility From cf7f891d0345b307da475323f8c7e9c2c6a6f494 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Tue, 2 May 2023 09:01:27 +0800 Subject: [PATCH 39/72] remove duplicated state update --- spec/app/ics-101-interchain-swap/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 844e83006..41759748b 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -227,7 +227,7 @@ class InterchainMarketMaker { const weight = asset.weight / 100 const issueAmount = supply * (math.pow(1+amount/asset.balance, weight) - 1) - asset.balance.amount += token.amount // update balance of the asset + // asset.balance.amount += token.amount // update balance of the asset return { amount: issueAmount, @@ -244,7 +244,7 @@ class InterchainMarketMaker { const supply = this.pool.supply.amount const weight = asset.weight / 100 const issueAmount = supply * (1+amount/asset.balance) - asset.balance.amount += token.amount // update balance of the asset + // asset.balance.amount += token.amount // update balance of the asset lpTokens.push({ amount: issueAmount, denom: this.pool.supply.denom From d80c705a02454e577ef249a432d4ff3da5c3984f Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Tue, 2 May 2023 09:02:04 +0800 Subject: [PATCH 40/72] update diagram --- spec/app/ics-101-interchain-swap/interchain-swap.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/interchain-swap.svg b/spec/app/ics-101-interchain-swap/interchain-swap.svg index 336c7f39e..eaec6cca0 100644 --- a/spec/app/ics-101-interchain-swap/interchain-swap.svg +++ b/spec/app/ics-101-interchain-swap/interchain-swap.svg @@ -1 +1 @@ -Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap Packet3 Execute Swap(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool StateOver slippage tolerance1. Calculate Swap Output Amount2. Check slippage tolerance3. Send Token4. Update pool states1. Create Swap Order2. Lock  Swap In Assets2. Delegate Swap RequestMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well. \ No newline at end of file +Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap PacketUpdate Pool StateSend Token(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool State1. Calculate Swap Output Amount2. Check slippage tolerance3. Send Token4. Update pool states1. Create Swap Order2. Lock  Swap In Assets2. Delegate Swap RequestMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well.Update pool stateExecute SwapUpdate Pool StateOver Slippage Tolerance \ No newline at end of file From 7a34cc627e35daa1009dc62fc2bcc9fc8f38edac Mon Sep 17 00:00:00 2001 From: EddyG Date: Tue, 2 May 2023 22:03:43 +0800 Subject: [PATCH 41/72] additional polish --- spec/app/ics-101-interchain-swap/README.md | 200 ++++++++++++--------- 1 file changed, 114 insertions(+), 86 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 41759748b..f5ba59192 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -16,17 +16,17 @@ This standard document specifies the packet data structure, state machine handli ### Motivation -ICS-101 Interchain Swaps enables chains their own token pricing mechanism and exchange protocol via IBC transactions. Each chain can thus play a role in a fully decentralised exchange network. +ICS-101 Interchain Swaps enables chains to have their own token pricing mechanism and exchange protocol via IBC transactions. By enabling their own token pricing mechanism and exchange protocol, each chain can play a role in a fully decentralised exchange network. Features include an option to provide liquidity with a single asset instead of a pair, which users might prefer as it reduces the risk of impermanent loss. ### Definitions -`Interchain swap`: a IBC token swap protocol, built on top of an automated marketing making system, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. +`Interchain swap`: a IBC token swap protocol, built on top of an automated market making model, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. `Interchain liquidity pool`: a single-asset liquidity pool on a chain, with a corresponding single-asset liquidity pool on a separate chain. This comprises an interchain liquidity pool and can execute interchain swaps to exchange between the assets. -`Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. +`Automated market makers(AMM)`: are decentralized exchanges that pool liquidity and allow tokens to be traded in a permissionless and automatic way. Usually uses an liquidity pool invariant for token swapping calculation. In this interchain standard, the Balancer algorithm is implemented. `Weighted pools`: liquidity pools characterized by the percentage weight of each token denomination maintained within. @@ -40,17 +40,17 @@ Features include an option to provide liquidity with a single asset instead of a `Pool state`: the entire state of a liquidity pool including its invariant value which is derived from its token balances and weights inside. -`Source chain`: a chain that begins a IBC transaction. +`Source chain`: a chain that initiates an IBC transaction. `Destination chain`: a chain that receives an IBC transaction from a source chain. ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurence of a user receiving tokens without the equivalent promised exchange. -- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. -- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. ## Technical Specification @@ -58,7 +58,7 @@ Features include an option to provide liquidity with a single asset instead of a An interchain liquidity pool comprises of two single-asset liquidity pools on separate chains, and maintains synced pool states on both chains. Pool state changes on one chain will update its corresponding pool state on the other chain through IBC packet relays, which occurs during a deposit, withdrawal or swap. -The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price abritrage, each chain can only execute sell orders of the token held by its single-asset liquidity pool. +The pool states can become temporarily unsynced due to packet relay flight-time. To avoid unnecessary price arbitrage, each chain can only execute sell orders of the token held by its single-asset liquidity pool. Both chains can initiate a swap, and both chains can subsequently update its pool state. @@ -70,15 +70,15 @@ This is an overview of how Interchain Swap works: #### Invariant -A constant invariant is maintained after trades which takes into consideration token weights and balance. The value function $V$ is defined as: +A constant invariant is maintained after trades, taking into consideration token weights and balance. The value function $V$ is defined as: $$V = {Π_tB_t^{W_t}}$$ Where -- $t$ ranges over the tokens in the pool -- $B_t$ is the balance of the token in the pool -- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. +- $t$ ranges over the tokens in the pool +- $B_t$ is the balance of the token in the pool +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. #### Spot Price @@ -86,10 +86,10 @@ Spot prices of tokens are defined entirely by the weights and balances of the to $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ -- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool -- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool -- $W_i$ is the weight of token $i$ -- $W_o$ is the weight of token $o$ +- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool +- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool +- $W_i$ is the weight of token $i$ +- $W_o$ is the weight of token $o$ #### Fees @@ -105,7 +105,7 @@ As the pool collects fees, liquidity providers automatically collect fees throug An interchain liquidity pool has two possible states: -`POOL_STATUS_INITIAL`: The interchain liquidity pool is created yet not ready to execute swaps. Pool parameters have been registered but assets for the swap pair have not been deposited. +`POOL_STATUS_INITIAL`: The interchain liquidity pool is created but not yet ready to execute swaps. Pool parameters have been registered but assets for the swap pair have not been deposited. `POOL_STATUS_READY`: The interchain liquidity pool is ready to execute swaps. Required assets have been deposited. @@ -117,8 +117,8 @@ The pool can be fully funded through the initial deposit or through subsequent d ```ts interface Coin { - amount: int64; - denom: string; + amount: int64; + denom: string; } ``` @@ -139,11 +139,11 @@ enum PoolStatus { ```ts interface PoolAsset { - side: PoolSide; - balance: Coin; - // percentage - weight: int32; - decimal: int32; + side: PoolSide; + balance: Coin; + // percentage + weight: int32; + decimal: int32; } ``` @@ -194,7 +194,7 @@ function generatePoolId(denoms: []string) { #### Interchain Market Maker -The `InterchainMarketMaker` is a core component for swap calculations, and is initialized with an `InterchainLiquidityPool` and `feeRate`. Since the `feeRate` for a trading pair can be updated through governance, it is possible to have different rates on each chain. +The `InterchainMarketMaker` is a core component for swap calculations. It is initialized with an `InterchainLiquidityPool` and `feeRate`. Since the `feeRate` for a trading pair can be updated through governance, it is possible to have different rates on each chain. ```ts class InterchainMarketMaker { @@ -335,7 +335,7 @@ class InterchainMarketMaker { #### Data packets -Only one packet data type is required: `IBCSwapDataPacket`, which specifies the message type and data(protobuf marshalled). It is a wrapper for interchain swap messages. +There is only one required packate data type: the `IBCSwapDataPacket`. This packet specifies the message type and data (protobuf marshalled), acting as a wrapper for interchain swap messages. ```ts enum MessageType { @@ -369,15 +369,17 @@ interface IBCSwapDataPacket { ``` ```typescript -type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; +type IBCSwapDataAcknowledgement = + | IBCSwapDataPacketSuccess + | IBCSwapDataPacketError; interface IBCSwapDataPacketSuccess { - // This is binary 0x01 base64 encoded - result: "AQ=="; + // This is binary 0x01 base64 encoded + result: "AQ=="; } interface IBCSwapDataPacketError { - error: string; + error: string; } ``` @@ -409,10 +411,10 @@ Once the setup function has been called, channels can be created via the IBC rou #### Channel lifecycle management -An interchain swap module will accept new channels from any module on another machine, if and only if: +An interchain swap module will accept new channels from any module on another machine, provided that the following conditions are met: -- The channel being created is unordered. -- The version string is `ics101-1`. +- The channel being created is unordered. +- The version string is `ics101-1`. ```typescript function onChanOpenInit( @@ -454,29 +456,36 @@ function onChanOpenTry( ```typescript function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string ) { - abortTransactionUnless(counterpartyVersion === "ics101-1"); + abortTransactionUnless(counterpartyVersion === "ics101-1"); } ``` #### Packet relay -`sendInterchainIBCSwapDataPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine. +The function `sendInterchainIBCSwapDataPacket` must be invoked by a transaction handler in the module, which also performs the appropriate signature checks, specific to the account owner on the host state machine. ```ts function sendInterchainIBCSwapDataPacket( - swapPacket: IBCSwapPacketData, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64 + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64 ) { - // send packet using the interface defined in ICS4 - handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); + // send packet using the interface defined in ICS4 + handler.sendPacket( + getCapability("port"), + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + swapPacket + ); } ``` @@ -581,8 +590,8 @@ function OnAcknowledgementPacket( ```ts function onTimeoutPacket(packet: Packet) { - // the packet timed-out, so refund the tokens - refundTokens(packet); + // the packet timed-out, so refund the tokens + refundTokens(packet); } ``` @@ -619,7 +628,7 @@ function refundToken(packet: Packet) { ### Sub-protocols -Interchain Swap implements the following sub-protocols: +These sub-protocols handle packets relayed from a source chain, including pool state updates and token transfers: ```protobuf rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); @@ -647,34 +656,34 @@ interface MsgCreatePoolResponse {} ```ts interface MsgDepositAssetRequest { - poolId: string; - sender: string; - token: Coin; + poolId: string; + sender: string; + token: Coin; } interface MsgSingleAssetDepositResponse { - poolToken: Coin; + poolToken: Coin; } ``` ```ts interface LocalDeposit { - sender: string; - token: Coin; + sender: string; + token: Coin; } interface RemoteDeposit { - sender: string; - sequence: int; // account transaction sequence - token: Coin; - signature: Uint8Array; + sender: string; + sequence: int; // account transaction sequence + token: Coin; + signature: Uint8Array; } interface MsgMultiAssetDepositRequest { - poolId: string; - localDeposit: LocalDeposit; - remoteDeposit: RemoteDeposit; + poolId: string; + localDeposit: LocalDeposit; + remoteDeposit: RemoteDeposit; } interface MsgMultiAssetDepositResponse { - poolTokens: Coin[]; + poolTokens: Coin[]; } ``` @@ -710,7 +719,7 @@ interface MsgSwapResponse { } ``` -### Control Flow And Life Scope +### Control flow And life scope These are methods that output a state change on the source chain, which will be subsequently synced to the destination chain. @@ -1177,55 +1186,74 @@ function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwa } ``` -Acknowledgement use for source chain to check if the transaction is succeeded or not. +Acknowledgement is used by the source chain to check if the transaction has succeeded or not. ```ts -function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { - // do nothing +function onCreatePoolAcknowledged( + request: MsgCreatePoolRequest, + response: MsgCreatePoolResponse +) { + // do nothing } function onSingleAssetDepositAcknowledged( - request: MsgSingleAssetDepositRequest, - response: MsgSingleAssetDepositResponse + request: MsgSingleAssetDepositRequest, + response: MsgSingleAssetDepositResponse ) { - bank.mintCoin(MODULE_NAME, request.sender, response.token); - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); + bank.mintCoin(MODULE_NAME, request.sender, response.token); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); } -function onMultiAssetDepositAcknowledged(request: MsgMultiAssetDepositRequest, response: MsgMultiAssetDepositResponse) { - bank.mintCoin(MODULE_NAME, response.tokens[0]); - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]); +function onMultiAssetDepositAcknowledged( + request: MsgMultiAssetDepositRequest, + response: MsgMultiAssetDepositResponse +) { + bank.mintCoin(MODULE_NAME, response.tokens[0]); + bank.sendCoinsFromModuleToAccount( + MODULE_NAME, + msg.localDeposit.sender, + response.tokens[0] + ); } -function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { - bank.burnCoin(MODULE_NAME, response.token); +function onWithdrawAcknowledged( + request: MsgWithdrawRequest, + response: MsgWithdrawResponse +) { + bank.burnCoin(MODULE_NAME, response.token); } -function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) {} +function onLeftSwapAcknowledged( + request: MsgSwapRequest, + response: MsgSwapResponse +) {} -function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) {} +function onRightSwapAcknowledged( + request: MsgRightSwapRequest, + response: MsgSwapResponse +) {} ``` ## RISKS ### Pool State Inconsistency -Pool state synchronization is extremely important for Interchain Swap, since there are two mirrored pool across the two chain. +Synchronization of pool states is crucial for Interchain Swap, as there are two mirrored pools across the two chains. However, pool state synchronization could be delayed due to relayer halts or network issues. This can create swap price differences. Solutions and mitigations: -- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. -- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. -- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. +- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. +- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. +- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. ### Price Impact Of Single Asset Deposit -Single-asset deposits are convenient for users and reduces the risk of impermanent loss, but can have a significant impact on the swap prices. +While single-asset deposits are convenient for users and reduce the risk of impermanent loss, they can significantly impact swap prices. Solution and mitigations: -- Set an upper limit for single-asset deposits. This would be proportional to the amount deposited and the balance of the asset in the liquidity pool. +- Set an upper limit for single-asset deposits. This limit would be proportional to the amount deposited and the balance of the asset in the liquidity pool. ## Backwards Compatibility From 883e65c23086cda904c7ba773b6c76fec6f47f14 Mon Sep 17 00:00:00 2001 From: EddyG Date: Tue, 2 May 2023 22:24:45 +0800 Subject: [PATCH 42/72] additional polish --- spec/app/ics-101-interchain-swap/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index f5ba59192..a378953c7 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -46,11 +46,11 @@ Features include an option to provide liquidity with a single asset instead of a ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. -- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. -- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. ## Technical Specification From d72bc0c325a4bac7f06d37d34721d0c76076bab4 Mon Sep 17 00:00:00 2001 From: EddyG Date: Tue, 2 May 2023 22:25:30 +0800 Subject: [PATCH 43/72] additional polish --- spec/app/ics-101-interchain-swap/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index a378953c7..decbee099 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -46,11 +46,11 @@ Features include an option to provide liquidity with a single asset instead of a ### Desired Properties -- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. -- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. -- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. -- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. +- `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. +- `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. +- `Gaurantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. +- `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. +- `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. ## Technical Specification From abceb40f8348180206e921fa24f2eb2b6e52fc4a Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Wed, 5 Jul 2023 12:33:57 +0300 Subject: [PATCH 44/72] chore: data structure/packet update --- spec/app/ics-101-interchain-swap/README.md | 330 +++++++++++---------- 1 file changed, 168 insertions(+), 162 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index decbee099..3ba0fe5ee 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -76,9 +76,9 @@ $$V = {Π_tB_t^{W_t}}$$ Where -- $t$ ranges over the tokens in the pool -- $B_t$ is the balance of the token in the pool -- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. +- $t$ ranges over the tokens in the pool +- $B_t$ is the balance of the token in the pool +- $W_t$ is the normalized weight of the tokens, such that the sum of all normalized weights is 1. #### Spot Price @@ -86,10 +86,10 @@ Spot prices of tokens are defined entirely by the weights and balances of the to $$SP_i^o = (B_i/W_i)/(B_o/W_o)$$ -- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool -- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool -- $W_i$ is the weight of token $i$ -- $W_o$ is the weight of token $o$ +- $B_i$ is the balance of token $i$, the token being sold by the trader which is going into the pool +- $B_o$ is the balance of token $o$, the token being bought by the trader which is going out of the pool +- $W_i$ is the weight of token $i$ +- $W_o$ is the weight of token $o$ #### Fees @@ -117,46 +117,49 @@ The pool can be fully funded through the initial deposit or through subsequent d ```ts interface Coin { - amount: int64; - denom: string; + amount: int32; + denom: string; } ``` ```ts -enum PoolSide { - Native = 1; - Remote = 2; +enum PoolAssetSide { + Source = 1; + Destination = 2; } ``` ```ts // PoolStatus defines if the pool is ready for trading enum PoolStatus { - POOL_STATUS_INITIAL = 0; - POOL_STATUS_READY = 1; + INITIALIZED = 0; + ACTIVE = 1; } ``` ```ts interface PoolAsset { - side: PoolSide; - balance: Coin; - // percentage - weight: int32; - decimal: int32; + side: PoolAssetSide; + balance: Coin; + // percentage + weight: int32; + decimal: int32; } ``` ```ts interface InterchainLiquidityPool { id: string; - assets []PoolAsset; + sourceCreator: string; + destinationCreator: string; + assets: []PoolAsset; + swapFee: int32; // the issued amount of pool token in the pool. the denom is pool id supply: Coin; status: PoolStatus; - counterpartyPort: string; - counterpartyChannel: string; - constructor(denoms: []string, decimals: []number, weight: string, portId string, channelId string) { + encounterPartyPort: string; + encounterPartyChannel: string; + constructor(denoms: []string, decimals: []number, weights: []number,swapFee: number, portId string, channelId string) { this.id = generatePoolId(denoms) this.supply = { @@ -164,21 +167,20 @@ interface InterchainLiquidityPool { denom: this.id } this.status = PoolStatus.POOL_STATUS_INITIAL - this.counterpartyPort = portId - this.counterpartyChannel = channelId - + this.encounterPartyPort = portId + this.encounterPartyChannel = channelId + this.swapFee = swapFee // construct assets - const weights = weight.split(':').length - if(denoms.length === decimals.length && denoms.length === weight.split(':').length) { - for(let i=0; i < denoms.length; i++) { + if(denoms.length === decimals.lenght && denoms.length === weight.length) { + for(let i=0; i < denoms.lenght; i++) { this.assets.push({ - side: store.hasSupply(denom[i]) ? PoolSide.Native: PoolSide.Remote, + side: store.hasSupply(denom[i]) ? PoolAssetSide.Source: PoolAssetSide.Destination, balance: { amount: 0, denom: denom[i], }, - weight: number(weights[i]) - decimal: decimals[i] + weight: weights[i], + decimal: decimals[i], }) } } @@ -187,8 +189,26 @@ interface InterchainLiquidityPool { ``` ```ts -function generatePoolId(denoms: []string) { - return "pool" + sha256(denoms.sort().join('')) +function generatePoolId(denoms: string[]) { + return "pool" + sha256(denoms.sort().join("")); +} + +function generatePoolId(sourceChainId: string, destinationChainId: string, denoms: string[]): string { + const connectionId: string = getConnectID([sourceChainId, destinationChainId]); + denoms.sort(); + + const poolIdHash = createHash("sha256"); + denoms.push(connectionId); + poolIdHash.update(denoms.join("")); + + const poolId = "pool" + poolIdHash.digest("hex"); + return poolId; +} + +function getConnectID(chainIds: string[]): string { + // Generate poolId + chainIds.sort(); + return chainIds.join("/"); } ``` @@ -199,12 +219,10 @@ The `InterchainMarketMaker` is a core component for swap calculations. It is ini ```ts class InterchainMarketMaker { pool :InterchainLiquidityPool - // basis point - feeRate: number // int32 - - construct(pool: InterchainLiquidityPool, feeRate: number) : InterchainMarketMaker { - this.pool = pool, - this.feeRate = feeRate, + static initialize(pool: InterchainLiquidityPool) : InterchainMarketMaker { + return { + pool: pool + } } // MarketPrice Bi / Wi / (Bo / Wo) @@ -227,7 +245,7 @@ class InterchainMarketMaker { const weight = asset.weight / 100 const issueAmount = supply * (math.pow(1+amount/asset.balance, weight) - 1) - // asset.balance.amount += token.amount // update balance of the asset + asset.balance.amount += token.amount // update balance of the asset return { amount: issueAmount, @@ -235,46 +253,63 @@ class InterchainMarketMaker { } } - // P_issued = P_supply * (1 + At/Bt) + // P_issued = P_supply * Wt * Dt/Bt function depositMultiAsset(tokens: Coin[]): Coin[] { - const lpTokens = []; - for (const token in tokens) { - const asset = this.pool.findAssetByDenom(token.denom) - const amount = token.amount - const supply = this.pool.supply.amount - const weight = asset.weight / 100 - const issueAmount = supply * (1+amount/asset.balance) - // asset.balance.amount += token.amount // update balance of the asset - lpTokens.push({ - amount: issueAmount, - denom: this.pool.supply.denom - }); + const outTokens: Coin[] = []; + for (const token of tokens) { + const asset = imm.Pool.FindAssetByDenom(token.Denom); + if (!asset) { + throw new Error("Asset not found"); + } + + let issueAmount: Int; + + if (imm.Pool.Status === PoolStatus_INITIALIZED) { + let totalAssetAmount = new Int(0); + for (const asset of imm.Pool.Assets) { + totalAssetAmount = totalAssetAmount.Add(asset.Balance.Amount); } - return lpTokens + issueAmount = totalAssetAmount.Mul(new Int(asset.Weight)).Quo(new Int(100)); + } else { + const decToken = new DecCoinFromCoin(token); + const decAsset = new DecCoinFromCoin(asset.Balance); + const decSupply = new DecCoinFromCoin(imm.Pool.Supply); + + const ratio = decToken.Amount.Quo(decAsset.Amount).Mul(new Dec(Multiplier)); + issueAmount = decSupply.Amount.Mul(new Dec(asset.Weight)).Mul(ratio).Quo(new Dec(100)).Quo(new Dec(Multiplier)).RoundInt(); + } + + const outputToken: Coin = { + Amount: issueAmount, + Denom: imm.Pool.Supply.Denom, + }; + outTokens.push(outputToken); } - // input the supply token, output the expected token. - // At = Bt * (1 - (1 - P_redeemed / P_supply) ** 1/Wt) - function withdraw(redeem: Coin, denomOut: string): Coin { - - const asset = this.pool.findAssetByDenom(denomOut) + return outTokens; + } - abortTransactionUnless(asset != null) - abortTransactionUnless(this.pool.status === PoolStatus.POOL_STATUS_READY) - abortTransactionUnless(redeem.amount <= this.pool.supply.amount) - abortTransactionUnless(redeem.denom == this.pool.supply.denom) + // input the supply token, output the expected token. + // At = Bt * (P_redeemed / P_supply)/Wt + multiAssetWithdraw(redeem: Coin): Coin[] { + const outs: Coin[] = []; - const balance = asset.balance.amount - const supply = this.pool.supply.amount - const weight = asset.weight / 100 // convert int to percent + if (redeem.Amount.GT(imm.Pool.Supply.Amount)) { + throw new Error("Overflow amount"); + } - const amountOut = balance * redeem.amount / supply - return { - amount: amountOut, - denom: denomOut, - } + for (const asset of imm.Pool.Assets) { + const out = asset.Balance.Amount.Mul(redeem.Amount).Quo(imm.Pool.Supply.Amount); + const outputCoin: Coin = { + Denom: asset.Balance.Denom, + Amount: out, + }; + outs.push(outputCoin); } + return outs; + } + // LeftSwap implements OutGivenIn // Input how many coins you want to sell, output an amount you will receive // Ao = Bo * (1 -(Bi / (Bi + Ai)) ** Wi/Wo) @@ -339,23 +374,22 @@ There is only one required packate data type: the `IBCSwapDataPacket`. This pack ```ts enum MessageType { - CreatePool, - SingleAssetDeposit, - MultiAssetDeposit, - SingleAssetWithdraw, + MakePool, + TakePool, + MakeMultiAssetDeposit + TakeMultiAssetDeposit MultiAssetWithdraw, - Swap -} - -enum SwapType { - Right - Left + LeftSwap, + RightSwap, } interface StateChange { in: Coin[]; - out: Coin[]; + out: Out[]; poolTokens: Coin[]; + poolId: string; + multiDepositOrderId: string; + sourceChainId: string; } ``` @@ -369,17 +403,15 @@ interface IBCSwapDataPacket { ``` ```typescript -type IBCSwapDataAcknowledgement = - | IBCSwapDataPacketSuccess - | IBCSwapDataPacketError; +type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; interface IBCSwapDataPacketSuccess { - // This is binary 0x01 base64 encoded - result: "AQ=="; + // This is binary 0x01 base64 encoded + result: "AQ=="; } interface IBCSwapDataPacketError { - error: string; + error: string; } ``` @@ -413,8 +445,8 @@ Once the setup function has been called, channels can be created via the IBC rou An interchain swap module will accept new channels from any module on another machine, provided that the following conditions are met: -- The channel being created is unordered. -- The version string is `ics101-1`. +- The channel being created is unordered. +- The version string is `ics101-1`. ```typescript function onChanOpenInit( @@ -456,12 +488,12 @@ function onChanOpenTry( ```typescript function onChanOpenAck( - portIdentifier: Identifier, - channelIdentifier: Identifier, - counterpartyChannelIdentifier: Identifier, - counterpartyVersion: string + portIdentifier: Identifier, + channelIdentifier: Identifier, + counterpartyChannelIdentifier: Identifier, + counterpartyVersion: string ) { - abortTransactionUnless(counterpartyVersion === "ics101-1"); + abortTransactionUnless(counterpartyVersion === "ics101-1"); } ``` @@ -471,21 +503,14 @@ The function `sendInterchainIBCSwapDataPacket` must be invoked by a transaction ```ts function sendInterchainIBCSwapDataPacket( - swapPacket: IBCSwapPacketData, - sourcePort: string, - sourceChannel: string, - timeoutHeight: Height, - timeoutTimestamp: uint64 + swapPacket: IBCSwapPacketData, + sourcePort: string, + sourceChannel: string, + timeoutHeight: Height, + timeoutTimestamp: uint64 ) { - // send packet using the interface defined in ICS4 - handler.sendPacket( - getCapability("port"), - sourcePort, - sourceChannel, - timeoutHeight, - timeoutTimestamp, - swapPacket - ); + // send packet using the interface defined in ICS4 + handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); } ``` @@ -590,8 +615,8 @@ function OnAcknowledgementPacket( ```ts function onTimeoutPacket(packet: Packet) { - // the packet timed-out, so refund the tokens - refundTokens(packet); + // the packet timed-out, so refund the tokens + refundTokens(packet); } ``` @@ -656,34 +681,34 @@ interface MsgCreatePoolResponse {} ```ts interface MsgDepositAssetRequest { - poolId: string; - sender: string; - token: Coin; + poolId: string; + sender: string; + token: Coin; } interface MsgSingleAssetDepositResponse { - poolToken: Coin; + poolToken: Coin; } ``` ```ts interface LocalDeposit { - sender: string; - token: Coin; + sender: string; + token: Coin; } interface RemoteDeposit { - sender: string; - sequence: int; // account transaction sequence - token: Coin; - signature: Uint8Array; + sender: string; + sequence: int; // account transaction sequence + token: Coin; + signature: Uint8Array; } interface MsgMultiAssetDepositRequest { - poolId: string; - localDeposit: LocalDeposit; - remoteDeposit: RemoteDeposit; + poolId: string; + localDeposit: LocalDeposit; + remoteDeposit: RemoteDeposit; } interface MsgMultiAssetDepositResponse { - poolTokens: Coin[]; + poolTokens: Coin[]; } ``` @@ -1189,49 +1214,30 @@ function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwa Acknowledgement is used by the source chain to check if the transaction has succeeded or not. ```ts -function onCreatePoolAcknowledged( - request: MsgCreatePoolRequest, - response: MsgCreatePoolResponse -) { - // do nothing +function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { + // do nothing } function onSingleAssetDepositAcknowledged( - request: MsgSingleAssetDepositRequest, - response: MsgSingleAssetDepositResponse + request: MsgSingleAssetDepositRequest, + response: MsgSingleAssetDepositResponse ) { - bank.mintCoin(MODULE_NAME, request.sender, response.token); - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); + bank.mintCoin(MODULE_NAME, request.sender, response.token); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); } -function onMultiAssetDepositAcknowledged( - request: MsgMultiAssetDepositRequest, - response: MsgMultiAssetDepositResponse -) { - bank.mintCoin(MODULE_NAME, response.tokens[0]); - bank.sendCoinsFromModuleToAccount( - MODULE_NAME, - msg.localDeposit.sender, - response.tokens[0] - ); +function onMultiAssetDepositAcknowledged(request: MsgMultiAssetDepositRequest, response: MsgMultiAssetDepositResponse) { + bank.mintCoin(MODULE_NAME, response.tokens[0]); + bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]); } -function onWithdrawAcknowledged( - request: MsgWithdrawRequest, - response: MsgWithdrawResponse -) { - bank.burnCoin(MODULE_NAME, response.token); +function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { + bank.burnCoin(MODULE_NAME, response.token); } -function onLeftSwapAcknowledged( - request: MsgSwapRequest, - response: MsgSwapResponse -) {} +function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) {} -function onRightSwapAcknowledged( - request: MsgRightSwapRequest, - response: MsgSwapResponse -) {} +function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) {} ``` ## RISKS @@ -1243,9 +1249,9 @@ However, pool state synchronization could be delayed due to relayer halts or net Solutions and mitigations: -- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. -- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. -- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. +- Timeout: Packets timeout when receipt are delayed for an extended period of time. This limits the impact of inconsistent pool states on mispriced swap orders. +- Slippage Tolerance: Cancel orders if executed price difference to quoted price is outside of tolerance range. +- Single-sided trade: Each chain can only execute sell orders of the token held by its single-asset liquidity pool. This removes unnecessary arbitrage opportunities. ### Price Impact Of Single Asset Deposit @@ -1253,7 +1259,7 @@ While single-asset deposits are convenient for users and reduce the risk of impe Solution and mitigations: -- Set an upper limit for single-asset deposits. This limit would be proportional to the amount deposited and the balance of the asset in the liquidity pool. +- Set an upper limit for single-asset deposits. This limit would be proportional to the amount deposited and the balance of the asset in the liquidity pool. ## Backwards Compatibility From 78747b94c4413a425bb0defffc971e4edc4536dc Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Wed, 5 Jul 2023 13:23:14 +0300 Subject: [PATCH 45/72] chore: acknowledge part update --- spec/app/ics-101-interchain-swap/README.md | 1185 ++++++++++++-------- 1 file changed, 709 insertions(+), 476 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 3ba0fe5ee..f18eea387 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -568,46 +568,70 @@ function onRecvPacket(packet: Packet) { `onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged. ```ts - // OnAcknowledgementPacket implements the IBCModule interface -function OnAcknowledgementPacket( - packet: channeltypes.Packet, - ack channeltypes.Acknowledgement, -) { - - var ack channeltypes.Acknowledgement - if (!ack.success()) { - refund(packet) - } else { - const swapPacket = protobuf.decode(packet.data) - switch swapPacket.type { - case CreatePool: - onCreatePoolAcknowledged(msg) - break; - case SingleAssetDeposit: - onSingleAssetDepositAcknowledged(msg) - break; - case MultiAssetDeposit: - onMultiAssetDepositAcknowledged(msg) - break; - case SingleAssetWithdraw: - onSingleAssetWithdrawAcknowledged(msg) - break; - case MultiAssetWithdraw: - onMultiAssetWithdrawAcknowledged(msg) - break; - case Swap: - var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) - if(msg.SwapType === SwapType.Left) { - onLeftSwapAcknowledged(msg) - }else{ - onRightSwapAcknowledged(msg) - } - break; - } - } - return nil +// OnAcknowledgementPacket processes the packet acknowledgement and performs actions based on the acknowledgement type +function OnAcknowledgementPacket(packet: Packet, data: IBCSwapPacketData, ack: Acknowledgement) { + switch (ack.response.type) { + case "Acknowledgement_Error": + return store.refundPacketToken(packet, data); + default: + switch (data.type) { + case "MAKE_POOL": + const msgMakePool: MsgMakePoolRequest = protobuf.decode(data.Data); + const errMakePool = store.OnMakePoolAcknowledged(msgMakePool, data.StateChange.PoolId); + abortTransactionUnless(errMakePool === undefined); + break; + + case "TAKE_POOL": + const msgTakePool: MsgTakePoolRequest = protobuf.decode(data.Data); + const errTakePool = store.OnTakePoolAcknowledged(msgTakePool); + abortTransactionUnless(errTakePool === undefined); + break; + + case "SINGLE_DEPOSIT": + const msgSingleDeposit: MsgSingleAssetDepositRequest = protobuf.decode(data.Data); + const resSingleDeposit: MsgSingleAssetDepositResponse = protobuf.decode(ack.GetResult()); + const errSingleDeposit = store.OnSingleAssetDepositAcknowledged(msgSingleDeposit, resSingleDeposit); + abortTransactionUnless(errSingleDeposit === undefined); + break; + + case "MAKE_MULTI_DEPOSIT": + const msgMakeMultiDeposit: MsgMakeMultiAssetDepositRequest = protobuf.decode(data.Data); + const resMakeMultiDeposit: MsgMultiAssetDepositResponse = protobuf.decode(ack.GetResult()); + const errMakeMultiDeposit = store.OnMakeMultiAssetDepositAcknowledged( + msgMakeMultiDeposit, + resMakeMultiDeposit + ); + abortTransactionUnless(errMakeMultiDeposit === undefined); + break; + + case "TAKE_MULTI_DEPOSIT": + const msgTakeMultiDeposit: MsgTakeMultiAssetDepositRequest = protobuf.decode(data.Data); + const resTakeMultiDeposit: MsgTakePoolResponse = protobuf.decode(ack.GetResult()); + const errTakeMultiDeposit = store.OnTakeMultiAssetDepositAcknowledged(msgTakeMultiDeposit, data.StateChange); + abortTransactionUnless(errTakeMultiDeposit === undefined); + break; + + case "MULTI_WITHDRAW": + const msgMultiWithdraw: MsgMultiAssetWithdrawRequest = protobuf.decode(data.Data); + const resMultiWithdraw: MsgMultiAssetWithdrawResponse = protobuf.decode(ack.GetResult()); + const errMultiWithdraw = store.OnMultiAssetWithdrawAcknowledged(msgMultiWithdraw, resMultiWithdraw); + abortTransactionUnless(errMultiWithdraw === undefined); + break; + + case "LEFT_SWAP": + case "RIGHT_SWAP": + const msgSwap: MsgSwapRequest = protobuf.decode(data.Data); + const resSwap: MsgSwapResponse = protobuf.decode(ack.GetResult()); + const errSwap = store.OnSwapAcknowledged(msgSwap, resSwap); + abortTransactionUnless(errSwap === undefined); + break; + + default: + return; + } + } } ``` @@ -656,91 +680,121 @@ function refundToken(packet: Packet) { These sub-protocols handle packets relayed from a source chain, including pool state updates and token transfers: ```protobuf - rpc CreatePool(MsgCreatePoolRequest) returns (MsgCreatePoolResponse); - rpc SingleAssetDeposit(MsgSingleAssetDepositRequest) returns (MsgSingleAssetDepositResponse); - rpc MultiAssetDeposit(MsgMultiAssetDepositRequest) returns (MsgMultiAssetDepositResponse); - rpc SingleAssetWithdraw(MsgSingleAssetWithdrawRequest) returns (MsgSingleAssetWithdrawResponse); - rpc MultiAssetWithdraw(MsgMultiAssetWithdrawRequest) returns (MsgMultiAssetWithdrawResponse); - rpc Swap(MsgSwapRequest) returns (MsgSwapResponse); + rpc MakePool (MsgMakePoolRequest) returns (MsgMakePoolResponse); + rpc TakePool (MsgTakePoolRequest) returns (MsgTakePoolResponse); + rpc SingleAssetDeposit (MsgSingleAssetDepositRequest ) returns (MsgSingleAssetDepositResponse ); + rpc MakeMultiAssetDeposit (MsgMakeMultiAssetDepositRequest ) return (MsgMultiAssetDepositResponse ); + rpc TakeMultiAssetDeposit (MsgTakeMultiAssetDepositRequest ) returns (MsgMultiAssetDepositResponse ); + rpc MultiAssetWithdraw (MsgMultiAssetWithdrawRequest ) returns (MsgMultiAssetWithdrawResponse ); + rpc Swap (MsgSwapRequest ) returns (MsgSwapResponse ); ``` #### Interfaces for sub-protocols ```ts -interface MsgCreatePoolRequest { - sourcePort: string, - sourceChannel: string, - sender: string, - denoms: []string, - decimals: []int32, - weight: string, +interface MsgMakePoolRequest { + sourcePort: string; + sourceChannel: string; + creator: string; + counterPartyCreator: string; + liquidity: PoolAsset[]; + sender: string; + denoms: string[]; + decimals: int32[]; + swapFee: int32; + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; } -interface MsgCreatePoolResponse {} +interface MsgMakePoolResponse { + poolId: string; +} ``` ```ts -interface MsgDepositAssetRequest { +interface MsgTakePoolRequest { + creator: string; + poolId: string; + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; +} + +interface MsgTakePoolResponse { + poolId: string; +} +``` + +```ts +interface MsgSingleAssetDepositRequest { poolId: string; sender: string; - token: Coin; + token: Coin; // only one element for now, might have two in the feature + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; } -interface MsgSingleAssetDepositResponse { +interface MsgSingleDepositResponse { poolToken: Coin; } ``` ```ts -interface LocalDeposit { +interface DepositAsset { sender: string; - token: Coin; + balance: Coin; } -interface RemoteDeposit { - sender: string; - sequence: int; // account transaction sequence - token: Coin; - signature: Uint8Array; + +interface MsgMakeMultiAssetDepositRequest { + poolId: string; + deposits: DepositAsset[]; + token: Coin; // only one element for now, might have two in the feature + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; } -interface MsgMultiAssetDepositRequest { +interface MsgTakeMultiAssetDepositRequest { + sender: string; poolId: string; - localDeposit: LocalDeposit; - remoteDeposit: RemoteDeposit; + orderId: uint64; + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; } + interface MsgMultiAssetDepositResponse { - poolTokens: Coin[]; + poolToken: Coin; } ``` ```ts -interface MsgSingleAssetWithdrawRequest { - sender: string, - poolCoin: Coin, - denomOut: string, -} -interface MsgSingleAssetWithdrawResponse { - token: Coin; -} interface MsgMultiAssetWithdrawRequest { - localWithdraw: MsgSingleAssetWithdrawRequest - remoteWithdraw: MsgSingleAssetWithdrawRequest + poolId: string; + receiver: string; + counterPartyReceiver: string; + poolToken: Coin; + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; } + interface MsgMultiAssetWithdrawResponse { - tokens: []Coin; + tokens: Coin[]; } ``` ```ts interface MsgSwapRequest { - sender: string, - swapType: SwapType, - tokenIn: Coin, - tokenOut: Coin, - slippage: number; // max tolerated slippage - recipient: string, + swap_type: SwapMsgType; + sender: string; + poolId: string; + tokenIn: Coin; + tokenOut: Coin; + slippage: uint64; + recipient: string; + timeHeight: TimeHeight; + timeoutTimeStamp: uint64; } + interface MsgSwapResponse { - tokens: []Coin; + swap_type: SwapMsgType; + tokens: Coin[]; } ``` @@ -749,495 +803,674 @@ interface MsgSwapResponse { These are methods that output a state change on the source chain, which will be subsequently synced to the destination chain. ```ts -function createPool(msg: MsgCreatePoolRequest) { - - // ICS 24 host check if both port and channel are validate - abortTransactionUnless(host.portIdentifierValidator(msg.sourcePort)) - abortTransactionUnless(host.channelIdentifierValidator(msg.sourceChannel)); + function makePool(msg: MsgMakePoolRequest): Promise { - // Only two assets in a pool - abortTransactionUnless(msg.denoms.length != 2) - abortTransactionUnless(msg.decimals.length != 2) - abortTransactionUnless(msg.weight.split(':').length != 2) // weight: "50:50" - abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) + const { counterPartyChainId, connected } = await store.GetCounterPartyChainID(msg.sourcePort, msg.sourceChannel); - const pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, msg.sourcePort, msg.sourceChannel) + abortTransactionUnless(connected) - const localAssetCount = 0 - for(var denom in msg.denoms) { - if (bank.hasSupply(denom)) { - localAssetCount += 1 - } + const denoms: string[] = []; + for (const liquidity of msg.liquidity) { + denoms.push(liquidity.balance.denom); } - // should have 1 native asset on the chain - abortTransactionUnless(localAssetCount >= 1) - // constructs the IBC data packet - const packet = { - type: MessageType.CreatePool, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) + const poolId = getPoolId(store.chainID(), counterPartyChainId, denoms); -} + const found = await k.getInterchainLiquidityPool(poolId); -function singleAssetDeposit(msg MsgSingleDepositRequest) { + abortTransactionUnless(found) - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.token.amount > 0) + // Validate message + const portValidationErr = host.PortIdentifierValidator(msg.SourcePort); - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) + abortTransactionUnless(portValidationErr === undefined) - const balance = bank.queryBalance(msg.sender, msg.token.denom) - // should have enough balance - abortTransactionUnless(balance.amount >= msg.token.amount) + const channelValidationErr = host.ChannelIdentifierValidator(msg.SourceChannel); - // the first initial - if(pool.status == POOL_STATUS_INITIAL) { - const asset = pool.findAssetByDenom(msg.token.denom) - abortTransactionUnless(balance.amount !== asset.amount) - } + abortTransactionUnless(channelValidationErr === undefined) - // deposit assets to the escrowed account - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokens) + const validationErr = msg.ValidateBasic(); - // calculation - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const poolToken = amm.depositSingleAsset(msg.token) + abortTransactionUnless(validationErr === undefined) - // update local pool state, - const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) - assetIn.balance.amount += msg.token.amount - pool.supply.amount += poolToken.amount - store.savePool(pool) + abortTransactionUnless(store.hasSupply(msg.liquidity[0].balance.denom)) - // pool token should be minted and sent onAcknowledgement. - // constructs the IBC data packet - const packet = { - type: MessageType.Deposit, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { in: [msg.token], [poolToken] } - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) -} + const sourceLiquidity = store.GetBalance(msg.creator, msg.liquidity[0].balance.denom); + abortTransactionUnless(sourceLiquidity.amount > msg.liquidity[0].balance.amount) -function multiAssetDeposit(msg MsgMultiAssetDepositRequest) { - - abortTransactionUnless(msg.localDeposit.sender != null) - abortTransactionUnless(msg.localDeposit.token != null) - abortTransactionUnless(msg.remoteDeposit.sender != null) - abortTransactionUnless(msg.remoteDeposit.token != null) - abortTransactionUnless(msg.remoteDeposit.signature != null) - abortTransactionUnless(msg.remoteDeposit.sequence != null) - - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) - - const balance = bank.queryBalance(sender, msg.localDeposit.token.denom) - // should have enough balance - abortTransactionUnless(balance.amount >= msg.localDeposit.token.amount) - - // check the ratio of local amount and remote amount - const localAssetInPool := pool.findAssetByDenom(msg.localDeposit.token.denom) - const remoteAssetInPool := pool.findAssetByDenom(msg.remoteDeposit.token.denom) - abortTransactionUnless(msg.localDeposit.token.amount/msg.remoteDeposit.token.amount !== localAssetInPool.amount/remoteAssetInPool.amount) - - // deposit assets to the escrowed account - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokens) - - // calculation - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const poolTokens = amm.depositMultiAsset([msg.localDeposit.token, msg.remoteDeposit.token]) // should replace with doubleDeposit() ? - - // update local pool state, - const assetIn = pool.findAssetByDenom(msg.localDeposit.token.denom) - assetIn.balance.amount += msg.localDeposit.token.amount - const assetIn2 = pool.findAssetByDenom(msg.remoteDeposit.token.denom) - assetIn2.balance.amount += msg.remoteDeposit.token.amount - pool.supply.amount += poolToken.amount - store.savePool(pool) - - // constructs the IBC data packet - const packet = { - type: MessageType.DoubleDeposit, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { in: [msg.localDeposit, msg.remoteDeposit], poolTokens }, - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) -} -function singleAssetWithdraw(msg MsgWithdrawRequest) { + const lockErr = store.lockTokens(msg.sourcePort, msg.sourceChannel, senderAddress, msg.liquidity[0].balance); + + abortTransactionUnless(lockErr === undefined) - const pool = store.findPoolById(msg.poolToken.denom) - abortTransactionUnless(pool != null) - abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) + const packet: IBCSwapPacketData = { + type: "MAKE_POOL", + data: protobuf.encode(msg), + stateChange: { + poolId: poolId, + sourceChainId: store.ChainID(), + }, + }; - const outToken = this.pool.findAssetByDenom(msg.denomOut) - abortTransactionUnless(outToken != null) - abortTransactionUnless(outToken.poolSide == PoolSide.Native) + const sendPacketErr = await store.sendIBCSwapPacket(msg.sourcePort, msg.sourceChannel, timeoutHeight, timeoutStamp, packet); - // lock pool token to the swap module - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) + abortTransactionUnless(sendPacketErr === undefined) + return { + poolId + }; + } - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const outToken = amm.withdraw(msg.poolToken) + function takePool(msg: MsgTakePoolRequest): MsgTakePoolResponse { + const { pool, found } = await store.getInterchainLiquidityPool(msg.PoolId); + abortTransactionUnless(found) - // constructs the IBC data packet - const packet = { - type: MessageType.Withdraw, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { - poolToken: msg.poolToken, - out: [outToken], - } - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) + abortTransactionUnless(pool.SourceChainId !== store.ChainID()) + abortTransactionUnless(pool.DestinationCreator === msg.Creator) + + const creatorAddr = sdk.MustAccAddressFromBech32(msg.Creator); + + const asset = pool.FindAssetBySide("SOURCE"); + abortTransactionUnless(asset) + + const liquidity = store.GetBalance(creatorAddr, asset.denom); + abortTransactionUnless(liquidity.amount > 0) + + + const lockErr = store.LockTokens(pool.counterPartyPort, pool.counterPartyChannel, creatorAddr, asset); + abortTransactionUnless(lockErr === undefined) + + const packet: IBCSwapPacketData = { + type: "TAKE_POOL", + data: protobuf.encode(msg), + }; + + + const sendPacketErr = await store.SendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + abortTransactionUnless(sendPacketErr === undefined) + + return { + poolId: msg.PoolId, + }; + } + + function makeMultiAssetDeposit(msg: MsgMakeMultiAssetDepositRequest): MsgMultiAssetDepositResponse { + + const { pool, found } = await store.getInterchainLiquidityPool(msg.poolId); + + abortTransactionUnless(found) + + // Check initial deposit condition + abortTransactionUnless(pool.status === "ACTIVE") + + // Check input ratio of tokens + const sourceAsset = pool.findAssetBySide("SOURCE"); + abortTransactionUnless(sourceAsset) + + const destinationAsset = pool.findAssetBySide("DESTINATION"); + abortTransactionUnless(destinationAsset) + + const currentRatio = sourceAsset.amount.Mul(sdk.NewInt(1e18)).Quo(destinationAsset.amount); + const inputRatio = msg.deposits[0].balance.amount.Mul(sdk.NewInt(1e18)).Quo(msg.deposits[1].balance.amount); + + const slippageErr = checkSlippage(currentRatio, inputRatio, 10); + abortTransactionUnless(slippageErr) + + // Create escrow module account here + const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, sdk.msg.deposits[0].sender, msg.deposits[0].balance); + abortTransactionUnless(lockErr) + + const amm = new InterchainMarketMaker(pool); + + const poolTokens = await amm.depositMultiAsset([ + msg.deposits[0].balance, + msg.deposits[1].balance, + ]); + + // create order + const order: MultiAssetDepositOrder = { + poolId: msg.poolId; + chainId: store.chainID(), + sourceMaker: msg.deposits[0].sender, + destinationTaker: msg.deposits[1].sender, + deposits: getCoinsFromDepositAssets(msg.deposits), + status: "PENDING"; + createdAt: store.blockHeight(), + }; + + // save order in source chain + store.appendMultiDepositOrder(pool.Id, order); + + const packet: IBCSwapPacketData = { + type: "MAKE_MULTI_DEPOSIT", + data: protobuf.encode(msg), + stateChange: { poolTokens: poolTokens }, + }; + + const sendPacketErr = await store.sendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + abortTransactionUnless(sendPacketErr === undefined) + + return { poolTokens }; + } + + + function takeMultiAssetDeposit(msg: MsgTakeMultiAssetDepositRequest): MsgMultiAssetDepositResponse { + + // check pool exist or not + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found) + + // check order exist or not + const { order, found } = store.getMultiDepositOrder(msg.poolId, msg.orderId); + abortTransactionUnless(found) + + abortTransactionUnless(order.chainId !== store.chainID()) + abortTransactionUnless(msg.sender === order.destinationTaker) + abortTransactionUnless(order.status !== "COMPLETE") + + // estimate pool token + const amm = new InterchainMarketMaker(pool); + const poolTokens = await amm.depositMultiAsset(order.deposits); + + // check asset owned status + const asset = order.deposits[1]; + const balance = store.getBalance(msg.sender, asset.denom); + abortTransactionUnless(balance.amount < asset.amount) + + + // Create escrow module account here + const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel,msg.sender, asset); + abortTransactionUnless(lockErr === undefined) + + const packet: IBCSwapPacketData = { + type: "TAKE_MULTI_DEPOSIT", + data: protobuf.encode(msg), + stateChange: { poolTokens }, + }; + + + const sendPacketErr = await store.sendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + abortTransactionUnless(sendPacketErr === undefined) + + return {}; } -function multiAssetWithdraw(msg MsgMultiAssetWithdrawRequest) { - - abortTransactionUnless(msg.localWithdraw.sender != null) - abortTransactionUnless(msg.remoteWithdraw.sender != null) - abortTransactionUnless(msg.localWithdraw.poolToken != null) - abortTransactionUnless(msg.remoteWithdraw.poolToken != null) - - const pool = store.findPoolById(msg.localWithdraw.poolToken.denom) - abortTransactionUnless(pool != null) - abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - - const outToken = this.pool.findAssetByDenom(msg.denomOut) - abortTransactionUnless(outToken != null) - abortTransactionUnless(outToken.poolSide == PoolSide.Native) - - // lock pool token to the swap module - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.poolToken) - - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const outTokens = amm.multiAssetWithdraw([msg.localDeposit.poolToken, msg.remoteDeposit.poolToken]) - - // update local pool state, - const assetOut = pool.findAssetByDenom(msg.denomOut) - assetOut.balance.amount -= outToken.amount - pool.supply.amount -= poolToken.amount - store.savePool(pool) - - // constructs the IBC data packet - const packet = { - type: MessageType.Withdraw, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { - poolTokens: [msg.localDeposit.poolToken, msg.remoteDeposit.poolToken] , - out: outTokens, - } - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) +function singleAssetDeposit(msg: MsgSingleAssetDepositRequest): MsgSingleAssetDepositResponse { + // Validate message + const validationErr = msg.validateBasic(); + abortTransactionUnless(validationErr === undefined); + + // Check if pool exists + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); + + // Deposit token to escrow account + const balance = store.getBalance(msg.sender, msg.token.denom); + abortTransactionUnless(balance.amount.gt(sdk.NewInt(0))); + // Check pool status + abortTransactionUnless(pool.status === "ACTIVE"); + + // Lock tokens in escrow account + const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, sdk.NewCoins(msg.token)); + abortTransactionUnless(lockErr === undefined); + + const amm = new InterchainMarketMaker(pool); + + const poolToken = await amm.depositSingleAsset(msg.token); + if (poolToken === undefined) { + throw new Error("Failed to deposit single asset."); + } + + const packet: IBCSwapPacketData = { + type: "SINGLE_DEPOSIT", + data: protobuf.encode(msg);, + stateChange: { poolTokens: [poolToken] }, + }; + + const sendPacketErr = await store.sendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + abortTransactionUnless(sendPacketErr === undefined); + + return { poolToken: pool.supply }; } -function leftSwap(msg MsgSwapRequest) { - - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) - abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) - abortTransactionUnless(msg.slippage > 0) - abortTransactionUnless(msg.recipient != null) - - const pool = store.findPoolById([msg.tokenIn.denom, msg.tokenOut.denom]) - abortTransactionUnless(pool != null) - abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - - // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn.denom) - - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut) - // TODO add slippage check here. - - // update local pool state, - const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) - assetIn.balance.amount += msg.tokenIn.amount - const assetOut = pool.findAssetByDenom(msg.tokenOut.denom) - assetOut.balance.amount -= outToken.amount - store.savePool(pool) - - // contructs the IBC data packet - const packet = { - type: MessageType.Swap, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: { out: [outToken] } - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) +function multiAssetWithdraw(msg: MsgMultiAssetWithdrawRequest): MsgMultiAssetWithdrawResponse { + // Validate message + const validationErr = msg.validateBasic(); + abortTransactionUnless(validationErr === undefined); + + // Check if pool token denom exists + const poolTokenDenom = msg.poolToken.denom; + abortTransactionUnless(store.bankKeeper.hasSupply(poolTokenDenom)); + + // Get the liquidity pool + const { pool, found } = k.getInterchainLiquidityPool(ctx, poolTokenDenom); + abortTransactionUnless(found); + + const amm = new InterchainMarketMaker(pool); + + const outs = await amm.multiAssetWithdraw(msg.poolToken); + abortTransactionUnless(outs === undefined); + + const packet: IBCSwapPacketData = { + type: "MULTI_WITHDRAW", + data: protobuf.encode(msg), + stateChange: { + out: outs, + poolTokens: [msg.poolToken], + }, + }; + + const sendPacketErr = await k.sendIBCSwapPacket(ctx, pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + abortTransactionUnless(sendPacketErr === undefined); + + return {}; } -function rightSwap(msg MsgRightSwapRequest) { - - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) - abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) - abortTransactionUnless(msg.slippage > 0) - abortTransactionUnless(msg.recipient != null) - - const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom]) - abortTransactionUnless(pool != null) - abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - - // lock swap-in token to the swap module - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(msg.sender, escrowAddr, msg.tokenIn) - - const amm = new InterchainMarketMaker(pool, params.getPoolFeeRate()) - const inAmount = amm.rightSwap(msg.tokenIn, msg.tokenOut) - abortTransactionUnless(msg.tokenIn > inAmount) - // TODO add slippage check here. - - // update local pool state, - const assetIn = pool.findAssetByDenom(msg.tokenIn.denom) - assetIn.balance.amount += msg.tokenIn.amount - const assetOut = pool.findAssetByDenom(msg.denomOut) - assetOut.balance.amount -= outToken.amount - store.savePool(pool) - - // contructs the IBC data packet - const packet = { - type: MessageType.Rightswap, - data: protobuf.encode(msg), // encode the request message to protobuf bytes. - stateChange: {out: [msg.TokenOut] } - } - sendInterchainIBCSwapDataPacket(packet, msg.sourcePort, msg.sourceChannel, msg.timeoutHeight, msg.timeoutTimestamp) +function swap(msg: MsgSwapRequest): MsgSwapResponse { + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); + + abortTransactionUnless(pool.status === "ACTIVE"); + + const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, msg.tokenIn); + abortTransactionUnless(lockErr === undefined); + + const amm = new InterchainMarketMaker(pool); + + let tokenOut: sdk.Coin | undefined; + let msgType: SwapMessageType; + + switch (msg.swapType) { + case "LEFT": + msgType = "LEFT_SWAP"; + tokenOut = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); + break; + case "RIGHT": + msgType = "RIGHT_SWAP"; + tokenOut = amm.rightSwap(msg.tokenIn, msg.tokenOut); + break; + default: + abortTransactionUnless(false); + } + + + abortTransactionUnless(tokenOut?.amount? <= 0); + + const factor = MaximumSlippage - msg.slippage; + const expected = msg.tokenOut.amount + .mul(sdk.NewIntFromUint64(factor)) + .quo(sdk.NewIntFromUint64(MaximumSlippage)); + + abortTransactionUnless(tokenOut?.amount?.gte(expected)); + + const packet: IBCSwapPacketData = { + type: msgType, + data: protobuf.encode(msg), + stateChange: { out: [tokenOut] }, + }; + + const sendPacketErr = store.sendIBCSwapPacket( + pool.counterPartyPort, + pool.counterPartyChannel, + timeoutHeight, + timeoutTimestamp, + packet + ); + abortTransactionUnless(sendPacketErr === undefined); + + return { + swapType: msg.swapType, + tokens: [msg.tokenIn, msg.tokenOut], + }; } ``` These are methods that handle packets relayed from a source chain, and includes pool state updates and token transfers. In this way, packets relayed on the source chain update pool states on the destination chain. ```ts -function onCreatePoolReceived(msg: MsgCreatePoolRequest, destPort: string, destChannel: string): MsgCreatePoolResponse { - - // Only two assets in a pool - abortTransactionUnless(msg.denoms.length != 2) - abortTransactionUnless(msg.decimals.length != 2) - abortTransactionUnless(msg.weight.split(':').length != 2) // weight format: "50:50" - abortTransactionUnless( !store.hasPool(generatePoolId(msg.denoms)) ) - - // construct mirror pool on destination chain - const pool = new InterchainLiquidityPool(msg.denoms, msg.decimals, msg.weight, destPort, destChannel) - - // count native tokens - const count = 0 - for(var denom in msg.denoms) { - if bank.hasSupply(ctx, denom) { - count += 1 - pool.updateAssetPoolSide(denom, PoolSide.Native) - } else { - pool.updateAssetPoolSide(denom, PoolSide.Remote) - } - } - // only one token (could be either native or IBC token) is validate - abortTransactionUnless(count == 1) +function onMakePoolReceived(msg: MsgMakePoolRequest, poolID: string, sourceChainId: string): string { + abortTransactionUnless(msg.validateBasic() === undefined); + const { pool, found } = store.getInterchainLiquidityPool(poolID); + abortTransactionUnless(msg.validateBasic() === undefined); + + const liquidityBalance = msg.liquidity[1].balance; + if (!store.bankKeeper.hasSupply(liquidityBalance.denom)) { + throw new Error(`Invalid decimal pair: ${types.ErrFailedOnDepositReceived}`); + } - store.savePool(pool) + const interchainLiquidityPool = new InterchainLiquidityPool( + poolID, + msg.creator, + msg.counterPartyCreator, + store.bankKeeper, + msg.liquidity, + msg.swapFee, + msg.sourcePort, + msg.sourceChannel + ); + interchainLiquidityPool.sourceChainId = sourceChainId; + + const interchainMarketMaker = new InterchainMarketMaker(interchainLiquidityPool); + interchainLiquidityPool.poolPrice = interchainMarketMaker.lpPrice(); + + store.setInterchainLiquidityPool(interchainLiquidityPool); + return poolID; +} - return { - poolId: pool.id, - } +function onTakePoolReceived(msg: MsgTakePoolRequest): string { + abortTransactionUnless(msg.validateBasic() === undefined); + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); + + pool.status = "ACTIVE"; + const asset = pool.findPoolAssetBySide("DESTINATION"); + abortTransactionUnless(asset === undefined); + + const totalAmount = pool.sumOfPoolAssets(); + const mintAmount = totalAmount.mul(sdk.NewInt(asset.weight)).quo(sdk.NewInt(100)); + + store.mintTokens(pool.sourceCreator, new sdk.Coin(pool.supply.denom, mintAmount)); + store.setInterchainLiquidityPool(pool); + return pool.id; } -function onSingleAssetDepositReceived(msg: MsgSingleAssetDepositRequest, state: StateChange): MsgSingleAssetDepositResponse { +function onSingleAssetDepositReceived( + msg: MsgSingleAssetDepositRequest, + stateChange: StateChange +): MsgSingleAssetDepositResponse { + abortTransactionUnless(msg.validateBasic() === undefined); + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) + pool.addPoolSupply(stateChange.poolTokens[0]); + pool.addAsset(msg.token); - if (pool.Status == PoolStatus_POOL_STATUS_INIT) { - // switch pool status to 'READY' - pool.Status = PoolStatus_POOL_STATUS_READY - } + store.setInterchainLiquidityPool(pool); - // add pool token to keep consistency, no need to mint pool token since the deposit is executed on the source chain. - const assetIn = pool.findAssetByDenom(state.in[0].denom) - assetIn.balance.amount += state.in[0].amount - pool.supply.amount += state.poolToken[0].amount - store.savePool(pool) + return { + poolToken: stateChange.poolTokens[0], + }; +} - return { poolToken: state.poolToken } +function onMakeMultiAssetDepositReceived( + msg: MsgMakeMultiAssetDepositRequest, + stateChange: StateChange +): MsgMultiAssetDepositResponse { + abortTransactionUnless(msg.validateBasic() === undefined); + const [senderPrefix, , err] = bech32.decode(msg.deposits[1].sender); + abortTransactionUnless(store.getConfig().getBech32AccountAddrPrefix() !== senderPrefix); + + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); + + const order: MultiAssetDepositOrder = { + poolId: msg.poolId, + chainId: pool.sourceChainId, + sourceMaker: msg.deposits[0].sender, + destinationTaker: msg.deposits[1].sender, + deposits: getCoinsFromDepositAssets(msg.deposits), + status: "PENDING", + createdAt: store.blockHeight(), + }; + + store.appendMultiDepositOrder(msg.poolId, order); + + return { + poolTokens: stateChange.poolTokens, + }; } +function onTakeMultiAssetDepositReceived( + msg: MsgTakeMultiAssetDepositRequest, + stateChange: StateChange +): MsgMultiAssetDepositResponse { + abortTransactionUnless(msg.validateBasic() === undefined); -function onMultiAssetDepositReceived(msg: MsgMultiAssetDepositRequest, state: StateChange): MsgMultiAssetDepositResponse { + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); - abortTransactionUnless(msg.remoteDeposit.sender != null) - abortTransactionUnless(msg.remoteDeposit.token != null) + const { order, found: orderFound } = store.getMultiDepositOrder(msg.poolId, msg.orderId); + abortTransactionUnless(orderFound); + order.status = "COMPLETE"; - const pool = store.findPoolById(msg.poolId) - abortTransactionUnless(pool != null) + for (const supply of stateChange.poolTokens) { + pool.addPoolSupply(supply); + } - // Remove it , since calulation move to source chain - const amm = store.findAmmById(msg.poolId) - if(amm !== null) { - // fetch fee rate from the params module, maintained by goverance - const feeRate = params.getPoolFeeRate() - const amm = new InterchainMarketMaker(pool, feeRate) - } - // */ + for (const asset of order.deposits) { + pool.addAsset(asset); + } - // verify signature - const sender = account.GetAccount(msg.remoteDeposit.sender) - abortTransactionUnless(sender != null) - abortTransactionUnless(msg.remoteDeposit.sequence != senderGetSequence()) + const totalPoolToken = sdk.NewCoin(msg.poolId, sdk.NewInt(0)); + for (const poolToken of stateChange.poolTokens) { + totalPoolToken.amount = totalPoolToken.amount.add(poolToken.amount); + } - const remoteDeposit = { - sender: sender.GetAddress(); - sequence: sender.GetSequence(); - token: msg.remoteDeposit.Token; - } - const encoder = new TextEncoder(); - const rawRemoteDepositTx = encoder.encode(JSON.stringify(remoteDeposit)); - const pubKey = account.GetPubKey() - const isValid = pubKey.VerifySignature(rawRemoteDepositTx, msg.remoteDeposit.signature) - abortTransactionUnless(isValid != false) - - if (pool.Status == PoolStatus_POOL_STATUS_INIT) { - // switch pool status to 'READY' - pool.Status = PoolStatus_POOL_STATUS_READY - } + store.mintTokens(order.sourceMaker, totalPoolToken); - // deposit remote token - const poolTokens = amm.multiAssetDeposit([msg.localDeposit.token, msg.remoteDeposit.token]) + store.setInterchainLiquidityPool(pool); + store.setMultiDepositOrder(pool.id, order); - // update counterparty state - state.in.forEach(in => { - const assetIn = pool.findAssetByDenom(in.denom) - assetIn.balance.amount += in.amount - }) - state.poolTokens.forEach(lp => { - pool.supply.amount += lp.amount - }) - store.savePool(amm.pool) // update pool states + return {}; +} + +function onMultiAssetWithdrawReceived( + msg: MsgMultiAssetWithdrawRequest, + stateChange: StateChange +): MsgMultiAssetWithdrawResponse { + abortTransactionUnless(msg.validateBasic() === undefined); + const { pool, found } = store.getInterchainLiquidityPool(msg.poolToken.denom); + abortTransactionUnless(found); - // mint voucher token - bank.mintCoin(MODULE_NAME, state.poolTokens[1]) - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.remoteDeposit.sender, poolTokens[1]) + for (const poolAsset of stateChange.out) { + pool.subtractAsset(poolAsset); + } - return { poolToken } + for (const poolToken of stateChange.poolTokens) { + pool.subtractPoolSupply(poolToken); + } + + store.unlockTokens( + pool.counterPartyPort, + pool.counterPartyChannel, + msg.counterPartyReceiver, + sdk.NewCoins(stateChange.out[1]) + ); + + if (pool.supply.amount.isZero()) { + store.removeInterchainLiquidityPool(pool.id); + } else { + store.setInterchainLiquidityPool(pool); + } + + return { tokens: stateChange.out }; } -function onWithdrawReceived(msg: MsgWithdrawRequest, state: StateChange) MsgWithdrawResponse { - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.denomOut != null) - abortTransactionUnless(msg.poolCoin.amount > 0) +function onSwapReceived(msg: MsgSwapRequest, stateChange: StateChange): MsgSwapResponse { + abortTransactionUnless(msg.validateBasic() === undefined); - const pool = store.findPoolById(msg.poolCoin.denom) - abortTransactionUnless(pool != null) + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); + store.unlockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.recipient, sdk.NewCoins(stateChange.out[0])); - // update counterparty state - state.out.forEech(out => { - const assetOut = pool.findAssetByDenom(out.denom) - assetOut.balance.amount += out.amount - }) - pool.supply.amount -= state.poolToken[0].amount - store.savePool(amm.pool) // update pool states + pool.subtractAsset(stateChange.out[0]); + pool.addAsset(msg.tokenIn); - // the outToken will sent to msg's sender in `onAcknowledgement()` + store.setInterchainLiquidityPool(pool); - return { tokens: outToken } + return { tokens: stateChange.out }; } -function onLeftSwapReceived(msg: MsgSwapRequest, state: StateChange) MsgSwapResponse { +function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { + const pool = new InterchainLiquidityPool( + ctx, + msg.creator, + msg.counterPartyCreator, + k.bankKeeper, + poolId, + msg.liquidity, + msg.swapFee, + msg.sourcePort, + msg.sourceChannel + ); + + pool.sourceChainId = store.chainID(); + + const totalAmount = sdk.NewInt(0); + for (const asset of msg.liquidity) { + totalAmount = totalAmount.add(asset.balance.amount); + } - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) - abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) - abortTransactionUnless(msg.slippage > 0) - abortTransactionUnless(msg.recipient != null) + store.mintTokens(msg.creator, { + denom: pool.supply.denom, + amount: totalAmount.mul(msg.liquidity[0].weight).quo(100), + }); - const pool = store.findPoolById(generatePoolId([tokenIn.denom, denomOut])) - abortTransactionUnless(pool != null) - // fetch fee rate from the params module, maintained by goverance - const feeRate = params.getPoolFeeRate() + const amm = new InterchainMarketMaker(pool); + pool.poolPrice = amm.lpPrice(); - const amm = new InterchainMarketMaker(pool, feeRate) - const outToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom) + store.setInterchainLiquidityPool(pool); +} - const expected = msg.tokenOut.amount +function onTakePoolAcknowledged(msg: MsgTakePoolRequest): void { + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); - // tolerance check - abortTransactionUnless(outToken.amount > expected * (1 - msg.slippage / 10000)) + const amm = new InterchainMarketMaker(pool); + pool.poolPrice = amm.lpPrice(); + pool.status = "ACTIVE"; - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(escrowAddr, msg.recipient, outToken) + store.setInterchainLiquidityPool(pool); +} - store.savePool(amm.pool) // update pool states +function onSingleAssetDepositAcknowledged(req: MsgSingleAssetDepositRequest, res: MsgSingleAssetDepositResponse): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); - return { tokens: outToken } -} + store.mintTokens(req.sender, res.poolToken); -function onRightSwapReceived(msg MsgRightSwapRequest, state: StateChange) MsgSwapResponse { + pool.addAsset(req.token); + pool.addPoolSupply(res.poolToken); - abortTransactionUnless(msg.sender != null) - abortTransactionUnless(msg.tokenIn != null && msg.tokenIn.amount > 0) - abortTransactionUnless(msg.tokenOut != null && msg.tokenOut.amount > 0) - abortTransactionUnless(msg.slippage > 0) - abortTransactionUnless(msg.recipient != null) + store.setInterchainLiquidityPool(pool); +} - const pool = store.findPoolById(generatePoolId[tokenIn.denom, tokenOut.denom])) - abortTransactionUnless(pool != null) - abortTransactionUnless(pool.status == PoolStatus.POOL_STATUS_READY) - // fetch fee rate from the params module, maintained by goverance - const feeRate = params.getPoolFeeRate() +function onMakeMultiAssetDepositAcknowledged( + req: MsgMakeMultiAssetDepositRequest, + res: MsgMultiAssetDepositResponse +): void { + const { pool, found } = k.getInterchainLiquidityPool(ctx, req.poolId); + abortTransactionUnless(found); + store.setInterchainLiquidityPool(pool); +} - const amm = new InterchainMarketMaker(pool, feeRate) - const minTokenIn = amm.rightSwap(msg.tokenIn, msg.tokenOut) +function onTakeMultiAssetDepositAcknowledged(req: MsgTakeMultiAssetDepositRequest, stateChange: StateChange): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); - // tolerance check - abortTransactionUnless(tokenIn.amount > minTokenIn.amount) - abortTransactionUnless((tokenIn.amount - minTokenIn.amount)/minTokenIn.amount > msg.slippage / 10000)) + const order = store.getMultiDepositOrder(req.poolId, req.orderId); + abortTransactionUnless(order.found); - const escrowAddr = escrowAddress(pool.counterpartyPort, pool.counterpartyChannel) - bank.sendCoins(escrowAddr, msg.recipient, msg.tokenOut) + for (const poolToken of stateChange.poolTokens) { + pool.addPoolSupply(poolToken); + } - store.savePool(amm.pool) // update pool states + for (const deposit of order.deposits) { + pool.addAsset(deposit); + } + + order.status = "COMPLETE"; - return { tokens: minTokenIn } + store.setInterchainLiquidityPool(pool); + store.setMultiDepositOrder(pool.id, order); } -``` -Acknowledgement is used by the source chain to check if the transaction has succeeded or not. +function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { + const pool = new InterchainLiquidityPool( + ctx, + msg.creator, + msg.counterPartyCreator, + k.bankKeeper, + poolId, + msg.liquidity, + msg.swapFee, + msg.sourcePort, + msg.sourceChannel + ); + + pool.sourceChainId = store.chainID(); + + const totalAmount = sdk.NewInt(0); + for (const asset of msg.liquidity) { + totalAmount = totalAmount.add(asset.balance.amount); + } -```ts -function onCreatePoolAcknowledged(request: MsgCreatePoolRequest, response: MsgCreatePoolResponse) { - // do nothing + store.mintTokens(ctx, msg.creator, { + denom: pool.supply.denom, + amount: totalAmount.mul(sdk.NewInt(Number(msg.liquidity[0].weight))).quo(sdk.NewInt(100)), + }); + + const amm = new InterchainMarketMaker(pool); + pool.poolPrice = amm.lpPrice(); + + store.setInterchainLiquidityPool(pool); } -function onSingleAssetDepositAcknowledged( - request: MsgSingleAssetDepositRequest, - response: MsgSingleAssetDepositResponse -) { - bank.mintCoin(MODULE_NAME, request.sender, response.token); - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.sender, response.tokens); +function onTakePoolAcknowledged(msg: MsgTakePoolRequest): void { + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); + abortTransactionUnless(found); + const amm = new InterchainMarketMaker(pool); + pool.poolPrice = amm.lpPrice(); + pool.status = "ACTIVE"; + store.setInterchainLiquidityPool(pool); } -function onMultiAssetDepositAcknowledged(request: MsgMultiAssetDepositRequest, response: MsgMultiAssetDepositResponse) { - bank.mintCoin(MODULE_NAME, response.tokens[0]); - bank.sendCoinsFromModuleToAccount(MODULE_NAME, msg.localDeposit.sender, response.tokens[0]); +function onSingleAssetDepositAcknowledged(req: MsgSingleAssetDepositRequest, res: MsgSingleAssetDepositResponse): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); + + store.mintTokens(req.sender, res.poolToken); + + pool.addAsset(req.token); + pool.addPoolSupply(res.poolToken); + + store.setInterchainLiquidityPool(pool); } -function onWithdrawAcknowledged(request: MsgWithdrawRequest, response: MsgWithdrawResponse) { - bank.burnCoin(MODULE_NAME, response.token); +function onMakeMultiAssetDepositAcknowledged( + req: MsgMakeMultiAssetDepositRequest, + res: MsgMultiAssetDepositResponse +): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); + store.setInterchainLiquidityPool(pool); } -function onLeftSwapAcknowledged(request: MsgSwapRequest, response: MsgSwapResponse) {} +function onTakeMultiAssetDepositAcknowledged(req: MsgTakeMultiAssetDepositRequest, stateChange: StateChange): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); + const order, + found = store.getMultiDepositOrder(req.poolId, req.orderId); + abortTransactionUnless(found); + for (const poolToken of stateChange.poolTokens) { + pool.addPoolSupply(poolToken); + } + + for (const deposit of order.deposits) { + pool.addAsset(deposit); + } -function onRightSwapAcknowledged(request: MsgRightSwapRequest, response: MsgSwapResponse) {} + order.status = "COMPLETE"; + + store.setInterchainLiquidityPool(pool); + store.setMultiDepositOrder(pool.id, order); +} ``` ## RISKS From ddbf972cb8a23cad0bb9f8d0fe776ea393835586 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Wed, 5 Jul 2023 13:38:37 +0300 Subject: [PATCH 46/72] chore: onReceive packet update --- spec/app/ics-101-interchain-swap/README.md | 99 ++++++++++++---------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index f18eea387..d7146e54d 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -517,51 +517,62 @@ function sendInterchainIBCSwapDataPacket( `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. ```ts -function onRecvPacket(packet: Packet) { - - IBCSwapPacketData swapPacket = packet.data - // construct default acknowledgement of success - const ack: IBCSwapDataAcknowledgement = new IBCSwapDataPacketSuccess() - - try{ - switch swapPacket.type { - case CreatePool: - var msg: MsgCreatePoolRequest = protobuf.decode(swapPacket.data) - onCreatePoolReceived(msg, packet.destPortId, packet.destChannelId) - break - case SingleAssetDeposit: - var msg: MsgSingleDepositRequest = protobuf.decode(swapPacket.data) - onSingleDepositReceived(msg) - break - - case MultiAssetDeposit: - var msg: MsgDoubleDepositRequest = protobuf.decode(swapPacket.data) - onDoubleDepositReceived(msg) - break - - case SingleAssetWithdraw: - var msg: MsgSingleAssetWithdrawRequest = protobuf.decode(swapPacket.data) - onSingleAssetWithdrawReceived(msg) - break - case MultiAssetWithdraw: - var msg: MsgMultiAssetWithdrawRequest = protobuf.decode(swapPacket.data) - onMultiAssetWithdrawReceived(msg) - break - case Swap: - var msg: MsgSwapRequest = protobuf.decode(swapPacket.data) - if(msg.SwapType === SwapType.Left) { - onLeftSwapReceived(msg) - }else{ - onRightSwapReceived(msg) - } - break - } - } catch { - ack = new IBCSwapDataPacketError() - } +function OnRecvPacket(packet: Packet, data: IBCSwapPacketData): Uint8Array | undefined { + switch (data.type) { + case "MAKE_POOL": + const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); + abortTransactionUnless(data.stateChange.poolId === ""); + const poolId = store.OnMakePoolReceived(makePoolMsg, data.stateChange.poolId, data.stateChange.sourceChainId); + const makePoolRes = protobuf.encode({ poolId }); + return makePoolRes; + + case "TAKE_POOL": + const takePoolMsg: MsgTakePoolRequest = protobuf.decode(MsgTakePoolRequest, data.Data); + const takePoolRes = store.OnTakePoolReceived(takePoolMsg); + const takePoolResEncoded = protobuf.encode({ poolId: takePoolRes }); + return takePoolResEncoded; + + case "SINGLE_DEPOSIT": + const singleDepositMsg: MsgSingleAssetDepositRequest = protobuf.decode(MsgSingleAssetDepositRequest, data.Data); + abortTransactionUnless(data.stateChange.poolId === ""); + const singleDepositRes = store.OnSingleAssetDepositReceived(singleDepositMsg, data.stateChange); + const singleDepositResEncoded = protobuf.encode(singleDepositRes); + return singleDepositResEncoded; + + case "MAKE_MULTI_DEPOSIT": + const makeMultiDepositMsg: MsgMakeMultiAssetDepositRequest = protobuf.decode( + MsgMakeMultiAssetDepositRequest, + data.Data + ); + const makeMultiDepositRes = k.OnMakeMultiAssetDepositReceived(makeMultiDepositMsg, data.stateChange); + const makeMultiDepositResEncoded = protobuf.encode(makeMultiDepositRes); + return makeMultiDepositResEncoded; + + case "TAKE_MULTI_DEPOSIT": + const takeMultiDepositMsg: MsgTakeMultiAssetDepositRequest = protobuf.decode( + MsgTakeMultiAssetDepositRequest, + data.Data + ); + const takeMultiDepositRes = k.OnTakeMultiAssetDepositReceived(takeMultiDepositMsg, data.stateChange); + const takeMultiDepositResEncoded = protobuf.encode(takeMultiDepositRes); + return takeMultiDepositResEncoded; + + case "MULTI_WITHDRAW": + const multiWithdrawMsg: MsgMultiAssetWithdrawRequest = protobuf.decode(MsgMultiAssetWithdrawRequest, data.Data); + const multiWithdrawRes = k.OnMultiAssetWithdrawReceived(multiWithdrawMsg, data.stateChange); + const multiWithdrawResEncoded = protobuf.encode(multiWithdrawRes); + return multiWithdrawResEncoded; + + case "LEFT_SWAP": + case "RIGHT_SWAP": + const swapMsg: MsgSwapRequest = protobuf.decode(MsgSwapRequest, data.Data); + const swapRes = k.OnSwapReceived(swapMsg, data.stateChange); + const swapResEncoded = protobuf.encode(swapRes); + return swapResEncoded; - // NOTE: acknowledgement will be written synchronously during IBC handler execution. - return ack + default: + return; + } } ``` From f6dfc6d69ebdeb0e97d003a69a2f8c0b52a2e82a Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Wed, 5 Jul 2023 13:53:30 +0300 Subject: [PATCH 47/72] chore: refund packet update --- spec/app/ics-101-interchain-swap/README.md | 77 +++++++++++++++------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index d7146e54d..1ce238a63 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -656,33 +656,62 @@ function onTimeoutPacket(packet: Packet) { ``` ```ts -// TODO: need to decode the subpacket from packet -function refundToken(packet: Packet) { - const msg = packet.data.Data.toJSON() - let token - let sender - switch packet.type { - case Create: - token = msg.tokens[0] - case Swap: - token = msg.tokenIn - sender = msg.sender +function refundPacketToken(packet: Packet, data: IBCSwapPacketData): Error | undefined { + let token: Coin | undefined; + let sender: string | undefined; + + switch (data.type) { + case "MAKE_POOL": + const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); + // Refund initial liquidity + sender = makePoolMsg.creator; + token = makePoolMsg.liquidity[0].balance; + break; + + case "SINGLE_DEPOSIT": + const singleDepositMsg: MsgSingleAssetDepositRequest = protobuf.decode(MsgSingleAssetDepositRequest, data.Data); + token = singleDepositMsg.token; + sender = singleDepositMsg.sender; + break; + + case "MAKE_MULTI_DEPOSIT": + const makeMultiDepositMsg: MsgMakeMultiAssetDepositRequest = protobuf.decode( + MsgMakeMultiAssetDepositRequest, + data.Data + ); + token = makeMultiDepositMsg.deposits[0].balance; + sender = makeMultiDepositMsg.deposits[0].sender; + break; + + case "TAKE_MULTI_DEPOSIT": + const takeMultiDepositMsg: MsgTakeMultiAssetDepositRequest = protobuf.decode( + MsgTakeMultiAssetDepositRequest, + data.Data + ); + const { order, found } = store.getMultiDepositOrder(takeMultiDepositMsg.poolId, takeMultiDepositMsg.orderId); + abortTransactionUnless(found); + token = order.Deposits[1]; + sender = msg.Sender; break; - case SingleAssetDeposit: - token = msg.token - sender = msg.sender + case "MULTI_WITHDRAW": + const multiWithdrawMsg: MsgMultiAssetWithdrawRequest = protobuf.decode(MsgMultiAssetWithdrawRequest, data.Data); + token = multiWithdrawMsg.poolToken; + sender = multiWithdrawMsg.receiver; break; - case MultiAssetDeposit: - token = msg.localDeposit.token - sender = msg.localDeposit.sender + + case "RIGHT_SWAP": + const swapMsg: MsgSwapRequest = protobuf.decode(MsgSwapRequest, data.Data); + token = swapMsg.tokenIn; + sender = swapMsg.sender; break; - case SingleAssetWithdraw: - token = packet.pool_token - case MultiAssetWithdraw: - token = packet.localWithdraw.pool_token - } - escrowAccount = channelEscrowAddresses[packet.srcChannel] - bank.TransferCoins(escrowAccount, sender, token.denom, token.amount) + + default: + return; + } + + const escrowAccount = getEscrowAddress(packet.sourcePort, packet.sourceChannel); + const err = store.sendCoins(escrowAccount, sender, token); + return err; } ``` From 93501af1c7c282105e7de0af86f65594f2ead2db Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 6 Jul 2023 12:46:48 +0300 Subject: [PATCH 48/72] feat: resolve opened problems --- spec/app/ics-101-interchain-swap/README.md | 168 ++++++++++++++++----- 1 file changed, 133 insertions(+), 35 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 1ce238a63..9a8316b7d 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -117,11 +117,16 @@ The pool can be fully funded through the initial deposit or through subsequent d ```ts interface Coin { - amount: int32; + amount: int64; denom: string; } ``` +```ts +const Multiplier = 1e18; +const MaximumSlippage = 10000; +``` + ```ts enum PoolAssetSide { Source = 1; @@ -159,9 +164,9 @@ interface InterchainLiquidityPool { status: PoolStatus; encounterPartyPort: string; encounterPartyChannel: string; - constructor(denoms: []string, decimals: []number, weights: []number,swapFee: number, portId string, channelId string) { + constructor(id:string, denoms: []string, decimals: []number, weights: []number,swapFee: number, portId string, channelId string) { - this.id = generatePoolId(denoms) + this.id = id this.supply = { amount: 0, denom: this.id @@ -185,14 +190,80 @@ interface InterchainLiquidityPool { } } } + + function findDenomBySide(side: PoolAssetSide): string | undefined { + for (const asset of this.pool.assets) { + if (asset.side === side) { + return asset.balance.denom; + } + } + return; } -``` -```ts -function generatePoolId(denoms: string[]) { - return "pool" + sha256(denoms.sort().join("")); +function findAssetBySide(side: PoolAssetSide): Coin | undefined { + for (const asset of this.pool.assets) { + if (asset.side === side) { + return asset.balance; + } + } + throw undefined; +} + +function findPoolAssetBySide(side: PoolAssetSide): PoolAsset | undefined { + for (const asset of this.pool.assets) { + if (asset.side === side) { + return asset; + } + } + return; +} + +function updateAssetPoolSide(denom: string, side: PoolAssetSide): PoolAsset | undefined { + for (const asset of this.pool.assets) { + if (asset.balance.denom === denom) { + asset.side = side; + return asset; + } + } + return undefined; +} + +function addAsset(token: Coin): void { + for (const asset of this.pool.assets) { + if (asset.balance.denom === token.denom) { + asset.balance.amount += token.amount; + return; + } + } +} + +function subtractAsset(token: Coin): Coin | undefined { + for (const asset of this.pool.assets) { + if (asset.balance.denom === token.denom) { + asset.balance.amount -=token.amount; + return asset.balance; + } + } + return +} + +function addPoolSupply(token: Coin): void { + if (token.denom !== this.pool.id) { + return + } + this.supply.amount += token.amount; } +function subtractPoolSupply(token: Coin): void { + if (token.denom !== this.pool.id) { + return + } + ilp.supply.amount -= token.amount; +} +} +``` + +```ts function generatePoolId(sourceChainId: string, destinationChainId: string, denoms: string[]): string { const connectionId: string = getConnectID([sourceChainId, destinationChainId]); denoms.sort(); @@ -265,23 +336,19 @@ class InterchainMarketMaker { let issueAmount: Int; if (imm.Pool.Status === PoolStatus_INITIALIZED) { - let totalAssetAmount = new Int(0); + let totalAssetAmount = 0; for (const asset of imm.Pool.Assets) { - totalAssetAmount = totalAssetAmount.Add(asset.Balance.Amount); + totalAssetAmount = totalAssetAmount+asset.balance.amount; } - issueAmount = totalAssetAmount.Mul(new Int(asset.Weight)).Quo(new Int(100)); + issueAmount = totalAssetAmount*asset.Weight/100; } else { - const decToken = new DecCoinFromCoin(token); - const decAsset = new DecCoinFromCoin(asset.Balance); - const decSupply = new DecCoinFromCoin(imm.Pool.Supply); - - const ratio = decToken.Amount.Quo(decAsset.Amount).Mul(new Dec(Multiplier)); - issueAmount = decSupply.Amount.Mul(new Dec(asset.Weight)).Mul(ratio).Quo(new Dec(100)).Quo(new Dec(Multiplier)).RoundInt(); + const ratio = token.amount/asset.balance/Multiplier; + issueAmount = supply.amount*asset.weight*ratio/100/Multiplier; } const outputToken: Coin = { Amount: issueAmount, - Denom: imm.Pool.Supply.Denom, + Denom: imm.pool.supply.denom, }; outTokens.push(outputToken); } @@ -294,12 +361,12 @@ class InterchainMarketMaker { multiAssetWithdraw(redeem: Coin): Coin[] { const outs: Coin[] = []; - if (redeem.Amount.GT(imm.Pool.Supply.Amount)) { + if (redeem.amount>imm.pool.supply.amount) { throw new Error("Overflow amount"); } - for (const asset of imm.Pool.Assets) { - const out = asset.Balance.Amount.Mul(redeem.Amount).Quo(imm.Pool.Supply.Amount); + for (const asset of imm.pool.assets) { + const out = asset.balance.amount*redeem.amount/imm.pool.supply.amount; const outputCoin: Coin = { Denom: asset.Balance.Denom, Amount: out, @@ -316,10 +383,10 @@ class InterchainMarketMaker { function leftSwap(amountIn: Coin, denomOut: string): Coin { const assetIn = this.pool.findAssetByDenom(amountIn.denom) - abortTransactionUnless(assetIn != null) + abortTransactionUnless(assetIn !== undefined) const assetOut = this.pool.findAssetByDenom(denomOut) - abortTransactionUnless(assetOut != null) + abortTransactionUnless(assetOut !== undefined) // redeem.weight is percentage const balanceOut = assetOut.balance.amount @@ -351,7 +418,7 @@ class InterchainMarketMaker { const weightIn = assetIn.weight / 100 const weightOut = assetOut.weight / 100 - const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1) + const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1)) abortTransactionUnless(amountIn.amount > amount) @@ -362,9 +429,42 @@ class InterchainMarketMaker { } // amount - amount * feeRate / 10000 - function minusFees(amount sdk.Int) sdk.Int { + function minusFees(amount:number):number { return amount * (1 - this.pool.feeRate / 10000)) } + + function invariant(): number { + let v = 1.0; + for (const asset of imm.pool.assets) { + const decimal = Math.pow(10,asset.decimal); + const balance = asset.balance.amount/decimal; + const w = asset.weight / 100.0; + v *= Math.pow(balance, w); + } + return v; + } + + function invariantWithInput(tokenIn: Coin): number { + let v = 1.0; + for (const asset of imm.pool.assets) { + const decimal = Math.pow(10,asset.decimal); + let balance: number; + if (tokenIn.denom !== asset.balance.denom) { + balance = asset.balance.amount/decimal; + } else { + balance = (asset.balance.amount + tokenIn.amount)/decimal; + } + + const w = asset.weight / 100.0; + v *= Math.pow(balance, w); + } + return v; + } + + function lpPrice(): number { + const lpPrice = this.invariant() / imm.pool.supply.amount; + return lpPrice; + } } ``` @@ -385,7 +485,7 @@ enum MessageType { interface StateChange { in: Coin[]; - out: Out[]; + out: Coin[]; poolTokens: Coin[]; poolId: string; multiDepositOrderId: string; @@ -952,8 +1052,8 @@ These are methods that output a state change on the source chain, which will be const destinationAsset = pool.findAssetBySide("DESTINATION"); abortTransactionUnless(destinationAsset) - const currentRatio = sourceAsset.amount.Mul(sdk.NewInt(1e18)).Quo(destinationAsset.amount); - const inputRatio = msg.deposits[0].balance.amount.Mul(sdk.NewInt(1e18)).Quo(msg.deposits[1].balance.amount); + const currentRatio = sourceAsset.amount*Multiplier/destinationAsset.amount; + const inputRatio = msg.deposits[0].balance.amount*.Multiplier/msg.deposits[1].balance.amount; const slippageErr = checkSlippage(currentRatio, inputRatio, 10); abortTransactionUnless(slippageErr) @@ -977,7 +1077,7 @@ These are methods that output a state change on the source chain, which will be destinationTaker: msg.deposits[1].sender, deposits: getCoinsFromDepositAssets(msg.deposits), status: "PENDING"; - createdAt: store.blockHeight(), + createdAt: store.blockTime(), }; // save order in source chain @@ -1143,9 +1243,7 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { abortTransactionUnless(tokenOut?.amount? <= 0); const factor = MaximumSlippage - msg.slippage; - const expected = msg.tokenOut.amount - .mul(sdk.NewIntFromUint64(factor)) - .quo(sdk.NewIntFromUint64(MaximumSlippage)); + const expected = msg.tokenOut.amount*factor/MaximumSlippage; abortTransactionUnless(tokenOut?.amount?.gte(expected)); @@ -1213,7 +1311,7 @@ function onTakePoolReceived(msg: MsgTakePoolRequest): string { abortTransactionUnless(asset === undefined); const totalAmount = pool.sumOfPoolAssets(); - const mintAmount = totalAmount.mul(sdk.NewInt(asset.weight)).quo(sdk.NewInt(100)); + const mintAmount = (totalAmount * asset.weight) / 100; store.mintTokens(pool.sourceCreator, new sdk.Coin(pool.supply.denom, mintAmount)); store.setInterchainLiquidityPool(pool); @@ -1323,7 +1421,7 @@ function onMultiAssetWithdrawReceived( sdk.NewCoins(stateChange.out[1]) ); - if (pool.supply.amount.isZero()) { + if (pool.supply.amount == 0) { store.removeInterchainLiquidityPool(pool.id); } else { store.setInterchainLiquidityPool(pool); @@ -1369,7 +1467,7 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { store.mintTokens(msg.creator, { denom: pool.supply.denom, - amount: totalAmount.mul(msg.liquidity[0].weight).quo(100), + amount: (totalAmount * msg.liquidity[0].weight) / 100, }); const amm = new InterchainMarketMaker(pool); @@ -1453,7 +1551,7 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { store.mintTokens(ctx, msg.creator, { denom: pool.supply.denom, - amount: totalAmount.mul(sdk.NewInt(Number(msg.liquidity[0].weight))).quo(sdk.NewInt(100)), + amount: (totalAmount * msg.liquidity[0].weight) / 100, }); const amm = new InterchainMarketMaker(pool); From a07ca574fe40c0f136c5ab24eee0aff6558de149 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Fri, 7 Jul 2023 16:38:42 +0800 Subject: [PATCH 49/72] remove some functions --- spec/app/ics-101-interchain-swap/README.md | 44 ++++++++-------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 9a8316b7d..c8859ed04 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -122,11 +122,6 @@ interface Coin { } ``` -```ts -const Multiplier = 1e18; -const MaximumSlippage = 10000; -``` - ```ts enum PoolAssetSide { Source = 1; @@ -164,8 +159,8 @@ interface InterchainLiquidityPool { status: PoolStatus; encounterPartyPort: string; encounterPartyChannel: string; + constructor(id:string, denoms: []string, decimals: []number, weights: []number,swapFee: number, portId string, channelId string) { - this.id = id this.supply = { amount: 0, @@ -265,22 +260,11 @@ function subtractPoolSupply(token: Coin): void { ```ts function generatePoolId(sourceChainId: string, destinationChainId: string, denoms: string[]): string { - const connectionId: string = getConnectID([sourceChainId, destinationChainId]); - denoms.sort(); - - const poolIdHash = createHash("sha256"); - denoms.push(connectionId); - poolIdHash.update(denoms.join("")); - - const poolId = "pool" + poolIdHash.digest("hex"); + const chainPrefix = [sourceChainId, destinationChainId].sort().join(',') + const id = chainPrefx.concat(denoms.sort().join(',')) + const poolId = "pool" + sha256.hash(id); return poolId; } - -function getConnectID(chainIds: string[]): string { - // Generate poolId - chainIds.sort(); - return chainIds.join("/"); -} ``` #### Interchain Market Maker @@ -342,8 +326,8 @@ class InterchainMarketMaker { } issueAmount = totalAssetAmount*asset.Weight/100; } else { - const ratio = token.amount/asset.balance/Multiplier; - issueAmount = supply.amount*asset.weight*ratio/100/Multiplier; + const ratio = token.amount/asset.balance; + issueAmount = supply.amount*asset.weight*ratio/100; } const outputToken: Coin = { @@ -433,6 +417,7 @@ class InterchainMarketMaker { return amount * (1 - this.pool.feeRate / 10000)) } + /// Can we remove these 3 functions? don't calculate price on backend side. function invariant(): number { let v = 1.0; for (const asset of imm.pool.assets) { @@ -622,6 +607,7 @@ function OnRecvPacket(packet: Packet, data: IBCSwapPacketData): Uint8Array | und case "MAKE_POOL": const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); abortTransactionUnless(data.stateChange.poolId === ""); + abortTransactionUnless(store.has(data.stateChange.poolId)) // existed already. const poolId = store.OnMakePoolReceived(makePoolMsg, data.stateChange.poolId, data.stateChange.sourceChainId); const makePoolRes = protobuf.encode({ poolId }); return makePoolRes; @@ -943,9 +929,9 @@ interface MsgSwapResponse { These are methods that output a state change on the source chain, which will be subsequently synced to the destination chain. ```ts - function makePool(msg: MsgMakePoolRequest): Promise { + function makePool(msg: MsgMakePoolRequest): MsgMakePoolResponse { - const { counterPartyChainId, connected } = await store.GetCounterPartyChainID(msg.sourcePort, msg.sourceChannel); + const { counterPartyChainId, connected } = store.GetCounterPartyChainID(msg.sourcePort, msg.sourceChannel); abortTransactionUnless(connected) @@ -1052,8 +1038,8 @@ These are methods that output a state change on the source chain, which will be const destinationAsset = pool.findAssetBySide("DESTINATION"); abortTransactionUnless(destinationAsset) - const currentRatio = sourceAsset.amount*Multiplier/destinationAsset.amount; - const inputRatio = msg.deposits[0].balance.amount*.Multiplier/msg.deposits[1].balance.amount; + const currentRatio = sourceAsset.amount/destinationAsset.amount; + const inputRatio = msg.deposits[0].balance.amount/msg.deposits[1].balance.amount; const slippageErr = checkSlippage(currentRatio, inputRatio, 10); abortTransactionUnless(slippageErr) @@ -1242,8 +1228,7 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { abortTransactionUnless(tokenOut?.amount? <= 0); - const factor = MaximumSlippage - msg.slippage; - const expected = msg.tokenOut.amount*factor/MaximumSlippage; + const expected = msg.tokenOut.amount * (1 - msg.slippage); abortTransactionUnless(tokenOut?.amount?.gte(expected)); @@ -1295,7 +1280,8 @@ function onMakePoolReceived(msg: MsgMakePoolRequest, poolID: string, sourceChain interchainLiquidityPool.sourceChainId = sourceChainId; const interchainMarketMaker = new InterchainMarketMaker(interchainLiquidityPool); - interchainLiquidityPool.poolPrice = interchainMarketMaker.lpPrice(); + // remove it? + // interchainLiquidityPool.poolPrice = interchainMarketMaker.lpPrice(); store.setInterchainLiquidityPool(interchainLiquidityPool); return poolID; From 5e07beb7cc6d65c328af75bf8de4fce2834a6dfe Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Sat, 8 Jul 2023 10:37:57 +0800 Subject: [PATCH 50/72] remove price --- spec/app/ics-101-interchain-swap/README.md | 50 +--------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index c8859ed04..e1e0cf439 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -416,40 +416,6 @@ class InterchainMarketMaker { function minusFees(amount:number):number { return amount * (1 - this.pool.feeRate / 10000)) } - - /// Can we remove these 3 functions? don't calculate price on backend side. - function invariant(): number { - let v = 1.0; - for (const asset of imm.pool.assets) { - const decimal = Math.pow(10,asset.decimal); - const balance = asset.balance.amount/decimal; - const w = asset.weight / 100.0; - v *= Math.pow(balance, w); - } - return v; - } - - function invariantWithInput(tokenIn: Coin): number { - let v = 1.0; - for (const asset of imm.pool.assets) { - const decimal = Math.pow(10,asset.decimal); - let balance: number; - if (tokenIn.denom !== asset.balance.denom) { - balance = asset.balance.amount/decimal; - } else { - balance = (asset.balance.amount + tokenIn.amount)/decimal; - } - - const w = asset.weight / 100.0; - v *= Math.pow(balance, w); - } - return v; - } - - function lpPrice(): number { - const lpPrice = this.invariant() / imm.pool.supply.amount; - return lpPrice; - } } ``` @@ -1278,11 +1244,6 @@ function onMakePoolReceived(msg: MsgMakePoolRequest, poolID: string, sourceChain msg.sourceChannel ); interchainLiquidityPool.sourceChainId = sourceChainId; - - const interchainMarketMaker = new InterchainMarketMaker(interchainLiquidityPool); - // remove it? - // interchainLiquidityPool.poolPrice = interchainMarketMaker.lpPrice(); - store.setInterchainLiquidityPool(interchainLiquidityPool); return poolID; } @@ -1456,9 +1417,6 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { amount: (totalAmount * msg.liquidity[0].weight) / 100, }); - const amm = new InterchainMarketMaker(pool); - pool.poolPrice = amm.lpPrice(); - store.setInterchainLiquidityPool(pool); } @@ -1466,8 +1424,6 @@ function onTakePoolAcknowledged(msg: MsgTakePoolRequest): void { const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); abortTransactionUnless(found); - const amm = new InterchainMarketMaker(pool); - pool.poolPrice = amm.lpPrice(); pool.status = "ACTIVE"; store.setInterchainLiquidityPool(pool); @@ -1540,17 +1496,13 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { amount: (totalAmount * msg.liquidity[0].weight) / 100, }); - const amm = new InterchainMarketMaker(pool); - pool.poolPrice = amm.lpPrice(); - store.setInterchainLiquidityPool(pool); } function onTakePoolAcknowledged(msg: MsgTakePoolRequest): void { const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); abortTransactionUnless(found); - const amm = new InterchainMarketMaker(pool); - pool.poolPrice = amm.lpPrice(); + pool.status = "ACTIVE"; store.setInterchainLiquidityPool(pool); } From 2e1696edb9634f900fbfec2e107272cb905ce738 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Sat, 8 Jul 2023 15:00:51 +0300 Subject: [PATCH 51/72] chore: change blockTime and exposer port and channel --- spec/app/ics-101-interchain-swap/README.md | 38 ++++++++++++++-------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index e1e0cf439..ac16bb855 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -159,7 +159,7 @@ interface InterchainLiquidityPool { status: PoolStatus; encounterPartyPort: string; encounterPartyChannel: string; - + constructor(id:string, denoms: []string, decimals: []number, weights: []number,swapFee: number, portId string, channelId string) { this.id = id this.supply = { @@ -260,8 +260,8 @@ function subtractPoolSupply(token: Coin): void { ```ts function generatePoolId(sourceChainId: string, destinationChainId: string, denoms: string[]): string { - const chainPrefix = [sourceChainId, destinationChainId].sort().join(',') - const id = chainPrefx.concat(denoms.sort().join(',')) + const chainPrefix = [sourceChainId, destinationChainId].sort().join(","); + const id = chainPrefx.concat(denoms.sort().join(",")); const poolId = "pool" + sha256.hash(id); return poolId; } @@ -573,7 +573,7 @@ function OnRecvPacket(packet: Packet, data: IBCSwapPacketData): Uint8Array | und case "MAKE_POOL": const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); abortTransactionUnless(data.stateChange.poolId === ""); - abortTransactionUnless(store.has(data.stateChange.poolId)) // existed already. + abortTransactionUnless(store.has(data.stateChange.poolId)); // existed already. const poolId = store.OnMakePoolReceived(makePoolMsg, data.stateChange.poolId, data.stateChange.sourceChainId); const makePoolRes = protobuf.encode({ poolId }); return makePoolRes; @@ -807,6 +807,8 @@ interface MsgMakePoolResponse { interface MsgTakePoolRequest { creator: string; poolId: string; + port: string; + channel: string; timeHeight: TimeHeight; timeoutTimeStamp: uint64; } @@ -821,6 +823,8 @@ interface MsgSingleAssetDepositRequest { poolId: string; sender: string; token: Coin; // only one element for now, might have two in the feature + port: string; + channel: string; timeHeight: TimeHeight; timeoutTimeStamp: uint64; } @@ -839,6 +843,8 @@ interface MsgMakeMultiAssetDepositRequest { poolId: string; deposits: DepositAsset[]; token: Coin; // only one element for now, might have two in the feature + port: string; + channel: string; timeHeight: TimeHeight; timeoutTimeStamp: uint64; } @@ -847,6 +853,8 @@ interface MsgTakeMultiAssetDepositRequest { sender: string; poolId: string; orderId: uint64; + port: string; + channel: string; timeHeight: TimeHeight; timeoutTimeStamp: uint64; } @@ -862,6 +870,8 @@ interface MsgMultiAssetWithdrawRequest { receiver: string; counterPartyReceiver: string; poolToken: Coin; + port: string; + channel: string; timeHeight: TimeHeight; timeoutTimeStamp: uint64; } @@ -880,6 +890,8 @@ interface MsgSwapRequest { tokenOut: Coin; slippage: uint64; recipient: string; + port: string; + channel: string; timeHeight: TimeHeight; timeoutTimeStamp: uint64; } @@ -971,7 +983,7 @@ These are methods that output a state change on the source chain, which will be abortTransactionUnless(liquidity.amount > 0) - const lockErr = store.LockTokens(pool.counterPartyPort, pool.counterPartyChannel, creatorAddr, asset); + const lockErr = store.LockTokens(msg.port, pool.channel, creatorAddr, asset); abortTransactionUnless(lockErr === undefined) const packet: IBCSwapPacketData = { @@ -1011,7 +1023,7 @@ These are methods that output a state change on the source chain, which will be abortTransactionUnless(slippageErr) // Create escrow module account here - const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, sdk.msg.deposits[0].sender, msg.deposits[0].balance); + const lockErr = store.lockTokens(msg.port, msg.channel, sdk.msg.deposits[0].sender, msg.deposits[0].balance); abortTransactionUnless(lockErr) const amm = new InterchainMarketMaker(pool); @@ -1084,7 +1096,7 @@ These are methods that output a state change on the source chain, which will be }; - const sendPacketErr = await store.sendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + const sendPacketErr = await store.sendIBCSwapPacket(msg.port, msg.channel, timeoutHeight, timeoutStamp, packet); abortTransactionUnless(sendPacketErr === undefined) return {}; @@ -1123,7 +1135,7 @@ function singleAssetDeposit(msg: MsgSingleAssetDepositRequest): MsgSingleAssetDe stateChange: { poolTokens: [poolToken] }, }; - const sendPacketErr = await store.sendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + const sendPacketErr = await store.sendIBCSwapPacket(msg.port, msg.channel, timeoutHeight, timeoutStamp, packet); abortTransactionUnless(sendPacketErr === undefined); return { poolToken: pool.supply }; @@ -1157,7 +1169,7 @@ function multiAssetWithdraw(msg: MsgMultiAssetWithdrawRequest): MsgMultiAssetWit }, }; - const sendPacketErr = await k.sendIBCSwapPacket(ctx, pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); + const sendPacketErr = await k.sendIBCSwapPacket(ctx, msg.port, msg.channel, timeoutHeight, timeoutStamp, packet); abortTransactionUnless(sendPacketErr === undefined); return {}; @@ -1205,8 +1217,8 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { }; const sendPacketErr = store.sendIBCSwapPacket( - pool.counterPartyPort, - pool.counterPartyChannel, + msg.port, + msg.channel, timeoutHeight, timeoutTimestamp, packet @@ -1301,7 +1313,7 @@ function onMakeMultiAssetDepositReceived( destinationTaker: msg.deposits[1].sender, deposits: getCoinsFromDepositAssets(msg.deposits), status: "PENDING", - createdAt: store.blockHeight(), + createdAt: store.blockTime(), }; store.appendMultiDepositOrder(msg.poolId, order); @@ -1502,7 +1514,7 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { function onTakePoolAcknowledged(msg: MsgTakePoolRequest): void { const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); abortTransactionUnless(found); - + pool.status = "ACTIVE"; store.setInterchainLiquidityPool(pool); } From 0808559e93d24d388ce6145fd6ad46af85246b20 Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Tue, 29 Aug 2023 09:25:19 +0800 Subject: [PATCH 52/72] Update spec/app/ics-101-interchain-swap/README.md Co-authored-by: Carlos Rodriguez --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index ac16bb855..2de35f018 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -22,7 +22,7 @@ Features include an option to provide liquidity with a single asset instead of a ### Definitions -`Interchain swap`: a IBC token swap protocol, built on top of an automated market making model, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. +`Interchain swap`: an IBC token swap protocol, built on top of an automated market making model, which leverages liquidity pools and incentives. Each chain that integrates this app becomes part of a decentralized exchange network. `Interchain liquidity pool`: a single-asset liquidity pool on a chain, with a corresponding single-asset liquidity pool on a separate chain. This comprises an interchain liquidity pool and can execute interchain swaps to exchange between the assets. From df73ab26fe2e7af0dba2808b307ba07c5fcaf252 Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Tue, 29 Aug 2023 09:25:32 +0800 Subject: [PATCH 53/72] Update spec/app/ics-101-interchain-swap/README.md Co-authored-by: Carlos Rodriguez --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 2de35f018..6e5bd5e32 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -30,7 +30,7 @@ Features include an option to provide liquidity with a single asset instead of a `Weighted pools`: liquidity pools characterized by the percentage weight of each token denomination maintained within. -`Single-asset deposit`: a deposit into a liquidity pool that does not require users to deposit both token denominations -- one is enough. While this deposit method will impact the exchange price of the pool, it also provide an opportunity for arbitrage. +`Single-asset deposit`: a deposit into a liquidity pool that does not require users to deposit both token denominations -- one is enough. While this deposit method will impact the exchange price of the pool, it also provides an opportunity for arbitrage. `Multi-asset deposit`: a deposit into a liquidity pool that require users to deposit both token denominations. This deposit method will not impact the exchange price of the pool. From 05b9a27775bf9a691b14301b69d1999f655bc21b Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Tue, 29 Aug 2023 09:25:42 +0800 Subject: [PATCH 54/72] Update spec/app/ics-101-interchain-swap/README.md Co-authored-by: Carlos Rodriguez --- spec/app/ics-101-interchain-swap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 6e5bd5e32..4f0b793ae 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -48,7 +48,7 @@ Features include an option to provide liquidity with a single asset instead of a - `Permissionless`: no need to whitelist connections, modules, or denominations. Individual implementations may have their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure. - `Decentralized`: all parameters are managed on chain via governance. Does not require any central authority or entity to function. Also does not require a single blockchain, acting as a hub, to function. -- `Gaurantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. +- `Guarantee of Exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange. - `Liquidity Incentives`: supports the collection of fees which are distributed to liquidity providers and acts as incentive for liquidity participation. - `Weighted Math`: allows the configuration of pool weights so users can choose their levels of exposure between the tokens. From b578c90764d71d8a48e3c823ec7c6b75f6e82488 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Tue, 29 Aug 2023 20:00:16 +0300 Subject: [PATCH 55/72] fix: fix pointed problems --- spec/app/ics-101-interchain-swap/README.md | 157 +++++++++++---------- 1 file changed, 86 insertions(+), 71 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 4f0b793ae..9c21df59f 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -124,16 +124,16 @@ interface Coin { ```ts enum PoolAssetSide { - Source = 1; - Destination = 2; + Source = 0; + Destination = 1; } ``` ```ts // PoolStatus defines if the pool is ready for trading enum PoolStatus { - INITIALIZED = 0; - ACTIVE = 1; + POOL_STATUS_INITIAL = 0; + POOL_STATUS_READY = 1; } ``` @@ -160,8 +160,17 @@ interface InterchainLiquidityPool { encounterPartyPort: string; encounterPartyChannel: string; - constructor(id:string, denoms: []string, decimals: []number, weights: []number,swapFee: number, portId string, channelId string) { - this.id = id + constructor( + poolId:string, + creator:string, + counterPartyCreator:string, + store, + sourceLiquidity:[]PoolAsset, + destinationLiquidity:[]PoolAsset, + swapFee: number, + sourcePort: string, + sourceChannel: string) { + this.id = poolId this.supply = { amount: 0, denom: this.id @@ -171,19 +180,7 @@ interface InterchainLiquidityPool { this.encounterPartyChannel = channelId this.swapFee = swapFee // construct assets - if(denoms.length === decimals.lenght && denoms.length === weight.length) { - for(let i=0; i < denoms.lenght; i++) { - this.assets.push({ - side: store.hasSupply(denom[i]) ? PoolAssetSide.Source: PoolAssetSide.Destination, - balance: { - amount: 0, - denom: denom[i], - }, - weight: weights[i], - decimal: decimals[i], - }) - } - } + this.assets = [...sourceLiquidity,...destinationLiquidity] } function findDenomBySide(side: PoolAssetSide): string | undefined { @@ -213,6 +210,15 @@ function findPoolAssetBySide(side: PoolAssetSide): PoolAsset | undefined { return; } + function findDenomByDenom(denom: string): Coin | undefined { + for (const asset of this.pool.assets) { + if (asset.amount.denom === denom) { + return asset.amount; + } + } + return; +} + function updateAssetPoolSide(denom: string, side: PoolAssetSide): PoolAsset | undefined { for (const asset of this.pool.assets) { if (asset.balance.denom === denom) { @@ -450,18 +456,19 @@ interface IBCSwapDataPacket { type: MessageType, data: []byte, // Bytes stateChange: StateChange + memo: string, } ``` ```typescript -type IBCSwapDataAcknowledgement = IBCSwapDataPacketSuccess | IBCSwapDataPacketError; +type IInterchainSwapPacketAcknowledgement = InterchainSwapPacketSuccess | InterchainSwapPacketError; -interface IBCSwapDataPacketSuccess { +interface InterchainSwapPacketSuccess { // This is binary 0x01 base64 encoded result: "AQ=="; } -interface IBCSwapDataPacketError { +interface InterchainSwapPacketError { error: string; } ``` @@ -568,7 +575,8 @@ function sendInterchainIBCSwapDataPacket( `onRecvPacket` is called by the routing module when a packet addressed to this module has been received. ```ts -function OnRecvPacket(packet: Packet, data: IBCSwapPacketData): Uint8Array | undefined { +function OnRecvPacket(packet: Packet): Uint8Array | undefined { + const data: IBCSwapPacketData = packet.data; switch (data.type) { case "MAKE_POOL": const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); @@ -789,12 +797,12 @@ interface MsgMakePoolRequest { sourceChannel: string; creator: string; counterPartyCreator: string; - liquidity: PoolAsset[]; + sourceLiquidity: PoolAsset[]; + destinationLiquidity: PoolAsset[]; sender: string; - denoms: string[]; decimals: int32[]; swapFee: int32; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } @@ -809,7 +817,7 @@ interface MsgTakePoolRequest { poolId: string; port: string; channel: string; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } @@ -825,7 +833,7 @@ interface MsgSingleAssetDepositRequest { token: Coin; // only one element for now, might have two in the feature port: string; channel: string; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } interface MsgSingleDepositResponse { @@ -845,7 +853,7 @@ interface MsgMakeMultiAssetDepositRequest { token: Coin; // only one element for now, might have two in the feature port: string; channel: string; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } @@ -855,7 +863,7 @@ interface MsgTakeMultiAssetDepositRequest { orderId: uint64; port: string; channel: string; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } @@ -872,7 +880,7 @@ interface MsgMultiAssetWithdrawRequest { poolToken: Coin; port: string; channel: string; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } @@ -892,7 +900,7 @@ interface MsgSwapRequest { recipient: string; port: string; channel: string; - timeHeight: TimeHeight; + timeHeight: Height; timeoutTimeStamp: uint64; } @@ -918,9 +926,9 @@ These are methods that output a state change on the source chain, which will be denoms.push(liquidity.balance.denom); } - const poolId = getPoolId(store.chainID(), counterPartyChainId, denoms); + const poolId = generatePoolId(store.chainID(), counterPartyChainId, denoms); - const found = await k.getInterchainLiquidityPool(poolId); + const found = await store.getInterchainLiquidityPool(poolId); abortTransactionUnless(found) @@ -1125,9 +1133,7 @@ function singleAssetDeposit(msg: MsgSingleAssetDepositRequest): MsgSingleAssetDe const amm = new InterchainMarketMaker(pool); const poolToken = await amm.depositSingleAsset(msg.token); - if (poolToken === undefined) { - throw new Error("Failed to deposit single asset."); - } + abortTransactionUnless(poolToken === undefined); const packet: IBCSwapPacketData = { type: "SINGLE_DEPOSIT", @@ -1240,7 +1246,7 @@ function onMakePoolReceived(msg: MsgMakePoolRequest, poolID: string, sourceChain const { pool, found } = store.getInterchainLiquidityPool(poolID); abortTransactionUnless(msg.validateBasic() === undefined); - const liquidityBalance = msg.liquidity[1].balance; + const liquidityBalance = msg.sourceLiquidity[0].balance; if (!store.bankKeeper.hasSupply(liquidityBalance.denom)) { throw new Error(`Invalid decimal pair: ${types.ErrFailedOnDepositReceived}`); } @@ -1249,12 +1255,14 @@ function onMakePoolReceived(msg: MsgMakePoolRequest, poolID: string, sourceChain poolID, msg.creator, msg.counterPartyCreator, - store.bankKeeper, - msg.liquidity, + store, + msg.sourceLiquidity, + msg.destinationLiquidity, msg.swapFee, msg.sourcePort, msg.sourceChannel ); + interchainLiquidityPool.sourceChainId = sourceChainId; store.setInterchainLiquidityPool(interchainLiquidityPool); return poolID; @@ -1406,11 +1414,10 @@ function onSwapReceived(msg: MsgSwapRequest, stateChange: StateChange): MsgSwapR function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { const pool = new InterchainLiquidityPool( - ctx, + poolId, msg.creator, msg.counterPartyCreator, - k.bankKeeper, - poolId, + store, msg.liquidity, msg.swapFee, msg.sourcePort, @@ -1483,34 +1490,6 @@ function onTakeMultiAssetDepositAcknowledged(req: MsgTakeMultiAssetDepositReques store.setMultiDepositOrder(pool.id, order); } -function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { - const pool = new InterchainLiquidityPool( - ctx, - msg.creator, - msg.counterPartyCreator, - k.bankKeeper, - poolId, - msg.liquidity, - msg.swapFee, - msg.sourcePort, - msg.sourceChannel - ); - - pool.sourceChainId = store.chainID(); - - const totalAmount = sdk.NewInt(0); - for (const asset of msg.liquidity) { - totalAmount = totalAmount.add(asset.balance.amount); - } - - store.mintTokens(ctx, msg.creator, { - denom: pool.supply.denom, - amount: (totalAmount * msg.liquidity[0].weight) / 100, - }); - - store.setInterchainLiquidityPool(pool); -} - function onTakePoolAcknowledged(msg: MsgTakePoolRequest): void { const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); abortTransactionUnless(found); @@ -1559,6 +1538,42 @@ function onTakeMultiAssetDepositAcknowledged(req: MsgTakeMultiAssetDepositReques store.setInterchainLiquidityPool(pool); store.setMultiDepositOrder(pool.id, order); } + +function onMultiAssetWithdrawAcknowledged(req: MsgTakeMultiAssetDepositRequest, stateChange: StateChange): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); + + // update pool status + for (const out of stateChange.Out) { + pool.subtractAsset(out); + } + pool.subtractPoolSupply(req.poolToken); + + const { nativeToken, found } = pool.FindDenomBySide("PoolAssetSide_SOURCE"); + abortTransactionUnless(found); + + const { out, found } = stateChange.FindOutByDenom(nativeToken); + abortTransactionUnless(found); + + // unlock token + store.UnlockTokens(pool.counterPartyPort, pool.counterPartyChannel, req.Receiver, out); + + if ((pool.supply.amount = 0)) { + store.removeInterchainLiquidityPool(req.PoolId); + } else { + // Save pool + store.setInterchainLiquidityPool(pool); + } +} + +function OnSwapAcknowledged(req: MsgSwapRequest, res: MsgSwapResponse): void { + const { pool, found } = store.getInterchainLiquidityPool(req.poolId); + abortTransactionUnless(found); + // pool status update + pool.addAsset(req.tokenIn); + pool.subtractAsset(req.tokenOut); + store.setInterchainLiquidityPool(pool); +} ``` ## RISKS From 2a6042abdbd18b23567d457627ab0f05b59e2dfb Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Wed, 30 Aug 2023 20:39:49 +0800 Subject: [PATCH 56/72] Update interchain-swap.svg --- spec/app/ics-101-interchain-swap/interchain-swap.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/interchain-swap.svg b/spec/app/ics-101-interchain-swap/interchain-swap.svg index eaec6cca0..bbdbe8c6b 100644 --- a/spec/app/ics-101-interchain-swap/interchain-swap.svg +++ b/spec/app/ics-101-interchain-swap/interchain-swap.svg @@ -1 +1 @@ -Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap PacketUpdate Pool StateSend Token(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool State1. Calculate Swap Output Amount2. Check slippage tolerance3. Send Token4. Update pool states1. Create Swap Order2. Lock  Swap In Assets2. Delegate Swap RequestMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well.Update pool stateExecute SwapUpdate Pool StateOver Slippage Tolerance \ No newline at end of file +Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap PacketUpdate Pool StateSend Token(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool State1. Apply pool changes2. Send Token1. Create Swap Order2. Lock Swap In Assets3. Calculate Swap Output Amount4. Check slippage tolerance5. Send amount changes to counterparty ChainMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well.Update pool stateExecute SwapUpdate Pool StateOver Slippage Tolerance From 418de4c48e9980b9d28d76c0ec8d6a7b80eb7d7e Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Wed, 30 Aug 2023 20:41:57 +0800 Subject: [PATCH 57/72] Update interchain-swap.svg --- spec/app/ics-101-interchain-swap/interchain-swap.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-swap/interchain-swap.svg b/spec/app/ics-101-interchain-swap/interchain-swap.svg index bbdbe8c6b..076a1661f 100644 --- a/spec/app/ics-101-interchain-swap/interchain-swap.svg +++ b/spec/app/ics-101-interchain-swap/interchain-swap.svg @@ -1 +1 @@ -Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap PacketUpdate Pool StateSend Token(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool State1. Apply pool changes2. Send Token1. Create Swap Order2. Lock Swap In Assets3. Calculate Swap Output Amount4. Check slippage tolerance5. Send amount changes to counterparty ChainMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well.Update pool stateExecute SwapUpdate Pool StateOver Slippage Tolerance +Source ChainDestination Chain1.Create Pool & Initial Deposit3.1.Save Pool(On Received)7.1.Ready for swap / issue pool token(On Receive)9.Ready for swap / issue pool token(On Acknowledge)Relayer2.Relay Create Pool Packet4.Ack Creat Pool Packet6. Relay Deposit Packet8. Acknowledge Deposit PacketTimeout3.2.Refund5. Initial/Single Deposit7.2. RefundTimeout1. Multi Deposit8. Acknowledge Multi Asset Deposit Packet3.Execute Deposit Tx(On Received)3.2.Refund6. Relay Deposit PacketMake Swap8. Acknowledge Swap PacketUpdate Pool StateSend Token(On Received)Timeout3.2.Refund6. Relay Swap PacketSingle Withdrawupdate pool state 8. Acknowledge Withdraw Packet3.Update Pool State(On Received)3.2.Refund6. Relay Withdraw PacketClose Order / Update Pool State1. Apply pool changes2. Send Token1. Create Swap Order2. Lock Swap In Assets3. Calculate Swap Output Amount4. Check slippage tolerance5. Send amount changes to counterparty ChainMulti Assets Withdrawupdate pool state8. Acknowledge Withdraw Packet3.Update pool stateWithdraw token(On Received)3.2.Refund6. Relay Withdraw PacketBurn LP TokenWithdraw TokenBurn LP TokenWithdraw Token1. Amount of Initial Deposit was specificed at pool creation2. Single Deposit can execute on source chain as well.Update pool stateExecute SwapUpdate Pool StateOver Slippage Tolerance From abe717aeb9cd8e06d8c56587d59385fd5461dd94 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 31 Aug 2023 12:32:20 +0300 Subject: [PATCH 58/72] chore: encode full package as a bytes --- spec/app/ics-101-interchain-swap/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index 9c21df59f..aaf1a69fd 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -568,7 +568,14 @@ function sendInterchainIBCSwapDataPacket( timeoutTimestamp: uint64 ) { // send packet using the interface defined in ICS4 - handler.sendPacket(getCapability("port"), sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, swapPacket); + handler.sendPacket( + getCapability("port"), + sourcePort, + sourceChannel, + timeoutHeight, + timeoutTimestamp, + protobuf.encode(swapPacket) + ); } ``` @@ -1175,7 +1182,7 @@ function multiAssetWithdraw(msg: MsgMultiAssetWithdrawRequest): MsgMultiAssetWit }, }; - const sendPacketErr = await k.sendIBCSwapPacket(ctx, msg.port, msg.channel, timeoutHeight, timeoutStamp, packet); + const sendPacketErr = await store.sendIBCSwapPacket(ctx, msg.port, msg.channel, timeoutHeight, timeoutStamp, packet); abortTransactionUnless(sendPacketErr === undefined); return {}; From 020b6cfc1726925ecc774672f8ed59543d325803 Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Wed, 20 Sep 2023 13:30:51 +0800 Subject: [PATCH 59/72] Update README.md --- spec/app/ics-101-interchain-swap/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-swap/README.md index aaf1a69fd..d1f2cf4bb 100644 --- a/spec/app/ics-101-interchain-swap/README.md +++ b/spec/app/ics-101-interchain-swap/README.md @@ -1,6 +1,6 @@ --- ics: 101 -title: Interchain Swap +title: Interchain Liquidity stage: draft category: IBC/APP kind: instantiation @@ -16,7 +16,7 @@ This standard document specifies the packet data structure, state machine handli ### Motivation -ICS-101 Interchain Swaps enables chains to have their own token pricing mechanism and exchange protocol via IBC transactions. By enabling their own token pricing mechanism and exchange protocol, each chain can play a role in a fully decentralised exchange network. +ICS-101 Interchain Liquidity enables chains to have their own token pricing mechanism and exchange protocol via IBC transactions. By enabling their own token pricing mechanism and exchange protocol, each chain can play a role in a fully decentralised exchange network. Features include an option to provide liquidity with a single asset instead of a pair, which users might prefer as it reduces the risk of impermanent loss. From d52e10384825dda720ff6db8aaa171154fe30c7a Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 20 Sep 2023 13:41:44 +0800 Subject: [PATCH 60/72] rename to interchain liquidity --- .../README.md | 0 .../interchain-swap.svg | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename spec/app/{ics-101-interchain-swap => ics-101-interchain-liquidity}/README.md (100%) rename spec/app/{ics-101-interchain-swap => ics-101-interchain-liquidity}/interchain-swap.svg (100%) diff --git a/spec/app/ics-101-interchain-swap/README.md b/spec/app/ics-101-interchain-liquidity/README.md similarity index 100% rename from spec/app/ics-101-interchain-swap/README.md rename to spec/app/ics-101-interchain-liquidity/README.md diff --git a/spec/app/ics-101-interchain-swap/interchain-swap.svg b/spec/app/ics-101-interchain-liquidity/interchain-swap.svg similarity index 100% rename from spec/app/ics-101-interchain-swap/interchain-swap.svg rename to spec/app/ics-101-interchain-liquidity/interchain-swap.svg From 2a45dfa828e650e68194d277564f53e4bc6e852a Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 20 Sep 2023 19:30:49 +0800 Subject: [PATCH 61/72] change `liquidity` to `source liquidity` --- .../ics-101-interchain-liquidity/README.md | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index d1f2cf4bb..679d4aa2b 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -587,7 +587,7 @@ function OnRecvPacket(packet: Packet): Uint8Array | undefined { switch (data.type) { case "MAKE_POOL": const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); - abortTransactionUnless(data.stateChange.poolId === ""); + abortTransactionUnless(data.stateChange.poolId !== ""); abortTransactionUnless(store.has(data.stateChange.poolId)); // existed already. const poolId = store.OnMakePoolReceived(makePoolMsg, data.stateChange.poolId, data.stateChange.sourceChainId); const makePoolRes = protobuf.encode({ poolId }); @@ -802,7 +802,6 @@ These sub-protocols handle packets relayed from a source chain, including pool s interface MsgMakePoolRequest { sourcePort: string; sourceChannel: string; - creator: string; counterPartyCreator: string; sourceLiquidity: PoolAsset[]; destinationLiquidity: PoolAsset[]; @@ -820,7 +819,7 @@ interface MsgMakePoolResponse { ```ts interface MsgTakePoolRequest { - creator: string; + sender: string; poolId: string; port: string; channel: string; @@ -934,34 +933,18 @@ These are methods that output a state change on the source chain, which will be } const poolId = generatePoolId(store.chainID(), counterPartyChainId, denoms); - const found = await store.getInterchainLiquidityPool(poolId); - abortTransactionUnless(found) - // Validate message const portValidationErr = host.PortIdentifierValidator(msg.SourcePort); - abortTransactionUnless(portValidationErr === undefined) - const channelValidationErr = host.ChannelIdentifierValidator(msg.SourceChannel); - abortTransactionUnless(channelValidationErr === undefined) - const validationErr = msg.ValidateBasic(); - abortTransactionUnless(validationErr === undefined) - - abortTransactionUnless(store.hasSupply(msg.liquidity[0].balance.denom)) - - - const sourceLiquidity = store.GetBalance(msg.creator, msg.liquidity[0].balance.denom); - - abortTransactionUnless(sourceLiquidity.amount > msg.liquidity[0].balance.amount) - - - const lockErr = store.lockTokens(msg.sourcePort, msg.sourceChannel, senderAddress, msg.liquidity[0].balance); - + const sourceLiquidity = store.GetBalance(msg.creator, msg.sourceLiquidity[0].balance.denom); + abortTransactionUnless(sourceLiquidity.amount > msg.sourceLiquidity[0].balance.amount) + const lockErr = store.lockTokens(msg.sourcePort, msg.sourceChannel, senderAddress, msg.sourceLiquidity[0].balance); abortTransactionUnless(lockErr === undefined) const packet: IBCSwapPacketData = { @@ -997,7 +980,6 @@ These are methods that output a state change on the source chain, which will be const liquidity = store.GetBalance(creatorAddr, asset.denom); abortTransactionUnless(liquidity.amount > 0) - const lockErr = store.LockTokens(msg.port, pool.channel, creatorAddr, asset); abortTransactionUnless(lockErr === undefined) @@ -1006,7 +988,6 @@ These are methods that output a state change on the source chain, which will be data: protobuf.encode(msg), }; - const sendPacketErr = await store.SendIBCSwapPacket(pool.counterPartyPort, pool.counterPartyChannel, timeoutHeight, timeoutStamp, packet); abortTransactionUnless(sendPacketErr === undefined) @@ -1101,7 +1082,7 @@ These are methods that output a state change on the source chain, which will be // Create escrow module account here - const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel,msg.sender, asset); + const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, asset); abortTransactionUnless(lockErr === undefined) const packet: IBCSwapPacketData = { @@ -1251,7 +1232,7 @@ These are methods that handle packets relayed from a source chain, and includes function onMakePoolReceived(msg: MsgMakePoolRequest, poolID: string, sourceChainId: string): string { abortTransactionUnless(msg.validateBasic() === undefined); const { pool, found } = store.getInterchainLiquidityPool(poolID); - abortTransactionUnless(msg.validateBasic() === undefined); + abortTransactionUnless(found === false); const liquidityBalance = msg.sourceLiquidity[0].balance; if (!store.bankKeeper.hasSupply(liquidityBalance.denom)) { @@ -1425,7 +1406,8 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { msg.creator, msg.counterPartyCreator, store, - msg.liquidity, + msg.sourceLiquidity, + msg.destinationLiquidity, msg.swapFee, msg.sourcePort, msg.sourceChannel @@ -1440,7 +1422,7 @@ function onMakePoolAcknowledged(msg: MsgMakePoolRequest, poolId: string): void { store.mintTokens(msg.creator, { denom: pool.supply.denom, - amount: (totalAmount * msg.liquidity[0].weight) / 100, + amount: (totalAmount * msg.sourceliquidity[0].weight) / 100, }); store.setInterchainLiquidityPool(pool); From 2aca657ae765dc53087f2d9386d4381cbcd3f7bf Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Wed, 20 Sep 2023 19:40:00 +0800 Subject: [PATCH 62/72] FIX: use store to get liquidity pool --- spec/app/ics-101-interchain-liquidity/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 679d4aa2b..92ae2bdfc 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -1146,7 +1146,7 @@ function multiAssetWithdraw(msg: MsgMultiAssetWithdrawRequest): MsgMultiAssetWit abortTransactionUnless(store.bankKeeper.hasSupply(poolTokenDenom)); // Get the liquidity pool - const { pool, found } = k.getInterchainLiquidityPool(ctx, poolTokenDenom); + const { pool, found } = store.getInterchainLiquidityPool(msg.poolId); abortTransactionUnless(found); const amm = new InterchainMarketMaker(pool); @@ -1268,7 +1268,8 @@ function onTakePoolReceived(msg: MsgTakePoolRequest): string { const totalAmount = pool.sumOfPoolAssets(); const mintAmount = (totalAmount * asset.weight) / 100; - store.mintTokens(pool.sourceCreator, new sdk.Coin(pool.supply.denom, mintAmount)); + // TODO remove it or add a options for depositor to choose where LP is minted. + // store.mintTokens(pool.sourceCreator, new sdk.Coin(pool.supply.denom, mintAmount)); store.setInterchainLiquidityPool(pool); return pool.id; } From 45a39568a402d41a3a8eb2f87634e3fb4c3c0f1f Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 21 Sep 2023 01:02:25 +0300 Subject: [PATCH 63/72] chore: data marshaling --- .../ics-101-interchain-liquidity/README.md | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 92ae2bdfc..c0fd09e22 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -586,7 +586,7 @@ function OnRecvPacket(packet: Packet): Uint8Array | undefined { const data: IBCSwapPacketData = packet.data; switch (data.type) { case "MAKE_POOL": - const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); + const makePoolMsg: MsgMakePoolRequest = unmarshalJSON(data.Data); abortTransactionUnless(data.stateChange.poolId !== ""); abortTransactionUnless(store.has(data.stateChange.poolId)); // existed already. const poolId = store.OnMakePoolReceived(makePoolMsg, data.stateChange.poolId, data.stateChange.sourceChainId); @@ -594,45 +594,39 @@ function OnRecvPacket(packet: Packet): Uint8Array | undefined { return makePoolRes; case "TAKE_POOL": - const takePoolMsg: MsgTakePoolRequest = protobuf.decode(MsgTakePoolRequest, data.Data); + const takePoolMsg: MsgTakePoolRequest = unmarshalJSON(data.Data); const takePoolRes = store.OnTakePoolReceived(takePoolMsg); const takePoolResEncoded = protobuf.encode({ poolId: takePoolRes }); return takePoolResEncoded; case "SINGLE_DEPOSIT": - const singleDepositMsg: MsgSingleAssetDepositRequest = protobuf.decode(MsgSingleAssetDepositRequest, data.Data); + const singleDepositMsg: MsgSingleAssetDepositRequest = unmarshalJSON(data.Data); abortTransactionUnless(data.stateChange.poolId === ""); const singleDepositRes = store.OnSingleAssetDepositReceived(singleDepositMsg, data.stateChange); const singleDepositResEncoded = protobuf.encode(singleDepositRes); return singleDepositResEncoded; case "MAKE_MULTI_DEPOSIT": - const makeMultiDepositMsg: MsgMakeMultiAssetDepositRequest = protobuf.decode( - MsgMakeMultiAssetDepositRequest, - data.Data - ); + const makeMultiDepositMsg: MsgMakeMultiAssetDepositRequest = unmarshalJSON(data.Data); const makeMultiDepositRes = k.OnMakeMultiAssetDepositReceived(makeMultiDepositMsg, data.stateChange); const makeMultiDepositResEncoded = protobuf.encode(makeMultiDepositRes); return makeMultiDepositResEncoded; case "TAKE_MULTI_DEPOSIT": - const takeMultiDepositMsg: MsgTakeMultiAssetDepositRequest = protobuf.decode( - MsgTakeMultiAssetDepositRequest, - data.Data - ); + const takeMultiDepositMsg: MsgTakeMultiAssetDepositRequest = unmarshalJSON(data.Data); const takeMultiDepositRes = k.OnTakeMultiAssetDepositReceived(takeMultiDepositMsg, data.stateChange); const takeMultiDepositResEncoded = protobuf.encode(takeMultiDepositRes); return takeMultiDepositResEncoded; case "MULTI_WITHDRAW": - const multiWithdrawMsg: MsgMultiAssetWithdrawRequest = protobuf.decode(MsgMultiAssetWithdrawRequest, data.Data); + const multiWithdrawMsg: MsgMultiAssetWithdrawRequest = unmarshalJSON(data.Data); const multiWithdrawRes = k.OnMultiAssetWithdrawReceived(multiWithdrawMsg, data.stateChange); const multiWithdrawResEncoded = protobuf.encode(multiWithdrawRes); return multiWithdrawResEncoded; case "LEFT_SWAP": case "RIGHT_SWAP": - const swapMsg: MsgSwapRequest = protobuf.decode(MsgSwapRequest, data.Data); + const swapMsg: MsgSwapRequest = unmarshalJSON(data.Data); const swapRes = k.OnSwapReceived(swapMsg, data.stateChange); const swapResEncoded = protobuf.encode(swapRes); return swapResEncoded; @@ -656,26 +650,26 @@ function OnAcknowledgementPacket(packet: Packet, data: IBCSwapPacketData, ack: A default: switch (data.type) { case "MAKE_POOL": - const msgMakePool: MsgMakePoolRequest = protobuf.decode(data.Data); + const msgMakePool: MsgMakePoolRequest = unmarshalJSON(data.Data); const errMakePool = store.OnMakePoolAcknowledged(msgMakePool, data.StateChange.PoolId); abortTransactionUnless(errMakePool === undefined); break; case "TAKE_POOL": - const msgTakePool: MsgTakePoolRequest = protobuf.decode(data.Data); + const msgTakePool: MsgTakePoolRequest = unmarshalJSON(data.Data); const errTakePool = store.OnTakePoolAcknowledged(msgTakePool); abortTransactionUnless(errTakePool === undefined); break; case "SINGLE_DEPOSIT": - const msgSingleDeposit: MsgSingleAssetDepositRequest = protobuf.decode(data.Data); + const msgSingleDeposit: MsgSingleAssetDepositRequest = punmarshalJSON(data.Data); const resSingleDeposit: MsgSingleAssetDepositResponse = protobuf.decode(ack.GetResult()); const errSingleDeposit = store.OnSingleAssetDepositAcknowledged(msgSingleDeposit, resSingleDeposit); abortTransactionUnless(errSingleDeposit === undefined); break; case "MAKE_MULTI_DEPOSIT": - const msgMakeMultiDeposit: MsgMakeMultiAssetDepositRequest = protobuf.decode(data.Data); + const msgMakeMultiDeposit: MsgMakeMultiAssetDepositRequest = unmarshalJSON(data.Data); const resMakeMultiDeposit: MsgMultiAssetDepositResponse = protobuf.decode(ack.GetResult()); const errMakeMultiDeposit = store.OnMakeMultiAssetDepositAcknowledged( msgMakeMultiDeposit, @@ -685,14 +679,14 @@ function OnAcknowledgementPacket(packet: Packet, data: IBCSwapPacketData, ack: A break; case "TAKE_MULTI_DEPOSIT": - const msgTakeMultiDeposit: MsgTakeMultiAssetDepositRequest = protobuf.decode(data.Data); + const msgTakeMultiDeposit: MsgTakeMultiAssetDepositRequest = unmarshalJSON(data.Data); const resTakeMultiDeposit: MsgTakePoolResponse = protobuf.decode(ack.GetResult()); const errTakeMultiDeposit = store.OnTakeMultiAssetDepositAcknowledged(msgTakeMultiDeposit, data.StateChange); abortTransactionUnless(errTakeMultiDeposit === undefined); break; case "MULTI_WITHDRAW": - const msgMultiWithdraw: MsgMultiAssetWithdrawRequest = protobuf.decode(data.Data); + const msgMultiWithdraw: MsgMultiAssetWithdrawRequest = unmarshalJSON(data.Data); const resMultiWithdraw: MsgMultiAssetWithdrawResponse = protobuf.decode(ack.GetResult()); const errMultiWithdraw = store.OnMultiAssetWithdrawAcknowledged(msgMultiWithdraw, resMultiWithdraw); abortTransactionUnless(errMultiWithdraw === undefined); @@ -700,7 +694,7 @@ function OnAcknowledgementPacket(packet: Packet, data: IBCSwapPacketData, ack: A case "LEFT_SWAP": case "RIGHT_SWAP": - const msgSwap: MsgSwapRequest = protobuf.decode(data.Data); + const msgSwap: MsgSwapRequest = unmarshalJSON(data.Data); const resSwap: MsgSwapResponse = protobuf.decode(ack.GetResult()); const errSwap = store.OnSwapAcknowledged(msgSwap, resSwap); abortTransactionUnless(errSwap === undefined); @@ -729,45 +723,39 @@ function refundPacketToken(packet: Packet, data: IBCSwapPacketData): Error | und switch (data.type) { case "MAKE_POOL": - const makePoolMsg: MsgMakePoolRequest = protobuf.decode(MsgMakePoolRequest, data.Data); + const makePoolMsg: MsgMakePoolRequest = unmarshalJSON(data.Data); // Refund initial liquidity sender = makePoolMsg.creator; token = makePoolMsg.liquidity[0].balance; break; case "SINGLE_DEPOSIT": - const singleDepositMsg: MsgSingleAssetDepositRequest = protobuf.decode(MsgSingleAssetDepositRequest, data.Data); + const singleDepositMsg: MsgSingleAssetDepositRequest = unmarshalJSON(data.Data); token = singleDepositMsg.token; sender = singleDepositMsg.sender; break; case "MAKE_MULTI_DEPOSIT": - const makeMultiDepositMsg: MsgMakeMultiAssetDepositRequest = protobuf.decode( - MsgMakeMultiAssetDepositRequest, - data.Data - ); + const makeMultiDepositMsg: MsgMakeMultiAssetDepositRequest = unmarshalJSON(data.Data); token = makeMultiDepositMsg.deposits[0].balance; sender = makeMultiDepositMsg.deposits[0].sender; break; case "TAKE_MULTI_DEPOSIT": - const takeMultiDepositMsg: MsgTakeMultiAssetDepositRequest = protobuf.decode( - MsgTakeMultiAssetDepositRequest, - data.Data - ); + const takeMultiDepositMsg: MsgTakeMultiAssetDepositRequest = unmarshalJSON(data.Data); const { order, found } = store.getMultiDepositOrder(takeMultiDepositMsg.poolId, takeMultiDepositMsg.orderId); abortTransactionUnless(found); token = order.Deposits[1]; sender = msg.Sender; break; case "MULTI_WITHDRAW": - const multiWithdrawMsg: MsgMultiAssetWithdrawRequest = protobuf.decode(MsgMultiAssetWithdrawRequest, data.Data); + const multiWithdrawMsg: MsgMultiAssetWithdrawRequest = unmarshalJSON(data.Data); token = multiWithdrawMsg.poolToken; sender = multiWithdrawMsg.receiver; break; case "RIGHT_SWAP": - const swapMsg: MsgSwapRequest = protobuf.decode(MsgSwapRequest, data.Data); + const swapMsg: MsgSwapRequest = unmarshalJSON(data.Data); token = swapMsg.tokenIn; sender = swapMsg.sender; break; @@ -921,6 +909,17 @@ interface MsgSwapResponse { These are methods that output a state change on the source chain, which will be subsequently synced to the destination chain. ```ts + // Custom helper to support compatability with cosmwasm: Same function MarshalJSON/unmarshalJSON in Golang + function marshalJSON(data:any): Uint8Array { + const jsonData = JSON.stringify(any); + return Base64.encode(json).toBytes(); + } + + function unmarshalJSON(data:any): T { + const jsonData = Base64.decode(data); + return json.parse(jsonData) as T + } + function makePool(msg: MsgMakePoolRequest): MsgMakePoolResponse { const { counterPartyChainId, connected } = store.GetCounterPartyChainID(msg.sourcePort, msg.sourceChannel); @@ -949,7 +948,7 @@ These are methods that output a state change on the source chain, which will be const packet: IBCSwapPacketData = { type: "MAKE_POOL", - data: protobuf.encode(msg), + data: marshalJSON(msg), stateChange: { poolId: poolId, sourceChainId: store.ChainID(), @@ -1045,7 +1044,7 @@ These are methods that output a state change on the source chain, which will be const packet: IBCSwapPacketData = { type: "MAKE_MULTI_DEPOSIT", - data: protobuf.encode(msg), + data: marshalJSON(msg), stateChange: { poolTokens: poolTokens }, }; @@ -1087,7 +1086,7 @@ These are methods that output a state change on the source chain, which will be const packet: IBCSwapPacketData = { type: "TAKE_MULTI_DEPOSIT", - data: protobuf.encode(msg), + data: marshalJSON(msg), stateChange: { poolTokens }, }; @@ -1125,7 +1124,7 @@ function singleAssetDeposit(msg: MsgSingleAssetDepositRequest): MsgSingleAssetDe const packet: IBCSwapPacketData = { type: "SINGLE_DEPOSIT", - data: protobuf.encode(msg);, + data: marshalJSON(msg);, stateChange: { poolTokens: [poolToken] }, }; @@ -1156,7 +1155,7 @@ function multiAssetWithdraw(msg: MsgMultiAssetWithdrawRequest): MsgMultiAssetWit const packet: IBCSwapPacketData = { type: "MULTI_WITHDRAW", - data: protobuf.encode(msg), + data: marshalJSON(msg), stateChange: { out: outs, poolTokens: [msg.poolToken], @@ -1206,7 +1205,7 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { const packet: IBCSwapPacketData = { type: msgType, - data: protobuf.encode(msg), + data: marshalJSON(msg), stateChange: { out: [tokenOut] }, }; From bdac84f44f9250e545a9b59142c9d6ef9dae7945 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Thu, 21 Sep 2023 08:35:37 +0800 Subject: [PATCH 64/72] fix: typo --- spec/app/ics-101-interchain-liquidity/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index c0fd09e22..d426fdbc9 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -662,7 +662,7 @@ function OnAcknowledgementPacket(packet: Packet, data: IBCSwapPacketData, ack: A break; case "SINGLE_DEPOSIT": - const msgSingleDeposit: MsgSingleAssetDepositRequest = punmarshalJSON(data.Data); + const msgSingleDeposit: MsgSingleAssetDepositRequest = unmarshalJSON(data.Data); const resSingleDeposit: MsgSingleAssetDepositResponse = protobuf.decode(ack.GetResult()); const errSingleDeposit = store.OnSingleAssetDepositAcknowledged(msgSingleDeposit, resSingleDeposit); abortTransactionUnless(errSingleDeposit === undefined); From 7bb850b482eaac66199b5da3634612aaaa7c75e6 Mon Sep 17 00:00:00 2001 From: liangping <18786721@qq.com> Date: Thu, 21 Sep 2023 09:38:55 +0800 Subject: [PATCH 65/72] adding LP allocation option --- spec/app/ics-101-interchain-liquidity/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index d426fdbc9..2e45b43c5 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -841,9 +841,16 @@ interface DepositAsset { balance: Coin; } +enum LPAllocation { + MAKER_CHAIN, // All LP tokens are minted on maker chain + TAKER_CHAIN, // All LP tokens are minted on taker chain + SPLIT, // LP tokens are minted on both chains and divided based on the pool ratio. +} + interface MsgMakeMultiAssetDepositRequest { poolId: string; deposits: DepositAsset[]; + lpAllocation: LPAllocation.MAKER_CHAIN, token: Coin; // only one element for now, might have two in the feature port: string; channel: string; From d213eaa409525ccb83e38e5449ee3add3df2cf6f Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Thu, 21 Sep 2023 12:46:24 +0300 Subject: [PATCH 66/72] chore: fix formula --- .../ics-101-interchain-liquidity/README.md | 64 +++++++++++++------ 1 file changed, 43 insertions(+), 21 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 2e45b43c5..4fce5e3fb 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -396,27 +396,28 @@ class InterchainMarketMaker { // RightSwap implements InGivenOut // Input how many coins you want to buy, output an amount you need to pay // Ai = Bi * ((Bo/(Bo - Ao)) ** Wo/Wi -1) - function rightSwap(amountIn: Coin, amountOut: Coin) Coin { + function rightSwap(amountOut: Coin) Coin { - const assetIn = this.pool.findAssetByDenom(amountIn.denom) - abortTransactionUnless(assetIn != null) - const AssetOut = this.pool.findAssetByDenom(amountOut.denom) - abortTransactionUnless(assetOut != null) + const AssetOut = this.pool.findAssetByDenom(amountOut.denom) + abortTransactionUnless(assetOut != null) - const balanceIn = assetIn.balance.amount - const balanceOut = assetOut.balance.amount - const weightIn = assetIn.weight / 100 - const weightOut = assetOut.weight / 100 + // Assuming that the pool has a default input asset (say ETH or similar). + // If this is not the case, you might want to specify which asset you're using as input. + const assetIn = this.pool.defaultAsset + abortTransactionUnless(assetIn != null) - const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1)) + const balanceIn = assetIn.balance.amount + const balanceOut = assetOut.balance.amount + const weightIn = assetIn.weight / 100 + const weightOut = assetOut.weight / 100 - abortTransactionUnless(amountIn.amount > amount) + const amount = balanceIn * ((balanceOut / (balanceOut - amountOut.amount)) ** (weightOut/weightIn) - 1) - return { - amount, - denom: amountIn.denom - } + return { + amount, + denom: assetIn.denom } +} // amount - amount * feeRate / 10000 function minusFees(amount:number):number { @@ -842,15 +843,15 @@ interface DepositAsset { } enum LPAllocation { - MAKER_CHAIN, // All LP tokens are minted on maker chain - TAKER_CHAIN, // All LP tokens are minted on taker chain - SPLIT, // LP tokens are minted on both chains and divided based on the pool ratio. + MAKER_CHAIN, // All LP tokens are minted on maker chain + TAKER_CHAIN, // All LP tokens are minted on taker chain + SPLIT, // LP tokens are minted on both chains and divided based on the pool ratio. } interface MsgMakeMultiAssetDepositRequest { poolId: string; deposits: DepositAsset[]; - lpAllocation: LPAllocation.MAKER_CHAIN, + lpAllocation: LPAllocation.MAKER_CHAIN; token: Coin; // only one element for now, might have two in the feature port: string; channel: string; @@ -1044,6 +1045,7 @@ These are methods that output a state change on the source chain, which will be deposits: getCoinsFromDepositAssets(msg.deposits), status: "PENDING"; createdAt: store.blockTime(), + lpAllocation: msg.lpAllocation, }; // save order in source chain @@ -1352,7 +1354,14 @@ function onTakeMultiAssetDepositReceived( totalPoolToken.amount = totalPoolToken.amount.add(poolToken.amount); } - store.mintTokens(order.sourceMaker, totalPoolToken); + switch (order.lpAllocation) { + case LPAllocation.MAKER_CHAIN: + store.mintTokens(order.destinationTaker, totalPoolToken); + case LPAllocation.SPLIT: + store.mintTokens(order.sourceMaker, totalPoolToken[0]); + store.mintTokens(order.destinationTaker, totalPoolToken[1]); + default: + } store.setInterchainLiquidityPool(pool); store.setMultiDepositOrder(pool.id, order); @@ -1472,8 +1481,21 @@ function onTakeMultiAssetDepositAcknowledged(req: MsgTakeMultiAssetDepositReques const order = store.getMultiDepositOrder(req.poolId, req.orderId); abortTransactionUnless(order.found); + const totalMintAmount := 0 for (const poolToken of stateChange.poolTokens) { - pool.addPoolSupply(poolToken); + totalMintAmount += poolToken + } + if totalMintAmount > 0 { + pool.addPoolSupply(poolToken); + + switch (order.lpAllocation) { + case LPAllocation.TAKER_CHAIN: + store.mintTokens(order.destinationTaker, totalPoolToken); + case LPAllocation.SPLIT: + store.mintTokens(order.sourceMaker, totalPoolToken[0]); + store.mintTokens(order.destinationTaker, totalPoolToken[1]); + default: + } } for (const deposit of order.deposits) { From 12dfbbd1a75c92d4f7600f070911ef1cb4d0d7b9 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Fri, 22 Sep 2023 04:59:05 +0300 Subject: [PATCH 67/72] chore: fix pointed problems --- .../ics-101-interchain-liquidity/README.md | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 4fce5e3fb..5f60b95ba 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -396,27 +396,24 @@ class InterchainMarketMaker { // RightSwap implements InGivenOut // Input how many coins you want to buy, output an amount you need to pay // Ai = Bi * ((Bo/(Bo - Ao)) ** Wo/Wi -1) - function rightSwap(amountOut: Coin) Coin { + function rightSwap(amountIn: Coin,amountOut: Coin) Coin { - const AssetOut = this.pool.findAssetByDenom(amountOut.denom) - abortTransactionUnless(assetOut != null) + const assetIn = this.pool.findAssetByDenom(amountIn.denom) + abortTransactionUnless(assetIn != null) + const AssetOut = this.pool.findAssetByDenom(amountOut.denom) + abortTransactionUnless(assetOut != null) + const balanceIn = assetIn.balance.amount + const balanceOut = assetOut.balance.amount + const weightIn = assetIn.weight / 100 + const weightOut = assetOut.weight / 100 + const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1)) - // Assuming that the pool has a default input asset (say ETH or similar). - // If this is not the case, you might want to specify which asset you're using as input. - const assetIn = this.pool.defaultAsset - abortTransactionUnless(assetIn != null) + abortTransactionUnless(amountIn.amount > amount) - const balanceIn = assetIn.balance.amount - const balanceOut = assetOut.balance.amount - const weightIn = assetIn.weight / 100 - const weightOut = assetOut.weight / 100 - - const amount = balanceIn * ((balanceOut / (balanceOut - amountOut.amount)) ** (weightOut/weightIn) - 1) - - return { - amount, - denom: assetIn.denom - } + return { + amount, + denom: amountIn.denom + } } // amount - amount * feeRate / 10000 @@ -1359,7 +1356,6 @@ function onTakeMultiAssetDepositReceived( store.mintTokens(order.destinationTaker, totalPoolToken); case LPAllocation.SPLIT: store.mintTokens(order.sourceMaker, totalPoolToken[0]); - store.mintTokens(order.destinationTaker, totalPoolToken[1]); default: } @@ -1492,7 +1488,6 @@ function onTakeMultiAssetDepositAcknowledged(req: MsgTakeMultiAssetDepositReques case LPAllocation.TAKER_CHAIN: store.mintTokens(order.destinationTaker, totalPoolToken); case LPAllocation.SPLIT: - store.mintTokens(order.sourceMaker, totalPoolToken[0]); store.mintTokens(order.destinationTaker, totalPoolToken[1]); default: } From da6ea4add85e81f064876deea1c21bb953d5589a Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Fri, 22 Sep 2023 12:26:57 +0300 Subject: [PATCH 68/72] chore: move lock point --- spec/app/ics-101-interchain-liquidity/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 5f60b95ba..657441e0a 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -1181,9 +1181,6 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { abortTransactionUnless(pool.status === "ACTIVE"); - const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, msg.tokenIn); - abortTransactionUnless(lockErr === undefined); - const amm = new InterchainMarketMaker(pool); let tokenOut: sdk.Coin | undefined; @@ -1209,6 +1206,9 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { abortTransactionUnless(tokenOut?.amount?.gte(expected)); + const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, msg.tokenIn); + abortTransactionUnless(lockErr === undefined); + const packet: IBCSwapPacketData = { type: msgType, data: marshalJSON(msg), From fe21ff75945b99d89ccec75c748a1f23d085bea4 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Fri, 22 Sep 2023 12:49:59 +0300 Subject: [PATCH 69/72] chore: change slippage calcuation in right swap --- spec/app/ics-101-interchain-liquidity/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 657441e0a..85bde96ba 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -1184,16 +1184,20 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { const amm = new InterchainMarketMaker(pool); let tokenOut: sdk.Coin | undefined; + let tokenIn: sdk.Coin | undefined; let msgType: SwapMessageType; + let expected:number switch (msg.swapType) { case "LEFT": msgType = "LEFT_SWAP"; tokenOut = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); + expected = msg.tokenOut.amount * (1 - msg.slippage); break; case "RIGHT": msgType = "RIGHT_SWAP"; tokenOut = amm.rightSwap(msg.tokenIn, msg.tokenOut); + expected = msg.tokenIn.amount * (1 - msg.slippage); break; default: abortTransactionUnless(false); @@ -1201,9 +1205,6 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { abortTransactionUnless(tokenOut?.amount? <= 0); - - const expected = msg.tokenOut.amount * (1 - msg.slippage); - abortTransactionUnless(tokenOut?.amount?.gte(expected)); const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, msg.tokenIn); From e5412549c98bec1158e328140fa493ed2a6c8af4 Mon Sep 17 00:00:00 2001 From: DedicatedDev Date: Fri, 22 Sep 2023 14:36:58 +0300 Subject: [PATCH 70/72] chore: right swap swap fee fix --- spec/app/ics-101-interchain-liquidity/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index 85bde96ba..f7133aeb9 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -406,7 +406,8 @@ class InterchainMarketMaker { const balanceOut = assetOut.balance.amount const weightIn = assetIn.weight / 100 const weightOut = assetOut.weight / 100 - const amount = balanceIn * ((balanceOut/(balanceOut - amountOut.amount) ** (weightOut/weightIn) - 1)) + + const amount = balanceIn * ((balanceOut/(balanceOut -this.minusFees(amountOut.amount)) ** (weightOut/weightIn) - 1)) abortTransactionUnless(amountIn.amount > amount) @@ -1183,7 +1184,7 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { const amm = new InterchainMarketMaker(pool); - let tokenOut: sdk.Coin | undefined; + let swappedToken: sdk.Coin | undefined; let tokenIn: sdk.Coin | undefined; let msgType: SwapMessageType; let expected:number @@ -1191,12 +1192,12 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { switch (msg.swapType) { case "LEFT": msgType = "LEFT_SWAP"; - tokenOut = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); - expected = msg.tokenOut.amount * (1 - msg.slippage); + swappedToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); + break; case "RIGHT": msgType = "RIGHT_SWAP"; - tokenOut = amm.rightSwap(msg.tokenIn, msg.tokenOut); + swappedToken = amm.rightSwap(msg.tokenIn, msg.tokenOut); expected = msg.tokenIn.amount * (1 - msg.slippage); break; default: @@ -1204,8 +1205,8 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { } - abortTransactionUnless(tokenOut?.amount? <= 0); - abortTransactionUnless(tokenOut?.amount?.gte(expected)); + abortTransactionUnless(swappedToken?.amount? <= 0); + abortTransactionUnless(swappedToken?.amount?.gte(expected)); const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, msg.tokenIn); abortTransactionUnless(lockErr === undefined); @@ -1213,7 +1214,7 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { const packet: IBCSwapPacketData = { type: msgType, data: marshalJSON(msg), - stateChange: { out: [tokenOut] }, + stateChange: { out: [swappedToken] }, }; const sendPacketErr = store.sendIBCSwapPacket( From c2a20dbe59ef81d8f6038ead3c73f68e4ff8f61a Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Thu, 28 Sep 2023 14:01:19 +0800 Subject: [PATCH 71/72] check Left & right swap in a different code block. --- spec/app/ics-101-interchain-liquidity/README.md | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index f7133aeb9..e07378875 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -1184,30 +1184,25 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { const amm = new InterchainMarketMaker(pool); - let swappedToken: sdk.Coin | undefined; - let tokenIn: sdk.Coin | undefined; let msgType: SwapMessageType; - let expected:number switch (msg.swapType) { case "LEFT": msgType = "LEFT_SWAP"; - swappedToken = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); - + let actualOut: Coin = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); + abortTransactionUnless(actualOut.amount >= 0); + abortTransactionUnless(actualOut?.amount? >= msg.tokenOut.amount * (1 - msg.slippage)); // Note: slippage is base point, need convert to percentage. break; case "RIGHT": msgType = "RIGHT_SWAP"; - swappedToken = amm.rightSwap(msg.tokenIn, msg.tokenOut); - expected = msg.tokenIn.amount * (1 - msg.slippage); + let requiredTokenIn = amm.rightSwap(msg.tokenIn, msg.tokenOut); + abortTransactionUnless(requiredTokenIn?.amount >= 0); + abortTransactionUnless(msg.tokenIn.amount * (1 - msg.slippage) > requiredTokenIn?.amount); break; default: abortTransactionUnless(false); } - - abortTransactionUnless(swappedToken?.amount? <= 0); - abortTransactionUnless(swappedToken?.amount?.gte(expected)); - const lockErr = store.lockTokens(pool.counterPartyPort, pool.counterPartyChannel, msg.sender, msg.tokenIn); abortTransactionUnless(lockErr === undefined); From 286217d85a8d3c47e7142665edcd93c2a6cdc712 Mon Sep 17 00:00:00 2001 From: ping <18786721@qq.com> Date: Thu, 28 Sep 2023 14:06:12 +0800 Subject: [PATCH 72/72] edit >=, > on conditional check --- spec/app/ics-101-interchain-liquidity/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/app/ics-101-interchain-liquidity/README.md b/spec/app/ics-101-interchain-liquidity/README.md index e07378875..f5a1e9be5 100644 --- a/spec/app/ics-101-interchain-liquidity/README.md +++ b/spec/app/ics-101-interchain-liquidity/README.md @@ -1190,14 +1190,14 @@ function swap(msg: MsgSwapRequest): MsgSwapResponse { case "LEFT": msgType = "LEFT_SWAP"; let actualOut: Coin = amm.leftSwap(msg.tokenIn, msg.tokenOut.denom); - abortTransactionUnless(actualOut.amount >= 0); - abortTransactionUnless(actualOut?.amount? >= msg.tokenOut.amount * (1 - msg.slippage)); // Note: slippage is base point, need convert to percentage. + abortTransactionUnless(actualOut.amount > 0); + abortTransactionUnless(actualOut.amount >= msg.tokenOut.amount * (1 - msg.slippage)); // Note: slippage is base point, need convert to percentage. break; case "RIGHT": msgType = "RIGHT_SWAP"; let requiredTokenIn = amm.rightSwap(msg.tokenIn, msg.tokenOut); - abortTransactionUnless(requiredTokenIn?.amount >= 0); - abortTransactionUnless(msg.tokenIn.amount * (1 - msg.slippage) > requiredTokenIn?.amount); + abortTransactionUnless(requiredTokenIn.amount > 0); + abortTransactionUnless(msg.tokenIn.amount * (1 - msg.slippage) >= requiredTokenIn?.amount); break; default: abortTransactionUnless(false);