Skip to content
This repository has been archived by the owner on Nov 28, 2023. It is now read-only.

coinswap Module #17

Closed
4 tasks
colin-axner opened this issue May 30, 2019 · 6 comments
Closed
4 tasks

coinswap Module #17

colin-axner opened this issue May 30, 2019 · 6 comments

Comments

@colin-axner
Copy link

Summary

Add a module that implements the functionality of uniswap

Problem Definition

As more assets are added to the hub/network, it will be useful to have a decentralized asset exchange. Uniswap is a fairly simple and accessible exchange that could fulfill this role.

Proposal

This module takes four different types of messages. One for each of the following actions: creating an exchange between two coins, swapping coins, adding liquidity to an exchange, removing liquidity from an exchange. A param for the native asset used to swap between coins will need to be specified. Each exchange will use its coin denomination as its unique id. Coins can only be directly swapped with the native asset. All other swaps will do an extra swap with the native asset to return the desired output coin.

Uniswap refers to the tokens receieved in exchange for contributing to a liquidity pool as UNI, that naming convention has been maintained in this write up. Uniswap uses x*y=k invariant to set prices. In this implementation nativePool * coinPool = k. Uniswap enforces a fee of %0.3 paid to liquidity providers, this can be added as a param. Uniswap allows for two types of orders, buy/sell. In a buy order, the input is calculated, the output is specified. In a sell order, the input is specified, the output is calculated

msgs.go

type MsgCreateExchange struct {
    NewCoin string
}

Initializes a new exchange pair between the specified coin and the native asset

type MsgSwapOrder struct {
    SwapDenom  string         // The desired denomination either to be bought or sold
    Coin       sdk.Coin       // The specified amount to be either bought or sold 
    Bound      sdk.Int        // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought
    Deadline   time.Time      // deadline for the transaction to still be considered valid
    Recipient  sdk.AccAddress // address output coin is being sent to
    IsBuyOrder bool           // boolean indicating whether the order should be treated as a buy or sell
}

If exchanging between 2 coins that are not the native asset, input coin -> native then native -> output coin. Otherwise, simply swap between native asset and specified coin. The cost of buying or selling is determined by current ratio at the time the transaction is included in a block. Bound is used to limit the amount a user is willing to buy or sell.

type MsgAddLiquidity struct {
    DepositAmount sdk.Int  // exact amount of native asset being add to the liquidity pool
    ExchangeDenom string   // denomination of the exchange being added to  
    MinLiquidity  sdk.Int  // lower bound UNI sender is willing to accept for deposited coins
    MaxCoins      sdk.Int  // maximum amount of the coin the sender is willing to deposit. 
    Deadline      time.Time 
}

DepositAmount is 50% of the total value that is added. Must deposit an amount for native asset and coin pair; If first provider, then amount deposited determines exchange ratio (arbitrage traders bring prices to equilibrium if the ratio is off). The bounds are used to manage price changes between tx signed and executed. If the total liquidity is 0 for this exchange, then MaxCoins is used for the amount of the exchange coin to be deposited

type MsgRemoveLiquidity struct {
    WithdrawAmount sdk.Int // amount of UNI to be burned to withdraw liquidity from an exchange
    ExchangeDenom  string  // denomination of the exchange being withdrawn from
    MinNative      sdk.Int // minimum amount of the native asset the sender is willing to accept
    MinCoins       sdk.Int // minimum amount of the exchange coin the sender is willing to accept
    Deadline       time.Time
}

ante.go

  • exchange registry
    • check if exchange already exists
    • check if denom provided is valid?
  • sell order
    • check that sender has > msg.Input.Amount
  • buy order
    • check that sender has > msg.MaxCoins
  • adding liquidy
    • check that sender has sufficient amount of the native asset and exchange coin
  • removing liquid
    • check that sender has sufficient amount of UNI to burn

keeper.go

type Keeper struct {
    // The key used to access the store which maps accounts to UNI balances
    key sdk.StoreKey

    // The reference to the CoinKeeper to modify balances after swaps or liquidity is deposited/withdrawn
    ck BankKeeper

    // The codec codec for binary encoding/decoding.
    cdc *codec.Codec

    CreateExchange(ctx sdk.context, coinDenom string)
    Deposit(ctx sdk.context, amount sdk.Int, acc sdk.AccAddress) // Add Liquidity, adds specified amount of UNI to account
    Withdraw(ctx sdk.context, amount sdk.Int, acc sdk.AccAddress) // Remove Liquidity, removes specified amount of UNI from account

    // The following differs in where the fee is taken from
    // Return the amount of coins sold given the output amount being bought
    GetInputAmount(ctx sdk.context, outputAmount, inputDenom, outputDenom) sdk.Int // Buy order
    // Return the amount of coins bought given the output amount being sold
    GetOutputAmount(ctx sdk.context, inputAmount, inputDenom, outputDenom) sdk.Int // Sell order
}

Formulas for calculating buy/sell amount will be taken from the uniswap implementation
GetInputAmount() would correspond to GetOutputPrice()
GetOutputAmount() would correspond to GetInputPrice()

handleMsgCreateExchange

  • add new exchange pairing

handleMsgSwapOrder

if msg.IsBuyOrder {
    inputAmount := GetInputAmount(ctx, msg.Output.Amount, msg.InputDenom, msg.Output.Denom)
    ck.SendCoins(ctx, exchangeAcc, senderAcc, msg.Output)
    coinSold := sdk.NewCoin(msg.InputDenom, inputAmount)
    ck.SendCoins(ctx, senderAcc, exchangeAcc, coinSold)
} else {
    outputAmount := GetOutputAmount(ctx, msg.Input.Amount, msg.Input.Denom, msg.OutputDenom)
    ck.SendCoins(ctx, senderAcc, exchangeAcc, msg.Input)
    coinBought := sdk.NewCoin(msg.OutputDenom, outputAmount)
    ck.SendCoins(ctx, exchangeAcc, senderAcc, coinBought)
}

handleAddLiquidity

UNI Minted = totalLiquidity * nativeDeposited / nativePool
coinDeposited = coinPool * nativeDeposited / nativePool
nativeCoin = sdk.NewCoin(nativeDenom, msg.DepositAmount)
exchangeCoin = sdk.NewCoin(msg.ExchangeDenom, coinDeposited)
ck.SendCoins(ctx, senderAcc, exchangeAcc, nativeCoin)
ck.SendCoins(ctx, senderAcc, exchangeAcc, exchangeCoin)
keeper.Deposit(ctx, UNI Minted)

totalLiquidity is the total number of UNI in existence

handleRemoveLiquidity

nativeWithdrawn = UNI burned * nativePool / totalLiquidity
coinWithdrawn = UNI burned * coinPool / totalLiquidity
nativeCoin = sdk.NewCoin(nativeDenom, msg.nativeWithdrawn)
exchangeCoin = sdk.NewCoin(msg.ExchangeDenom, coinWithdrawn)
Withdraw(ctx, UNI burned)
ck.SendCoins(ctx, exchangeAcc, senderAcc, nativeCoin)
ck.SendCoins(ctx, exchangeAcc, senderAcc, exchangeCoin)

params.go

  • specify native asset for exchanging between coins
  • specify fee percent to be taken for each swap

Notes:
Each exchange will need to maintain a balance of liquidity that is deposited into it.What would be the best way to create accounts for each exchange? If exchanges have accounts with the CoinKeeper then the total liquidity of each exchange can be retrieved from there.


For Admin Use

  • Not duplicate issue
  • Appropriate labels applied
  • Appropriate contributors tagged
  • Contributor assigned/self-assigned
@colin-axner
Copy link
Author

colin-axner commented Jun 26, 2019

Version 1

The development of this module should initially deliver a MVP with the following functionality.

  • native denomination that cannot be changed
  • adding/removing liquidity
  • direct swaps and indirect swaps through native denomination
  • ability to transfer UNI
  • fee as a parameter

Version 2 and beyond

I think version 2 should transition the coinswap module from being a basic decentralized exchange into adding governance for the zone and for each trading pair.

Governance for the module:

  • adding custom direct to direct trading pairs that don't go through the native denom
  • zone upgrades

Governance for trading pairs voted on by LPs (and users?):

  • setting how the fee is computed (taking into account: volume, spread, and volatility)
  • making equivalent coins from different sources fungible to avoid splitting coins into many different trading pairs (i.e. bitcoin coming from different peg zones)

@colin-axner colin-axner changed the title Uniswap Module coinswap Module Jun 27, 2019
@fedekunze
Copy link

Imo this shouldn't live on the SDK but on Gaia and should be approved through governance. cosmos/cosmos-sdk#4642 could be more suitable for the SDK as it's a general-purpose module as opposed to an uniswap module. Thoughts @alexanderbez @jackzampolin @sunnya97 @marbar3778 ?

@colin-axner
Copy link
Author

I agree @fedekunze . I think this feature will probably need its own repo. From its repo, its module level code could lie in x/coinswap so its still easily importable. I don't think this needs to go through governance however, since I don't think it should live on the hub. Makes more sense to me if coinswap is its own zone with its own governance/validators

@alexanderbez
Copy link
Contributor

I'm OK with this living in the SDK as I could see it providing high utility for many projects. It does however continue to beg the questions: What belongs in the SDK and what doesn't (rather in it's own repo)?

@AdityaSripal
Copy link
Member

I disagree @alexanderbez. The problem is if it stays in the SDK, it becomes incumbent on SDK developers to maintain it as we make breaking changes to the rest of the SDK.

I think we should only burden the SDK team to maintain modules that are necessary for most end-users.

If the coinswap module exists as a separate repo we could keep it on an older version of the SDK and make updates to it more gradually

@alexanderbez
Copy link
Contributor

yeah I'm not too opinionated on where this lives tbh. Outside of the SDK seems more appropriate.

@tac0turtle tac0turtle transferred this issue from cosmos/cosmos-sdk Nov 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants