Skip to content

Commit

Permalink
further split into separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
svilenkov committed Jun 8, 2021
1 parent f449037 commit c000e16
Show file tree
Hide file tree
Showing 8 changed files with 841 additions and 617 deletions.
5 changes: 3 additions & 2 deletions AltLabs/dex-token-swap/pab/Main.hs
Expand Up @@ -54,6 +54,7 @@ import System.Directory (getCurrentDirectory)
import System.FilePath ((</>))
import Plutus.PAB.Core (PABEffects)
import Ledger (CurrencySymbol)
import Plutus.Contracts.Data

uniswapMintingContract :: Eff (PABEffects
(Builtin UniswapContracts)
Expand Down Expand Up @@ -99,7 +100,7 @@ uniswapLiquidityPoolContract cids cs = do
let coins = Map.fromList [(tn, Uniswap.mkCoin cs tn) | tn <- tokenNames]
ada = Uniswap.mkCoin adaSymbol adaToken

let cp = Uniswap.CreateParams ada (coins Map.! "A") 100000 500000
let cp = CreateParams ada (coins Map.! "A") 100000 500000
Simulator.logString @(Builtin UniswapContracts) $ "creating liquidity pool: " ++ show (encode cp)
-- _ <- Simulator.callEndpointOnInstance (cids Map.! Wallet 2) "create" cp
let cid2 = cids Map.! Wallet 2
Expand Down Expand Up @@ -141,7 +142,7 @@ main = do
outputDir <- getCurrentDirectory
print outputDir
BSL.writeFile
(outputDir </> "full_report_response.json")
(outputDir </> "./log/full_report_response.json")
(JSON.encodePretty fullReport)

data UniswapContracts =
Expand Down
4 changes: 4 additions & 0 deletions AltLabs/dex-token-swap/plutus-starter.cabal
Expand Up @@ -39,10 +39,14 @@ library
-- Plutus.PAB.Effects.ContractTest.Uniswap
Effects.Uniswap
Plutus.Contracts.Data
Plutus.Contracts.Helpers
-- Plutus.Contracts.UniswapHelpers
Plutus.Contracts.Validators
Plutus.Contracts.PubKey
Plutus.Contracts.Uniswap
Plutus.Contracts.Currency
Plutus.Contracts.LiquidityPool
Plutus.Contracts.PoolForgery
build-depends:
base >= 4.9 && < 5,
directory,
Expand Down
120 changes: 115 additions & 5 deletions AltLabs/dex-token-swap/src/Plutus/Contracts/Data.hs
Expand Up @@ -19,15 +19,43 @@
{-# LANGUAGE StandaloneDeriving #-}
module Plutus.Contracts.Data
where
-- (
-- poolStateTokenName,
-- uniswapTokenName,
-- usCoin,
-- Coin,
-- Uniswap, Uniswapping,
-- UniswapDatum,
-- LiquidityPool,
-- CreateParams,
-- AddParams,
-- RemoveParams,
-- CloseParams,
-- SwapParams,
-- UniswapAction,
-- UserContractState,
-- ) where

import Ledger
import Ledger.Value (AssetClass (..), assetClass, assetClassValue, assetClassValueOf)
import Playground.Contract (FromJSON, Generic, ToJSON, ToSchema)
import qualified Ledger.Typed.Scripts as Scripts
import qualified PlutusTx
import PlutusTx.Prelude hiding (Semigroup (..), unless)
import qualified Prelude as Haskell
import PlutusTx.Sqrt
import Prelude (Semigroup (..))
import qualified Prelude
import Text.Printf (PrintfArg)

uniswapTokenName, poolStateTokenName :: TokenName
-- state token for the "factory" (unique token)
-- "factory" is first created, this token gets minted
-- and put to the UTXO of the "factory" and stays there forever
uniswapTokenName = "Uniswap"

-- state token of the liquidity pools (each pool has same token)
poolStateTokenName = "Pool State"

-- uniswapTokenName and poolStateTokenName share the same minting policy
-- | A handy alias to put things in the language of "Coins" instead of
-- "AssetClass".
Expand All @@ -44,10 +72,92 @@ data LiquidityPool = LiquidityPool
}
deriving (Show, Generic, ToJSON, FromJSON, ToSchema)

PlutusTx.unstableMakeIsData ''LiquidityPool
PlutusTx.makeLift ''LiquidityPool

instance Eq LiquidityPool where
{-# INLINABLE (==) #-}
x == y = (lpCoinA x == lpCoinA y && lpCoinB x == lpCoinB y) ||
(lpCoinA x == lpCoinB y && lpCoinB x == lpCoinA y)
(lpCoinA x == lpCoinB y && lpCoinB x == lpCoinA y)

-- | Type of the Uniswap user contract state.
data UserContractState =
Pools [((Coin, Integer), (Coin, Integer))]
| Funds Value
| Created
| Swapped
| Added
| Removed
| Closed
| Stopped
deriving (Show, Generic, FromJSON, ToJSON)

-- DATUM that each UTXO carries
--
data UniswapDatum =
Factory [LiquidityPool] -- list of existing liquidity pools
| Pool LiquidityPool Integer -- Integer is the amount of tokens that have been minted for this LP
deriving stock (Show)

-- Redeemer
-- possible actions AFTER the "factory" is setup
data UniswapAction = Create LiquidityPool | Close | Swap | Remove | Add
deriving Show

-- just a wrapper around the "Coin" type (CurrencySymbol + TokenNAme)
newtype Uniswap = Uniswap
{ usCoin :: Coin
} deriving stock (Show, Generic)
deriving anyclass (ToJSON, FromJSON, ToSchema)
deriving newtype (Prelude.Eq, Prelude.Ord)

data Uniswapping
instance Scripts.ScriptType Uniswapping where
type instance RedeemerType Uniswapping = UniswapAction
type instance DatumType Uniswapping = UniswapDatum

-- | Parameters for the @create@-endpoint, which creates a new liquidity pool.
data CreateParams = CreateParams
{ cpCoinA :: Coin -- ^ One 'Coin' of the liquidity pair.
, cpCoinB :: Coin -- ^ The other 'Coin'.
, cpAmountA :: Integer -- ^ Amount of liquidity for the first 'Coin'.
, cpAmountB :: Integer -- ^ Amount of liquidity for the second 'Coin'.
} deriving (Show, Generic, ToJSON, FromJSON, ToSchema)

-- | Parameters for the @swap@-endpoint, which allows swaps between the two different coins in a liquidity pool.
-- One of the provided amounts must be positive, the other must be zero.
data SwapParams = SwapParams
{ spCoinA :: Coin -- ^ One 'Coin' of the liquidity pair.
, spCoinB :: Coin -- ^ The other 'Coin'.
, spAmountA :: Integer -- ^ The amount the first 'Coin' that should be swapped.
, spAmountB :: Integer -- ^ The amount of the second 'Coin' that should be swapped.
} deriving (Show, Generic, ToJSON, FromJSON, ToSchema)

-- | Parameters for the @close@-endpoint, which closes a liquidity pool.
data CloseParams = CloseParams
{ clpCoinA :: Coin -- ^ One 'Coin' of the liquidity pair.
, clpCoinB :: Coin -- ^ The other 'Coin' of the liquidity pair.
} deriving (Show, Generic, ToJSON, FromJSON, ToSchema)

-- | Parameters for the @remove@-endpoint, which removes some liquidity from a liquidity pool.
data RemoveParams = RemoveParams
{ rpCoinA :: Coin -- ^ One 'Coin' of the liquidity pair.
, rpCoinB :: Coin -- ^ The other 'Coin' of the liquidity pair.
, rpDiff :: Integer -- ^ The amount of liquidity tokens to burn in exchange for liquidity from the pool.
} deriving (Show, Generic, ToJSON, FromJSON, ToSchema)

-- | Parameters for the @add@-endpoint, which adds liquidity to a liquidity pool in exchange for liquidity tokens.
data AddParams = AddParams
{ apCoinA :: Coin -- ^ One 'Coin' of the liquidity pair.
, apCoinB :: Coin -- ^ The other 'Coin' of the liquidity pair.
, apAmountA :: Integer -- ^ The amount of coins of the first kind to add to the pool.
, apAmountB :: Integer -- ^ The amount of coins of the second kind to add to the pool.
} deriving (Show, Generic, ToJSON, FromJSON, ToSchema)

PlutusTx.unstableMakeIsData ''LiquidityPool
PlutusTx.makeLift ''LiquidityPool

PlutusTx.makeLift ''Uniswap

PlutusTx.unstableMakeIsData ''UniswapAction
PlutusTx.makeLift ''UniswapAction

PlutusTx.unstableMakeIsData ''UniswapDatum
PlutusTx.makeLift ''UniswapDatum
186 changes: 186 additions & 0 deletions AltLabs/dex-token-swap/src/Plutus/Contracts/Helpers.hs
@@ -0,0 +1,186 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# options_ghc -fno-warn-orphans #-}

-- | A decentralized exchange for arbitrary token pairs following the
-- [Uniswap protocol](https://uniswap.org/whitepaper.pdf).
--
module Plutus.Contracts.Helpers
(
poolStateCoin,
coinValueOf,
mkCoin,
findOwnInput, findOwnInput',
valueWithin,
liquidityPolicy, liquidityCurrency,
checkSwap,
coin,
findSwapA, findSwapB,
funds
) where

import Control.Monad hiding (fmap)
import qualified Data.Map as Map
import Data.Monoid (Last (..))
import Data.Proxy (Proxy (..))
import Data.Text (Text, pack)
import Data.Void (Void)
import Ledger hiding (singleton)
import Ledger.Constraints as Constraints
import Ledger.Constraints.OnChain as Constraints
import Ledger.Constraints.TxConstraints as Constraints
import qualified Ledger.Typed.Scripts as Scripts
import Ledger.Value (AssetClass (..), assetClass, assetClassValue, assetClassValueOf,
symbols, unCurrencySymbol, unTokenName)
import Playground.Contract
import Plutus.Contract hiding (when)
import qualified Plutus.Contracts.Currency as Currency
import qualified PlutusTx
import PlutusTx.Prelude hiding (Semigroup (..), unless)
import PlutusTx.Sqrt
import Prelude (Semigroup (..))
import qualified Prelude
import Text.Printf

import Plutus.Contracts.Data
import Plutus.Contracts.LiquidityPool

-- helper function to construct Values
-- Value (Cardano) is a "bag" of coins (1 ADA, 3 NFT etc...)
-- Dictionary of Dictionary of Int { CurrencySymbol: { TokenName: Int }}
{-# INLINABLE coin #-}
coin :: AssetClass -> Integer -> Value
coin = assetClassValue

-- How many pieces of this Coin are contained in Value
{-# INLINABLE coinValueOf #-}
coinValueOf :: Value -> AssetClass -> Integer
coinValueOf = assetClassValueOf

-- CurrencySymbol = hash of the pliutus script (which is run when you mint/burn token)
-- TokenName = bytestring (aka "Uniswap")
{-# INLINABLE mkCoin #-}
mkCoin:: CurrencySymbol -> TokenName -> AssetClass
mkCoin = assetClass

{-# INLINABLE findOwnInput' #-}
findOwnInput' :: ScriptContext -> TxInInfo
findOwnInput' ctx = fromMaybe (error ()) (findOwnInput ctx)

{-# INLINABLE valueWithin #-}
valueWithin :: TxInInfo -> Value
valueWithin = txOutValue . txInInfoResolved


-- GETTERS
-- | Gets the caller's funds.
funds :: HasBlockchainActions s => Contract w s Text Value
funds = do
pkh <- pubKeyHash <$> ownPubKey
os <- map snd . Map.toList <$> utxoAt (pubKeyHashAddress pkh)
return $ mconcat [txOutValue $ txOutTxOut o | o <- os]

-- Checks if swap is possible
-- oldA, oldB = existing amount
-- newA, newB = amount after the swap
-- calculates that the product does NOT decrease
{-# INLINABLE checkSwap #-}
checkSwap :: Integer -> Integer -> Integer -> Integer -> Bool
checkSwap oldA oldB newA newB =
traceIfFalse "expected positive oldA" (oldA > 0) &&
traceIfFalse "expected positive oldB" (oldB > 0) &&
traceIfFalse "expected positive-newA" (newA > 0) &&
traceIfFalse "expected positive-newB" (newB > 0) &&
traceIfFalse "expected product to increase"
((((newA * feeDen) - (inA * feeNum)) * ((newB * feeDen) - (inB * feeNum)))
>= (feeDen * feeDen * oldA * oldB))
where
inA, inB :: Integer
inA = max 0 $ newA - oldA
inB = max 0 $ newB - oldB
-- The uniswap fee is 0.3%; here it is multiplied by 1000, so that the
-- on-chain code deals only in integers.
-- See: <https://uniswap.org/whitepaper.pdf> Eq (11) (Page 7.)
feeNum, feeDen :: Integer
feeNum = 3
feeDen = 1000

findSwapA :: Integer -> Integer -> Integer -> Integer
findSwapA oldA oldB inA
| ub' <= 1 = 0
| otherwise = go 1 ub'
where
cs :: Integer -> Bool
cs outB = checkSwap oldA oldB (oldA + inA) (oldB - outB)

ub' :: Integer
ub' = head $ dropWhile cs [2 ^ i | i <- [0 :: Int ..]]

go :: Integer -> Integer -> Integer
go lb ub
| ub == (lb + 1) = lb
| otherwise =
let
m = div (ub + lb) 2
in
if cs m then go m ub else go lb m

findSwapB :: Integer -> Integer -> Integer -> Integer
findSwapB oldA oldB = findSwapA oldB oldA

validateLiquidityForging :: Uniswap -> TokenName -> ScriptContext -> Bool
validateLiquidityForging us tn ctx = case [ i
| i <- txInfoInputs $ scriptContextTxInfo ctx
, let v = valueWithin i
, (coinValueOf v usC == 1) ||
(coinValueOf v lpC == 1)
] of
[_] -> True
[_, _] -> True
_ -> traceError "pool state forging without Uniswap input"
where
usC, lpC :: Coin
usC = usCoin us
lpC = mkCoin (ownCurrencySymbol ctx) tn

liquidityPolicy :: Uniswap -> MonetaryPolicy
liquidityPolicy us = mkMonetaryPolicyScript $
$$(PlutusTx.compile [|| \u t -> Scripts.wrapMonetaryPolicy (validateLiquidityForging u t) ||])
`PlutusTx.applyCode` PlutusTx.liftCode us
`PlutusTx.applyCode` PlutusTx.liftCode poolStateTokenName

liquidityCurrency :: Uniswap -> CurrencySymbol
liquidityCurrency = scriptCurrencySymbol . liquidityPolicy

poolStateCoin :: Uniswap -> Coin
poolStateCoin = flip mkCoin poolStateTokenName . liquidityCurrency

-- uniswapInstance :: Uniswap -> Scripts.ScriptInstance Uniswapping
-- uniswapInstance us = Scripts.validator @Uniswapping
-- ($$(PlutusTx.compile [|| mkUniswapValidator ||])
-- `PlutusTx.applyCode` PlutusTx.liftCode us
-- `PlutusTx.applyCode` PlutusTx.liftCode c)
-- $$(PlutusTx.compile [|| wrap ||])
-- where
-- c :: Coin
-- c = poolStateCoin us

-- wrap = Scripts.wrapValidator @UniswapDatum @UniswapAction

-- uniswapScript :: Uniswap -> Validator
-- uniswapScript = Scripts.validatorScript . uniswapInstance
3 changes: 0 additions & 3 deletions AltLabs/dex-token-swap/src/Plutus/Contracts/LiquidityPool.hs
Expand Up @@ -6,9 +6,6 @@ module Plutus.Contracts.LiquidityPool
( calculateAdditionalLiquidity
, calculateInitialLiquidity
, calculateRemoval
-- , checkSwap
-- , lpTicker
, LiquidityPool
) where

import Ledger.Value (TokenName (..), unAssetClass, unCurrencySymbol)
Expand Down

0 comments on commit c000e16

Please sign in to comment.