agora/agora/Agora/Effect/TreasuryWithdrawal.hs
2022-05-12 13:54:31 +02:00

176 lines
6.5 KiB
Haskell

{-# LANGUAGE TemplateHaskell #-}
{- |
Module : Agora.Effect.TreasuryWithdrawal
Maintainer : seungheon.ooh@gmail.com
Description: An Effect that withdraws treasury deposit
An Effect that withdraws treasury deposit
-}
module Agora.Effect.TreasuryWithdrawal (
TreasuryWithdrawalDatum (..),
PTreasuryWithdrawalDatum (..),
treasuryWithdrawalValidator,
) where
import Control.Applicative (Const)
import GHC.Generics qualified as GHC
import Generics.SOP (Generic, I (I))
import Agora.Effect (makeEffect)
import Agora.Utils (findTxOutByTxOutRef, paddValue, tcassert, tclet, tcmatch)
import Plutarch.Api.V1 (
PCredential (..),
PTuple,
PValidator,
PValue,
ptuple,
)
import Plutarch.Internal (punsafeCoerce)
import Plutarch.DataRepr (
DerivePConstantViaData (..),
PDataFields,
PIsDataReprInstances (..),
)
import Plutarch.Lift (PConstantDecl, PUnsafeLiftDecl (..))
import Plutarch.TryFrom (PTryFrom (..))
import Plutus.V1.Ledger.Credential (Credential)
import Plutus.V1.Ledger.Value (CurrencySymbol, Value)
import PlutusTx qualified
{- | Datum that encodes behavior of Treasury Withdrawal effect.
Note: This Datum acts like a "predefined redeemer". Which is to say that
it encodes the properties a redeemer would, but is locked in-place until
spend.
-}
data TreasuryWithdrawalDatum = TreasuryWithdrawalDatum
{ receivers :: [(Credential, Value)]
-- ^ AssocMap for Value sent to each receiver from the treasury.
, treasuries :: [Credential]
-- ^ What Credentials is spending from legal.
}
deriving stock (Show, GHC.Generic)
deriving anyclass (Generic)
PlutusTx.makeLift ''TreasuryWithdrawalDatum
PlutusTx.makeIsDataIndexed ''TreasuryWithdrawalDatum [('TreasuryWithdrawalDatum, 0)]
-- | Haskell-level version of 'TreasuryWithdrawalDatum'.
newtype PTreasuryWithdrawalDatum (s :: S)
= PTreasuryWithdrawalDatum
( Term
s
( PDataRecord
'[ "receivers" ':= PBuiltinList (PAsData (PTuple PCredential PValue))
, "treasuries" ':= PBuiltinList (PAsData PCredential)
]
)
)
deriving stock (GHC.Generic)
deriving anyclass (Generic, PIsDataRepr)
deriving
(PlutusType, PIsData, PDataFields)
via PIsDataReprInstances PTreasuryWithdrawalDatum
instance PUnsafeLiftDecl PTreasuryWithdrawalDatum where
type PLifted PTreasuryWithdrawalDatum = TreasuryWithdrawalDatum
deriving via
(DerivePConstantViaData TreasuryWithdrawalDatum PTreasuryWithdrawalDatum)
instance
(PConstantDecl TreasuryWithdrawalDatum)
instance PTryFrom PData PTreasuryWithdrawalDatum where
type PTryFromExcess PData PTreasuryWithdrawalDatum = Const ()
ptryFrom' opq cont =
-- TODO: This should not use 'punsafeCoerce'.
-- Blocked by 'PCredential', and 'PTuple'.
cont (punsafeCoerce opq, ())
{- | Withdraws given list of values to specific target addresses.
It can be evoked by burning GAT. The transaction should have correct
outputs to the users and any left overs should be paid back to the treasury.
The validator does not accept any Redeemer as all "parameters" are provided
via encoded Datum.
Note:
It should check...
1. Transaction outputs should contain all of what Datum specified
2. Left over assets should be redirected back to Treasury
It can be more flexiable over...
- The number of outputs themselves
-}
treasuryWithdrawalValidator :: forall {s :: S}. CurrencySymbol -> Term s PValidator
treasuryWithdrawalValidator currSymbol = makeEffect currSymbol $
\_cs (datum' :: Term _ PTreasuryWithdrawalDatum) txOutRef' txInfo' -> unTermCont $ do
datum <- tcont $ pletFields @'["receivers", "treasuries"] datum'
txInfo <- tcont $ pletFields @'["outputs", "inputs"] txInfo'
PJust txOut <- tcmatch $ findTxOutByTxOutRef # txOutRef' # pfromData txInfo.inputs
effInput <- tcont $ pletFields @'["address", "value"] $ txOut
outputValues <-
tclet $
pmap
# plam
( \(pfromData -> txOut') -> unTermCont $ do
txOut <- tcont $ pletFields @'["address", "value"] $ txOut'
let cred = pfield @"credential" # pfromData txOut.address
pure . pdata $ ptuple # cred # txOut.value
)
# txInfo.outputs
inputValues <-
tclet $
pmap
# plam
( \((pfield @"resolved" #) . pfromData -> txOut') -> unTermCont $ do
txOut <- tcont $ pletFields @'["address", "value"] $ txOut'
let cred = pfield @"credential" # pfromData txOut.address
pure . pdata $ ptuple # cred # txOut.value
)
# txInfo.inputs
let ofTreasury =
pfilter
# plam (\((pfield @"_0" #) . pfromData -> cred) -> pelem # cred # datum.treasuries)
sumValues =
pfoldr
# plam (\((pfield @"_1" #) . pfromData -> x) y -> paddValue # pfromData x # y)
# pconstant (mempty :: Value)
treasuryInputValuesSum = sumValues #$ ofTreasury # inputValues
treasuryOutputValuesSum = sumValues #$ ofTreasury # outputValues
receiverValuesSum = sumValues # datum.receivers
isPubkey = plam $ \cred ->
pmatch cred $
\case
PPubKeyCredential _ -> pcon PTrue
PScriptCredential _ -> pcon PFalse
-- Constraints
outputContentMatchesRecivers =
pall # plam (\out -> pelem # out # outputValues)
#$ datum.receivers
excessShouldBePaidToInputs =
pdata (paddValue # receiverValuesSum # treasuryOutputValuesSum) #== pdata treasuryInputValuesSum
shouldNotPayToEffect =
pnot #$ pany
# plam
( \x ->
effInput.address #== pfield @"address" # pfromData x
)
# pfromData txInfo.outputs
inputsAreOnlyTreasuriesOrCollateral =
pall
# plam
( \((pfield @"_0" #) . pfromData -> cred) ->
cred #== pfield @"credential" # effInput.address
#|| pelem # cred # datum.treasuries
#|| isPubkey # pfromData cred
)
# inputValues
tcassert "Transaction should not pay to effects" shouldNotPayToEffect
tcassert "Transaction output does not match receivers" outputContentMatchesRecivers
tcassert "Remainders should be returned to the treasury" excessShouldBePaidToInputs
tcassert "Transaction should only have treasuries specified in the datum as input" inputsAreOnlyTreasuriesOrCollateral
pure . popaque $ pconstant ()