Merge branch 'staging' into seungheonoh/updatepse

This commit is contained in:
SeungheonOh 2022-11-23 08:50:19 -06:00 committed by GitHub
commit dac9dc2394
42 changed files with 2344 additions and 1716 deletions

View file

@ -11,10 +11,9 @@ module Agora.AuthorityToken (
singleAuthorityTokenBurned,
) where
import Agora.Utils (
passert,
psymbolValueOf',
)
import Agora.Governor (PGovernorRedeemer (PMintGATs), presolveGovernorRedeemer)
import Agora.SafeMoney (AuthorityTokenTag, GovernorSTTag)
import Agora.Utils (ptag, ptaggedSymbolValueOf, ptoScottEncodingT, puntag)
import Plutarch.Api.V1 (
PCredential (..),
PCurrencySymbol (..),
@ -26,17 +25,16 @@ import Plutarch.Api.V2 (
KeyGuarantees,
PAddress (PAddress),
PMintingPolicy,
PScriptContext (PScriptContext),
PScriptPurpose (PMinting),
PTxInInfo (PTxInInfo),
PTxInfo (PTxInfo),
PTxOut (PTxOut),
)
import Plutarch.Extra.AssetClass (PAssetClassData, ptoScottEncoding)
import Plutarch.Extra.AssetClass (PAssetClassData)
import Plutarch.Extra.Bool (passert)
import "liqwid-plutarch-extra" Plutarch.Extra.List (plookupAssoc)
import Plutarch.Extra.Maybe (pfromJust)
import Plutarch.Extra.ScriptContext (pisTokenSpent)
import Plutarch.Extra.Maybe (passertPJust, pfromJust)
import Plutarch.Extra.Sum (PSum (PSum))
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
pguardC,
pletC,
@ -44,7 +42,7 @@ import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
pmatchC,
)
import Plutarch.Extra.Traversable (pfoldMap)
import Plutarch.Extra.Value (psymbolValueOf)
import Plutarch.Extra.Value (psymbolValueOf')
--------------------------------------------------------------------------------
@ -66,7 +64,7 @@ import Plutarch.Extra.Value (psymbolValueOf)
@since 1.0.0
-}
authorityTokensValidIn :: forall (s :: S). Term s (PCurrencySymbol :--> PTxOut :--> PBool)
authorityTokensValidIn :: forall (s :: S). Term s (PTagged AuthorityTokenTag PCurrencySymbol :--> PTxOut :--> PBool)
authorityTokensValidIn = phoistAcyclic $
plam $ \authorityTokenSym txOut'' -> unTermCont $ do
PTxOut txOut' <- pmatchC txOut''
@ -75,7 +73,7 @@ authorityTokensValidIn = phoistAcyclic $
PValue value' <- pmatchC txOut.value
PMap value <- pmatchC value'
pure $
pmatch (plookupAssoc # pfstBuiltin # psndBuiltin # pdata authorityTokenSym # value) $ \case
pmatch (plookupAssoc # pfstBuiltin # psndBuiltin # pdata (puntag authorityTokenSym) # value) $ \case
PJust (pfromData -> _tokenMap') ->
pmatch (pfield @"credential" # address) $ \case
PPubKeyCredential _ ->
@ -97,13 +95,13 @@ authorityTokensValidIn = phoistAcyclic $
-}
singleAuthorityTokenBurned ::
forall (keys :: KeyGuarantees) (amounts :: AmountGuarantees) (s :: S).
Term s PCurrencySymbol ->
Term s (PTagged AuthorityTokenTag PCurrencySymbol) ->
Term s (PBuiltinList PTxInInfo) ->
Term s (PValue keys amounts) ->
Term s PBool
singleAuthorityTokenBurned gatCs inputs mint = unTermCont $ do
let gatAmountMinted :: Term _ PInteger
gatAmountMinted = psymbolValueOf # gatCs # mint
gatAmountMinted = ptaggedSymbolValueOf # gatCs # mint
let inputsWithGAT =
pfoldMap
@ -119,12 +117,13 @@ singleAuthorityTokenBurned gatCs inputs mint = unTermCont $ do
$ resolved
pure . pcon . PSum $
psymbolValueOf
ptaggedSymbolValueOf
# gatCs
#$ pfield @"value"
#$ resolved
)
# inputs
pure $
foldr1
(#&&)
@ -147,35 +146,46 @@ singleAuthorityTokenBurned gatCs inputs mint = unTermCont $ do
@since 0.1.0
-}
authorityTokenPolicy :: ClosedTerm (PAssetClassData :--> PMintingPolicy)
authorityTokenPolicy :: ClosedTerm (PTagged GovernorSTTag PAssetClassData :--> PMintingPolicy)
authorityTokenPolicy =
plam $ \atAssetClass _redeemer ctx' ->
pmatch ctx' $ \(PScriptContext ctx') -> unTermCont $ do
ctx <- pletFieldsC @'["txInfo", "purpose"] ctx'
PTxInfo txInfo' <- pmatchC $ pfromData ctx.txInfo
txInfo <- pletFieldsC @'["inputs", "mint", "outputs"] txInfo'
let inputs = txInfo.inputs
govTokenSpent = pisTokenSpent # (ptoScottEncoding # atAssetClass) # inputs
plam $ \gstAssetClass _redeemer ctx -> unTermCont $ do
ctxF <- pletFieldsC @'["txInfo", "purpose"] ctx
txInfoF <-
pletFieldsC
@'[ "inputs"
, "mint"
, "outputs"
, "redeemers"
]
ctxF.txInfo
PMinting ownSymbol' <- pmatchC $ pfromData ctx.purpose
PMinting ownSymbol' <- pmatchC $ pfromData ctxF.purpose
let ownSymbol = pfromData $ pfield @"_0" # ownSymbol'
let ownSymbol = pfromData $ pfield @"_0" # ownSymbol'
PPair mintedATs burntATs <-
pmatchC $ pfromJust #$ psymbolValueOf' # ownSymbol # txInfo.mint
PPair mintedATs burntATs <-
pmatchC $ pfromJust #$ psymbolValueOf' # ownSymbol # txInfoF.mint
pure $
popaque $
pif
(0 #< mintedATs)
( unTermCont $ do
pguardC "No GAT burnt" $ 0 #== burntATs
pguardC "Parent token did not move in minting GATs" govTokenSpent
pguardC "All outputs only emit valid GATs" $
pall
# plam
(authorityTokensValidIn # ownSymbol #)
# txInfo.outputs
pure $ pconstant ()
)
(passert "No GAT minted" (0 #== mintedATs) (pconstant ()))
pure $
popaque $
pif
(0 #< mintedATs)
( unTermCont $ do
pguardC "No GAT burnt" $ 0 #== burntATs
let governorRedeemer =
passertPJust
# "GST should move"
#$ presolveGovernorRedeemer
# (ptoScottEncodingT # gstAssetClass)
# pfromData txInfoF.inputs
# txInfoF.redeemers
pguardC "Governor redeemr correct" $
pcon PMintGATs #== governorRedeemer
pguardC "All outputs only emit valid GATs" $
pall
# plam
(authorityTokensValidIn # ptag ownSymbol #)
# txInfoF.outputs
pure $ pconstant ()
)
(passert "No GAT minted" (0 #== mintedATs) (pconstant ()))

View file

@ -8,6 +8,7 @@ Helpers for constructing effects.
module Agora.Effect (makeEffect) where
import Agora.AuthorityToken (singleAuthorityTokenBurned)
import Agora.SafeMoney (AuthorityTokenTag)
import Plutarch.Api.V1 (
PCurrencySymbol,
)
@ -17,6 +18,7 @@ import Plutarch.Api.V2 (
PTxOutRef,
PValidator,
)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pletFieldsC, pmatchC, ptryFromC)
{- | Helper "template" for creating effect validator.
@ -30,13 +32,13 @@ import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pletFiel
makeEffect ::
forall (datum :: PType) (s :: S).
(PTryFrom PData datum, PIsData datum) =>
( Term s PCurrencySymbol ->
( Term s (PTagged AuthorityTokenTag PCurrencySymbol) ->
Term s datum ->
Term s PTxOutRef ->
Term s (PAsData PTxInfo) ->
Term s POpaque
) ->
Term s PCurrencySymbol ->
Term s (PTagged AuthorityTokenTag PCurrencySymbol) ->
Term s PValidator
makeEffect f atSymbol =
plam $ \datum _redeemer ctx' -> unTermCont $ do

View file

@ -25,8 +25,8 @@ import Agora.Governor (
PGovernorDatum,
PGovernorRedeemer,
)
import Agora.Plutarch.Orphans ()
import Agora.Utils (pfromSingleton, ptryFromRedeemer)
import Agora.SafeMoney (AuthorityTokenTag, GovernorSTTag)
import Agora.Utils (ptaggedSymbolValueOf)
import Plutarch.Api.V1 (PCurrencySymbol, PValidatorHash)
import Plutarch.Api.V2 (
PScriptPurpose (PSpending),
@ -38,11 +38,17 @@ import Plutarch.DataRepr (
PDataFields,
)
import Plutarch.Extra.Field (pletAll, pletAllC)
import Plutarch.Extra.Maybe (passertPJust, pdnothing)
import "liqwid-plutarch-extra" Plutarch.Extra.List (ptryFromSingleton)
import Plutarch.Extra.Maybe (passertPJust, pfromJust)
import Plutarch.Extra.Record (mkRecordConstr, (.=))
import Plutarch.Extra.ScriptContext (paddressFromValidatorHash, pfromOutputDatum, pisScriptAddress)
import Plutarch.Extra.ScriptContext (
pisScriptAddress,
ptryFromOutputDatum,
ptryFromRedeemer,
pvalidatorHashFromAddress,
)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pletFieldsC)
import Plutarch.Extra.Value (psymbolValueOf)
import Plutarch.Lift (PConstantDecl, PLifted, PUnsafeLiftDecl)
import PlutusLedgerApi.V1 (TxOutRef)
import PlutusTx qualified
@ -145,8 +151,8 @@ deriving anyclass instance PTryFrom PData PMutateGovernorDatum
mutateGovernorValidator ::
ClosedTerm
( PValidatorHash
:--> PCurrencySymbol
:--> PCurrencySymbol
:--> PTagged GovernorSTTag PCurrencySymbol
:--> PTagged AuthorityTokenTag PCurrencySymbol
:--> PValidator
)
mutateGovernorValidator =
@ -177,25 +183,23 @@ mutateGovernorValidator =
pany
# plam
( flip pletAll $ \inputF ->
let governorAddress =
paddressFromValidatorHash
# govValidatorHash
# pdnothing
isGovernorInput =
let isGovernorInput =
foldl1
(#&&)
[ ptraceIfFalse "Governor UTxO should carry GST" $
psymbolValueOf
ptaggedSymbolValueOf
# gstSymbol
# (pfield @"value" # inputF.resolved)
#== 1
, ptraceIfFalse "Can only modify the pinned governor" $
inputF.outRef #== effectDatumF.governorRef
, ptraceIfFalse "Governor validator run" $
pfield @"address"
# inputF.resolved
#== governorAddress
let inputValidatorHash =
pfromJust
#$ pvalidatorHashFromAddress
#$ pfield @"address"
# inputF.resolved
in inputValidatorHash #== govValidatorHash
]
in isGovernorInput
)
@ -216,11 +220,11 @@ mutateGovernorValidator =
let governorOutput =
ptrace "Only governor output is allowed" $
pfromSingleton # pfromData txInfoF.outputs
ptryFromSingleton # pfromData txInfoF.outputs
governorOutputDatum =
ptrace "Resolve governor outoput datum" $
pfromOutputDatum @PGovernorDatum
ptryFromOutputDatum @PGovernorDatum
# (pfield @"datum" # governorOutput)
# txInfoF.datums

View file

@ -8,10 +8,10 @@ A dumb effect that only burns its GAT.
module Agora.Effect.NoOp (noOpValidator, PNoOp) where
import Agora.Effect (makeEffect)
import Agora.Plutarch.Orphans ()
import Agora.SafeMoney (AuthorityTokenTag)
import Plutarch.Api.V1 (PCurrencySymbol)
import Plutarch.Api.V2 (PValidator)
import Plutarch.Orphans ()
import Plutarch.Extra.Tagged (PTagged)
{- | Dummy datum for NoOp effect.
@ -40,7 +40,7 @@ instance PTryFrom PData (PAsData PNoOp)
@since 1.0.0
-}
noOpValidator :: ClosedTerm (PCurrencySymbol :--> PValidator)
noOpValidator :: ClosedTerm (PTagged AuthorityTokenTag PCurrencySymbol :--> PValidator)
noOpValidator = plam $
makeEffect $
\_ (_datum :: Term s (PAsData PNoOp)) _ _ -> popaque (pconstant ())

View file

@ -14,8 +14,7 @@ module Agora.Effect.TreasuryWithdrawal (
) where
import Agora.Effect (makeEffect)
import Agora.Plutarch.Orphans ()
import Agora.Utils (pdelete)
import Agora.SafeMoney (AuthorityTokenTag)
import Plutarch.Api.V1 (
PCredential,
PCurrencySymbol,
@ -35,7 +34,9 @@ import Plutarch.DataRepr (
PDataFields,
)
import Plutarch.Extra.Field (pletAllC)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pdeleteFirst)
import Plutarch.Extra.ScriptContext (pisPubKey)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pletFieldsC)
import Plutarch.Lift (PConstantDecl, PUnsafeLiftDecl (PLifted))
import PlutusLedgerApi.V1.Credential (Credential)
@ -133,7 +134,7 @@ instance PTryFrom PData PTreasuryWithdrawalDatum
-}
treasuryWithdrawalValidator ::
forall (s :: S).
Term s (PCurrencySymbol :--> PValidator)
Term s (PTagged AuthorityTokenTag PCurrencySymbol :--> PValidator)
treasuryWithdrawalValidator = plam $
makeEffect $
\_cs (datum :: Term _ PTreasuryWithdrawalDatum) effectInputRef txInfo -> unTermCont $ do
@ -178,7 +179,7 @@ treasuryWithdrawalValidator = plam $
(ptraceError "Invalid receiver")
pure $
pmatch (pdelete # credValue # receivers) $ \case
pmatch (pdeleteFirst # credValue # receivers) $ \case
PJust updatedReceivers ->
ptrace "Receiver output" updatedReceivers
PNothing ->

View file

@ -21,6 +21,7 @@ module Agora.Governor (
pgetNextProposalId,
getNextProposalId,
pisGovernorDatumValid,
presolveGovernorRedeemer,
) where
import Agora.Aeson.Orphans ()
@ -39,20 +40,33 @@ import Agora.Proposal.Time (
pisMaxTimeRangeWidthValid,
pisProposalTimingConfigValid,
)
import Agora.SafeMoney (GTTag)
import Agora.SafeMoney (GTTag, GovernorSTTag)
import Data.Aeson qualified as Aeson
import Data.Tagged (Tagged)
import Optics.TH (makeFieldLabelsNoPrefix)
import Plutarch.Api.V1.Scripts (PRedeemer)
import Plutarch.Api.V2 (KeyGuarantees (Unsorted), PMap, PScriptPurpose (PSpending), PTxInInfo)
import Plutarch.DataRepr (
DerivePConstantViaData (DerivePConstantViaData),
PDataFields,
)
import Plutarch.Extra.AssetClass (AssetClass)
import Plutarch.Extra.AssetClass (AssetClass, PAssetClass)
import Plutarch.Extra.Bind (PBind ((#>>=)))
import Plutarch.Extra.Field (pletAll)
import Plutarch.Extra.Function (pflip)
import Plutarch.Extra.Functor (PFunctor (pfmap))
import Plutarch.Extra.IsData (
DerivePConstantViaEnum (DerivePConstantEnum),
EnumIsData (EnumIsData),
PlutusTypeEnumData,
)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
import Plutarch.Extra.Maybe (pjust, pnothing)
import Plutarch.Extra.Record (mkRecordConstr, (.=))
import Plutarch.Extra.ScriptContext (ptryFromRedeemer)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pletFieldsC)
import Plutarch.Extra.Value (passetClassValueOfT)
import Plutarch.Lift (PConstantDecl, PUnsafeLiftDecl (PLifted))
import PlutusLedgerApi.V1 (TxOutRef)
import PlutusTx qualified
@ -73,9 +87,8 @@ data GovernorDatum = GovernorDatum
-- Will get copied over upon the creation of proposals.
, createProposalTimeRangeMaxWidth :: MaxTimeRangeWidth
-- ^ The maximum valid duration of a transaction that creats a proposal.
, maximumProposalsPerStake :: Integer
-- ^ The maximum number of unfinished proposals that a stake is allowed to be
-- associated to.
, maximumCreatedProposalsPerStake :: Integer
-- ^ The maximum number of proposals created by any given stakes.
}
deriving stock
( -- | @since 0.1.0
@ -84,6 +97,9 @@ data GovernorDatum = GovernorDatum
Generic
)
-- | @since 0.2.1
makeFieldLabelsNoPrefix ''GovernorDatum
-- | @since 0.1.0
PlutusTx.makeIsDataIndexed ''GovernorDatum [('GovernorDatum, 0)]
@ -149,6 +165,8 @@ data Governor = Governor
Aeson.FromJSON
)
makeFieldLabelsNoPrefix ''Governor
--------------------------------------------------------------------------------
{- | Plutarch-level datum for the Governor script.
@ -164,7 +182,7 @@ newtype PGovernorDatum (s :: S) = PGovernorDatum
, "nextProposalId" ':= PProposalId
, "proposalTimings" ':= PProposalTimingConfig
, "createProposalTimeRangeMaxWidth" ':= PMaxTimeRangeWidth
, "maximumProposalsPerStake" ':= PInteger
, "maximumCreatedProposalsPerStake" ':= PInteger
]
)
}
@ -181,6 +199,8 @@ newtype PGovernorDatum (s :: S) = PGovernorDatum
PDataFields
, -- | @since 0.1.0
PEq
, -- | @since 0.2.1
PShow
)
-- | @since 0.2.0
@ -277,3 +297,53 @@ pisGovernorDatumValid = phoistAcyclic $
, ptraceIfFalse "time range valid" $
pisMaxTimeRangeWidthValid # datumF.createProposalTimeRangeMaxWidth
]
{- | Find the governor input and resolve the corresponding governor redeemer,
given the assetclass of GST.
@since 1.0.0
-}
presolveGovernorRedeemer ::
forall (s :: S).
Term
s
( PTagged GovernorSTTag PAssetClass
:--> PBuiltinList PTxInInfo
:--> PMap 'Unsorted PScriptPurpose PRedeemer
:--> PMaybe PGovernorRedeemer
)
presolveGovernorRedeemer = phoistAcyclic $
plam $ \gstClass inputs redeemers ->
let governorInputRef =
pfindJust
# plam
( flip pletAll $ \inputF ->
let value = pfield @"value" # inputF.resolved
isGovernorInput =
passetClassValueOfT
# gstClass
# value
#== 1
in pif
isGovernorInput
(pjust # inputF.outRef)
pnothing
)
# inputs
governorScriptPurpose =
pfmap
# plam
( \ref ->
mkRecordConstr
PSpending
(#_0 .= ref)
)
# governorInputRef
governorRedeemer =
governorScriptPurpose
#>>= pflip
# ptryFromRedeemer @(PAsData PGovernorRedeemer)
# redeemers
in pfmap # plam pfromData # governorRedeemer

View file

@ -35,41 +35,42 @@ import Agora.Proposal (
pneutralOption,
pwinner,
)
import Agora.Proposal.Time (validateProposalStartingTime)
import Agora.Proposal.Time (pvalidateProposalStartingTime)
import Agora.SafeMoney (AuthorityTokenTag, GovernorSTTag, ProposalSTTag, StakeSTTag)
import Agora.Stake (
pnumCreatedProposals,
presolveStakeInputDatum,
)
import Agora.Utils (
plistEqualsBy,
pscriptHashToTokenName,
)
import Plutarch.Api.V1 (PCurrencySymbol)
import Agora.Utils (ptaggedSymbolValueOf, ptoScottEncodingT, puntag)
import Data.Function (on)
import Plutarch.Api.V1 (PCurrencySymbol, PValidatorHash)
import Plutarch.Api.V1.AssocMap (plookup)
import Plutarch.Api.V1.AssocMap qualified as AssocMap
import Plutarch.Api.V2 (
PAddress,
PMintingPolicy,
PScriptPurpose (PMinting, PSpending),
PTxOut,
PTxOutRef,
PValidator,
)
import Plutarch.Extra.AssetClass (PAssetClassData, passetClass, ptoScottEncoding)
import Plutarch.Extra.AssetClass (PAssetClassData, passetClass)
import Plutarch.Extra.Field (pletAll, pletAllC)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust, pmapMaybe)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust, plistEqualsBy, pmapMaybe)
import "liqwid-plutarch-extra" Plutarch.Extra.Map (pkeys, ptryLookup)
import Plutarch.Extra.Maybe (passertPJust, pjust, pmaybe, pmaybeData, pnothing)
import Plutarch.Extra.Ord (psort)
import Plutarch.Extra.Maybe (passertPJust, pfromJust, pjust, pmaybeData, pnothing)
import Plutarch.Extra.Ord (POrdering (..), pcompareBy, pfromOrd, psort)
import Plutarch.Extra.Record (mkRecordConstr, (.&), (.=))
import Plutarch.Extra.ScriptContext (
pfindTxInByTxOutRef,
pfromDatumHash,
pfromOutputDatum,
pisUTXOSpent,
pscriptHashFromAddress,
pscriptHashToTokenName,
ptryFromDatumHash,
ptryFromOutputDatum,
pvalidatorHashFromAddress,
pvalueSpent,
)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
pguardC,
pletC,
@ -153,7 +154,7 @@ governorPolicy =
governorDatum =
ptrace "Resolve governor datum" $
pfromOutputDatum @PGovernorDatum
ptryFromOutputDatum @PGovernorDatum
# txOutF.datum
# txInfoF.datums
in pif isGovernorUTxO (pjust # governorDatum) pnothing
@ -263,15 +264,15 @@ governorPolicy =
governorValidator ::
-- | Lazy precompiled scripts.
ClosedTerm
( PAddress
:--> PAssetClassData
:--> PCurrencySymbol
:--> PCurrencySymbol
:--> PCurrencySymbol
( PValidatorHash
:--> PTagged StakeSTTag PAssetClassData
:--> PTagged GovernorSTTag PCurrencySymbol
:--> PTagged ProposalSTTag PCurrencySymbol
:--> PTagged AuthorityTokenTag PCurrencySymbol
:--> PValidator
)
governorValidator =
plam $ \proposalValidatorAddress sstClass gstSymbol pstSymbol atSymbol datum redeemer ctx -> unTermCont $ do
plam $ \proposalValidatorHash sstClass gstSymbol pstSymbol atSymbol datum redeemer ctx -> unTermCont $ do
ctxF <- pletAllC ctx
txInfo <- pletC $ pfromData ctxF.txInfo
txInfoF <-
@ -316,14 +317,16 @@ governorValidator =
foldl1
(#&&)
[ ptraceIfFalse "Own by governor validator" $
outputF.address #== governorInputF.address
((#==) `on` (pvalidatorHashFromAddress #))
outputF.address
governorInputF.address
, ptraceIfFalse "Has governor ST" $
psymbolValueOf # gstSymbol # outputF.value #== 1
ptaggedSymbolValueOf # gstSymbol # outputF.value #== 1
]
datum =
ptrace "Resolve governor datum" $
pfromOutputDatum @PGovernorDatum
ptryFromOutputDatum @PGovernorDatum
# outputF.datum
# txInfoF.datums
in pif
@ -335,22 +338,24 @@ governorValidator =
----------------------------------------------------------------------------
pstClass <- pletC $ passetClass # pto pstSymbol # pconstant ""
getProposalDatum :: Term _ (PTxOut :--> PMaybe PProposalDatum) <-
pletC $
plam $
flip (pletFields @'["value", "datum", "address"]) $ \txOutF ->
let isProposalUTxO =
txOutF.address
#== pdata proposalValidatorAddress
#&& psymbolValueOf
# pstSymbol
(pfromJust #$ pvalidatorHashFromAddress # pfromData txOutF.address)
#== proposalValidatorHash
#&& passetClassValueOf
# pstClass
# txOutF.value
#== 1
proposalDatum =
ptrace "Resolve proposal output datum" $
pfromData $
pfromOutputDatum
ptryFromOutputDatum
# txOutF.datum
# txInfoF.datums
in pif isProposalUTxO (pjust # proposalDatum) pnothing
@ -378,8 +383,8 @@ governorValidator =
.= governorInputDatumF.proposalTimings
.& #createProposalTimeRangeMaxWidth
.= governorInputDatumF.createProposalTimeRangeMaxWidth
.& #maximumProposalsPerStake
.= governorInputDatumF.maximumProposalsPerStake
.& #maximumCreatedProposalsPerStake
.= governorInputDatumF.maximumCreatedProposalsPerStake
)
pguardC "Only next proposal id gets advanced" $
@ -388,16 +393,7 @@ governorValidator =
-- Check that exactly one proposal token is being minted.
pguardC "Exactly one proposal token must be minted" $
let vMap = pfromData $ pto txInfoF.mint
tnMap = plookup # pstSymbol # vMap
-- Ada and PST
onlyPST = plength # pto vMap #== 2
onePST =
pmaybe
# pconstant False
# plam (#== AssocMap.psingleton # pconstant "" # 1)
# tnMap
in onlyPST #&& onePST
passetClassValueOf # pstClass # txInfoF.mint #== 1
-- Check that a stake is spent to create the propsal,
-- and the value it contains meets the requirement.
@ -407,7 +403,7 @@ governorValidator =
# "Stake input should present"
#$ pfindJust
# ( presolveStakeInputDatum
# (ptoScottEncoding # sstClass)
# (ptoScottEncodingT # sstClass)
# txInfoF.datums
)
# pfromData txInfoF.inputs
@ -417,7 +413,7 @@ governorValidator =
pguardC "Proposals created by the stake must not exceed the limit" $
pnumCreatedProposals
# stakeInputDatumF.lockedBy
#< governorInputDatumF.maximumProposalsPerStake
#< governorInputDatumF.maximumCreatedProposalsPerStake
let gtThreshold =
pfromData $
@ -425,7 +421,7 @@ governorValidator =
# governorInputDatumF.proposalThresholds
pguardC "Require minimum amount of GTs" $
gtThreshold #< stakeInputDatumF.stakedAmount
gtThreshold #<= stakeInputDatumF.stakedAmount
-- Check that the newly minted PST is sent to the proposal validator,
-- and the datum it carries is legal.
@ -457,7 +453,7 @@ governorValidator =
, ptraceIfFalse "cosigners correct" $
plistEquals # pfromData proposalOutputDatumF.cosigners # expectedCosigners
, ptraceIfFalse "starting time valid" $
validateProposalStartingTime
pvalidateProposalStartingTime
# governorInputDatumF.createProposalTimeRangeMaxWidth
# txInfoF.validRange
# proposalOutputDatumF.startingTime
@ -478,7 +474,7 @@ governorValidator =
-- Filter out proposal inputs and ouputs using PST and the address of proposal validator.
pguardC "The governor can only process one proposal at a time" $
(psymbolValueOf # pstSymbol #$ pvalueSpent # txInfoF.inputs) #== 1
(ptaggedSymbolValueOf # pstSymbol #$ pvalueSpent # txInfoF.inputs) #== 1
let proposalInputDatum =
passertPJust
@ -510,14 +506,13 @@ governorValidator =
( \output -> unTermCont $ do
outputF <- pletFieldsC @'["address", "datum", "value"] output
let isAuthorityUTxO =
psymbolValueOf
let atAmount =
ptaggedSymbolValueOf
# atSymbol
# outputF.value
#== 1
handleAuthorityUTxO =
unTermCont $ do
do
receiverScriptHash <-
pletC $
passertPJust
@ -538,7 +533,7 @@ governorValidator =
# pconstant ""
# plam (pscriptHashToTokenName . pfromData)
# effect.scriptHash
gatAssetClass = passetClass # atSymbol # tagToken
gatAssetClass = passetClass # puntag atSymbol # tagToken
valueGATCorrect =
passetClassValueOf
# gatAssetClass
@ -546,7 +541,7 @@ governorValidator =
#== 1
let hasCorrectDatum =
effect.datumHash #== pfromDatumHash # outputF.datum
effect.datumHash #== ptryFromDatumHash # outputF.datum
pguardC "Authority output valid" $
foldr1
@ -556,19 +551,27 @@ governorValidator =
, ptraceIfFalse "Value correctly encodes Auth Check script" valueGATCorrect
]
pure receiverScriptHash
pure $ pjust # receiverScriptHash
pure $
pif
isAuthorityUTxO
(pjust # handleAuthorityUTxO)
pnothing
pmatchC
( pcompareBy
# pfromOrd
# atAmount
# 1
)
>>= \case
-- atAmount == 1
PEQ -> handleAuthorityUTxO
-- atAmount < 1
PLT -> pure pnothing
-- atAmount > 1
PGT -> pure $ ptraceError "More than one GAT in one UTxO"
)
-- The sorted hashes of all the GAT receivers.
actualReceivers =
psort
#$ pmapMaybe
#$ pmapMaybe @PList
# getReceiverScriptHash
# pfromData txInfoF.outputs

View file

@ -3,13 +3,14 @@
module Agora.Linker (linker, AgoraScriptInfo (..)) where
import Agora.Governor (Governor (gstOutRef, gtClassRef, maximumCosigners))
import Agora.Utils (validatorHashToAddress, validatorHashToTokenName)
import Agora.SafeMoney (AuthorityTokenTag, GTTag, GovernorSTTag, ProposalSTTag, StakeSTTag)
import Data.Aeson qualified as Aeson
import Data.Map (fromList)
import Data.Tagged (untag)
import Data.Tagged (Tagged (Tagged))
import Plutarch.Api.V2 (mintingPolicySymbol, validatorHash)
import Plutarch.Extra.AssetClass (AssetClass (AssetClass))
import PlutusLedgerApi.V1 (Address, CurrencySymbol, TxOutRef, ValidatorHash)
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
import PlutusLedgerApi.V1 (CurrencySymbol, TxOutRef, ValidatorHash)
import Ply (
ScriptRole (MintingPolicyRole, ValidatorRole),
toMintingPolicy,
@ -30,10 +31,10 @@ import Prelude hiding ((#))
@since 1.0.0
-}
data AgoraScriptInfo = AgoraScriptInfo
{ governorAssetClass :: AssetClass
, authorityTokenSymbol :: CurrencySymbol
, proposalAssetClass :: AssetClass
, stakeAssetClass :: AssetClass
{ governorAssetClass :: Tagged GovernorSTTag AssetClass
, authorityTokenSymbol :: Tagged AuthorityTokenTag CurrencySymbol
, proposalAssetClass :: Tagged ProposalSTTag AssetClass
, stakeAssetClass :: Tagged StakeSTTag AssetClass
, governor :: Governor
}
deriving stock (Generic, Show)
@ -45,28 +46,86 @@ data AgoraScriptInfo = AgoraScriptInfo
-}
linker :: Linker Governor (ScriptExport AgoraScriptInfo)
linker = do
govPol <- fetchTS @MintingPolicyRole @'[TxOutRef] "agora:governorPolicy"
govVal <- fetchTS @ValidatorRole @'[Address, AssetClass, CurrencySymbol, CurrencySymbol, CurrencySymbol] "agora:governorValidator"
stkPol <- fetchTS @MintingPolicyRole @'[AssetClass] "agora:stakePolicy"
stkVal <- fetchTS @ValidatorRole @'[CurrencySymbol, AssetClass, AssetClass] "agora:stakeValidator"
prpPol <- fetchTS @MintingPolicyRole @'[AssetClass] "agora:proposalPolicy"
prpVal <- fetchTS @ValidatorRole @'[AssetClass, CurrencySymbol, CurrencySymbol, Integer] "agora:proposalValidator"
treVal <- fetchTS @ValidatorRole @'[CurrencySymbol] "agora:treasuryValidator"
atkPol <- fetchTS @MintingPolicyRole @'[AssetClass] "agora:authorityTokenPolicy"
noOpVal <- fetchTS @ValidatorRole @'[CurrencySymbol] "agora:noOpValidator"
treaWithdrawalVal <- fetchTS @ValidatorRole @'[CurrencySymbol] "agora:treasuryWithdrawalValidator"
mutateGovVal <- fetchTS @ValidatorRole @'[ValidatorHash, CurrencySymbol, CurrencySymbol] "agora:mutateGovernorValidator"
govPol <-
fetchTS
@MintingPolicyRole
@'[TxOutRef]
"agora:governorPolicy"
govVal <-
fetchTS
@ValidatorRole
@'[ ValidatorHash
, Tagged StakeSTTag AssetClass
, Tagged GovernorSTTag CurrencySymbol
, Tagged ProposalSTTag CurrencySymbol
, Tagged AuthorityTokenTag CurrencySymbol
]
"agora:governorValidator"
stkPol <-
fetchTS
@MintingPolicyRole
@'[Tagged GTTag AssetClass]
"agora:stakePolicy"
stkVal <-
fetchTS
@ValidatorRole
@'[ Tagged StakeSTTag CurrencySymbol
, Tagged ProposalSTTag AssetClass
, Tagged GTTag AssetClass
]
"agora:stakeValidator"
prpPol <-
fetchTS @MintingPolicyRole
@'[Tagged GovernorSTTag AssetClass]
"agora:proposalPolicy"
prpVal <-
fetchTS
@ValidatorRole
@'[ Tagged StakeSTTag AssetClass
, Tagged GovernorSTTag CurrencySymbol
, Tagged ProposalSTTag CurrencySymbol
, Integer
]
"agora:proposalValidator"
treVal <-
fetchTS
@ValidatorRole
@'[Tagged AuthorityTokenTag CurrencySymbol]
"agora:treasuryValidator"
atkPol <-
fetchTS
@MintingPolicyRole
@'[Tagged GovernorSTTag AssetClass]
"agora:authorityTokenPolicy"
noOpVal <-
fetchTS
@ValidatorRole
@'[Tagged AuthorityTokenTag CurrencySymbol]
"agora:noOpValidator"
treaWithdrawalVal <-
fetchTS
@ValidatorRole
@'[Tagged AuthorityTokenTag CurrencySymbol]
"agora:treasuryWithdrawalValidator"
mutateGovVal <-
fetchTS
@ValidatorRole
@'[ ValidatorHash
, Tagged GovernorSTTag CurrencySymbol
, Tagged AuthorityTokenTag CurrencySymbol
]
"agora:mutateGovernorValidator"
governor <- getParam
let govPol' = govPol # governor.gstOutRef
govVal' =
govVal
# propValAddress
# sstAssetClass
# gstSymbol
# pstSymbol
# atSymbol
# propValHash
# Tagged sstAssetClass
# Tagged gstSymbol
# Tagged pstSymbol
# Tagged atSymbol
gstSymbol =
mintingPolicySymbol $
toMintingPolicy
@ -75,34 +134,40 @@ linker = do
AssetClass gstSymbol ""
govValHash = validatorHash $ toValidator govVal'
at = gstAssetClass
atPol' = atkPol # at
atPol' = atkPol # Tagged gstAssetClass
atSymbol = mintingPolicySymbol $ toMintingPolicy atPol'
propPol' = prpPol # gstAssetClass
propPol' = prpPol # Tagged gstAssetClass
propVal' =
prpVal
# sstAssetClass
# gstSymbol
# pstSymbol
# Tagged sstAssetClass
# Tagged gstSymbol
# Tagged pstSymbol
# governor.maximumCosigners
propValAddress =
validatorHashToAddress $ validatorHash $ toValidator propVal'
propValHash = validatorHash $ toValidator propVal'
pstSymbol = mintingPolicySymbol $ toMintingPolicy propPol'
pstAssetClass = AssetClass pstSymbol ""
stakPol' = stkPol # untag governor.gtClassRef
stakVal' = stkVal # sstSymbol # pstAssetClass # untag governor.gtClassRef
stakPol' = stkPol # governor.gtClassRef
stakVal' =
stkVal
# Tagged sstSymbol
# Tagged pstAssetClass
# governor.gtClassRef
sstSymbol = mintingPolicySymbol $ toMintingPolicy stakPol'
stakValTokenName =
validatorHashToTokenName $ validatorHash $ toValidator stakVal'
sstAssetClass = AssetClass sstSymbol stakValTokenName
treaVal' = treVal # atSymbol
treaVal' = treVal # Tagged atSymbol
noOpVal' = noOpVal # atSymbol
treaWithdrawalVal' = treaWithdrawalVal # atSymbol
mutateGovVal' = mutateGovVal # govValHash # gstSymbol # atSymbol
noOpVal' = noOpVal # Tagged atSymbol
treaWithdrawalVal' = treaWithdrawalVal # Tagged atSymbol
mutateGovVal' =
mutateGovVal
# govValHash
# Tagged gstSymbol
# Tagged atSymbol
return $
ScriptExport
@ -122,10 +187,10 @@ linker = do
]
, information =
AgoraScriptInfo
{ governorAssetClass = gstAssetClass
, authorityTokenSymbol = atSymbol
, proposalAssetClass = pstAssetClass
, stakeAssetClass = sstAssetClass
{ governorAssetClass = Tagged gstAssetClass
, authorityTokenSymbol = Tagged atSymbol
, proposalAssetClass = Tagged pstAssetClass
, stakeAssetClass = Tagged sstAssetClass
, governor = governor
}
}

View file

@ -6,10 +6,14 @@ import Plutarch.Lift (PConstantDecl (..), PUnsafeLiftDecl (PLifted))
import Data.Bifunctor (Bifunctor (bimap))
import Data.Map.Strict qualified as StrictMap
import Data.Tagged (Tagged (Tagged))
import Data.Traversable (for)
import Plutarch.Api.V1 (KeyGuarantees (Sorted), PMap)
import Plutarch.Extra.Tagged (PTagged)
import PlutusTx qualified
import PlutusTx.AssocMap qualified as AssocMap
import Ply (PlyArg)
import Ply.Plutarch.Class (PlyArgOf)
-- | @since 1.0.0
instance
@ -74,3 +78,9 @@ instance
isSorted [] = True
isSorted [_] = True
isSorted (x : y : xs) = x < y && isSorted (y : xs)
-- | @since 1.0.0
type instance PlyArgOf (PTagged tag a) = Tagged tag (PlyArgOf a)
-- | @since 1.0.0
deriving newtype instance PlyArg a => PlyArg (Tagged tag a)

View file

@ -162,7 +162,7 @@ newtype ResultTag = ResultTag {getResultTag :: Integer}
data ProposalStatus
= -- | A draft proposal represents a proposal that has yet to be realized.
--
-- In effect, this means one which didn't have enough LQ to be a full
-- In effect, this means one which didn't have enough GT to be a full
-- proposal, and needs cosigners to enable that to happen. This is
-- similar to a "temperature check", but only useful if multiple people
-- want to pool governance tokens together. If the proposal doesn't get to
@ -579,6 +579,8 @@ newtype PProposalThresholds (s :: S) = PProposalThresholds
PIsData
, -- | @since 0.1.0
PDataFields
, -- | @since 0.2.1
PShow
)
-- | @since 0.2.0

View file

@ -10,7 +10,7 @@ module Agora.Proposal.Scripts (
proposalPolicy,
) where
import Agora.Governor (PGovernorRedeemer (PCreateProposal))
import Agora.Governor (PGovernorRedeemer (PCreateProposal), presolveGovernorRedeemer)
import Agora.Proposal (
PProposalDatum (PProposalDatum),
PProposalRedeemer (PAdvanceProposal, PCosign, PUnlockStake, PVote),
@ -23,10 +23,12 @@ import Agora.Proposal (
import Agora.Proposal.Time (
PPeriod (PDraftingPeriod, PExecutingPeriod, PLockingPeriod, PVotingPeriod),
PTimingRelation (PAfter, PWithin),
currentProposalTime,
pcurrentProposalTime,
pgetRelation,
pisWithin,
psatisfyMaximumWidth,
)
import Agora.SafeMoney (GovernorSTTag, ProposalSTTag, StakeSTTag)
import Agora.Stake (
PStakeDatum,
pextractVoteOption,
@ -35,13 +37,8 @@ import Agora.Stake (
pisVoter,
presolveStakeInputDatum,
)
import Agora.Utils (
pfromSingleton,
pinsertUniqueBy,
plistEqualsBy,
pmapMaybe,
ptryFromRedeemer,
)
import Agora.Utils (ptaggedSymbolValueOf, ptoScottEncodingT)
import Data.Function (on)
import Plutarch.Api.V1 (PCredential, PCurrencySymbol)
import Plutarch.Api.V1.AssocMap (plookup)
import Plutarch.Api.V2 (
@ -52,11 +49,15 @@ import Plutarch.Api.V2 (
)
import Plutarch.Extra.AssetClass (
PAssetClassData,
ptoScottEncoding,
)
import Plutarch.Extra.Category (PCategory (pidentity))
import Plutarch.Extra.Field (pletAll, pletAllC)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
import "liqwid-plutarch-extra" Plutarch.Extra.List (
pfindJust,
plistEqualsBy,
pmapMaybe,
ptryFromSingleton,
)
import "plutarch-extra" Plutarch.Extra.Map (pupdate)
import Plutarch.Extra.Maybe (
passertPJust,
@ -66,13 +67,15 @@ import Plutarch.Extra.Maybe (
pmaybe,
pnothing,
)
import Plutarch.Extra.Ord (pfromOrdBy, psort)
import Plutarch.Extra.Ord (pfromOrdBy, pinsertUniqueBy, psort)
import Plutarch.Extra.Record (mkRecordConstr, (.&), (.=))
import Plutarch.Extra.ScriptContext (
pfindTxInByTxOutRef,
pfromOutputDatum,
ptryFromOutputDatum,
pvalidatorHashFromAddress,
)
import Plutarch.Extra.Sum (PSum (PSum))
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
pguardC,
pletC,
@ -80,8 +83,9 @@ import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
pmatchC,
ptryFromC,
)
import Plutarch.Extra.Time (PCurrentTime)
import Plutarch.Extra.Traversable (pfoldMap)
import Plutarch.Extra.Value (passetClassValueOf, psymbolValueOf)
import Plutarch.Extra.Value (psymbolValueOf')
import Plutarch.Unsafe (punsafeCoerce)
{- | Policy for Proposals.
@ -109,7 +113,7 @@ import Plutarch.Unsafe (punsafeCoerce)
@since 1.0.0
-}
proposalPolicy :: ClosedTerm (PAssetClassData :--> PMintingPolicy)
proposalPolicy :: ClosedTerm (PTagged GovernorSTTag PAssetClassData :--> PMintingPolicy)
proposalPolicy =
plam $ \gstAssetClass _redeemer ctx -> unTermCont $ do
ctxF <- pletAllC ctx
@ -117,44 +121,25 @@ proposalPolicy =
PMinting ((pfield @"_0" #) -> ownSymbol) <- pmatchC $ pfromData ctxF.purpose
let mintedProposalST =
psymbolValueOf
pguardC "Minted exactly one proposal ST"
$ pmatch
( pfromJust
#$ psymbolValueOf'
# ownSymbol
# txInfoF.mint
)
$ \(PPair minted burnt) ->
minted
#== 1
#&& ptraceIfFalse "Burning a proposal is not supported" (burnt #== 0)
pguardC "Minted exactly one proposal ST" $
mintedProposalST #== 1
let governorInputRef =
let governorRedeemer =
passertPJust
# "GST should move"
#$ pfindJust
# plam
( flip pletAll $ \inputF ->
let value = pfield @"value" # inputF.resolved
isGovernorInput =
passetClassValueOf
# (ptoScottEncoding # gstAssetClass)
# value
#== 1
in pif
isGovernorInput
(pjust # inputF.outRef)
pnothing
)
#$ presolveGovernorRedeemer
# (ptoScottEncodingT # gstAssetClass)
# pfromData txInfoF.inputs
governorScriptPurpose =
mkRecordConstr
PSpending
(#_0 .= governorInputRef)
governorRedeemer =
pfromData $
pfromJust
#$ ptryFromRedeemer @(PAsData PGovernorRedeemer)
# governorScriptPurpose
# txInfoF.redeemers
# txInfoF.redeemers
pguardC "Govenor redeemer correct" $
pcon PCreateProposal #== governorRedeemer
@ -239,9 +224,9 @@ instance DerivePlutusType PStakeInputsContext where
-}
proposalValidator ::
ClosedTerm
( PAssetClassData
:--> PCurrencySymbol
:--> PCurrencySymbol
( PTagged StakeSTTag PAssetClassData
:--> PTagged GovernorSTTag PCurrencySymbol
:--> PTagged ProposalSTTag PCurrencySymbol
:--> PInteger
:--> PValidator
)
@ -300,16 +285,18 @@ proposalValidator =
foldl1
(#&&)
[ ptraceIfFalse "Own by proposal validator" $
outputF.address #== proposalInputF.address
((#==) `on` (pvalidatorHashFromAddress #))
outputF.address
proposalInputF.address
, ptraceIfFalse "Has proposal ST" $
psymbolValueOf # pstSymbol # outputF.value #== 1
ptaggedSymbolValueOf # pstSymbol # outputF.value #== 1
]
handleProposalUTxO =
-- Using inline datum to avoid O(n^2) lookup.
pfromData $
ptrace "Resolve proposal datum" $
pfromOutputDatum @(PAsData PProposalDatum)
ptryFromOutputDatum @(PAsData PProposalDatum)
# outputF.datum
# txInfoF.datums
in pif
@ -321,17 +308,23 @@ proposalValidator =
--------------------------------------------------------------------------
currentTime <- pletC $ pcurrentProposalTime # txInfoF.validRange
let withCurrentTime ::
forall (a :: PType).
Term _ (PCurrentTime :--> a) ->
Term _ a
withCurrentTime f =
pmatch currentTime $ \case
PJust currentTime -> f # currentTime
PNothing -> ptraceError "Unable to resolve current time"
getTimingRelation' <-
pletC $
let currentTime =
passertPJust
# "Current time should be resolved"
#$ currentProposalTime
# txInfoF.validRange
in pgetRelation
# proposalInputDatumF.timingConfig
# proposalInputDatumF.startingTime
# currentTime
withCurrentTime $
pgetRelation
# proposalInputDatumF.timingConfig
# proposalInputDatumF.startingTime
let getTimingRelation = (getTimingRelation' #) . pcon
@ -342,18 +335,23 @@ proposalValidator =
resolveStakeInputDatum <-
pletC $
presolveStakeInputDatum
# (ptoScottEncoding # sstClass)
# (ptoScottEncodingT # sstClass)
# txInfoF.datums
spendStakes' :: Term _ ((PStakeInputsContext :--> PUnit) :--> PUnit) <-
pletC $
plam $
let stakeInputs =
pmapMaybe
# resolveStakeInputDatum
# pfromData txInfoF.inputs
plam $ \val -> unTermCont $ do
stakeInputs <-
pletC $
pmapMaybe @PList
# resolveStakeInputDatum
# pfromData txInfoF.inputs
ctx = pcon $ PStakeInputsContext stakeInputs
in (# ctx)
pguardC "Stake inputs not null" $
pnot #$ pnull # stakeInputs
let ctx = pcon $ PStakeInputsContext stakeInputs
pure $ val # ctx
let spendStakes ::
( PStakeInputsContext _ ->
@ -439,7 +437,7 @@ proposalValidator =
stakeF <-
pletFieldsC @'["owner", "stakedAmount"] $
ptrace "Exactly one stake input" $
pfromSingleton # sctxF.inputStakes
ptryFromSingleton # sctxF.inputStakes
let newCosigner = stakeF.owner
@ -512,6 +510,12 @@ proposalValidator =
pguardC "Proposal time should be wthin the voting period" $
pisWithin # getTimingRelation PVotingPeriod
pguardC "Width of time should meet maximum requirement" $
withCurrentTime $
psatisfyMaximumWidth
#$ pfield @"votingTimeRangeMaxWidth"
# proposalInputDatumF.timingConfig
-- Ensure the transaction is voting to a valid 'ResultTag'(outcome).
PProposalVotes voteMap <- pmatchC proposalInputDatumF.votes
voteFor <- pletC $ pfromData $ pfield @"resultTag" # r
@ -563,40 +567,41 @@ proposalValidator =
PUnlockStake _ -> spendStakes $ \sctxF -> do
let expectedVotes =
pfoldl
# plam
( \votes stake -> unTermCont $ do
stakeF <-
pletFieldsC
@'["stakedAmount", "lockedBy"]
stake
pdata $
pfoldl
# plam
( \votes stake -> unTermCont $ do
stakeF <-
pletFieldsC
@'["stakedAmount", "lockedBy"]
stake
stakeRoles <-
pletC $
pgetStakeRoles
# proposalInputDatumF.proposalId
# stakeF.lockedBy
stakeRoles <-
pletC $
pgetStakeRoles
# proposalInputDatumF.proposalId
# stakeF.lockedBy
pguardC "Stake input should be relevant" $
pnot #$ pisIrrelevant # stakeRoles
pguardC "Stake input should be relevant" $
pnot #$ pisIrrelevant # stakeRoles
let canRetractVotes =
pisVoter # stakeRoles
let canRetractVotes =
pisVoter # stakeRoles
voteCount =
pto $
pfromData stakeF.stakedAmount
voteCount =
pto $
pfromData stakeF.stakedAmount
newVotes =
pretractVotes
# (pextractVoteOption # stakeRoles)
# voteCount
# votes
newVotes =
pretractVotes
# (pextractVoteOption # stakeRoles)
# voteCount
# votes
pure $ pif canRetractVotes newVotes votes
)
# proposalInputDatumF.votes
# sctxF.inputStakes
pure $ pif canRetractVotes newVotes votes
)
# proposalInputDatumF.votes
# sctxF.inputStakes
inVotingPeriod =
pisWithin # getTimingRelation PVotingPeriod
@ -625,14 +630,19 @@ proposalValidator =
.& #thresholds
.= proposalInputDatumF.thresholds
.& #votes
.= pdata expectedVotes
.= expectedVotes
.& #timingConfig
.= proposalInputDatumF.timingConfig
.& #startingTime
.= proposalInputDatumF.startingTime
)
in ptraceIfFalse "Update votes" $
expectedProposalOut #== proposalOutputDatum
in foldl1
(#&&)
[ ptraceIfFalse "Votes changed" $
pnot #$ expectedVotes #== proposalInputDatumF.votes
, ptraceIfFalse "Proposal update correct" $
expectedProposalOut #== proposalOutputDatum
]
)
-- No change to the proposal is allowed.
( ptraceIfFalse "Proposal unchanged" $
@ -728,7 +738,7 @@ proposalValidator =
. (pfield @"resolved" #) ->
value
) ->
psymbolValueOf # gstSymbol # value #== 1
ptaggedSymbolValueOf # gstSymbol # value #== 1
)
# pfromData txInfoF.inputs

View file

@ -22,16 +22,15 @@ module Agora.Proposal.Time (
PPeriod (..),
-- * Compute periods given config and starting time.
validateProposalStartingTime,
currentProposalTime,
pvalidateProposalStartingTime,
pcurrentProposalTime,
pisProposalTimingConfigValid,
pisMaxTimeRangeWidthValid,
pgetRelation,
pisWithin,
psatisfyMaximumWidth,
) where
import Agora.Utils (pcurrentTimeDuration)
import Control.Composition ((.*))
import Data.Functor ((<&>))
import Plutarch.Api.V1 (
PExtended (PFinite),
@ -46,12 +45,14 @@ import Plutarch.DataRepr (
PDataFields,
)
import Plutarch.Extra.Applicative (PApply (pliftA2))
import Plutarch.Extra.Bool (passert)
import Plutarch.Extra.Field (pletAll, pletAllC)
import Plutarch.Extra.IsData (PlutusTypeEnumData)
import Plutarch.Extra.Maybe (pjust, pmaybe, pnothing)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pletC, pmatchC)
import Plutarch.Extra.Time (
PCurrentTime (PCurrentTime),
pcurrentTimeDuration,
pisWithinCurrentTime,
)
import Plutarch.Lift (
@ -59,6 +60,7 @@ import Plutarch.Lift (
PConstantDecl,
PUnsafeLiftDecl (PLifted),
)
import Plutarch.Num (PNum)
import PlutusLedgerApi.V1 (POSIXTime)
import PlutusTx qualified
@ -88,33 +90,6 @@ newtype ProposalStartingTime = ProposalStartingTime
PlutusTx.UnsafeFromData
)
{- | Configuration of proposal timings.
See: https://liqwid.notion.site/Proposals-589853145a994057aa77f397079f75e4#d25ea378768d4c76b52dd4c1b6bc0fcd
@since 0.1.0
-}
data ProposalTimingConfig = ProposalTimingConfig
{ draftTime :: POSIXTime
-- ^ "D": the length of the draft period.
, votingTime :: POSIXTime
-- ^ "V": the length of the voting period.
, lockingTime :: POSIXTime
-- ^ "L": the length of the locking period.
, executingTime :: POSIXTime
-- ^ "E": the length of the execution period.
}
deriving stock
( -- | @since 0.1.0
Eq
, -- | @since 0.1.0
Show
, -- | @since 0.1.0
Generic
)
PlutusTx.makeIsDataIndexed 'ProposalTimingConfig [('ProposalTimingConfig, 0)]
-- | Represents the maximum width of a 'PlutusLedgerApi.V1.Time.POSIXTimeRange'.
newtype MaxTimeRangeWidth = MaxTimeRangeWidth {getMaxWidth :: POSIXTime}
deriving stock
@ -134,8 +109,41 @@ newtype MaxTimeRangeWidth = MaxTimeRangeWidth {getMaxWidth :: POSIXTime}
PlutusTx.FromData
, -- | @since 0.1.0
PlutusTx.UnsafeFromData
, -- | @since 1.0.0
Num
)
{- | Configuration of proposal timings.
See: https://liqwid.notion.site/Proposals-589853145a994057aa77f397079f75e4#d25ea378768d4c76b52dd4c1b6bc0fcd
@since 0.1.0
-}
data ProposalTimingConfig = ProposalTimingConfig
{ draftTime :: POSIXTime
-- ^ "D": the length of the draft period.
, votingTime :: POSIXTime
-- ^ "V": the length of the voting period.
, lockingTime :: POSIXTime
-- ^ "L": the length of the locking period.
, executingTime :: POSIXTime
-- ^ "E": the length of the execution period.
, minStakeVotingTime :: POSIXTime
-- ^ Minimum time from creating a voting lock until it can be destroyed.
, votingTimeRangeMaxWidth :: MaxTimeRangeWidth
-- ^ The maximum width of transaction time range while voting.
}
deriving stock
( -- | @since 0.1.0
Eq
, -- | @since 0.1.0
Show
, -- | @since 0.1.0
Generic
)
PlutusTx.makeIsDataIndexed 'ProposalTimingConfig [('ProposalTimingConfig, 0)]
--------------------------------------------------------------------------------
{- | == Establishing timing in Proposal interactions.
@ -210,6 +218,8 @@ newtype PProposalTimingConfig (s :: S) = PProposalTimingConfig
, "votingTime" ':= PPOSIXTime
, "lockingTime" ':= PPOSIXTime
, "executingTime" ':= PPOSIXTime
, "minStakeVotingTime" ':= PPOSIXTime
, "votingTimeRangeMaxWidth" ':= PMaxTimeRangeWidth
]
)
}
@ -224,6 +234,8 @@ newtype PProposalTimingConfig (s :: S) = PProposalTimingConfig
PIsData
, -- | @since 0.1.0
PDataFields
, -- | @since 0.2.1
PShow
)
instance DerivePlutusType PProposalTimingConfig where
@ -260,6 +272,10 @@ newtype PMaxTimeRangeWidth (s :: S)
PPartialOrd
, -- | @since 0.1.0
POrd
, -- | @since 0.2.1
PShow
, -- | @since 1.0.0
PNum
)
instance DerivePlutusType PMaxTimeRangeWidth where
@ -303,6 +319,8 @@ pisProposalTimingConfigValid = phoistAcyclic $
, confF.votingTime
, confF.lockingTime
, confF.executingTime
, confF.minStakeVotingTime
, pto confF.votingTimeRangeMaxWidth
]
{- | Return true if the maximum time width is greater than 0.
@ -322,7 +340,7 @@ pisMaxTimeRangeWidthValid =
@since 1.0.0
-}
validateProposalStartingTime ::
pvalidateProposalStartingTime ::
forall (s :: S).
Term
s
@ -331,26 +349,23 @@ validateProposalStartingTime ::
:--> PProposalStartingTime
:--> PBool
)
validateProposalStartingTime = phoistAcyclic $
plam $ \(pto -> maxDuration) iv (pto -> st) ->
pvalidateProposalStartingTime = phoistAcyclic $
plam $ \maxWidth iv (pto -> st) ->
pmaybe
# ptrace
"validateProposalStartingTime: unable to get current time"
(pconstant False)
# pconstant False
# plam
( \ct ->
let duration = pcurrentTimeDuration # ct
isTightEnough =
let isTightEnough =
ptraceIfFalse
"createProposalStartingTime: given time range should be tight enough"
$ duration #<= maxDuration
$ psatisfyMaximumWidth # maxWidth # ct
isInCurrentTimeRange =
ptraceIfFalse
"createProposalStartingTime: starting time should be in current time range"
$ pisWithinCurrentTime # st # ct
in isTightEnough #&& isInCurrentTimeRange
)
# (currentProposalTime # iv)
# (pcurrentProposalTime # iv)
{- | Get the current proposal time, given the 'PlutusLedgerApi.V1.txInfoValidPeriod' field.
@ -364,8 +379,8 @@ validateProposalStartingTime = phoistAcyclic $
@since 0.1.0
-}
currentProposalTime :: forall (s :: S). Term s (PPOSIXTimeRange :--> PMaybe PProposalTime)
currentProposalTime = phoistAcyclic $
pcurrentProposalTime :: forall (s :: S). Term s (PPOSIXTimeRange :--> PMaybe PProposalTime)
pcurrentProposalTime = phoistAcyclic $
plam $ \iv -> unTermCont $ do
PInterval iv' <- pmatchC iv
ivf <- pletAllC iv'
@ -386,7 +401,13 @@ currentProposalTime = phoistAcyclic $
PFinite (pfromData . (pfield @"_0" #) -> d) -> pjust # d
_ -> ptrace "currentProposalTime: time range should be bounded" pnothing
mkTime = phoistAcyclic $ plam $ pcon .* PCurrentTime
mkTime = phoistAcyclic $
plam $ \lb ub ->
passert
"Upper bound bigger than lower bound"
(lb #< ub)
(pcon $ PCurrentTime lb ub)
pure $ pliftA2 # mkTime # lowerBound # upperBound
{- | Represent relation between current time and a given period.
@ -494,3 +515,22 @@ pgetRelation = phoistAcyclic $
pif (plb #<= lb #&& ub #<= pub) (pcon PWithin) $
pif (pub #< lb) (pcon PAfter) $
ptraceError "pgetRelation: too early or invalid current time"
{- | Return true if the width of given 'PProposalTime' is shorter than the
maximum.
@since 1.0.0
-}
psatisfyMaximumWidth ::
forall (s :: S).
Term
s
( PMaxTimeRangeWidth
:--> PProposalTime
:--> PBool
)
psatisfyMaximumWidth = phoistAcyclic $
plam $ \maxWidth time ->
let width = pcurrentTimeDuration # time
max = pto maxWidth
in width #<= max

View file

@ -12,11 +12,13 @@ module Agora.Stake (
-- * Haskell-land
StakeDatum (..),
StakeRedeemer (..),
ProposalAction (..),
ProposalLock (..),
-- * Plutarch-land
PStakeDatum (..),
PStakeRedeemer (..),
PProposalAction (..),
PProposalLock (..),
PStakeRole (..),
@ -42,18 +44,18 @@ module Agora.Stake (
) where
import Agora.Proposal (
PProposalDatum,
PProposalId,
PProposalRedeemer,
PProposalStatus,
PResultTag,
ProposalId,
ResultTag,
)
import Agora.SafeMoney (GTTag)
import Agora.Utils (pmapMaybe, ppureIf)
import Agora.Proposal.Time (PProposalTime)
import Agora.SafeMoney (GTTag, StakeSTTag)
import Data.Tagged (Tagged)
import Generics.SOP qualified as SOP
import Plutarch.Api.V1 (PCredential)
import Plutarch.Api.V1 (PCredential, PPOSIXTime)
import Plutarch.Api.V2 (
KeyGuarantees (Unsorted),
PDatum,
@ -67,26 +69,62 @@ import Plutarch.DataRepr (
DerivePConstantViaData (DerivePConstantViaData),
PDataFields,
)
import Plutarch.Extra.Applicative (ppureIf)
import Plutarch.Extra.AssetClass (PAssetClass)
import Plutarch.Extra.Field (pletAll)
import Plutarch.Extra.IsData (
DerivePConstantViaDataList (DerivePConstantViaDataList),
ProductIsData (ProductIsData),
)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust, pmapMaybe)
import Plutarch.Extra.Maybe (passertPJust, pjust, pnothing)
import Plutarch.Extra.ScriptContext (pfromOutputDatum)
import Plutarch.Extra.ScriptContext (ptryFromOutputDatum)
import Plutarch.Extra.Sum (PSum (PSum))
import Plutarch.Extra.Tagged (PTagged)
import Plutarch.Extra.Traversable (pfoldMap)
import Plutarch.Extra.Value (passetClassValueOf)
import Plutarch.Extra.Value (passetClassValueOfT)
import Plutarch.Lift (PConstantDecl, PUnsafeLiftDecl (PLifted))
import Plutarch.Orphans ()
import PlutusLedgerApi.V2 (Credential)
import PlutusLedgerApi.V2 (Credential, POSIXTime)
import PlutusTx qualified
--------------------------------------------------------------------------------
{- | The action that was performed on a particular proposal.
@since 1.0.0
-}
data ProposalAction
= -- | The stake was used to create a proposal.
--
-- This kind of lock is placed upon the creation of a proposal, in order
-- to limit creation of proposals per stake.
--
-- See also: https://github.com/Liqwid-Labs/agora/issues/68
Created
| -- | The stake was used to vote on a proposal.
--
-- This kind of lock is placed while voting on a proposal, in order to
-- prevent depositing and withdrawing when votes are in place.
Voted
ResultTag
-- ^ The option which was voted on. This allows votes to be retracted.
POSIXTime
-- ^ The upper bound of the transaction time range when the lock is created.
| -- | The stake was used to cosign a proposal.`
Cosigned
deriving stock
( -- | @since 1.0.0
Show
, -- | @since 1.0.0
Generic
)
PlutusTx.makeIsDataIndexed
''ProposalAction
[ ('Created, 0)
, ('Voted, 1)
, ('Cosigned, 2)
]
{- | Locks that are stored in the stake datums for various purposes.
NOTE: Due to retracting votes always being possible,
@ -112,45 +150,31 @@ import PlutusTx qualified
@
@since 0.1.0
@since 1.0.0
-}
data ProposalLock
= -- | The stake was used to create a proposal.
--
-- This kind of lock is placed upon the creation of a proposal, in order
-- to limit creation of proposals per stake.
--
-- See also: https://github.com/Liqwid-Labs/agora/issues/68
--
-- @since 0.2.0
Created
ProposalId
-- ^ The identifier of the proposal.
| -- | The stake was used to vote on a proposal.
--
-- This kind of lock is placed while voting on a proposal, in order to
-- prevent depositing and withdrawing when votes are in place.
--
-- @since 0.2.0
Voted
ProposalId
-- ^ The identifier of the proposal.
ResultTag
-- ^ The option which was voted on. This allows votes to be retracted.
| Cosigned ProposalId
data ProposalLock = ProposalLock
{ proposalId :: ProposalId
-- ^ The identifier of the proposal.
, action :: ProposalAction
-- ^ The action that has been performed.
}
deriving stock
( -- | @since 0.1.0
Show
, -- | @since 0.1.0
Generic
)
PlutusTx.makeIsDataIndexed
''ProposalLock
[ ('Created, 0)
, ('Voted, 1)
, ('Cosigned, 2)
]
deriving anyclass
( -- | @since 0.1.0
SOP.Generic
)
deriving
( -- | @since 0.1.0
PlutusTx.ToData
, -- | @since 0.1.0
PlutusTx.FromData
)
via (ProductIsData ProposalLock)
{- | Haskell-level redeemer for Stake scripts.
@ -160,7 +184,7 @@ data StakeRedeemer
= -- | Deposit or withdraw a discrete amount of the staked governance token.
-- Stake must be unlocked.
DepositWithdraw (Tagged GTTag Integer)
| -- | Destroy a stake, retrieving its LQ, the minimum ADA and any other assets.
| -- | Destroy a stake, retrieving its GT, the minimum ADA and any other assets.
-- Stake must be unlocked.
Destroy
| -- | Permit a Vote to be added onto a 'Agora.Proposal.Proposal'.
@ -268,6 +292,7 @@ newtype PStakeDatum (s :: S) = PStakeDatum
PShow
)
-- | @since 1.0.0
instance DerivePlutusType PStakeDatum where
type DPTStrat _ = PlutusTypeNewtype
@ -291,7 +316,7 @@ instance PTryFrom PData (PAsData PStakeDatum)
data PStakeRedeemer (s :: S)
= -- | Deposit or withdraw a discrete amount of the staked governance token.
PDepositWithdraw (Term s (PDataRecord '["delta" ':= PTagged GTTag PInteger]))
| -- | Destroy a stake, retrieving its LQ, the minimum ADA and any other assets.
| -- | Destroy a stake, retrieving its GT, the minimum ADA and any other assets.
PDestroy (Term s (PDataRecord '[]))
| PPermitVote (Term s (PDataRecord '[]))
| PRetractVotes (Term s (PDataRecord '[]))
@ -325,32 +350,65 @@ deriving via
instance
(PConstantDecl StakeRedeemer)
{- | Plutarch-level version of 'ProposalLock'.
{- | Plutarch-level version of 'ProposalAction'.
@since 0.2.0
@since 1.0.0
-}
data PProposalLock (s :: S)
= PCreated
( Term
s
( PDataRecord
'["created" ':= PProposalId]
)
)
data PProposalAction (s :: S)
= PCreated (Term s (PDataRecord '[]))
| PVoted
( Term
s
( PDataRecord
'[ "votedOn" ':= PProposalId
, "votedFor" ':= PResultTag
'[ "votedFor" ':= PResultTag
, "createdAt" ':= PPOSIXTime
]
)
)
| PCosigned
| PCosigned (Term s (PDataRecord '[]))
deriving stock
( -- | @since 1.0.0
Generic
)
deriving anyclass
( -- | @since 1.0.0
PlutusType
, -- | @since 1.0.0
PIsData
, -- | @since 1.0.0
PEq
, -- | @since 1.0.0
PShow
)
-- | @since 1.0.0
instance DerivePlutusType PProposalAction where
type DPTStrat _ = PlutusTypeData
-- | @since 1.0.0
instance PUnsafeLiftDecl PProposalAction where
type PLifted _ = ProposalAction
-- | @since 1.0.0
deriving via
(DerivePConstantViaData ProposalAction PProposalAction)
instance
(PConstantDecl ProposalAction)
-- | @since 1.0.0
instance PTryFrom PData PProposalAction
{- | Plutarch-level version of 'ProposalLock'.
@since 1.0.0
-}
newtype PProposalLock (s :: S)
= PProposalLock
( Term
s
( PDataRecord
'[ "cosigned" ':= PProposalId
'[ "proposalId" ':= PProposalId
, "action" ':= PProposalAction
]
)
)
@ -365,15 +423,15 @@ data PProposalLock (s :: S)
PIsData
, -- | @since 0.1.0
PEq
, -- | @since 1.0.0
PDataFields
, -- | @since 0.2.0
PShow
)
-- | @since 0.2.0
instance DerivePlutusType PProposalLock where
type DPTStrat _ = PlutusTypeData
-- | @since 0.1.0
instance PTryFrom PData PProposalLock
type DPTStrat _ = PlutusTypeNewtype
-- | @since 0.2.0
instance PTryFrom PData (PAsData PProposalLock)
@ -384,7 +442,7 @@ instance PUnsafeLiftDecl PProposalLock where
-- | @since 0.1.0
deriving via
(DerivePConstantViaData ProposalLock PProposalLock)
(DerivePConstantViaDataList ProposalLock PProposalLock)
instance
(PConstantDecl ProposalLock)
@ -412,9 +470,11 @@ pnumCreatedProposals =
pto $
pfoldMap
# plam
( \(pfromData -> lock) -> pmatch lock $ \case
PCreated _ -> pcon $ PSum 1
_ -> mempty
( \lock ->
let action = pfromData $ pfield @"action" # lock
in pmatch action $ \case
PCreated _ -> pcon $ PSum 1
_ -> mempty
)
# l
@ -525,9 +585,9 @@ instance DerivePlutusType PStakeRedeemerContext where
data PProposalContext (s :: S)
= -- | A proposal is spent.
PSpendProposal
(Term s PProposalId)
(Term s PProposalStatus)
(Term s PProposalDatum)
(Term s PProposalRedeemer)
(Term s PProposalTime)
| -- | A new proposal is created.
PNewProposal
(Term s PProposalId)
@ -665,26 +725,17 @@ pgetStakeRoles ::
)
pgetStakeRoles = phoistAcyclic $
plam $ \pid ->
pmapMaybe
# plam
( flip
pmatch
( \case
PCreated ((pfield @"created" #) -> pid') ->
ppureIf
# (pid' #== pid)
# pcon PCreator
PVoted r -> pletAll r $ \rF ->
ppureIf
# (rF.votedOn #== pid)
# pcon (PVoter rF.votedFor)
PCosigned ((pfield @"cosigned" #) -> pid') ->
ppureIf
# (pid' #== pid)
# pcon PCosigner
)
. pfromData
)
let getStakeRole = flip (pletFields @'["proposalId", "action"]) $
\lockF ->
ppureIf
# (pid #== lockF.proposalId)
#$ pmatch lockF.action
$ \case
PCreated _ -> pcon PCreator
PVoted ((pfield @"votedFor" #) -> tag) ->
pcon $ PVoter tag
PCosigned _ -> pcon PCosigner
in pmapMaybe # plam (getStakeRole . pfromData)
{- | Get the outcome that was voted for.
@ -715,7 +766,7 @@ presolveStakeInputDatum ::
forall (s :: S).
Term
s
( PAssetClass
( PTagged StakeSTTag PAssetClass
:--> PMap 'Unsorted PDatumHash PDatum
:--> PTxInInfo
:--> PMaybe PStakeDatum
@ -726,7 +777,7 @@ presolveStakeInputDatum = phoistAcyclic $
(pletFields @'["value", "datum", "address"])
( \txOutF ->
let isStakeUTxO =
passetClassValueOf
passetClassValueOfT
# sstClass
# txOutF.value
#== 1
@ -734,7 +785,7 @@ presolveStakeInputDatum = phoistAcyclic $
datum =
ptrace "Resolve stake datum" $
pfromData $
pfromOutputDatum @(PAsData PStakeDatum)
ptryFromOutputDatum @(PAsData PStakeDatum)
# txOutF.datum
# datums
in pif

View file

@ -19,13 +19,15 @@ import Agora.Proposal (
PProposalRedeemer (PCosign, PUnlockStake, PVote),
ProposalStatus (Finished),
)
import Agora.Proposal.Time (PProposalTime)
import Agora.Stake (
PProposalAction (PCosigned, PCreated, PVoted),
PProposalContext (
PNewProposal,
PNoProposal,
PSpendProposal
),
PProposalLock (PCosigned, PCreated, PVoted),
PProposalLock (PProposalLock),
PSigContext (owner, signedBy),
PSignedBy (
PSignedByDelegate,
@ -48,13 +50,20 @@ import Agora.Stake (
),
pstakeLocked,
)
import Agora.Utils (pfromSingleton, pisSingleton, pmustDeleteBy)
import Data.Functor ((<&>))
import Plutarch.Api.V1.Address (PCredential)
import Plutarch.Api.V2 (PMaybeData)
import Plutarch.Api.V2 (PMaybeData, PPOSIXTime)
import Plutarch.Extra.Bool (passert)
import Plutarch.Extra.Field (pletAll, pletAllC)
import Plutarch.Extra.Maybe (pdjust, pdnothing, pmaybeData)
import "liqwid-plutarch-extra" Plutarch.Extra.List (
pisSingleton,
ptryDeleteFirstBy,
ptryFromSingleton,
)
import Plutarch.Extra.Maybe (pdjust, pdnothing, pjust, pmaybe, pmaybeData, pnothing)
import Plutarch.Extra.Record (mkRecordConstr, (.&), (.=))
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pmatchC)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pletFieldsC, pmatchC)
import Plutarch.Extra.Time (PCurrentTime (PCurrentTime))
-- | A wrapper which ensures that no proposal is presented in the transaction.
pwithoutProposal ::
@ -87,7 +96,7 @@ pbatchUpdateInputs = phoistAcyclic $
plam $ \f -> flip pmatch $ \ctxF ->
pnull
#$ pfoldr
# (pmustDeleteBy # f)
# plam (\x -> ptryDeleteFirstBy # (f # x))
# ctxF.stakeOutputDatums
# ctxF.stakeInputDatums
@ -159,17 +168,13 @@ pvoteHelper ::
:--> PStakeRedeemerHandler
)
pvoteHelper = phoistAcyclic $
plam $ \valProposalCtx ctx -> unTermCont $ do
pguardC "Owner or delegate signs this transaction" $
pisSignedBy # pconstant True # ctx
plam $ \valProposalCtx ctx ->
-- This puts trust into the Proposal. The Proposal must necessarily check
-- that this is not abused.
pguardC "Correct outputs" $
ponlyLocksUpdated # (valProposalCtx # ctx) # ctx
pure $ pconstant ()
passert
"Correct outputs"
(ponlyLocksUpdated # (valProposalCtx # ctx) # ctx)
(pconstant ())
-- | Add new lock the the existing list of locked.
paddNewLock ::
@ -199,33 +204,60 @@ ppermitVote = pvoteHelper #$ phoistAcyclic $
pguardC "Only one stake input allowed" $
pisSingleton # ctxF.stakeInputDatums
pguardC "Owner signs this transaction" $
pisSignedBy # pconstant False # ctx
pure lock
pure $
paddNewLock #$ pmatch ctxF.proposalContext $ \case
PSpendProposal pid _ r -> pmatch r $ \case
PVote ((pfromData . (pfield @"resultTag" #)) -> voteFor) ->
mkRecordConstr
PVoted
( #votedOn
.= pdata pid
.& #votedFor
.= pdata voteFor
)
PCosign _ ->
withOnlyOneStakeInput
#$ mkRecordConstr
PCosigned
( #cosigned .= pdata pid
PSpendProposal proposal redeemer currentTime -> unTermCont $ do
mkLock <- pletC $
plam $ \action ->
mkRecordConstr
PProposalLock
( #proposalId
.= pfield @"proposalId"
# proposal
.& #action
.= pdata action
)
_ -> ptraceError "Expected Vote"
PNewProposal pid ->
withOnlyOneStakeInput
#$ mkRecordConstr
PCreated
( #created .= pdata pid
)
_ -> ptraceError "Expected proposal"
pure $
pmatch redeemer $ \case
PVote ((pfromData . (pfield @"resultTag" #)) -> voteFor) ->
unTermCont $ do
pguardC "Owner or delegatee signs the transaction" $
pisSignedBy # pconstant True # ctx
PCurrentTime _ upperBound <- pmatchC currentTime
let action =
mkRecordConstr
PVoted
( #votedFor
.= pdata voteFor
.& #createdAt
.= pdata upperBound
)
pure $ mkLock # action
PCosign _ ->
let action = pcon $ PCosigned pdnil
in withOnlyOneStakeInput #$ mkLock # action
_ -> ptraceError "Expected Vote or Cosign"
PNewProposal proposalId ->
let action = pcon $ PCreated pdnil
lock =
mkRecordConstr
PProposalLock
( #proposalId
.= pdata proposalId
.& #action
.= pdata action
)
in withOnlyOneStakeInput # lock
_ -> ptraceError "Expected a proposal to be spent or created"
data PRemoveLocksMode (s :: S) = PRemoveVoterLockOnly | PRemoveAllLocks
deriving stock (Generic)
@ -235,33 +267,59 @@ instance DerivePlutusType PRemoveLocksMode where
type DPTStrat _ = PlutusTypeScott
{- | Remove stake locks with the proposal id given the list of existing locks.
The first parameter controls whether to revmove creator locks or not.
The first parameter controls whether to remove creator locks or not. If
one of the locks performed voting action, the unlock cooldown will be
checked if it's given.
-}
premoveLocks ::
forall (s :: S).
Term
s
( PProposalId
:--> PMaybe PPOSIXTime
:--> PProposalTime
:--> PRemoveLocksMode
:--> PBuiltinList (PAsData PProposalLock)
:--> PBuiltinList (PAsData PProposalLock)
)
premoveLocks = phoistAcyclic $
plam $ \pid rl -> unTermCont $ do
shouldRemoveOtherLocks <- pletC $
plam $ \pid' ->
pid' #== pid #&& rl #== pcon PRemoveAllLocks
premoveLocks =
phoistAcyclic $
plam $ \proposalId unlockCooldown currentTime mode -> unTermCont $ do
shouldRemoveAllLocks <- pletC $ mode #== pcon PRemoveAllLocks
pure $
pfilter
# plam
( \(pfromData -> l) -> pnot #$ pmatch l $ \case
PCosigned ((pfield @"cosigned" #) -> pid') ->
shouldRemoveOtherLocks # pid'
PCreated ((pfield @"created" #) -> pid') ->
shouldRemoveOtherLocks # pid'
PVoted ((pfield @"votedOn" #) -> pid') -> pid' #== pid
)
PCurrentTime lowerBound _ <- pmatchC currentTime
let handleVoter
( (pfield @"createdAt" #) ->
createdAt
) =
let notInCooldown =
pmaybe
# pconstant True
# plam (\c -> createdAt + c #<= lowerBound)
# unlockCooldown
in foldl1
(#||)
[ shouldRemoveAllLocks
, ptraceIfFalse "Stake lock in cooldown" notInCooldown
]
handleLock =
plam $
flip
pletAll
( \lockF ->
foldl1
(#&&)
[ proposalId #== lockF.proposalId
, pmatch lockF.action $ \case
PVoted r -> handleVoter r
_ -> shouldRemoveAllLocks
]
)
. pfromData
pure $ pfilter # handleLock
{- | Default implementation of 'Agora.Stake.RetractVotes'.
@ -269,17 +327,41 @@ premoveLocks = phoistAcyclic $
-}
pretractVote :: forall (s :: S). Term s PStakeRedeemerHandler
pretractVote = pvoteHelper #$ phoistAcyclic $
plam $
flip pmatch $ \ctxF ->
plam $ \ctx ->
pmatch ctx $ \ctxF ->
pmatch ctxF.proposalContext $ \case
PSpendProposal pid s r -> pmatch r $ \case
PUnlockStake _ ->
let mode =
pif
(s #== pconstant Finished)
(pcon PRemoveAllLocks)
(pcon PRemoveVoterLockOnly)
in premoveLocks # pid # mode
PSpendProposal proposal redeemer currentTime -> pmatch redeemer $ \case
PUnlockStake _ -> unTermCont $ do
proposalF <-
pletFieldsC
@'[ "proposalId"
, "status"
, "timingConfig"
]
proposal
(mode, unlockCooldown) <-
pmatchC (proposalF.status #== pconstant Finished) <&> \case
PTrue ->
( pcon PRemoveAllLocks
, pnothing
)
_ ->
( pcon PRemoveVoterLockOnly
, pjust
#$ pfield @"minStakeVotingTime"
# proposalF.timingConfig
)
pguardC "Authorized by either opwner or delegatee" $
pisSignedBy # pconstant True # ctx
pure $
premoveLocks
# proposalF.proposalId
# unlockCooldown
# currentTime
# mode
_ -> ptraceError "Expected unlock"
_ -> ptraceError "Expected spending proposal"
@ -387,12 +469,12 @@ pdepositWithdraw = phoistAcyclic $
stakeInputDatum <-
pletC $
ptrace "Single stake input" $
pfromSingleton # ctxF.stakeInputDatums
ptryFromSingleton # ctxF.stakeInputDatums
stakeInputDatumF <- pletAllC stakeInputDatum
let stakeOutputDatum =
ptrace "Single stake output" $
pfromSingleton # ctxF.stakeOutputDatums
ptryFromSingleton # ctxF.stakeOutputDatums
----------------------------------------------------------------------------

View file

@ -13,6 +13,8 @@ module Agora.Stake.Scripts (
import Agora.Credential (authorizationContext, pauthorizedBy)
import Agora.Proposal (PProposalDatum, PProposalRedeemer)
import Agora.Proposal.Time (pcurrentProposalTime)
import Agora.SafeMoney (GTTag, ProposalSTTag, StakeSTTag)
import Agora.Stake (
PProposalContext (
PNewProposal,
@ -52,13 +54,7 @@ import Agora.Stake.Redeemers (
ppermitVote,
pretractVote,
)
import Agora.Utils (
passert,
pisDNothing,
pmapMaybe,
psymbolValueOf',
pvalidatorHashToTokenName,
)
import Agora.Utils (pisDNothing, ptoScottEncodingT, puntag)
import Plutarch.Api.V1 (
PCredential (PPubKeyCredential, PScriptCredential),
PCurrencySymbol,
@ -77,13 +73,14 @@ import Plutarch.Extra.AssetClass (
PAssetClass,
PAssetClassData,
passetClass,
ptoScottEncoding,
)
import Plutarch.Extra.Bool (passert)
import Plutarch.Extra.Field (pletAll, pletAllC)
import Plutarch.Extra.Functor (PFunctor (pfmap))
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust, pmapMaybe)
import Plutarch.Extra.Maybe (
passertPJust,
pdjust,
pfromJust,
pfromMaybe,
pjust,
@ -93,9 +90,12 @@ import Plutarch.Extra.Maybe (
import Plutarch.Extra.Ord (POrdering (PEQ, PGT, PLT), pcompareBy, pfromOrd)
import Plutarch.Extra.ScriptContext (
pfindTxInByTxOutRef,
pfromOutputDatum,
ptryFromOutputDatum,
pvalidatorHashFromAddress,
pvalidatorHashToTokenName,
pvalueSpent,
)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
pguardC,
pletC,
@ -105,7 +105,9 @@ import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
)
import Plutarch.Extra.Value (
passetClassValueOf,
passetClassValueOfT,
psymbolValueOf,
psymbolValueOf',
)
import Plutarch.Num (PNum (pnegate))
import Plutarch.Unsafe (punsafeCoerce)
@ -131,15 +133,14 @@ import Prelude hiding (Num ((+)))
== Arguments
Following arguments should be provided(in this order):
1. governor ST assetclass
1. governance token assetclass
@since 1.0.0
-}
stakePolicy ::
-- | The (governance) token that a Stake can store.
ClosedTerm (PAssetClassData :--> PMintingPolicy)
ClosedTerm (PTagged GTTag PAssetClassData :--> PMintingPolicy)
stakePolicy =
plam $ \gstClass _redeemer ctx' -> unTermCont $ do
plam $ \gtClass _redeemer ctx' -> unTermCont $ do
ctx <- pletFieldsC @'["txInfo", "purpose"] ctx'
txInfo <- pletC $ ctx.txInfo
let _a :: Term _ PTxInfo
@ -197,7 +198,7 @@ stakePolicy =
datumF <-
pletAllC $
pfromData $
pfromOutputDatum @(PAsData PStakeDatum)
ptryFromOutputDatum @(PAsData PStakeDatum)
# outputF.datum
# txInfoF.datums
@ -205,10 +206,10 @@ stakePolicy =
foldl1
(#&&)
[ ptraceIfFalse "Stake ouput has expected amount of stake token" $
passetClassValueOf
# (ptoScottEncoding # gstClass)
passetClassValueOfT
# (ptoScottEncodingT # gtClass)
# outputF.value
#== pto (pfromData datumF.stakedAmount)
#== pfromData datumF.stakedAmount
, ptraceIfFalse "Stake Owner should sign the transaction" $
pauthorizedBy
# authorizationContext txInfoF
@ -232,17 +233,17 @@ stakePolicy =
Following arguments should be provided(in this order):
1. stake ST symbol
2. proposal ST assetclass
3. governor ST assetclass
3. governance token assetclass
@since 1.0.0
-}
mkStakeValidator ::
StakeRedeemerImpl s ->
Term s PCurrencySymbol ->
Term s PAssetClass ->
Term s PAssetClass ->
Term s (PTagged StakeSTTag PCurrencySymbol) ->
Term s (PTagged ProposalSTTag PAssetClass) ->
Term s (PTagged GTTag PAssetClass) ->
Term s PValidator
mkStakeValidator impl sstSymbol pstClass gstClass =
mkStakeValidator impl sstSymbol pstClass gtClass =
plam $ \_datum redeemer ctx -> unTermCont $ do
ctxF <- pletFieldsC @'["txInfo", "purpose"] ctx
txInfo <- pletC $ pfromData ctxF.txInfo
@ -256,6 +257,7 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
, "signatories"
, "redeemers"
, "datums"
, "validRange"
]
txInfo
@ -271,18 +273,16 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
# (pfield @"_0" # stakeInputRef)
# txInfoF.inputs
stakeValidatorCredential <-
stakeValidatorHash <-
pletC $
pfield @"credential"
pfromJust
#$ pvalidatorHashFromAddress
#$ pfield @"address"
# validatedInput
let sstName = pvalidatorHashToTokenName #$ pmatch stakeValidatorCredential $
\case
PScriptCredential r -> pfield @"_0" # r
_ -> perror
let sstName = pvalidatorHashToTokenName stakeValidatorHash
sstClass <- pletC $ passetClass # sstSymbol # sstName
sstClass <- pletC $ passetClass # puntag sstSymbol # sstName
--------------------------------------------------------------------------
@ -302,15 +302,18 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
PGT -> ptraceError "More than one SST in one UTxO"
-- 1
PEQ ->
let ownerCredential = pfield @"credential" # txOutF.address
let ownerValidatoHash =
pfromJust
#$ pvalidatorHashFromAddress
# txOutF.address
isOwnedByStakeValidator =
ownerCredential #== stakeValidatorCredential
ownerValidatoHash #== stakeValidatorHash
datum =
ptrace "Resolve stake datum" $
pfromData $
pfromOutputDatum @(PAsData PStakeDatum)
ptryFromOutputDatum @(PAsData PStakeDatum)
# txOutF.datum
# txInfoF.datums
in passert
@ -342,7 +345,7 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
authorizedBy <- pletC $ pauthorizedBy # authorizationContext txInfoF
PPair allHaveSameOwner allHaveSameDelegatee <-
PPair allHaveSameOwner allHaveSameOrOwnedByDelegatee <-
pmatchC $
pfoldr
# plam
@ -355,11 +358,15 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
allHaveSameOwner
#&& dF.owner
#== firstStakeInputDatumF.owner
allHaveSameDelegatee' =
allHaveSameDelegatee
#&& dF.delegatedTo
#== firstStakeInputDatumF.delegatedTo
in pcon $ PPair allHaveSameOwner' allHaveSameDelegatee'
allHaveSameOrOwnedByDelegatee' =
let delegated =
dF.delegatedTo #== firstStakeInputDatumF.delegatedTo
ownedByDelegatee =
pdata (pdjust # dF.owner)
#== firstStakeInputDatumF.delegatedTo
in allHaveSameDelegatee
#&& (delegated #|| ownedByDelegatee)
in pcon $ PPair allHaveSameOwner' allHaveSameOrOwnedByDelegatee'
)
# pcon (PPair (pconstant True) (pconstant True))
# restOfStakeInputDatums
@ -370,7 +377,7 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
# firstStakeInputDatumF.owner
delegateSignsTransaction =
allHaveSameDelegatee
allHaveSameOrOwnedByDelegatee
#&& pmaybeData
# pconstant False
# plam ((authorizedBy #) . pfromData)
@ -407,13 +414,12 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
( \output ->
let validateGT = plam $ \stakeDatum ->
let expected =
pto $
pfromData $
pfield @"stakedAmount" # stakeDatum
pfromData $
pfield @"stakedAmount" # stakeDatum
actual =
passetClassValueOf
# gstClass
passetClassValueOfT
# gtClass
# (pfield @"value" # output)
in pif
(expected #== actual)
@ -433,19 +439,20 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
plam $
flip pletAll $ \txOutF ->
let isProposalUTxO =
passetClassValueOf
passetClassValueOfT
# pstClass
# txOutF.value
#== 1
proposalDatum =
pfromData $
pfromOutputDatum @(PAsData PProposalDatum)
ptryFromOutputDatum @(PAsData PProposalDatum)
# txOutF.datum
# txInfoF.datums
in pif isProposalUTxO (pjust # proposalDatum) pnothing
let pstMinted =
passetClassValueOf # pstClass # txInfoF.mint #== 1
passetClassValueOfT # pstClass # txInfoF.mint #== 1
newProposalContext =
pcon $
@ -477,10 +484,13 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
pfmap
# plam
( \proposalDatum ->
let id = pfield @"proposalId" # proposalDatum
status = pfield @"status" # proposalDatum
redeemer = getProposalRedeemer # inInfoF.outRef
in pcon $ PSpendProposal id status redeemer
let redeemer = getProposalRedeemer # inInfoF.outRef
currentTime =
passertPJust
# "Should resolve proposal time"
#$ pcurrentProposalTime
# txInfoF.validRange
in pcon $ PSpendProposal proposalDatum redeemer currentTime
)
#$ getProposalDatum
# pfromData inInfoF.resolved
@ -598,15 +608,15 @@ mkStakeValidator impl sstSymbol pstClass gstClass =
Following arguments should be provided(in this order):
1. stake ST symbol
2. proposal ST assetclass
3. governor ST assetclass
3. governance token assetclass
@since 1.0.0
-}
stakeValidator ::
ClosedTerm
( PCurrencySymbol
:--> PAssetClassData
:--> PAssetClassData
( PTagged StakeSTTag PCurrencySymbol
:--> PTagged ProposalSTTag PAssetClassData
:--> PTagged GTTag PAssetClassData
:--> PValidator
)
stakeValidator =
@ -622,5 +632,5 @@ stakeValidator =
}
)
sstSymbol
(ptoScottEncoding # pstClass)
(ptoScottEncoding # gstClass)
(ptoScottEncodingT # pstClass)
(ptoScottEncodingT # gstClass)

View file

@ -13,8 +13,10 @@ module Agora.Treasury (
) where
import Agora.AuthorityToken (singleAuthorityTokenBurned)
import Agora.SafeMoney (AuthorityTokenTag)
import Plutarch.Api.V1.Value (PCurrencySymbol, PValue)
import Plutarch.Api.V2 (PScriptPurpose (PSpending), PValidator)
import Plutarch.Extra.Tagged (PTagged)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletFieldsC, pmatchC)
{- | Validator ensuring that transactions consuming the treasury
@ -25,10 +27,10 @@ import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletFieldsC, pm
Following arguments should be provided(in this order):
1. authority token symbol
@since 0.1.0
@since 1.0.0
-}
treasuryValidator ::
ClosedTerm (PCurrencySymbol :--> PValidator)
ClosedTerm (PTagged AuthorityTokenTag PCurrencySymbol :--> PValidator)
treasuryValidator = plam $ \atSymbol _ _ ctx' -> unTermCont $ do
-- plet required fields from script context.
ctx <- pletFieldsC @["txInfo", "purpose"] ctx'

View file

@ -8,101 +8,34 @@ Description: Plutarch utility functions that should be upstreamed or don't belon
Plutarch utility functions that should be upstreamed or don't belong anywhere else.
-}
module Agora.Utils (
validatorHashToTokenName,
validatorHashToAddress,
pltAsData,
withBuiltinPairAsData,
pvalidatorHashToTokenName,
pscriptHashToTokenName,
scriptHashToTokenName,
plistEqualsBy,
pstringIntercalate,
punwords,
pcurrentTimeDuration,
pdelete,
pdeleteBy,
pmustDeleteBy,
pisSingleton,
pfromSingleton,
pmapMaybe,
PAlternative (..),
ppureIf,
pltBy,
pinsertUniqueBy,
ptryFromRedeemer,
passert,
pisNothing,
pisDNothing,
psymbolValueOf',
ptoScottEncodingT,
ptaggedSymbolValueOf,
ptag,
puntag,
) where
import Plutarch.Api.V1 (
KeyGuarantees (Unsorted),
PPOSIXTime,
PRedeemer,
PValidatorHash,
)
import Plutarch.Api.V1.AssocMap (PMap, plookup)
import Plutarch.Api.V2 (
AmountGuarantees,
KeyGuarantees,
PCurrencySymbol,
PMaybeData (PDNothing),
PScriptHash,
PScriptPurpose,
PTokenName,
PValue,
)
import Plutarch.Extra.Applicative (PApplicative (ppure))
import Plutarch.Extra.Category (PCategory (pidentity))
import Plutarch.Extra.Functor (PFunctor (PSubcategory, pfmap))
import Plutarch.Extra.Maybe (pjust, pnothing)
import Plutarch.Extra.Ord (PComparator, POrdering (PLT), pcompareBy, pequateBy)
import Plutarch.Extra.Time (PCurrentTime (PCurrentTime))
import Plutarch.Unsafe (punsafeCoerce)
import Plutarch.Extra.AssetClass (PAssetClass, PAssetClassData, ptoScottEncoding)
import Plutarch.Extra.Tagged (PTagged)
import Plutarch.Extra.Value (psymbolValueOf)
import Plutarch.Unsafe (punsafeDowncast)
import PlutusLedgerApi.V2 (
Address (Address),
Credential (ScriptCredential),
ScriptHash (ScriptHash),
TokenName (TokenName),
ValidatorHash (ValidatorHash),
ValidatorHash,
)
{- Functions which should (probably) not be upstreamed
All of these functions are quite inefficient.
-}
{- | Safely convert a 'ValidatorHash' into a 'TokenName'. This can be useful for tagging
tokens for extra safety.
@since 0.1.0
-}
validatorHashToTokenName :: ValidatorHash -> TokenName
validatorHashToTokenName (ValidatorHash hash) = TokenName hash
{- | Safely convert a 'PValidatorHash' into a 'PTokenName'. This can be useful for tagging
tokens for extra safety.
@since 1.0.0
-}
pvalidatorHashToTokenName :: forall (s :: S). Term s (PValidatorHash :--> PTokenName)
pvalidatorHashToTokenName = phoistAcyclic $ plam punsafeCoerce
{- | Safely convert a 'PScriptHash' into a 'PTokenName'. This can be useful for tagging
tokens for extra safety.
@since 1.0.0
-}
scriptHashToTokenName :: ScriptHash -> TokenName
scriptHashToTokenName (ScriptHash hash) = TokenName hash
{- | Safely convert a 'PScriptHash' into a 'PTokenName'. This can be useful for tagging
tokens for extra safety.
@since 1.0.0
-}
pscriptHashToTokenName :: forall (s :: S). Term s PScriptHash -> Term s PTokenName
pscriptHashToTokenName = punsafeCoerce
{- | Create an 'Address' from a given 'ValidatorHash' with no 'PlutusLedgerApi.V1.Credential.StakingCredential'.
@since 0.1.0
@ -110,62 +43,6 @@ pscriptHashToTokenName = punsafeCoerce
validatorHashToAddress :: ValidatorHash -> Address
validatorHashToAddress vh = Address (ScriptCredential vh) Nothing
{- | Compare two 'PAsData' value, return true if the first one is less than the second one.
@since 0.2.0
-}
pltAsData ::
forall (a :: PType) (s :: S).
(POrd a, PIsData a) =>
Term s (PAsData a :--> PAsData a :--> PBool)
pltAsData = phoistAcyclic $
plam $
\(pfromData -> l) (pfromData -> r) -> l #< r
{- | Extract data stored in a 'PBuiltinPair' and call a function to process it.
@since 0.2.0
-}
withBuiltinPairAsData ::
forall (a :: PType) (b :: PType) (c :: PType) (s :: S).
(PIsData a, PIsData b) =>
(Term s a -> Term s b -> Term s c) ->
Term
s
(PBuiltinPair (PAsData a) (PAsData b)) ->
Term s c
withBuiltinPairAsData f p =
let a = pfromData $ pfstBuiltin # p
b = pfromData $ psndBuiltin # p
in f a b
-- | @since 1.0.0
plistEqualsBy ::
forall
(list1 :: PType -> PType)
(list2 :: PType -> PType)
(a :: PType)
(b :: PType)
(s :: S).
(PIsListLike list1 a, PIsListLike list2 b) =>
Term s ((a :--> b :--> PBool) :--> list1 a :--> list2 b :--> PBool)
plistEqualsBy = phoistAcyclic $
plam $ \eq -> pfix #$ plam $ \self l1 l2 ->
pelimList
( \x xs ->
pelimList
( \y ys ->
-- Avoid comparison if two lists have different length.
self # xs # ys #&& eq # x # y
)
-- l2 is empty, but l1 is not.
(pconstant False)
l2
)
-- l1 is empty, so l2 should be empty as well.
(pnull # l2)
l1
-- | @since 1.0.0
pstringIntercalate ::
forall (s :: S).
@ -183,225 +60,6 @@ punwords ::
Term s PString
punwords = pstringIntercalate " "
-- | @since 1.0.0
pcurrentTimeDuration ::
forall (s :: S).
Term
s
( PCurrentTime
:--> PPOSIXTime
)
pcurrentTimeDuration = phoistAcyclic $
plam $
flip pmatch $
\(PCurrentTime lb ub) -> ub - lb
{- | / O(n) /. Remove the first occurance of a value from the given list.
@since 1.0.0
-}
pdelete ::
forall (a :: PType) (list :: PType -> PType) (s :: S).
(PEq a, PIsListLike list a) =>
Term s (a :--> list a :--> PMaybe (list a))
pdelete = phoistAcyclic $ pdeleteBy # plam (#==)
-- | @since 1.0.0
pdeleteBy ::
forall (a :: PType) (list :: PType -> PType) (s :: S).
(PIsListLike list a) =>
Term s ((a :--> a :--> PBool) :--> a :--> list a :--> PMaybe (list a))
pdeleteBy = phoistAcyclic $
plam $ \f' x -> plet (f' # x) $ \f ->
precList
( \self h t ->
pif
(f # h)
(pjust # t)
(pfmap # (pcons # h) # (self # t))
)
(const pnothing)
-- | @since 1.0.0
pmustDeleteBy ::
forall (a :: PType) (list :: PType -> PType) (s :: S).
(PIsListLike list a) =>
Term s ((a :--> a :--> PBool) :--> a :--> list a :--> list a)
pmustDeleteBy = phoistAcyclic $
plam $ \f' x -> plet (f' # x) $ \f ->
precList
( \self h t ->
pif
(f # h)
t
(pcons # h #$ self # t)
)
(const $ ptraceError "Cannot delete element")
{- | / O(1) /.Return true if the given list has only one element.
@since 1.0.0
-}
pisSingleton ::
forall (a :: PType) (list :: PType -> PType) (s :: S).
(PIsListLike list a) =>
Term s (list a :--> PBool)
pisSingleton =
phoistAcyclic $
precList
(\_ _ t -> pnull # t)
(const $ pconstant False)
{- Throws an error if the given list contains zero or more than one elements.
Otherwise returns the only element.
@since 1.0.0
-}
pfromSingleton ::
forall (a :: PType) (list :: PType -> PType) (s :: S).
(PIsListLike list a) =>
Term s (list a :--> a)
pfromSingleton =
phoistAcyclic $
precList
( \_ h t ->
pif
(pnull # t)
h
(ptraceError "More than one element")
)
(const $ ptraceError "Empty list")
{- | A version of 'pmap' which can throw out elements and change the list type
along the way.
@since 1.0.0
-}
pmapMaybe ::
forall
(listO :: PType -> PType)
(b :: PType)
(listI :: PType -> PType)
(a :: PType)
(s :: S).
(PIsListLike listI a, PIsListLike listO b) =>
Term s ((a :--> PMaybe b) :--> listI a :--> listO b)
pmapMaybe = phoistAcyclic $
plam $ \f ->
precList
( \self h t ->
pmatch
(f # h)
( \case
PJust x -> pcons # x
PNothing -> pidentity
)
# (self # t)
)
(const pnil)
infixl 3 #<|>
-- | @since 1.0.0
class (PApplicative f) => PAlternative (f :: PType -> PType) where
(#<|>) ::
forall (a :: PType) (s :: S).
(PSubcategory f a) =>
Term s (f a :--> f a :--> f a)
pempty ::
forall (a :: PType) (s :: S).
(PSubcategory f a) =>
Term s (f a)
-- | @since 1.0.0
instance PAlternative PMaybe where
(#<|>) = phoistAcyclic $
plam $ \a b -> pmatch a $ \case
PNothing -> b
PJust _ -> a
pempty = pnothing
-- | @since 1.0.0
ppureIf ::
forall
(f :: PType -> PType)
(a :: PType)
(s :: S).
(PAlternative f, PSubcategory f a) =>
Term s (PBool :--> a :--> f a)
ppureIf = phoistAcyclic $
plam $ \cond x ->
pif
cond
(ppure # x)
pempty
{- | Less then check using a `PComparator`.
@ since 1.0.0
-}
pltBy ::
forall (a :: PType) (s :: S).
Term
s
( PComparator a
:--> a
:--> a
:--> PBool
)
pltBy = phoistAcyclic $
plam $ \c x y ->
pcompareBy # c # x # y #== pcon PLT
-- | @since 1.0.0
pinsertUniqueBy ::
forall (list :: PType -> PType) (a :: PType) (s :: S).
(PIsListLike list a) =>
Term s (PComparator a :--> a :--> list a :--> list a)
pinsertUniqueBy = phoistAcyclic $
plam $ \c x ->
let lt = pltBy # c
eq = pequateBy # c
in precList
( \self h t ->
let ensureUniqueness =
pif
(eq # x # h)
(ptraceError "inserted value already exists")
next =
pif
(lt # x # h)
(pcons # x #$ pcons # h # t)
(pcons # h #$ self # t)
in ensureUniqueness next
)
(const $ psingleton # x)
-- | @since 1.0.0
ptryFromRedeemer ::
forall (r :: PType) (s :: S).
(PTryFrom PData r) =>
Term
s
( PScriptPurpose
:--> PMap 'Unsorted PScriptPurpose PRedeemer
:--> PMaybe r
)
ptryFromRedeemer = phoistAcyclic $
plam $ \p m ->
pfmap
# plam (flip ptryFrom fst . pto)
# (plookup # p # m)
-- | @since 1.0.0
passert ::
forall (a :: PType) (s :: S).
Term s PString ->
Term s PBool ->
Term s a ->
Term s a
passert msg cond x = pif cond x $ ptraceError msg
-- | @since 1.0.0
pisNothing ::
forall (a :: PType) (s :: S).
@ -422,45 +80,38 @@ pisDNothing = phoistAcyclic $
PDNothing _ -> pconstant True
_ -> pconstant False
{- | Get the negative and positive amount of a particular 'CurrencySymbol', and
return nothing if it doesn't exist in the value.
-- | @since 1.0.0
ptoScottEncodingT ::
forall {k :: Type} (unit :: k) (s :: S).
Term s (PTagged unit PAssetClassData :--> PTagged unit PAssetClass)
ptoScottEncodingT = phoistAcyclic $
plam $ \d ->
punsafeDowncast $ ptoScottEncoding #$ pto d
{- | Get the sum of all values belonging to a particular tagged 'CurrencySymbol'.
@since 1.0.0
-}
psymbolValueOf' ::
ptaggedSymbolValueOf ::
forall
{k :: Type}
(unit :: k)
(keys :: KeyGuarantees)
(amounts :: AmountGuarantees)
(s :: S).
Term
s
( PCurrencySymbol
:--> PValue keys amounts
:--> PMaybe
( PPair
-- Positive amount
PInteger
-- Negative amount
PInteger
)
)
psymbolValueOf' = phoistAcyclic $
plam $ \sym value ->
let tnMap = plookup # sym # pto value
f =
plam $
( pfoldr
# plam
( \x r ->
let q = pfromData $ psndBuiltin # x
in pmatch r $ \(PPair p n) ->
pif
(0 #< q)
(pcon $ PPair (p + q) n)
(pcon $ PPair p (n + q))
)
# pcon (PPair 0 0)
#
)
. pto
in pfmap # f # tnMap
Term s (PTagged unit PCurrencySymbol :--> (PValue keys amounts :--> PInteger))
ptaggedSymbolValueOf = phoistAcyclic $ plam $ \tcs -> psymbolValueOf # pto tcs
-- | @since 1.0.0
ptag ::
forall {k :: Type} (tag :: k) (a :: PType) (s :: S).
Term s a ->
Term s (PTagged tag a)
ptag = punsafeDowncast
-- | @since 1.0.0
puntag ::
forall {k :: Type} (tag :: k) (a :: PType) (s :: S).
Term s (PTagged tag a) ->
Term s a
puntag = pto