{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -fno-ignore-interface-pragmas #-}
module Plutus.Contracts.Swap(
Swap(..),
swapValidator
) where
import Ledger (POSIXTime, PaymentPubKey, PaymentPubKeyHash (unPaymentPubKeyHash), PubKeyHash)
import Ledger.Typed.Scripts qualified as Scripts
import Plutus.Contract.Oracle (Observation (..), SignedMessage)
import Plutus.Contract.Oracle qualified as Oracle
import Plutus.Script.Utils.Ada (Ada)
import Plutus.Script.Utils.Ada qualified as Ada
import Plutus.Script.Utils.Value (Value)
import Plutus.V1.Ledger.Address (toPubKeyHash)
import Plutus.V2.Ledger.Api qualified as V2
import Plutus.V2.Ledger.Contexts qualified as V2
import PlutusTx qualified
import PlutusTx.Prelude
data Swap = Swap
{ Swap -> Ada
swapNotionalAmt :: !Ada
, Swap -> POSIXTime
swapObservationTime :: !POSIXTime
, Swap -> Rational
swapFixedRate :: !Rational
, Swap -> Rational
swapFloatingRate :: !Rational
, Swap -> Ada
swapMargin :: !Ada
, Swap -> PaymentPubKey
swapOracle :: PaymentPubKey
}
PlutusTx.makeLift ''Swap
data SwapOwners = SwapOwners {
SwapOwners -> PaymentPubKeyHash
swapOwnersFixedLeg :: PaymentPubKeyHash,
SwapOwners -> PaymentPubKeyHash
swapOwnersFloating :: PaymentPubKeyHash
}
PlutusTx.unstableMakeIsData ''SwapOwners
PlutusTx.makeLift ''SwapOwners
type SwapOracleMessage = SignedMessage (Observation Rational)
{-# INLINABLE mkValidator #-}
mkValidator :: Swap -> SwapOwners -> SwapOracleMessage -> V2.ScriptContext -> Bool
mkValidator :: Swap -> SwapOwners -> SwapOracleMessage -> ScriptContext -> Bool
mkValidator Swap{PaymentPubKey
POSIXTime
Ada
Rational
swapOracle :: PaymentPubKey
swapMargin :: Ada
swapFloatingRate :: Rational
swapFixedRate :: Rational
swapObservationTime :: POSIXTime
swapNotionalAmt :: Ada
swapOracle :: Swap -> PaymentPubKey
swapMargin :: Swap -> Ada
swapFloatingRate :: Swap -> Rational
swapFixedRate :: Swap -> Rational
swapObservationTime :: Swap -> POSIXTime
swapNotionalAmt :: Swap -> Ada
..} SwapOwners{PaymentPubKeyHash
swapOwnersFloating :: PaymentPubKeyHash
swapOwnersFixedLeg :: PaymentPubKeyHash
swapOwnersFloating :: SwapOwners -> PaymentPubKeyHash
swapOwnersFixedLeg :: SwapOwners -> PaymentPubKeyHash
..} SwapOracleMessage
redeemer p :: ScriptContext
p@V2.ScriptContext{scriptContextTxInfo :: ScriptContext -> TxInfo
V2.scriptContextTxInfo=TxInfo
txInfo} =
let
extractVerifyAt :: SignedMessage (Observation Rational) -> PaymentPubKey -> POSIXTime -> Rational
extractVerifyAt :: SwapOracleMessage -> PaymentPubKey -> POSIXTime -> Rational
extractVerifyAt SwapOracleMessage
sm PaymentPubKey
pk POSIXTime
time =
case ScriptContext
-> PaymentPubKey
-> SwapOracleMessage
-> Either SignedMessageCheckError (Observation Rational)
forall a.
FromData a =>
ScriptContext
-> PaymentPubKey
-> SignedMessage a
-> Either SignedMessageCheckError a
Oracle.verifySignedMessageOnChain ScriptContext
p PaymentPubKey
pk SwapOracleMessage
sm of
Left SignedMessageCheckError
_ -> BuiltinString -> Rational
forall a. BuiltinString -> a
traceError BuiltinString
"checkSignatureAndDecode failed"
Right Observation{Rational
obsValue :: forall a. Observation a -> a
obsValue :: Rational
obsValue, POSIXTime
obsTime :: forall a. Observation a -> POSIXTime
obsTime :: POSIXTime
obsTime} ->
if POSIXTime
obsTime POSIXTime -> POSIXTime -> Bool
forall a. Eq a => a -> a -> Bool
== POSIXTime
time
then Rational
obsValue
else BuiltinString -> Rational
forall a. BuiltinString -> a
traceError BuiltinString
"wrong time"
fromInt :: Integer -> Rational
fromInt :: Integer -> Rational
fromInt = () -> Integer -> Rational
forall a. () -> a
error ()
adaValueIn :: Value -> Integer
adaValueIn :: Value -> Integer
adaValueIn Value
v = Ada -> Integer
Ada.getLovelace (Value -> Ada
Ada.fromValue Value
v)
isPaymentPubKeyOutput :: V2.TxOut -> PaymentPubKeyHash -> Bool
isPaymentPubKeyOutput :: TxOut -> PaymentPubKeyHash -> Bool
isPaymentPubKeyOutput TxOut
o PaymentPubKeyHash
k = Bool -> (PubKeyHash -> Bool) -> Maybe PubKeyHash -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (PubKeyHash -> PubKeyHash -> Bool
forall a. Eq a => a -> a -> Bool
(==) (PaymentPubKeyHash -> PubKeyHash
unPaymentPubKeyHash PaymentPubKeyHash
k)) (TxOut -> Maybe PubKeyHash
pubKeyOutput TxOut
o)
pubKeyOutput :: V2.TxOut -> Maybe PubKeyHash
pubKeyOutput :: TxOut -> Maybe PubKeyHash
pubKeyOutput V2.TxOut{Address
txOutAddress :: TxOut -> Address
txOutAddress :: Address
txOutAddress} = Address -> Maybe PubKeyHash
toPubKeyHash Address
txOutAddress
rt :: Rational
rt = SwapOracleMessage -> PaymentPubKey -> POSIXTime -> Rational
extractVerifyAt SwapOracleMessage
redeemer PaymentPubKey
swapOracle POSIXTime
swapObservationTime
rtDiff :: Rational
rtDiff :: Rational
rtDiff = Rational
rt Rational -> Rational -> Rational
forall a. AdditiveGroup a => a -> a -> a
- Rational
swapFixedRate
amt :: Integer
amt = Ada -> Integer
Ada.getLovelace Ada
swapNotionalAmt
margin :: Integer
margin = Ada -> Integer
Ada.getLovelace Ada
swapMargin
amt' :: Rational
amt' :: Rational
amt' = Integer -> Rational
fromInt Integer
amt
delta :: Rational
delta :: Rational
delta = Rational
amt' Rational -> Rational -> Rational
forall a. MultiplicativeSemigroup a => a -> a -> a
* Rational
rtDiff
fixedPayment :: Integer
fixedPayment :: Integer
fixedPayment = Rational -> Integer
round (Rational
amt' Rational -> Rational -> Rational
forall a. AdditiveSemigroup a => a -> a -> a
+ Rational
delta)
floatPayment :: Integer
floatPayment :: Integer
floatPayment = Rational -> Integer
round (Rational
amt' Rational -> Rational -> Rational
forall a. AdditiveSemigroup a => a -> a -> a
+ Rational
delta)
clamp :: Integer -> Integer
clamp :: Integer -> Integer
clamp Integer
x = Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
min Integer
0 (Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
max (Integer
2 Integer -> Integer -> Integer
forall a. MultiplicativeSemigroup a => a -> a -> a
* Integer
margin) Integer
x)
fixedRemainder :: Integer
fixedRemainder = Integer -> Integer
clamp ((Integer
margin Integer -> Integer -> Integer
forall a. AdditiveGroup a => a -> a -> a
- Integer
fixedPayment) Integer -> Integer -> Integer
forall a. AdditiveSemigroup a => a -> a -> a
+ Integer
floatPayment)
floatRemainder :: Integer
floatRemainder = Integer -> Integer
clamp ((Integer
margin Integer -> Integer -> Integer
forall a. AdditiveGroup a => a -> a -> a
- Integer
floatPayment) Integer -> Integer -> Integer
forall a. AdditiveSemigroup a => a -> a -> a
+ Integer
fixedPayment)
[TxInInfo
t1, TxInInfo
t2] = TxInfo -> [TxInInfo]
V2.txInfoInputs TxInfo
txInfo
[TxOut
o1, TxOut
o2] = TxInfo -> [TxOut]
V2.txInfoOutputs TxInfo
txInfo
iP1 :: V2.TxInInfo -> Bool
iP1 :: TxInInfo -> Bool
iP1 V2.TxInInfo{txInInfoResolved :: TxInInfo -> TxOut
V2.txInInfoResolved=V2.TxOut{Value
txOutValue :: TxOut -> Value
txOutValue :: Value
txOutValue}} =
TxInfo -> PubKeyHash -> Bool
V2.txSignedBy TxInfo
txInfo (PaymentPubKeyHash -> PubKeyHash
unPaymentPubKeyHash PaymentPubKeyHash
swapOwnersFixedLeg) Bool -> Bool -> Bool
&& Value -> Integer
adaValueIn Value
txOutValue Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
margin
iP2 :: V2.TxInInfo -> Bool
iP2 :: TxInInfo -> Bool
iP2 V2.TxInInfo{txInInfoResolved :: TxInInfo -> TxOut
V2.txInInfoResolved=V2.TxOut{Value
txOutValue :: Value
txOutValue :: TxOut -> Value
txOutValue}} =
TxInfo -> PubKeyHash -> Bool
V2.txSignedBy TxInfo
txInfo (PaymentPubKeyHash -> PubKeyHash
unPaymentPubKeyHash PaymentPubKeyHash
swapOwnersFloating) Bool -> Bool -> Bool
&& Value -> Integer
adaValueIn Value
txOutValue Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
margin
inConditions :: Bool
inConditions = (TxInInfo -> Bool
iP1 TxInInfo
t1 Bool -> Bool -> Bool
&& TxInInfo -> Bool
iP2 TxInInfo
t2) Bool -> Bool -> Bool
|| (TxInInfo -> Bool
iP1 TxInInfo
t2 Bool -> Bool -> Bool
&& TxInInfo -> Bool
iP2 TxInInfo
t1)
ol1 :: V2.TxOut -> Bool
ol1 :: TxOut -> Bool
ol1 o :: TxOut
o@V2.TxOut{Value
txOutValue :: Value
txOutValue :: TxOut -> Value
V2.txOutValue} =
TxOut -> PaymentPubKeyHash -> Bool
isPaymentPubKeyOutput TxOut
o PaymentPubKeyHash
swapOwnersFixedLeg Bool -> Bool -> Bool
&& Value -> Integer
adaValueIn Value
txOutValue Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
fixedRemainder
ol2 :: V2.TxOut -> Bool
ol2 :: TxOut -> Bool
ol2 o :: TxOut
o@V2.TxOut{Value
txOutValue :: Value
txOutValue :: TxOut -> Value
V2.txOutValue} =
TxOut -> PaymentPubKeyHash -> Bool
isPaymentPubKeyOutput TxOut
o PaymentPubKeyHash
swapOwnersFloating Bool -> Bool -> Bool
&& Value -> Integer
adaValueIn Value
txOutValue Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
floatRemainder
outConditions :: Bool
outConditions = (TxOut -> Bool
ol1 TxOut
o1 Bool -> Bool -> Bool
&& TxOut -> Bool
ol2 TxOut
o2) Bool -> Bool -> Bool
|| (TxOut -> Bool
ol1 TxOut
o2 Bool -> Bool -> Bool
&& TxOut -> Bool
ol2 TxOut
o1)
in Bool
inConditions Bool -> Bool -> Bool
&& Bool
outConditions
swapValidator :: Swap -> V2.Validator
swapValidator :: Swap -> Validator
swapValidator Swap
swp = CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> ())
-> Validator
V2.mkValidatorScript (CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> ())
-> Validator)
-> CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> ())
-> Validator
forall a b. (a -> b) -> a -> b
$
$$(PlutusTx.compile [|| validatorParam ||])
CompiledCode
(Swap -> BuiltinData -> BuiltinData -> BuiltinData -> ())
-> CompiledCodeIn DefaultUni DefaultFun Swap
-> CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> ())
forall (uni :: * -> *) fun a b.
(Closed uni, Everywhere uni Flat, Flat fun,
Everywhere uni PrettyConst, GShow uni, Pretty fun) =>
CompiledCodeIn uni fun (a -> b)
-> CompiledCodeIn uni fun a -> CompiledCodeIn uni fun b
`PlutusTx.applyCode`
Swap -> CompiledCodeIn DefaultUni DefaultFun Swap
forall (uni :: * -> *) a fun.
(Lift uni a, Throwable uni fun, Typecheckable uni fun) =>
a -> CompiledCodeIn uni fun a
PlutusTx.liftCode Swap
swp
where validatorParam :: Swap -> BuiltinData -> BuiltinData -> BuiltinData -> ()
validatorParam Swap
s = (SwapOwners -> SwapOracleMessage -> ScriptContext -> Bool)
-> BuiltinData -> BuiltinData -> BuiltinData -> ()
forall sc d r.
(IsScriptContext sc, UnsafeFromData d, UnsafeFromData r) =>
(d -> r -> sc -> Bool)
-> BuiltinData -> BuiltinData -> BuiltinData -> ()
Scripts.mkUntypedValidator (Swap -> SwapOwners -> SwapOracleMessage -> ScriptContext -> Bool
mkValidator Swap
s)