Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

WIP: Uniswap Module #4518

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions x/uniswap/alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package uniswap

import (
"github.com/cosmos/cosmos-sdk/x/uniswap/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/uniswap/internal/types"
)

type (
Keeper = keeper.Keeper
NativeAsset = types.NativeAsset
MsgSwapOrder = types.MsgSwapOrder
MsgAddLiquidity = types.MsgAddLiquidity
MsgRemoveLiquidity = types.MsgRemoveLiquidity
)

const (
ModuleName = types.ModuleName
)
50 changes: 50 additions & 0 deletions x/uniswap/genesis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package uniswap

import (
"fmt"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// GenesisState - uniswap genesis state
type GenesisState struct {
NativeAssetDenom string `json:"native_asset_denom"`
FeeParams FeeParams `json:"fee_params"`
}

// NewGenesisState is the constructor function for GenesisState
func NewGenesisState(nativeAssetDenom string, feeParams FeeParams) GenesisState {
return GenesisState{
NativeAssetDenom: nativeAssetDenom,
FeeParams: feeParams,
}
}

// DefaultGenesisState creates a default GenesisState object
func DefaultGenesisState() GenesisState {
return NewGenesisState(sdk.DefaultBondDenom, DefaultParams())
}

// InitGenesis new uniswap genesis
// TODO
func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) {

}

// ExportGenesis returns a GenesisState for a given context and keeper.
// TODO
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
return NewGenesisState(sdk.DefaultBondDenom)
}

// ValidateGenesis - placeholder function
func ValidateGenesis(data GenesisState) error {
if strings.TrimSpace(data.NativeAssetDenom) == "" {
fmt.Errorf("no native asset denomination provided")
}
if err := data.FeeParams.Validate(); err != nil {
return err
}
return nil
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}
216 changes: 216 additions & 0 deletions x/uniswap/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package uniswap

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/supply/types"
)

// NewHandler routes the messages to the handlers
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
case MsgSwapOrder:
return HandleMsgSwapOrder(ctx, msg, k)
case MsgAddLiquidity:
return HandleMsgAddLiquidity(ctx, msg, k)
case MsgRemoveLiquidity:
return HandleMsgRemoveLiquidity(ctx, msg, k)
default:
errMsg := fmt.Sprintf("unrecognized uniswap message type: %T", msg)
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}

// HandleMsgSwapOrder handler for MsgSwapOrder
func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result {
var caclulatedAmount sdk.Int

// check that deadline has not passed
if msg.ctx.BlockTime.After(msg.Deadline) {
return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgSwapOrder")
}

if msg.IsBuyOrder {
calculatedAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount)
// ensure the maximum amount sender is willing to sell is less than
// the calculated amount
if msg.Input.Amount.LT(calculatedAmount) {
return types.ErrInvalidBound(DefaultCodespace, "maximum amount (%d) to be sold was exceeded (%d)", msg.Input.Amount, calculatedAmount).Result()
}

coinSold := sdk.NewCoins(sdk.NewCoin(msg.Input.Denom, calculatedAmount))
if !s.bk.HasCoins(ctx, msg.Sender, coinSold) {
return ErrInsufficientAmount(DefaultCodespace, "sender account does not have sufficient funds to fulfill the swap order").Result()
}

err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, coinSold)
if err != nil {
return err.Result()
}

err = k.sk.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(msg.Output))
if err != nil {
return err.Result()
}

} else {
outputAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount)
// ensure the minimum amount the sender is willing to buy is greater than
// the calculated amount
if msg.Output.Amount.GT(outputAmount) {
return sdk.ErrInvalidBound(DefaultCodespace, "minimum amount (%d) to be sold was not met (%d)", msg.Output.Amount, calculatedAmount).Result()
}

coinSold := sdk.NewCoins(msg.Input)
if !s.bk.HasCoins(ctx, msg.Sender, coinSold) {
return ErrInsifficientAmount(DefaultCodespace, "sender account does not have sufficient funds to fulfill the swap order").Result()
}

err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Input))
if err != nil {
return err.Result()
}

err = k.sk.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(sdk.NewCoin(msg.Output.Denom, calculatedAmount)))
if err != nil {
return err.Result()
}

}

return sdk.Result{}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I was going through 'GetInputPriceandGetOutputPrice` mentioned in https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf

After discussing with @AdityaSripal we both feel that you swapped lesser than and greater than when it comes to buy order and sell order.

For instance, for a buy order, the calculated amount should be lesser than the maximum amount the sender is willing to pay. Thus change if msg.Input.Amount.LT(calculatedAmount) to if calculatedAmount.LT(msg.input.Amount)

Similarly for a sell order, the calculated amount should be greater than the minimum amount the sender is willing to pay. Thus change if msg.Output.Amount.GT(outputAmount) to if calculatedAmount.GT(msg.Output.Amount). I would change outputAmount to calculatedAmount for uniformity.

A better way to write the comment would be to specify calculated amount first followed by the amount the sender is willing to pay.

// HandleMsgAddLiquidity handler for MsgAddLiquidity
// If the reserve pool does not exist, it will be created.
func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result {
// check that deadline has not passed
if msg.ctx.BlockTime.After(msg.Deadline) {
return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgAddLiquidity")
}

// create reserve pool if it does not exist
coinLiquidity := k.GetReservePool(ctx, msg.Deposit.Denom)
if err != nil {
k.CreateReservePool(ctx, msg.Denom)
}

nativeLiqudiity, err := k.GetReservePool(ctx, NativeAsset)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a minor typo.

if err != nil {
panic("error retrieving native asset total liquidity")
}

totalUNI, err := k.GetTotalUNI(ctx)
if err != nil {
panic("error retrieving total UNI")
}

// calculate amount of UNI to be minted for sender
// and coin amount to be deposited
MintedUNI := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity)
coinAmountDeposited := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity)
nativeCoinDeposited := sdk.NewCoin(NativeAsset, msg.DepositAmount)
coinDeposited := sdk.NewCoin(msg.Deposit.Denom, coinAmountDeposited)

coins := sdk.NewCoins(nativeCoinDeposited, coinDeposited)
if !s.bk.HasCoins(ctx, msg.Sender, coins) {
return ErrInsufficientCoins(DefaultCodespace, "sender does not have sufficient funds to add liquidity").Result()
}

// transfer deposited liquidity into uniswaps ModuleAccount
err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, coins)
if err != nil {
return err.Result()
}

// set updated total UNI
totalUNI = totalUNI.Add(MintedUNI)
k.SetTotalUNI(ctx, totalUNI)

// update senders account with minted UNI
UNIBalance := k.GetUNIForAddress(ctx, msg.Sender)
UNIBalance = UNIBalance.Add(MintedUNI)
k.SetUNIForAddress(ctx, UNIBalance)

return sdk.Result{}
}

// HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity
func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result {
// check that deadline has not passed
if msg.ctx.BlockTime.After(msg.Deadline) {
return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgRemoveLiquidity")
}

// check if reserve pool exists
coinLiquidity, err := k.GetReservePool(ctx, msg.Withdraw.Denom)
if err != nil {
panic(fmt.Sprintf("error retrieving total liquidity for denomination: %s", msg.Withdraw.Denom))
}

nativeLiquidity, err := k.GetReservePool(ctx, NativeAsset)
if err != nil {
panic("error retrieving native asset total liquidity")
}

totalUNI, err := k.GetTotalUNI(ctx)
if err != nil {
panic("error retrieving total UNI")
}

// calculate amount of UNI to be burned for sender
// and coin amount to be returned
nativeWithdrawn := msg.WithdrawAmount.Mul(nativeLiquidity).Quo(totalUNI)
coinWithdrawn := msg.WithdrawAmount.Mul(coinLiqudity).Quo(totalUNI)
nativeCoin := sdk.NewCoin(nativeDenom, nativeWithdrawn)
exchangeCoin = sdk.NewCoin(msg.Withdraw.Denom, coinWithdrawn)

// transfer withdrawn liquidity from uniswaps ModuleAccount to sender's account
err := k.sk.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited))
if err != nil {
return err.Result()
}

// set updated total UNI
totalUNI = totalUNI.Add(MintedUNI)
k.SetTotalUNI(ctx, totalUNI)

// update senders account with minted UNI
UNIBalance := k.GetUNIForAddress(ctx, msg.Sender)
UNIBalance = UNIBalance.Add(MintedUNI)
k.SetUNIForAddress(ctx, UNIBalance)

return sdk.Result{}
}

// GetInputAmount returns the amount of coins sold (calculated) given the output amount being bought (exact)
// The fee is included in the output coins being bought
// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf
// TODO: replace FeeD and FeeN with updated formula using fee as sdk.Dec
func getInputAmount(ctx sdk.Context, k Keeper, outputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int {
inputReserve := k.GetReservePool(inputDenom)
outputReserve := k.GetReservePool(outputDenom)
params := k.GetFeeParams(ctx)

numerator := inputReserve.Mul(outputReserve).Mul(params.FeeD)
denominator := (outputReserve.Sub(outputAmt)).Mul(parans.FeeN)
return numerator.Quo(denominator).Add(sdk.OneInt())
}

// GetOutputAmount returns the amount of coins bought (calculated) given the input amount being sold (exact)
// The fee is included in the input coins being bought
// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf
// TODO: replace FeeD and FeeN with updated formula using fee as sdk.Dec
func getOutputAmount(ctx sdk.Context, k Keeper, inputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int {
inputReserve := k.GetReservePool(inputDenom)
outputReserve := k.GetReservePool(outputDenom)
params := k.GetFeeParams(ctx)

inputAmtWithFee := inputAmt.Mul(params.FeeN)
numerator := inputAmtWithFee.Mul(outputReserve)
denominator := inputReserve.Mul(params.FeeD).Add(inputAmtWithFee)
return numerator.Quo(denominator)
}
Loading