From 2c93a7cb1cac3390acd4af768eae54065b201da8 Mon Sep 17 00:00:00 2001 From: fanghr Date: Tue, 12 Apr 2022 18:09:48 +0800 Subject: [PATCH 01/23] add an effect that mutates the governor settings --- agora.cabal | 1 + agora/Agora/Effects/GovernorMutation.hs | 155 ++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 agora/Agora/Effects/GovernorMutation.hs diff --git a/agora.cabal b/agora.cabal index c8ce871..a890add 100644 --- a/agora.cabal +++ b/agora.cabal @@ -131,6 +131,7 @@ library Agora.Effect Agora.Effect.NoOp Agora.Effect.TreasuryWithdrawal + Agora.Effects.GovernorMutation Agora.Governor Agora.Governor.Scripts Agora.MultiSig diff --git a/agora/Agora/Effects/GovernorMutation.hs b/agora/Agora/Effects/GovernorMutation.hs new file mode 100644 index 0000000..5cc03d1 --- /dev/null +++ b/agora/Agora/Effects/GovernorMutation.hs @@ -0,0 +1,155 @@ +{-# LANGUAGE TemplateHaskell #-} + +{- | +Module : Agora.Effects.GovernorMutation +Maintainer : chfanghr@gmail.com +Description: An effect that mutates governor settings + +An effect for mutating governor settings +-} +module Agora.Effects.GovernorMutation (mutateGovernorValidator, PMutateGovernorDatum (..), MutateGovernorDatum (..)) where + +-------------------------------------------------------------------------------- + +import GHC.Generics qualified as GHC +import Generics.SOP (Generic, I (I)) +import Prelude + +-------------------------------------------------------------------------------- + +import Plutarch (popaque) +import Plutarch.Api.V1 ( + PMaybeData (PDJust), + PTxOutRef, + PValidator, + PValue, + ) +import Plutarch.Builtin (pforgetData) +import Plutarch.DataRepr ( + DerivePConstantViaData (..), + PDataFields, + PIsDataReprInstances (PIsDataReprInstances), + ) +import Plutarch.Lift (PLifted, PUnsafeLiftDecl) +import Plutarch.Monadic qualified as P + +-------------------------------------------------------------------------------- + +import Plutus.V1.Ledger.Api (TxOutRef) +import PlutusTx qualified + +-------------------------------------------------------------------------------- + +import Agora.Effect (makeEffect) +import Agora.Governor ( + Governor, + GovernorDatum, + PGovernorDatum, + authorityTokenSymbolFromGovernor, + governorStateTokenAssetClass, + ) +import Agora.Utils ( + containsSingleCurrencySymbol, + findOutputsToAddress, + passert, + passetClassValueOf', + pfindDatum, + ) + +-------------------------------------------------------------------------------- + +data MutateGovernorDatum = MutateGovernorDatum + { governorRef :: TxOutRef + , newDatum :: GovernorDatum + } + deriving stock (Show, GHC.Generic) + deriving anyclass (Generic) + +PlutusTx.makeIsDataIndexed ''MutateGovernorDatum [('MutateGovernorDatum, 0)] + +-------------------------------------------------------------------------------- + +newtype PMutateGovernorDatum (s :: S) + = PMutateGovernorDatum + ( Term + s + ( PDataRecord + '[ "governorRef" ':= PTxOutRef + , "newDatum" ':= PGovernorDatum + ] + ) + ) + deriving stock (GHC.Generic) + deriving anyclass (Generic) + deriving anyclass (PIsDataRepr) + deriving + (PlutusType, PIsData, PDataFields) + via (PIsDataReprInstances PMutateGovernorDatum) + +instance PUnsafeLiftDecl PMutateGovernorDatum where type PLifted PMutateGovernorDatum = MutateGovernorDatum +deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) instance (PConstant MutateGovernorDatum) + +-------------------------------------------------------------------------------- + +mutateGovernorValidator :: Governor -> ClosedTerm PValidator +mutateGovernorValidator gov = makeEffect gatSymbol $ + \_gatCs (datum :: Term _ PMutateGovernorDatum) _txOutRef txInfo' -> P.do + let newDatum = pforgetData $ pfield @"newDatum" # datum + pinnedGovernor = pfield @"governorRef" # datum + + txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo' + + passert "Nothing should be minted/burnt other than GAT" $ + containsSingleCurrencySymbol # txInfo.mint + + filteredInputs <- + plet $ + pfilter + # ( plam $ \inInfo -> + let value = pfield @"value" #$ pfield @"resolved" # inInfo + in gstValueOf # value #== 1 + ) + # pfromData txInfo.inputs + + passert "Governor's state token must be moved" $ + plength # filteredInputs #== 1 + + input <- plet $ phead # filteredInputs + + passert "Can only modify the pinned governor" $ + pfield @"outRef" # input #== pinnedGovernor + + let govAddress = + pfield @"address" + #$ pfield @"resolved" + #$ pfromData input + + filteredOutputs <- plet $ findOutputsToAddress # pfromData txInfo' # govAddress + + passert "Exactly one output to the governor" $ + plength # filteredOutputs #== 1 + + outputToGovernor <- plet $ phead # filteredOutputs + + passert "Governor's state token must stay at governor's address" $ + (gstValueOf #$ pfield @"value" # outputToGovernor) #== 1 + + outputDatumHash' <- pmatch $ pfromData $ pfield @"datumHash" # outputToGovernor + + case outputDatumHash' of + PDJust ((pfromData . (pfield @"_0" #)) -> outputDatumHash) -> P.do + datum' <- pmatch $ pfindDatum # outputDatumHash # pfromData txInfo' + case datum' of + PJust datum -> P.do + passert "Unexpected output datum" $ + pto datum #== newDatum + + popaque $ pconstant () + _ -> ptraceError "Output datum not found" + _ -> ptraceError "Ouput to governor should have datum" + where + gatSymbol = authorityTokenSymbolFromGovernor gov + gstAssetClass = governorStateTokenAssetClass gov + + gstValueOf :: Term s (PValue :--> PInteger) + gstValueOf = passetClassValueOf' gstAssetClass From b8cd27fc2ac9be8516c404d73701a9986284e5aa Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 22 Apr 2022 18:19:01 +0800 Subject: [PATCH 02/23] fix compilation errors --- agora/Agora/Effects/GovernorMutation.hs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/agora/Agora/Effects/GovernorMutation.hs b/agora/Agora/Effects/GovernorMutation.hs index 5cc03d1..c6c5f87 100644 --- a/agora/Agora/Effects/GovernorMutation.hs +++ b/agora/Agora/Effects/GovernorMutation.hs @@ -23,6 +23,8 @@ import Plutarch.Api.V1 ( PTxOutRef, PValidator, PValue, + mintingPolicySymbol, + mkMintingPolicy, ) import Plutarch.Builtin (pforgetData) import Plutarch.DataRepr ( @@ -40,16 +42,15 @@ import PlutusTx qualified -------------------------------------------------------------------------------- +import Agora.AuthorityToken import Agora.Effect (makeEffect) import Agora.Governor ( Governor, GovernorDatum, PGovernorDatum, - authorityTokenSymbolFromGovernor, - governorStateTokenAssetClass, + gstAssetClass, ) import Agora.Utils ( - containsSingleCurrencySymbol, findOutputsToAddress, passert, passetClassValueOf', @@ -99,8 +100,11 @@ mutateGovernorValidator gov = makeEffect gatSymbol $ txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo' + let mint :: Term _ (PBuiltinList _) + mint = pto $ pto $ pto $ pfromData txInfo.mint + passert "Nothing should be minted/burnt other than GAT" $ - containsSingleCurrencySymbol # txInfo.mint + plength # mint #== 1 filteredInputs <- plet $ @@ -148,8 +152,12 @@ mutateGovernorValidator gov = makeEffect gatSymbol $ _ -> ptraceError "Output datum not found" _ -> ptraceError "Ouput to governor should have datum" where - gatSymbol = authorityTokenSymbolFromGovernor gov - gstAssetClass = governorStateTokenAssetClass gov + -- Copy-Paste from governor + -- TODO: export as a util function from governor + gatSymbol = mintingPolicySymbol policy + where + at = AuthorityToken $ gstAssetClass gov + policy = mkMintingPolicy $ authorityTokenPolicy at gstValueOf :: Term s (PValue :--> PInteger) - gstValueOf = passetClassValueOf' gstAssetClass + gstValueOf = passetClassValueOf' $ gstAssetClass gov From f63e20907ee7093921e565bcfb705b827fd3ef55 Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 22 Apr 2022 18:49:43 +0800 Subject: [PATCH 03/23] utilize `gatSymbol`; clean up imports --- agora/Agora/Effects/GovernorMutation.hs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/agora/Agora/Effects/GovernorMutation.hs b/agora/Agora/Effects/GovernorMutation.hs index c6c5f87..baae4d3 100644 --- a/agora/Agora/Effects/GovernorMutation.hs +++ b/agora/Agora/Effects/GovernorMutation.hs @@ -23,8 +23,6 @@ import Plutarch.Api.V1 ( PTxOutRef, PValidator, PValue, - mintingPolicySymbol, - mkMintingPolicy, ) import Plutarch.Builtin (pforgetData) import Plutarch.DataRepr ( @@ -42,13 +40,13 @@ import PlutusTx qualified -------------------------------------------------------------------------------- -import Agora.AuthorityToken import Agora.Effect (makeEffect) import Agora.Governor ( Governor, GovernorDatum, PGovernorDatum, gstAssetClass, + gatSymbol, ) import Agora.Utils ( findOutputsToAddress, @@ -93,7 +91,7 @@ deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) i -------------------------------------------------------------------------------- mutateGovernorValidator :: Governor -> ClosedTerm PValidator -mutateGovernorValidator gov = makeEffect gatSymbol $ +mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ \_gatCs (datum :: Term _ PMutateGovernorDatum) _txOutRef txInfo' -> P.do let newDatum = pforgetData $ pfield @"newDatum" # datum pinnedGovernor = pfield @"governorRef" # datum @@ -152,12 +150,5 @@ mutateGovernorValidator gov = makeEffect gatSymbol $ _ -> ptraceError "Output datum not found" _ -> ptraceError "Ouput to governor should have datum" where - -- Copy-Paste from governor - -- TODO: export as a util function from governor - gatSymbol = mintingPolicySymbol policy - where - at = AuthorityToken $ gstAssetClass gov - policy = mkMintingPolicy $ authorityTokenPolicy at - gstValueOf :: Term s (PValue :--> PInteger) gstValueOf = passetClassValueOf' $ gstAssetClass gov From 33365e0a31f1d5d8ef7a37b27423b37b0bdfc9f9 Mon Sep 17 00:00:00 2001 From: fanghr Date: Mon, 25 Apr 2022 17:29:25 +0800 Subject: [PATCH 04/23] add document for everything in `GovernorMutation.hs` --- agora/Agora/Effects/GovernorMutation.hs | 34 ++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/agora/Agora/Effects/GovernorMutation.hs b/agora/Agora/Effects/GovernorMutation.hs index baae4d3..e83225a 100644 --- a/agora/Agora/Effects/GovernorMutation.hs +++ b/agora/Agora/Effects/GovernorMutation.hs @@ -7,7 +7,16 @@ Description: An effect that mutates governor settings An effect for mutating governor settings -} -module Agora.Effects.GovernorMutation (mutateGovernorValidator, PMutateGovernorDatum (..), MutateGovernorDatum (..)) where +module Agora.Effects.GovernorMutation ( + -- * Haskell-land + MutateGovernorDatum (..), + + -- * Plutarch-land + PMutateGovernorDatum (..), + + -- * Scripts + mutateGovernorValidator, +) where -------------------------------------------------------------------------------- @@ -45,8 +54,8 @@ import Agora.Governor ( Governor, GovernorDatum, PGovernorDatum, - gstAssetClass, gatSymbol, + gstAssetClass, ) import Agora.Utils ( findOutputsToAddress, @@ -57,9 +66,12 @@ import Agora.Utils ( -------------------------------------------------------------------------------- +-- | Haskell-level datum for the governor mutation effect script. data MutateGovernorDatum = MutateGovernorDatum - { governorRef :: TxOutRef + { governorRef :: TxOutRef + -- ^ Referenced governor state UTXO should be updated by the effect. , newDatum :: GovernorDatum + -- ^ The new settings for the governor. } deriving stock (Show, GHC.Generic) deriving anyclass (Generic) @@ -68,6 +80,7 @@ PlutusTx.makeIsDataIndexed ''MutateGovernorDatum [('MutateGovernorDatum, 0)] -------------------------------------------------------------------------------- +-- | Plutarch-level version of 'MutateGovernorDatum'. newtype PMutateGovernorDatum (s :: S) = PMutateGovernorDatum ( Term @@ -90,6 +103,21 @@ deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) i -------------------------------------------------------------------------------- +{- | Validator for the governor mutation effect. + + This effect is implemented using the 'Agora.Effect.makeEffect' wrapper, + meaning that the burning of GAT is checked in the said wrapper. + + In order to locate the governor, the validator is parametrized with a 'Agora.Governor.Governor'. + + All the information it need to validate the effect is encoded in the 'MutateGovernorDatum', + so regardless what redeemer it's given, it will check: + + - No token is minted/burnt other than GAT. + - The reference UTXO in the datum should be spent. + - Said UTXO carries the GST. + - A new UTXO, containing the GST and the new governor state datum, is paid to the governor. +-} mutateGovernorValidator :: Governor -> ClosedTerm PValidator mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ \_gatCs (datum :: Term _ PMutateGovernorDatum) _txOutRef txInfo' -> P.do From 015ee0b9de1e57e086f6c1fd736da8835de04cf2 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 13:29:36 +0800 Subject: [PATCH 05/23] move `GovernorMutation` effect to `Effect` --- agora.cabal | 2 +- .../{Effects => Effect}/GovernorMutation.hs | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) rename agora/Agora/{Effects => Effect}/GovernorMutation.hs (94%) diff --git a/agora.cabal b/agora.cabal index a890add..b3005cc 100644 --- a/agora.cabal +++ b/agora.cabal @@ -129,9 +129,9 @@ library exposed-modules: Agora.AuthorityToken Agora.Effect + Agora.Effect.GovernorMutation Agora.Effect.NoOp Agora.Effect.TreasuryWithdrawal - Agora.Effects.GovernorMutation Agora.Governor Agora.Governor.Scripts Agora.MultiSig diff --git a/agora/Agora/Effects/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs similarity index 94% rename from agora/Agora/Effects/GovernorMutation.hs rename to agora/Agora/Effect/GovernorMutation.hs index e83225a..783b9f8 100644 --- a/agora/Agora/Effects/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -1,19 +1,19 @@ {-# LANGUAGE TemplateHaskell #-} {- | -Module : Agora.Effects.GovernorMutation +Module : Agora.Effect.GovernorMutation Maintainer : chfanghr@gmail.com Description: An effect that mutates governor settings An effect for mutating governor settings -} -module Agora.Effects.GovernorMutation ( +module Agora.Effect.GovernorMutation ( -- * Haskell-land MutateGovernorDatum (..), -- * Plutarch-land PMutateGovernorDatum (..), - + -- * Scripts mutateGovernorValidator, ) where @@ -68,10 +68,10 @@ import Agora.Utils ( -- | Haskell-level datum for the governor mutation effect script. data MutateGovernorDatum = MutateGovernorDatum - { governorRef :: TxOutRef - -- ^ Referenced governor state UTXO should be updated by the effect. + { governorRef :: TxOutRef + -- ^ Referenced governor state UTXO should be updated by the effect. , newDatum :: GovernorDatum - -- ^ The new settings for the governor. + -- ^ The new settings for the governor. } deriving stock (Show, GHC.Generic) deriving anyclass (Generic) @@ -103,9 +103,9 @@ deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) i -------------------------------------------------------------------------------- -{- | Validator for the governor mutation effect. +{- | Validator for the governor mutation effect. - This effect is implemented using the 'Agora.Effect.makeEffect' wrapper, + This effect is implemented using the 'Agora.Effect.makeEffect' wrapper, meaning that the burning of GAT is checked in the said wrapper. In order to locate the governor, the validator is parametrized with a 'Agora.Governor.Governor'. From dd79a7b73a68d6eaaff63c5e5cfafe80624475f8 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 13:34:09 +0800 Subject: [PATCH 06/23] mlabs email --- agora/Agora/Effect/GovernorMutation.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 783b9f8..8d40524 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -2,7 +2,7 @@ {- | Module : Agora.Effect.GovernorMutation -Maintainer : chfanghr@gmail.com +Maintainer : connor@mlabs.city Description: An effect that mutates governor settings An effect for mutating governor settings From 0440e00410aae88af8071f4f9fc531f6a0c6e09d Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 14:08:11 +0800 Subject: [PATCH 07/23] fix compilation errors --- agora/Agora/Effect/GovernorMutation.hs | 41 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 8d40524..af98850 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -20,31 +20,34 @@ module Agora.Effect.GovernorMutation ( -------------------------------------------------------------------------------- +import Control.Applicative (Const) import GHC.Generics qualified as GHC import Generics.SOP (Generic, I (I)) -import Prelude -------------------------------------------------------------------------------- -import Plutarch (popaque) import Plutarch.Api.V1 ( PMaybeData (PDJust), PTxOutRef, PValidator, PValue, ) +import Plutarch.Api.V1.Extra (pvalueOf) import Plutarch.Builtin (pforgetData) import Plutarch.DataRepr ( DerivePConstantViaData (..), PDataFields, PIsDataReprInstances (PIsDataReprInstances), ) -import Plutarch.Lift (PLifted, PUnsafeLiftDecl) +import Plutarch.Lift (PConstantDecl, PLifted, PUnsafeLiftDecl) import Plutarch.Monadic qualified as P +import Plutarch.TryFrom (PTryFrom (..)) +import Plutarch.Unsafe (punsafeCoerce) -------------------------------------------------------------------------------- import Plutus.V1.Ledger.Api (TxOutRef) +import Plutus.V1.Ledger.Value (AssetClass (..)) import PlutusTx qualified -------------------------------------------------------------------------------- @@ -54,13 +57,14 @@ import Agora.Governor ( Governor, GovernorDatum, PGovernorDatum, - gatSymbol, - gstAssetClass, + ) +import Agora.Governor.Scripts ( + authorityTokenSymbolFromGovernor, + governorSTAssetClassFromGovernor, ) import Agora.Utils ( findOutputsToAddress, passert, - passetClassValueOf', pfindDatum, ) @@ -99,7 +103,13 @@ newtype PMutateGovernorDatum (s :: S) via (PIsDataReprInstances PMutateGovernorDatum) instance PUnsafeLiftDecl PMutateGovernorDatum where type PLifted PMutateGovernorDatum = MutateGovernorDatum -deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) instance (PConstant MutateGovernorDatum) +deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) instance (PConstantDecl MutateGovernorDatum) + +-- TODO: Derive this. +instance PTryFrom PData PMutateGovernorDatum where + type PTryFromExcess PData PMutateGovernorDatum = Const () + ptryFrom' d k = + k (punsafeCoerce d, ()) -------------------------------------------------------------------------------- @@ -119,12 +129,12 @@ deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) i - A new UTXO, containing the GST and the new governor state datum, is paid to the governor. -} mutateGovernorValidator :: Governor -> ClosedTerm PValidator -mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ +mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) $ \_gatCs (datum :: Term _ PMutateGovernorDatum) _txOutRef txInfo' -> P.do let newDatum = pforgetData $ pfield @"newDatum" # datum pinnedGovernor = pfield @"governorRef" # datum - txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo' + txInfo <- pletFields @'["mint", "inputs", "outputs", "datums"] txInfo' let mint :: Term _ (PBuiltinList _) mint = pto $ pto $ pto $ pfromData txInfo.mint @@ -135,7 +145,8 @@ mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ filteredInputs <- plet $ pfilter - # ( plam $ \inInfo -> + # plam + ( \inInfo -> let value = pfield @"value" #$ pfield @"resolved" # inInfo in gstValueOf # value #== 1 ) @@ -154,7 +165,7 @@ mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ #$ pfield @"resolved" #$ pfromData input - filteredOutputs <- plet $ findOutputsToAddress # pfromData txInfo' # govAddress + filteredOutputs <- plet $ findOutputsToAddress # pfromData txInfo.outputs # govAddress passert "Exactly one output to the governor" $ plength # filteredOutputs #== 1 @@ -167,8 +178,8 @@ mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ outputDatumHash' <- pmatch $ pfromData $ pfield @"datumHash" # outputToGovernor case outputDatumHash' of - PDJust ((pfromData . (pfield @"_0" #)) -> outputDatumHash) -> P.do - datum' <- pmatch $ pfindDatum # outputDatumHash # pfromData txInfo' + PDJust (pfromData . (pfield @"_0" #) -> outputDatumHash) -> P.do + datum' <- pmatch $ pfindDatum # outputDatumHash # pfromData txInfo.datums case datum' of PJust datum -> P.do passert "Unexpected output datum" $ @@ -179,4 +190,6 @@ mutateGovernorValidator gov = makeEffect (gatSymbol gov) $ _ -> ptraceError "Ouput to governor should have datum" where gstValueOf :: Term s (PValue :--> PInteger) - gstValueOf = passetClassValueOf' $ gstAssetClass gov + gstValueOf = phoistAcyclic $ plam $ \v -> pvalueOf # v # pconstant cs # pconstant tn + where + AssetClass (cs, tn) = governorSTAssetClassFromGovernor gov From 50deac1175093edf7df4bcab2fa37c87fae438a8 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 14:13:33 +0800 Subject: [PATCH 08/23] require `PtryFrom PData (PAsData datum)` in `makeEffect` --- agora/Agora/Effect.hs | 4 ++-- agora/Agora/Effect/GovernorMutation.hs | 4 ++-- agora/Agora/Effect/NoOp.hs | 6 +++--- agora/Agora/Effect/TreasuryWithdrawal.hs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/agora/Agora/Effect.hs b/agora/Agora/Effect.hs index 8fb40ba..c35ed55 100644 --- a/agora/Agora/Effect.hs +++ b/agora/Agora/Effect.hs @@ -23,7 +23,7 @@ import Plutus.V1.Ledger.Value (CurrencySymbol) -} makeEffect :: forall (datum :: PType). - (PIsData datum, PTryFrom PData datum) => + (PIsData datum, PTryFrom PData (PAsData datum)) => CurrencySymbol -> (forall (s :: S). Term s PCurrencySymbol -> Term s datum -> Term s PTxOutRef -> Term s (PAsData PTxInfo) -> Term s POpaque) -> ClosedTerm PValidator @@ -35,7 +35,7 @@ makeEffect gatCs' f = -- convert input datum, PData, into desierable type -- the way this conversion is performed should be defined -- by PTryFrom for each datum in effect script. - (datum', _) <- tctryFrom @datum datum + (pfromData -> datum', _) <- tctryFrom datum -- ensure purpose is Spending. PSpending txOutRef <- tcmatch $ pfromData ctx.purpose diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index af98850..20ec5ba 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -106,8 +106,8 @@ instance PUnsafeLiftDecl PMutateGovernorDatum where type PLifted PMutateGovernor deriving via (DerivePConstantViaData MutateGovernorDatum PMutateGovernorDatum) instance (PConstantDecl MutateGovernorDatum) -- TODO: Derive this. -instance PTryFrom PData PMutateGovernorDatum where - type PTryFromExcess PData PMutateGovernorDatum = Const () +instance PTryFrom PData (PAsData PMutateGovernorDatum) where + type PTryFromExcess PData (PAsData PMutateGovernorDatum) = Const () ptryFrom' d k = k (punsafeCoerce d, ()) diff --git a/agora/Agora/Effect/NoOp.hs b/agora/Agora/Effect/NoOp.hs index a384675..39c63ee 100644 --- a/agora/Agora/Effect/NoOp.hs +++ b/agora/Agora/Effect/NoOp.hs @@ -18,13 +18,13 @@ import Plutus.V1.Ledger.Value (CurrencySymbol) newtype PNoOp (s :: S) = PNoOp (Term s PUnit) deriving (PlutusType, PIsData) via (DerivePNewtype PNoOp PUnit) -instance PTryFrom PData PNoOp where - type PTryFromExcess PData PNoOp = Const () +instance PTryFrom PData (PAsData PNoOp) where + type PTryFromExcess PData (PAsData PNoOp) = Const () ptryFrom' _ cont = -- JUSTIFICATION: -- We don't care anything about data. -- It should always be reduced to Unit. - cont (pcon $ PNoOp (pconstant ()), ()) + cont (pdata $ pcon $ PNoOp (pconstant ()), ()) -- | Dummy effect which can only burn its GAT. noOpValidator :: CurrencySymbol -> ClosedTerm PValidator diff --git a/agora/Agora/Effect/TreasuryWithdrawal.hs b/agora/Agora/Effect/TreasuryWithdrawal.hs index 5bad045..6bb2851 100644 --- a/agora/Agora/Effect/TreasuryWithdrawal.hs +++ b/agora/Agora/Effect/TreasuryWithdrawal.hs @@ -82,8 +82,8 @@ deriving via instance (PConstantDecl TreasuryWithdrawalDatum) -instance PTryFrom PData PTreasuryWithdrawalDatum where - type PTryFromExcess PData PTreasuryWithdrawalDatum = Const () +instance PTryFrom PData (PAsData PTreasuryWithdrawalDatum) where + type PTryFromExcess PData (PAsData PTreasuryWithdrawalDatum) = Const () ptryFrom' opq cont = -- TODO: This should not use 'punsafeCoerce'. -- Blocked by 'PCredential', and 'PTuple'. From 6ab6afbdb7e09503cbf0ed34dd706777461ae556 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 17:00:59 +0800 Subject: [PATCH 09/23] small doc fix to `pisJust` --- agora/Agora/Utils.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agora/Agora/Utils.hs b/agora/Agora/Utils.hs index 558bc13..f37712b 100644 --- a/agora/Agora/Utils.hs +++ b/agora/Agora/Utils.hs @@ -212,7 +212,7 @@ pfromMaybe = phoistAcyclic $ PJust a' -> a' PNothing -> e --- | Yield True if a given PMaybe is of form PJust _. +-- | Yield True if a given PMaybe is of form @'PJust' _@. pisJust :: forall a s. Term s (PMaybe a :--> PBool) pisJust = phoistAcyclic $ plam $ \v' -> From 40d5cf5f748d7ee0aea7c9a74de6102a045e21e5 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 17:03:47 +0800 Subject: [PATCH 10/23] safely check input/output governor datum ...utilize a bunch of util functions as well --- agora/Agora/Effect/GovernorMutation.hs | 55 +++++++++++++++----------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 20ec5ba..af7cee1 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -27,13 +27,11 @@ import Generics.SOP (Generic, I (I)) -------------------------------------------------------------------------------- import Plutarch.Api.V1 ( - PMaybeData (PDJust), PTxOutRef, PValidator, PValue, ) import Plutarch.Api.V1.Extra (pvalueOf) -import Plutarch.Builtin (pforgetData) import Plutarch.DataRepr ( DerivePConstantViaData (..), PDataFields, @@ -57,6 +55,7 @@ import Agora.Governor ( Governor, GovernorDatum, PGovernorDatum, + governorDatumValid, ) import Agora.Governor.Scripts ( authorityTokenSymbolFromGovernor, @@ -64,8 +63,11 @@ import Agora.Governor.Scripts ( ) import Agora.Utils ( findOutputsToAddress, + findTxOutByTxOutRef, + mustBePDJust, + mustBePJust, passert, - pfindDatum, + ptryFindDatum, ) -------------------------------------------------------------------------------- @@ -99,7 +101,7 @@ newtype PMutateGovernorDatum (s :: S) deriving anyclass (Generic) deriving anyclass (PIsDataRepr) deriving - (PlutusType, PIsData, PDataFields) + (PlutusType, PIsData, PDataFields, PEq) via (PIsDataReprInstances PMutateGovernorDatum) instance PUnsafeLiftDecl PMutateGovernorDatum where type PLifted PMutateGovernorDatum = MutateGovernorDatum @@ -130,12 +132,19 @@ instance PTryFrom PData (PAsData PMutateGovernorDatum) where -} mutateGovernorValidator :: Governor -> ClosedTerm PValidator mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) $ - \_gatCs (datum :: Term _ PMutateGovernorDatum) _txOutRef txInfo' -> P.do - let newDatum = pforgetData $ pfield @"newDatum" # datum - pinnedGovernor = pfield @"governorRef" # datum + \_gatCs (datum' :: Term _ PMutateGovernorDatum) txOutRef txInfo' -> P.do + datum <- pletFields @'["newDatum", "governorRef"] datum' txInfo <- pletFields @'["mint", "inputs", "outputs", "datums"] txInfo' + let selfAddress = + pfield @"address" + #$ mustBePJust # "Self governorInput not found" + #$ findTxOutByTxOutRef # txOutRef # txInfo.inputs + + passert "No output to the effect validator" $ + pnull #$ findOutputsToAddress # txInfo.outputs # selfAddress + let mint :: Term _ (PBuiltinList _) mint = pto $ pto $ pto $ pfromData txInfo.mint @@ -155,39 +164,39 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) passert "Governor's state token must be moved" $ plength # filteredInputs #== 1 - input <- plet $ phead # filteredInputs + governorInput <- plet $ phead # filteredInputs passert "Can only modify the pinned governor" $ - pfield @"outRef" # input #== pinnedGovernor + pfield @"outRef" # governorInput #== datum.governorRef let govAddress = pfield @"address" #$ pfield @"resolved" - #$ pfromData input + #$ pfromData governorInput filteredOutputs <- plet $ findOutputsToAddress # pfromData txInfo.outputs # govAddress passert "Exactly one output to the governor" $ plength # filteredOutputs #== 1 - outputToGovernor <- plet $ phead # filteredOutputs + governorOutput <- plet $ phead # filteredOutputs passert "Governor's state token must stay at governor's address" $ - (gstValueOf #$ pfield @"value" # outputToGovernor) #== 1 + (gstValueOf #$ pfield @"value" # governorOutput) #== 1 - outputDatumHash' <- pmatch $ pfromData $ pfield @"datumHash" # outputToGovernor + let governorOutputDatumHash = + mustBePDJust # "Governor output doesn't have datum" + #$ pfromData + $ pfield @"datumHash" # governorOutput + governorOutputDatum = + pfromData @PGovernorDatum $ + mustBePJust # "Governor output datum not found" + #$ ptryFindDatum # governorOutputDatumHash # txInfo.datums - case outputDatumHash' of - PDJust (pfromData . (pfield @"_0" #) -> outputDatumHash) -> P.do - datum' <- pmatch $ pfindDatum # outputDatumHash # pfromData txInfo.datums - case datum' of - PJust datum -> P.do - passert "Unexpected output datum" $ - pto datum #== newDatum + passert "Unexpected governor datum" $ datum.newDatum #== governorOutputDatum + passert "New governor datum should be valid" $ governorDatumValid # governorOutputDatum - popaque $ pconstant () - _ -> ptraceError "Output datum not found" - _ -> ptraceError "Ouput to governor should have datum" + popaque $ pconstant () where gstValueOf :: Term s (PValue :--> PInteger) gstValueOf = phoistAcyclic $ plam $ \v -> pvalueOf # v # pconstant cs # pconstant tn From 12f9a5bf1d2babb32ec6c5912c710e4a16b052f4 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 17:26:49 +0800 Subject: [PATCH 11/23] improve the docstring --- agora/Agora/Effect/GovernorMutation.hs | 30 ++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index af7cee1..628f130 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -3,9 +3,9 @@ {- | Module : Agora.Effect.GovernorMutation Maintainer : connor@mlabs.city -Description: An effect that mutates governor settings +Description: An effect that mutates governor settings. -An effect for mutating governor settings +An effect for mutating governor settings. -} module Agora.Effect.GovernorMutation ( -- * Haskell-land @@ -117,18 +117,26 @@ instance PTryFrom PData (PAsData PMutateGovernorDatum) where {- | Validator for the governor mutation effect. - This effect is implemented using the 'Agora.Effect.makeEffect' wrapper, - meaning that the burning of GAT is checked in the said wrapper. + This effect is implemented using the 'Agora.Effect.makeEffect' wrapper, + meaning that the burning of GAT is checked in the said wrapper. - In order to locate the governor, the validator is parametrized with a 'Agora.Governor.Governor'. + In order to locate the governor, the validator is parametrized with a 'Agora.Governor.Governor'. - All the information it need to validate the effect is encoded in the 'MutateGovernorDatum', - so regardless what redeemer it's given, it will check: + All the information it needs to validate the effect is encoded in the 'MutateGovernorDatum', + so regardless what redeemer it's given, it will check: - - No token is minted/burnt other than GAT. - - The reference UTXO in the datum should be spent. - - Said UTXO carries the GST. - - A new UTXO, containing the GST and the new governor state datum, is paid to the governor. + - No token is minted/burnt other than GAT. + - Nothing is being paid to the the effect validator. + - The governor's state UTXO must be spent: + + * It carries exactly one GST. + * It's referenced by 'governorRef' in the effect's datum. + + - A new state UTXO is paid to the governor: + + * It contains the GST. + * It has valid governor state datum. + * The datum is exactly the same as the 'newDatum'. -} mutateGovernorValidator :: Governor -> ClosedTerm PValidator mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) $ From 526ca67747ff6eaedc3642067f9f6421a6f5b425 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 17:36:33 +0800 Subject: [PATCH 12/23] add templates of tests and samples --- agora-sample/Sample/Effect/GovernorMutation.hs | 1 + agora-test/Spec.hs | 4 ++++ agora-test/Spec/Effect/GovernorMutation.hs | 6 ++++++ agora.cabal | 2 ++ 4 files changed, 13 insertions(+) create mode 100644 agora-sample/Sample/Effect/GovernorMutation.hs create mode 100644 agora-test/Spec/Effect/GovernorMutation.hs diff --git a/agora-sample/Sample/Effect/GovernorMutation.hs b/agora-sample/Sample/Effect/GovernorMutation.hs new file mode 100644 index 0000000..d369707 --- /dev/null +++ b/agora-sample/Sample/Effect/GovernorMutation.hs @@ -0,0 +1 @@ +module Sample.Effect.GovernorMutation () where diff --git a/agora-test/Spec.hs b/agora-test/Spec.hs index d2c90f7..2d97c1e 100644 --- a/agora-test/Spec.hs +++ b/agora-test/Spec.hs @@ -7,6 +7,7 @@ import Test.Tasty (defaultMain, testGroup) -------------------------------------------------------------------------------- import Spec.AuthorityToken qualified as AuthorityToken +import Spec.Effect.GovernorMutation qualified as GovernorMutation import Spec.Effect.TreasuryWithdrawal qualified as TreasuryWithdrawal import Spec.Governor qualified as Governor import Spec.Model.MultiSig qualified as MultiSig @@ -26,6 +27,9 @@ main = [ testGroup "Treasury Withdrawal Effect" TreasuryWithdrawal.tests + , testGroup + "Governor Mutation Effect" + GovernorMutation.tests ] , testGroup "Stake tests" diff --git a/agora-test/Spec/Effect/GovernorMutation.hs b/agora-test/Spec/Effect/GovernorMutation.hs new file mode 100644 index 0000000..7863cb6 --- /dev/null +++ b/agora-test/Spec/Effect/GovernorMutation.hs @@ -0,0 +1,6 @@ +module Spec.Effect.GovernorMutation (tests) where + +import Test.Tasty (TestTree) + +tests :: [TestTree] +tests = [] diff --git a/agora.cabal b/agora.cabal index b3005cc..88f32f0 100644 --- a/agora.cabal +++ b/agora.cabal @@ -167,6 +167,7 @@ library agora-sample import: lang, deps, test-deps build-depends: agora-testlib exposed-modules: + Sample.Effect.GovernorMutation Sample.Effect.TreasuryWithdrawal Sample.Governor Sample.Proposal @@ -183,6 +184,7 @@ test-suite agora-test hs-source-dirs: agora-test other-modules: Spec.AuthorityToken + Spec.Effect.GovernorMutation Spec.Effect.TreasuryWithdrawal Spec.Governor Spec.Model.MultiSig From bac4eaa74e90fcf8b9ba5a24da641ff3efd310d7 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 18:25:53 +0800 Subject: [PATCH 13/23] add a valid sample for the mutate governor effect --- .../Sample/Effect/GovernorMutation.hs | 142 +++++++++++++++++- 1 file changed, 141 insertions(+), 1 deletion(-) diff --git a/agora-sample/Sample/Effect/GovernorMutation.hs b/agora-sample/Sample/Effect/GovernorMutation.hs index d369707..be6e167 100644 --- a/agora-sample/Sample/Effect/GovernorMutation.hs +++ b/agora-sample/Sample/Effect/GovernorMutation.hs @@ -1 +1,141 @@ -module Sample.Effect.GovernorMutation () where +module Sample.Effect.GovernorMutation ( + validContext, + effectValidator, + effectValidatorAddress, + effectValidatorHash, + atAssetClass, +) where + +import Agora.Effect.GovernorMutation ( + MutateGovernorDatum (..), + mutateGovernorValidator, + ) +import Agora.Governor (GovernorDatum (..)) +import Agora.Proposal (ProposalId (..)) +import Plutarch.Api.V1 (mkValidator, validatorHash) +import Plutus.V1.Ledger.Address (scriptHashAddress) +import Plutus.V1.Ledger.Api ( + Address, + Datum (..), + ScriptContext (..), + ScriptPurpose (Spending), + ToData (..), + TokenName (..), + TxInInfo (..), + TxInfo (..), + TxOut (..), + TxOutRef (TxOutRef), + Validator, + ValidatorHash (..), + ) +import Plutus.V1.Ledger.Api qualified as Interval +import Plutus.V1.Ledger.Value (AssetClass, assetClass) +import Plutus.V1.Ledger.Value qualified as Value +import Sample.Shared +import Test.Util (datumPair, toDatumHash) + +effectValidator :: Validator +effectValidator = mkValidator $ mutateGovernorValidator governor + +effectValidatorHash :: ValidatorHash +effectValidatorHash = validatorHash effectValidator + +effectValidatorAddress :: Address +effectValidatorAddress = scriptHashAddress effectValidatorHash + +atAssetClass :: AssetClass +atAssetClass = assetClass authorityTokenSymbol tokenName + where + -- TODO: use 'validatorHashToTokenName' + ValidatorHash bs = effectValidatorHash + tokenName = TokenName bs + +validContext :: ScriptContext +validContext = + let gst = Value.assetClassValue govAssetClass 1 + at = Value.assetClassValue atAssetClass 1 + + burnt = Value.assetClassValue atAssetClass (-1) + + -- + + governorInputRef :: TxOutRef + governorInputRef = TxOutRef "614481d2159bfb72350222d61fce17e548e0fc00e5a1f841ff1837c431346ce7" 1 + + -- + + governorInputDatum' :: GovernorDatum + governorInputDatum' = + GovernorDatum + { proposalThresholds = defaultProposalThresholds + , nextProposalId = ProposalId 0 + } + governorInputDatum :: Datum + governorInputDatum = Datum $ toBuiltinData governorInputDatum' + governorInput :: TxOut + governorInput = + TxOut + { txOutAddress = govValidatorAddress + , txOutValue = gst + , txOutDatumHash = Just $ toDatumHash governorInputDatum + } + + -- + + -- The effect should update 'nextProposalId' + effectInputDatum' :: MutateGovernorDatum + effectInputDatum' = + MutateGovernorDatum + { governorRef = governorInputRef + , newDatum = + governorInputDatum' + { nextProposalId = ProposalId 42 + } + } + effectInputDatum :: Datum + effectInputDatum = Datum $ toBuiltinData effectInputDatum' + effectInput :: TxOut + effectInput = + TxOut + { txOutAddress = effectValidatorAddress + , txOutValue = at + , txOutDatumHash = Just $ toDatumHash effectInputDatum + } + + -- + + governorOutputDatum' :: GovernorDatum + governorOutputDatum' = effectInputDatum'.newDatum + governorOutputDatum :: Datum + governorOutputDatum = Datum $ toBuiltinData governorOutputDatum' + governorOutput :: TxOut + governorOutput = + TxOut + { txOutAddress = effectValidatorAddress + , txOutValue = withMinAda gst + , txOutDatumHash = Just $ toDatumHash governorOutputDatum + } + + -- + + ownInputRef :: TxOutRef + ownInputRef = TxOutRef "c31164dc11835de7eb6187f67d0e1a19c1dfc0786a456923eef5043189cdb578" 1 + in ScriptContext + { scriptContextPurpose = Spending ownInputRef + , scriptContextTxInfo = + TxInfo + { txInfoInputs = + [ TxInInfo ownInputRef effectInput + , TxInInfo governorInputRef governorInput + ] + , txInfoOutputs = [governorOutput] + , txInfoFee = Value.singleton "" "" 2 + , txInfoMint = burnt + , txInfoDCert = [] + , txInfoWdrl = [] + , txInfoValidRange = Interval.always + , txInfoSignatories = [signer] + , txInfoData = datumPair <$> [governorInputDatum, governorOutputDatum, effectInputDatum] + , txInfoId = "4dae3806cc69615b721d52ed09b758f43f25a8f39b7934d6b28514caf71f5f7b" + } + } From 8c9a69dfeccb3e8f5fe86cd8a99d3211f9809bc4 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 18:49:37 +0800 Subject: [PATCH 14/23] add a simple test --- agora-test/Spec/Effect/GovernorMutation.hs | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/agora-test/Spec/Effect/GovernorMutation.hs b/agora-test/Spec/Effect/GovernorMutation.hs index 7863cb6..38b9e38 100644 --- a/agora-test/Spec/Effect/GovernorMutation.hs +++ b/agora-test/Spec/Effect/GovernorMutation.hs @@ -1,6 +1,30 @@ module Spec.Effect.GovernorMutation (tests) where -import Test.Tasty (TestTree) +import Agora.Effect.GovernorMutation (MutateGovernorDatum (..), mutateGovernorValidator) +import Agora.Governor (GovernorDatum (..)) +import Agora.Proposal (ProposalId (..)) +import Plutus.V1.Ledger.Api (TxOutRef (..)) +import Spec.Sample.Effect.GovernorMutation +import Spec.Sample.Shared +import Spec.Util (effectSucceedsWith) +import Test.Tasty (TestTree, testGroup) tests :: [TestTree] -tests = [] +tests = + [ testGroup + "validator" + [ effectSucceedsWith + "Simple" + (mutateGovernorValidator governor) + ( MutateGovernorDatum + { governorRef = TxOutRef "614481d2159bfb72350222d61fce17e548e0fc00e5a1f841ff1837c431346ce7" 1 + , newDatum = + GovernorDatum + { nextProposalId = ProposalId 42 + , proposalThresholds = defaultProposalThresholds + } + } + ) + validContext + ] + ] From c556b3eda52e40676a8c5fcbaa433f58c6087981 Mon Sep 17 00:00:00 2001 From: fanghr Date: Sat, 7 May 2022 18:53:17 +0800 Subject: [PATCH 15/23] fix the broken test --- agora-sample/Sample/Effect/GovernorMutation.hs | 2 +- agora/Agora/Effect/GovernorMutation.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/agora-sample/Sample/Effect/GovernorMutation.hs b/agora-sample/Sample/Effect/GovernorMutation.hs index be6e167..e088abf 100644 --- a/agora-sample/Sample/Effect/GovernorMutation.hs +++ b/agora-sample/Sample/Effect/GovernorMutation.hs @@ -111,7 +111,7 @@ validContext = governorOutput :: TxOut governorOutput = TxOut - { txOutAddress = effectValidatorAddress + { txOutAddress = govValidatorAddress , txOutValue = withMinAda gst , txOutDatumHash = Just $ toDatumHash governorOutputDatum } diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 628f130..11c7629 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -147,7 +147,7 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) let selfAddress = pfield @"address" - #$ mustBePJust # "Self governorInput not found" + #$ mustBePJust # "Self input not found" #$ findTxOutByTxOutRef # txOutRef # txInfo.inputs passert "No output to the effect validator" $ From a65465b49496902fc0db1e47355f321b31650140 Mon Sep 17 00:00:00 2001 From: fanghr Date: Mon, 9 May 2022 20:33:21 +0800 Subject: [PATCH 16/23] only allow desired inputs/outputs in an effect tx --- agora/Agora/Effect/GovernorMutation.hs | 61 ++++++++++---------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 11c7629..18662e0 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -62,8 +62,6 @@ import Agora.Governor.Scripts ( governorSTAssetClassFromGovernor, ) import Agora.Utils ( - findOutputsToAddress, - findTxOutByTxOutRef, mustBePDJust, mustBePJust, passert, @@ -140,62 +138,49 @@ instance PTryFrom PData (PAsData PMutateGovernorDatum) where -} mutateGovernorValidator :: Governor -> ClosedTerm PValidator mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) $ - \_gatCs (datum' :: Term _ PMutateGovernorDatum) txOutRef txInfo' -> P.do + \_gatCs (datum' :: Term _ PMutateGovernorDatum) _ txInfo' -> P.do datum <- pletFields @'["newDatum", "governorRef"] datum' - txInfo <- pletFields @'["mint", "inputs", "outputs", "datums"] txInfo' - let selfAddress = - pfield @"address" - #$ mustBePJust # "Self input not found" - #$ findTxOutByTxOutRef # txOutRef # txInfo.inputs - - passert "No output to the effect validator" $ - pnull #$ findOutputsToAddress # txInfo.outputs # selfAddress - let mint :: Term _ (PBuiltinList _) mint = pto $ pto $ pto $ pfromData txInfo.mint passert "Nothing should be minted/burnt other than GAT" $ plength # mint #== 1 - filteredInputs <- - plet $ - pfilter - # plam - ( \inInfo -> - let value = pfield @"value" #$ pfield @"resolved" # inInfo - in gstValueOf # value #== 1 - ) - # pfromData txInfo.inputs + passert "Only self and governor inputs are allowed" $ + plength # pfromData txInfo.inputs #== 2 - passert "Governor's state token must be moved" $ - plength # filteredInputs #== 1 + let inputWithGST = + mustBePJust # "Governor input not found" #$ pfind + # phoistAcyclic + ( plam $ \inInfo -> + let value = pfield @"value" #$ pfield @"resolved" # inInfo + in gstValueOf # value #== 1 + ) + # pfromData txInfo.inputs - governorInput <- plet $ phead # filteredInputs + govInInfo <- pletFields @'["outRef", "resolved"] $ inputWithGST passert "Can only modify the pinned governor" $ - pfield @"outRef" # governorInput #== datum.governorRef + govInInfo.outRef #== datum.governorRef - let govAddress = - pfield @"address" - #$ pfield @"resolved" - #$ pfromData governorInput + passert "Only governor ouput is allowed" $ + plength # pfromData txInfo.outputs #== 1 - filteredOutputs <- plet $ findOutputsToAddress # pfromData txInfo.outputs # govAddress + let govAddress = pfield @"address" #$ govInInfo.resolved + govOutput' = pfromData $ phead # pfromData txInfo.outputs - passert "Exactly one output to the governor" $ - plength # filteredOutputs #== 1 + govOutput <- pletFields @'["address", "value", "datumHash"] govOutput' - governorOutput <- plet $ phead # filteredOutputs + passert "No output to the governor" $ + govOutput.address #== govAddress - passert "Governor's state token must stay at governor's address" $ - (gstValueOf #$ pfield @"value" # governorOutput) #== 1 + passert "Governor output doesn't carry the GST" $ + gstValueOf # govOutput.value #== 1 let governorOutputDatumHash = - mustBePDJust # "Governor output doesn't have datum" - #$ pfromData - $ pfield @"datumHash" # governorOutput + mustBePDJust # "Governor output doesn't have datum" # govOutput.datumHash governorOutputDatum = pfromData @PGovernorDatum $ mustBePJust # "Governor output datum not found" From 7f6e363c89e7a5308e6349fba696119c4e1bf6e9 Mon Sep 17 00:00:00 2001 From: fanghr Date: Tue, 10 May 2022 17:22:15 +0800 Subject: [PATCH 17/23] only allow script inputs from the effect and governor --- agora/Agora/Effect/GovernorMutation.hs | 16 ++++++++++++++-- agora/Agora/Utils.hs | 7 +++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 18662e0..ed224e5 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -62,6 +62,7 @@ import Agora.Governor.Scripts ( governorSTAssetClassFromGovernor, ) import Agora.Utils ( + isScriptAddress, mustBePDJust, mustBePJust, passert, @@ -148,8 +149,19 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) passert "Nothing should be minted/burnt other than GAT" $ plength # mint #== 1 - passert "Only self and governor inputs are allowed" $ - plength # pfromData txInfo.inputs #== 2 + passert "Only self and governor script inputs are allowed" $ + pfoldr + # phoistAcyclic + ( plam $ \inInfo count -> + let address = pfield @"address" #$ pfield @"resolved" # inInfo + in pif + (isScriptAddress # address) + (count + 1) + count + ) + # (0 :: Term _ PInteger) + # pfromData txInfo.inputs + #== 2 let inputWithGST = mustBePJust # "Governor input not found" #$ pfind diff --git a/agora/Agora/Utils.hs b/agora/Agora/Utils.hs index f37712b..000e788 100644 --- a/agora/Agora/Utils.hs +++ b/agora/Agora/Utils.hs @@ -59,6 +59,7 @@ module Agora.Utils ( validatorHashToAddress, pmergeBy, phalve, + isScriptAddress ) where -------------------------------------------------------------------------------- @@ -619,6 +620,12 @@ scriptHashFromAddress = phoistAcyclic $ PScriptCredential ((pfield @"_0" #) -> h) -> pcon $ PJust h _ -> pcon PNothing +isScriptAddress :: Term s (PAddress :--> PBool) +isScriptAddress = phoistAcyclic $ plam $ \addr -> + pmatch (pfromData $ pfield @"credential" # addr) $ \case + PScriptCredential _ -> pconstant True + _ -> pconstant False + -- | Find all TxOuts sent to an Address findOutputsToAddress :: Term s (PBuiltinList (PAsData PTxOut) :--> PAddress :--> PBuiltinList (PAsData PTxOut)) findOutputsToAddress = phoistAcyclic $ From fc050527a14a772011300e5ee3eac333790a99cd Mon Sep 17 00:00:00 2001 From: fanghr Date: Mon, 16 May 2022 21:24:43 +0800 Subject: [PATCH 18/23] switch to `TermCont`; fix a bunch of compilation errors; format --- .../Sample/Effect/GovernorMutation.hs | 12 +++++-- agora-test/Spec/Effect/GovernorMutation.hs | 6 ++-- agora/Agora/Effect/GovernorMutation.hs | 31 +++++++++---------- agora/Agora/Utils.hs | 7 +++-- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/agora-sample/Sample/Effect/GovernorMutation.hs b/agora-sample/Sample/Effect/GovernorMutation.hs index e088abf..b71457f 100644 --- a/agora-sample/Sample/Effect/GovernorMutation.hs +++ b/agora-sample/Sample/Effect/GovernorMutation.hs @@ -31,7 +31,15 @@ import Plutus.V1.Ledger.Api ( import Plutus.V1.Ledger.Api qualified as Interval import Plutus.V1.Ledger.Value (AssetClass, assetClass) import Plutus.V1.Ledger.Value qualified as Value -import Sample.Shared +import Sample.Shared ( + authorityTokenSymbol, + defaultProposalThresholds, + govAssetClass, + govValidatorAddress, + governor, + minAda, + signer, + ) import Test.Util (datumPair, toDatumHash) effectValidator :: Validator @@ -112,7 +120,7 @@ validContext = governorOutput = TxOut { txOutAddress = govValidatorAddress - , txOutValue = withMinAda gst + , txOutValue = mconcat [gst, minAda] , txOutDatumHash = Just $ toDatumHash governorOutputDatum } diff --git a/agora-test/Spec/Effect/GovernorMutation.hs b/agora-test/Spec/Effect/GovernorMutation.hs index 38b9e38..354bf0b 100644 --- a/agora-test/Spec/Effect/GovernorMutation.hs +++ b/agora-test/Spec/Effect/GovernorMutation.hs @@ -4,10 +4,10 @@ import Agora.Effect.GovernorMutation (MutateGovernorDatum (..), mutateGovernorVa import Agora.Governor (GovernorDatum (..)) import Agora.Proposal (ProposalId (..)) import Plutus.V1.Ledger.Api (TxOutRef (..)) -import Spec.Sample.Effect.GovernorMutation -import Spec.Sample.Shared -import Spec.Util (effectSucceedsWith) +import Sample.Effect.GovernorMutation (validContext) +import Sample.Shared (defaultProposalThresholds, governor) import Test.Tasty (TestTree, testGroup) +import Test.Util (effectSucceedsWith) tests :: [TestTree] tests = diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index ed224e5..8e7a9e9 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -38,7 +38,6 @@ import Plutarch.DataRepr ( PIsDataReprInstances (PIsDataReprInstances), ) import Plutarch.Lift (PConstantDecl, PLifted, PUnsafeLiftDecl) -import Plutarch.Monadic qualified as P import Plutarch.TryFrom (PTryFrom (..)) import Plutarch.Unsafe (punsafeCoerce) @@ -65,8 +64,8 @@ import Agora.Utils ( isScriptAddress, mustBePDJust, mustBePJust, - passert, ptryFindDatum, + tcassert, ) -------------------------------------------------------------------------------- @@ -139,17 +138,17 @@ instance PTryFrom PData (PAsData PMutateGovernorDatum) where -} mutateGovernorValidator :: Governor -> ClosedTerm PValidator mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) $ - \_gatCs (datum' :: Term _ PMutateGovernorDatum) _ txInfo' -> P.do - datum <- pletFields @'["newDatum", "governorRef"] datum' - txInfo <- pletFields @'["mint", "inputs", "outputs", "datums"] txInfo' + \_gatCs (datum' :: Term _ PMutateGovernorDatum) _ txInfo' -> unTermCont $ do + datum <- tcont $ pletFields @'["newDatum", "governorRef"] datum' + txInfo <- tcont $ pletFields @'["mint", "inputs", "outputs", "datums"] txInfo' let mint :: Term _ (PBuiltinList _) mint = pto $ pto $ pto $ pfromData txInfo.mint - passert "Nothing should be minted/burnt other than GAT" $ + tcassert "Nothing should be minted/burnt other than GAT" $ plength # mint #== 1 - passert "Only self and governor script inputs are allowed" $ + tcassert "Only self and governor script inputs are allowed" $ pfoldr # phoistAcyclic ( plam $ \inInfo count -> @@ -172,23 +171,23 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) ) # pfromData txInfo.inputs - govInInfo <- pletFields @'["outRef", "resolved"] $ inputWithGST + govInInfo <- tcont $ pletFields @'["outRef", "resolved"] $ inputWithGST - passert "Can only modify the pinned governor" $ + tcassert "Can only modify the pinned governor" $ govInInfo.outRef #== datum.governorRef - passert "Only governor ouput is allowed" $ + tcassert "Only governor ouput is allowed" $ plength # pfromData txInfo.outputs #== 1 let govAddress = pfield @"address" #$ govInInfo.resolved govOutput' = pfromData $ phead # pfromData txInfo.outputs - govOutput <- pletFields @'["address", "value", "datumHash"] govOutput' + govOutput <- tcont $ pletFields @'["address", "value", "datumHash"] govOutput' - passert "No output to the governor" $ + tcassert "No output to the governor" $ govOutput.address #== govAddress - passert "Governor output doesn't carry the GST" $ + tcassert "Governor output doesn't carry the GST" $ gstValueOf # govOutput.value #== 1 let governorOutputDatumHash = @@ -198,10 +197,10 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) mustBePJust # "Governor output datum not found" #$ ptryFindDatum # governorOutputDatumHash # txInfo.datums - passert "Unexpected governor datum" $ datum.newDatum #== governorOutputDatum - passert "New governor datum should be valid" $ governorDatumValid # governorOutputDatum + tcassert "Unexpected governor datum" $ datum.newDatum #== governorOutputDatum + tcassert "New governor datum should be valid" $ governorDatumValid # governorOutputDatum - popaque $ pconstant () + return $ popaque $ pconstant () where gstValueOf :: Term s (PValue :--> PInteger) gstValueOf = phoistAcyclic $ plam $ \v -> pvalueOf # v # pconstant cs # pconstant tn diff --git a/agora/Agora/Utils.hs b/agora/Agora/Utils.hs index 000e788..160d277 100644 --- a/agora/Agora/Utils.hs +++ b/agora/Agora/Utils.hs @@ -59,7 +59,7 @@ module Agora.Utils ( validatorHashToAddress, pmergeBy, phalve, - isScriptAddress + isScriptAddress, ) where -------------------------------------------------------------------------------- @@ -621,8 +621,9 @@ scriptHashFromAddress = phoistAcyclic $ _ -> pcon PNothing isScriptAddress :: Term s (PAddress :--> PBool) -isScriptAddress = phoistAcyclic $ plam $ \addr -> - pmatch (pfromData $ pfield @"credential" # addr) $ \case +isScriptAddress = phoistAcyclic $ + plam $ \addr -> + pmatch (pfromData $ pfield @"credential" # addr) $ \case PScriptCredential _ -> pconstant True _ -> pconstant False From ad33b0cbbce60b7d12ad93621966c6902dc47e99 Mon Sep 17 00:00:00 2001 From: fanghr Date: Thu, 19 May 2022 20:12:24 +0800 Subject: [PATCH 19/23] refactor tests of the effect * test both the effect and governor in the spec * test that the effect and governor will fail when try setting the governor state to a invalid one --- .../Sample/Effect/GovernorMutation.hs | 111 +++++++++++------- agora-sample/Sample/Governor.hs | 2 +- agora-test/Spec/Effect/GovernorMutation.hs | 75 +++++++++--- 3 files changed, 125 insertions(+), 63 deletions(-) diff --git a/agora-sample/Sample/Effect/GovernorMutation.hs b/agora-sample/Sample/Effect/GovernorMutation.hs index b71457f..bbaf698 100644 --- a/agora-sample/Sample/Effect/GovernorMutation.hs +++ b/agora-sample/Sample/Effect/GovernorMutation.hs @@ -1,9 +1,14 @@ module Sample.Effect.GovernorMutation ( - validContext, + mkEffectTransaction, effectValidator, effectValidatorAddress, effectValidatorHash, atAssetClass, + govRef, + effectRef, + invalidNewGovernorDatum, + validNewGovernorDatum, + mkEffectDatum, ) where import Agora.Effect.GovernorMutation ( @@ -11,14 +16,13 @@ import Agora.Effect.GovernorMutation ( mutateGovernorValidator, ) import Agora.Governor (GovernorDatum (..)) -import Agora.Proposal (ProposalId (..)) +import Agora.Proposal (ProposalId (..), ProposalThresholds (..)) import Plutarch.Api.V1 (mkValidator, validatorHash) +import Plutarch.SafeMoney (Tagged (Tagged)) import Plutus.V1.Ledger.Address (scriptHashAddress) import Plutus.V1.Ledger.Api ( Address, Datum (..), - ScriptContext (..), - ScriptPurpose (Spending), ToData (..), TokenName (..), TxInInfo (..), @@ -42,15 +46,19 @@ import Sample.Shared ( ) import Test.Util (datumPair, toDatumHash) +-- | The effect validator instance. effectValidator :: Validator effectValidator = mkValidator $ mutateGovernorValidator governor +-- | The hash of the validator instance. effectValidatorHash :: ValidatorHash effectValidatorHash = validatorHash effectValidator +-- | The address of the validator. effectValidatorAddress :: Address effectValidatorAddress = scriptHashAddress effectValidatorHash +-- | The assetclass of the authority token. atAssetClass :: AssetClass atAssetClass = assetClass authorityTokenSymbol tokenName where @@ -58,20 +66,36 @@ atAssetClass = assetClass authorityTokenSymbol tokenName ValidatorHash bs = effectValidatorHash tokenName = TokenName bs -validContext :: ScriptContext -validContext = +-- | The mock reference of the governor state UTXO. +govRef :: TxOutRef +govRef = TxOutRef "614481d2159bfb72350222d61fce17e548e0fc00e5a1f841ff1837c431346ce7" 1 + +-- | The mock reference of the effect UTXO. +effectRef :: TxOutRef +effectRef = TxOutRef "c31164dc11835de7eb6187f67d0e1a19c1dfc0786a456923eef5043189cdb578" 1 + +-- | The input effect datum in 'mkEffectTransaction'. +mkEffectDatum :: GovernorDatum -> MutateGovernorDatum +mkEffectDatum newGovDatum = + MutateGovernorDatum + { governorRef = govRef + , newDatum = newGovDatum + } + +{- | Given the new governor state, create an effect to update the governor's state. + + Note that the transaction is valid only if the given new datum is valid. +-} +mkEffectTransaction :: GovernorDatum -> TxInfo +mkEffectTransaction newGovDatum = let gst = Value.assetClassValue govAssetClass 1 at = Value.assetClassValue atAssetClass 1 + -- One authority token is burnt in the process. burnt = Value.assetClassValue atAssetClass (-1) -- - governorInputRef :: TxOutRef - governorInputRef = TxOutRef "614481d2159bfb72350222d61fce17e548e0fc00e5a1f841ff1837c431346ce7" 1 - - -- - governorInputDatum' :: GovernorDatum governorInputDatum' = GovernorDatum @@ -92,21 +116,14 @@ validContext = -- The effect should update 'nextProposalId' effectInputDatum' :: MutateGovernorDatum - effectInputDatum' = - MutateGovernorDatum - { governorRef = governorInputRef - , newDatum = - governorInputDatum' - { nextProposalId = ProposalId 42 - } - } + effectInputDatum' = mkEffectDatum newGovDatum effectInputDatum :: Datum effectInputDatum = Datum $ toBuiltinData effectInputDatum' effectInput :: TxOut effectInput = TxOut { txOutAddress = effectValidatorAddress - , txOutValue = at + , txOutValue = at -- The effect carry an authotity token. , txOutDatumHash = Just $ toDatumHash effectInputDatum } @@ -123,27 +140,35 @@ validContext = , txOutValue = mconcat [gst, minAda] , txOutDatumHash = Just $ toDatumHash governorOutputDatum } - - -- - - ownInputRef :: TxOutRef - ownInputRef = TxOutRef "c31164dc11835de7eb6187f67d0e1a19c1dfc0786a456923eef5043189cdb578" 1 - in ScriptContext - { scriptContextPurpose = Spending ownInputRef - , scriptContextTxInfo = - TxInfo - { txInfoInputs = - [ TxInInfo ownInputRef effectInput - , TxInInfo governorInputRef governorInput - ] - , txInfoOutputs = [governorOutput] - , txInfoFee = Value.singleton "" "" 2 - , txInfoMint = burnt - , txInfoDCert = [] - , txInfoWdrl = [] - , txInfoValidRange = Interval.always - , txInfoSignatories = [signer] - , txInfoData = datumPair <$> [governorInputDatum, governorOutputDatum, effectInputDatum] - , txInfoId = "4dae3806cc69615b721d52ed09b758f43f25a8f39b7934d6b28514caf71f5f7b" - } + in TxInfo + { txInfoInputs = + [ TxInInfo effectRef effectInput + , TxInInfo govRef governorInput + ] + , txInfoOutputs = [governorOutput] + , txInfoFee = Value.singleton "" "" 2 + , txInfoMint = burnt + , txInfoDCert = [] + , txInfoWdrl = [] + , txInfoValidRange = Interval.always + , txInfoSignatories = [signer] + , txInfoData = datumPair <$> [governorInputDatum, governorOutputDatum, effectInputDatum] + , txInfoId = "4dae3806cc69615b721d52ed09b758f43f25a8f39b7934d6b28514caf71f5f7b" } + +validNewGovernorDatum :: GovernorDatum +validNewGovernorDatum = + GovernorDatum + { proposalThresholds = defaultProposalThresholds + , nextProposalId = ProposalId 42 + } + +invalidNewGovernorDatum :: GovernorDatum +invalidNewGovernorDatum = + GovernorDatum + { proposalThresholds = + defaultProposalThresholds + { countVoting = Tagged (-1) + } + , nextProposalId = ProposalId 42 + } diff --git a/agora-sample/Sample/Governor.hs b/agora-sample/Sample/Governor.hs index ff5f0bf..1704035 100644 --- a/agora-sample/Sample/Governor.hs +++ b/agora-sample/Sample/Governor.hs @@ -502,7 +502,7 @@ mintGATs = The effect script should carry an valid tagged authority token, and said token will be burnt in the transaction. We use 'noOpValidator' here as a mock effect, so no actual change is done to the governor state. - TODO: use 'mutateGovernorEffect' as the mock effect in the future. + TODO: use 'Agora.Effect.GovernorMutation.mutateGovernorEffect' as the mock effect in the future. The governor will ensure the new governor state is valid. -} diff --git a/agora-test/Spec/Effect/GovernorMutation.hs b/agora-test/Spec/Effect/GovernorMutation.hs index 354bf0b..207e0b1 100644 --- a/agora-test/Spec/Effect/GovernorMutation.hs +++ b/agora-test/Spec/Effect/GovernorMutation.hs @@ -1,30 +1,67 @@ module Spec.Effect.GovernorMutation (tests) where -import Agora.Effect.GovernorMutation (MutateGovernorDatum (..), mutateGovernorValidator) -import Agora.Governor (GovernorDatum (..)) +import Agora.Effect.GovernorMutation (mutateGovernorValidator) +import Agora.Governor (GovernorDatum (..), GovernorRedeemer (MutateGovernor)) +import Agora.Governor.Scripts (governorValidator) import Agora.Proposal (ProposalId (..)) -import Plutus.V1.Ledger.Api (TxOutRef (..)) -import Sample.Effect.GovernorMutation (validContext) -import Sample.Shared (defaultProposalThresholds, governor) +import Plutus.V1.Ledger.Api (ScriptContext (ScriptContext), ScriptPurpose (Spending)) +import Sample.Effect.GovernorMutation ( + effectRef, + govRef, + invalidNewGovernorDatum, + mkEffectDatum, + mkEffectTransaction, + validNewGovernorDatum, + ) +import Sample.Shared qualified as Shared import Test.Tasty (TestTree, testGroup) -import Test.Util (effectSucceedsWith) +import Test.Util (effectFailsWith, effectSucceedsWith, validatorFailsWith, validatorSucceedsWith) tests :: [TestTree] tests = [ testGroup "validator" - [ effectSucceedsWith - "Simple" - (mutateGovernorValidator governor) - ( MutateGovernorDatum - { governorRef = TxOutRef "614481d2159bfb72350222d61fce17e548e0fc00e5a1f841ff1837c431346ce7" 1 - , newDatum = - GovernorDatum - { nextProposalId = ProposalId 42 - , proposalThresholds = defaultProposalThresholds - } - } - ) - validContext + [ testGroup + "valid new governor datum" + [ validatorSucceedsWith + "governor" + (governorValidator Shared.governor) + ( GovernorDatum + { proposalThresholds = Shared.defaultProposalThresholds + , nextProposalId = ProposalId 0 + } + ) + MutateGovernor + ( ScriptContext + (mkEffectTransaction validNewGovernorDatum) + (Spending govRef) + ) + , effectSucceedsWith + "effect" + (mutateGovernorValidator Shared.governor) + (mkEffectDatum validNewGovernorDatum) + (ScriptContext (mkEffectTransaction validNewGovernorDatum) (Spending effectRef)) + ] + , testGroup + "invalid new governor datum" + [ validatorFailsWith + "governor" + (governorValidator Shared.governor) + ( GovernorDatum + { proposalThresholds = Shared.defaultProposalThresholds + , nextProposalId = ProposalId 0 + } + ) + MutateGovernor + ( ScriptContext + (mkEffectTransaction invalidNewGovernorDatum) + (Spending govRef) + ) + , effectFailsWith + "effect" + (mutateGovernorValidator Shared.governor) + (mkEffectDatum validNewGovernorDatum) + (ScriptContext (mkEffectTransaction invalidNewGovernorDatum) (Spending effectRef)) + ] ] ] From 1ba11bc23a3b53ec4f6c065da584f9b249e2cfa0 Mon Sep 17 00:00:00 2001 From: fanghr Date: Thu, 19 May 2022 21:03:42 +0800 Subject: [PATCH 20/23] add missing docs && consistent naming --- agora/Agora/Effect/GovernorMutation.hs | 30 +++++++++++++++----------- agora/Agora/Utils.hs | 1 + 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 8e7a9e9..4b929f2 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -138,16 +138,17 @@ instance PTryFrom PData (PAsData PMutateGovernorDatum) where -} mutateGovernorValidator :: Governor -> ClosedTerm PValidator mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) $ - \_gatCs (datum' :: Term _ PMutateGovernorDatum) _ txInfo' -> unTermCont $ do - datum <- tcont $ pletFields @'["newDatum", "governorRef"] datum' - txInfo <- tcont $ pletFields @'["mint", "inputs", "outputs", "datums"] txInfo' + \_gatCs (datum :: Term _ PMutateGovernorDatum) _ txInfo -> unTermCont $ do + datumF <- tcont $ pletFields @'["newDatum", "governorRef"] datum + txInfoF <- tcont $ pletFields @'["mint", "inputs", "outputs", "datums"] txInfo let mint :: Term _ (PBuiltinList _) - mint = pto $ pto $ pto $ pfromData txInfo.mint + mint = pto $ pto $ pto $ pfromData txInfoF.mint tcassert "Nothing should be minted/burnt other than GAT" $ plength # mint #== 1 + -- Only two script inputs are alloed: one from the effect, one from the governor. tcassert "Only self and governor script inputs are allowed" $ pfoldr # phoistAcyclic @@ -159,9 +160,10 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) count ) # (0 :: Term _ PInteger) - # pfromData txInfo.inputs + # pfromData txInfoF.inputs #== 2 + -- Find the governor input by looking for GST. let inputWithGST = mustBePJust # "Governor input not found" #$ pfind # phoistAcyclic @@ -169,18 +171,20 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) let value = pfield @"value" #$ pfield @"resolved" # inInfo in gstValueOf # value #== 1 ) - # pfromData txInfo.inputs + # pfromData txInfoF.inputs govInInfo <- tcont $ pletFields @'["outRef", "resolved"] $ inputWithGST + -- The effect can only modify the governor UTXO referenced in the datum. tcassert "Can only modify the pinned governor" $ - govInInfo.outRef #== datum.governorRef + govInInfo.outRef #== datumF.governorRef - tcassert "Only governor ouput is allowed" $ - plength # pfromData txInfo.outputs #== 1 + -- The transaction can only have one output, which should be sent to the governor. + tcassert "Only governor output is allowed" $ + plength # pfromData txInfoF.outputs #== 1 let govAddress = pfield @"address" #$ govInInfo.resolved - govOutput' = pfromData $ phead # pfromData txInfo.outputs + govOutput' = pfromData $ phead # pfromData txInfoF.outputs govOutput <- tcont $ pletFields @'["address", "value", "datumHash"] govOutput' @@ -195,13 +199,15 @@ mutateGovernorValidator gov = makeEffect (authorityTokenSymbolFromGovernor gov) governorOutputDatum = pfromData @PGovernorDatum $ mustBePJust # "Governor output datum not found" - #$ ptryFindDatum # governorOutputDatumHash # txInfo.datums + #$ ptryFindDatum # governorOutputDatumHash # txInfoF.datums - tcassert "Unexpected governor datum" $ datum.newDatum #== governorOutputDatum + -- Ensure the output governor datum is what we want. + tcassert "Unexpected governor datum" $ datumF.newDatum #== governorOutputDatum tcassert "New governor datum should be valid" $ governorDatumValid # governorOutputDatum return $ popaque $ pconstant () where + -- Get the amount of GST in the a given value. gstValueOf :: Term s (PValue :--> PInteger) gstValueOf = phoistAcyclic $ plam $ \v -> pvalueOf # v # pconstant cs # pconstant tn where diff --git a/agora/Agora/Utils.hs b/agora/Agora/Utils.hs index 160d277..63cf022 100644 --- a/agora/Agora/Utils.hs +++ b/agora/Agora/Utils.hs @@ -620,6 +620,7 @@ scriptHashFromAddress = phoistAcyclic $ PScriptCredential ((pfield @"_0" #) -> h) -> pcon $ PJust h _ -> pcon PNothing +-- | Return true if the given address is a script address. isScriptAddress :: Term s (PAddress :--> PBool) isScriptAddress = phoistAcyclic $ plam $ \addr -> From 1b9aaf111d1b248f4064b631afb954c9ce45e104 Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 18:22:33 +0800 Subject: [PATCH 21/23] add `isPubKey` && utilize it in the `TreasuryWithdrawal` effect --- agora/Agora/Effect/TreasuryWithdrawal.hs | 10 ++-------- agora/Agora/Utils.hs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/agora/Agora/Effect/TreasuryWithdrawal.hs b/agora/Agora/Effect/TreasuryWithdrawal.hs index 6bb2851..5bf451c 100644 --- a/agora/Agora/Effect/TreasuryWithdrawal.hs +++ b/agora/Agora/Effect/TreasuryWithdrawal.hs @@ -18,7 +18,7 @@ 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 Agora.Utils (findTxOutByTxOutRef, isPubKey, paddValue, tcassert, tclet, tcmatch) import Plutarch.Api.V1 ( PCredential (..), PTuple, @@ -140,12 +140,6 @@ treasuryWithdrawalValidator currSymbol = makeEffect currSymbol $ 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) @@ -165,7 +159,7 @@ treasuryWithdrawalValidator currSymbol = makeEffect currSymbol $ ( \((pfield @"_0" #) . pfromData -> cred) -> cred #== pfield @"credential" # effInput.address #|| pelem # cred # datum.treasuries - #|| isPubkey # pfromData cred + #|| isPubKey # pfromData cred ) # inputValues diff --git a/agora/Agora/Utils.hs b/agora/Agora/Utils.hs index 63cf022..1a6424b 100644 --- a/agora/Agora/Utils.hs +++ b/agora/Agora/Utils.hs @@ -60,6 +60,7 @@ module Agora.Utils ( pmergeBy, phalve, isScriptAddress, + isPubKey, ) where -------------------------------------------------------------------------------- @@ -623,10 +624,15 @@ scriptHashFromAddress = phoistAcyclic $ -- | Return true if the given address is a script address. isScriptAddress :: Term s (PAddress :--> PBool) isScriptAddress = phoistAcyclic $ - plam $ \addr -> - pmatch (pfromData $ pfield @"credential" # addr) $ \case - PScriptCredential _ -> pconstant True - _ -> pconstant False + plam $ \addr -> pnot #$ isPubKey #$ pfromData $ pfield @"credential" # addr + +-- | Return true if the given credential is a pub-key-hash. +isPubKey :: Term s (PCredential :--> PBool) +isPubKey = phoistAcyclic $ + plam $ \cred -> + pmatch cred $ \case + PScriptCredential _ -> pconstant False + _ -> pconstant True -- | Find all TxOuts sent to an Address findOutputsToAddress :: Term s (PBuiltinList (PAsData PTxOut) :--> PAddress :--> PBuiltinList (PAsData PTxOut)) From 65dc55f37fae009f384782ae13d28d7553c870e2 Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 19:10:34 +0800 Subject: [PATCH 22/23] better naming --- agora-sample/Sample/Effect/GovernorMutation.hs | 6 +++--- agora-test/Spec/Effect/GovernorMutation.hs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/agora-sample/Sample/Effect/GovernorMutation.hs b/agora-sample/Sample/Effect/GovernorMutation.hs index bbaf698..7d41cb7 100644 --- a/agora-sample/Sample/Effect/GovernorMutation.hs +++ b/agora-sample/Sample/Effect/GovernorMutation.hs @@ -1,5 +1,5 @@ module Sample.Effect.GovernorMutation ( - mkEffectTransaction, + mkEffectTxInfo, effectValidator, effectValidatorAddress, effectValidatorHash, @@ -86,8 +86,8 @@ mkEffectDatum newGovDatum = Note that the transaction is valid only if the given new datum is valid. -} -mkEffectTransaction :: GovernorDatum -> TxInfo -mkEffectTransaction newGovDatum = +mkEffectTxInfo :: GovernorDatum -> TxInfo +mkEffectTxInfo newGovDatum = let gst = Value.assetClassValue govAssetClass 1 at = Value.assetClassValue atAssetClass 1 diff --git a/agora-test/Spec/Effect/GovernorMutation.hs b/agora-test/Spec/Effect/GovernorMutation.hs index 207e0b1..1436b5a 100644 --- a/agora-test/Spec/Effect/GovernorMutation.hs +++ b/agora-test/Spec/Effect/GovernorMutation.hs @@ -10,7 +10,7 @@ import Sample.Effect.GovernorMutation ( govRef, invalidNewGovernorDatum, mkEffectDatum, - mkEffectTransaction, + mkEffectTxInfo, validNewGovernorDatum, ) import Sample.Shared qualified as Shared @@ -24,7 +24,7 @@ tests = [ testGroup "valid new governor datum" [ validatorSucceedsWith - "governor" + "governor validator should pass" (governorValidator Shared.governor) ( GovernorDatum { proposalThresholds = Shared.defaultProposalThresholds @@ -33,19 +33,19 @@ tests = ) MutateGovernor ( ScriptContext - (mkEffectTransaction validNewGovernorDatum) + (mkEffectTxInfo validNewGovernorDatum) (Spending govRef) ) , effectSucceedsWith - "effect" + "effect validator should pass" (mutateGovernorValidator Shared.governor) (mkEffectDatum validNewGovernorDatum) - (ScriptContext (mkEffectTransaction validNewGovernorDatum) (Spending effectRef)) + (ScriptContext (mkEffectTxInfo validNewGovernorDatum) (Spending effectRef)) ] , testGroup "invalid new governor datum" [ validatorFailsWith - "governor" + "governor validator should fail" (governorValidator Shared.governor) ( GovernorDatum { proposalThresholds = Shared.defaultProposalThresholds @@ -54,14 +54,14 @@ tests = ) MutateGovernor ( ScriptContext - (mkEffectTransaction invalidNewGovernorDatum) + (mkEffectTxInfo invalidNewGovernorDatum) (Spending govRef) ) , effectFailsWith - "effect" + "effect validator should fail" (mutateGovernorValidator Shared.governor) (mkEffectDatum validNewGovernorDatum) - (ScriptContext (mkEffectTransaction invalidNewGovernorDatum) (Spending effectRef)) + (ScriptContext (mkEffectTxInfo invalidNewGovernorDatum) (Spending effectRef)) ] ] ] From f4f4ed0b2178d37b51ed9c7846d9b91552f02409 Mon Sep 17 00:00:00 2001 From: fanghr Date: Tue, 24 May 2022 01:27:04 +0800 Subject: [PATCH 23/23] fix a typo --- agora/Agora/Effect/GovernorMutation.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agora/Agora/Effect/GovernorMutation.hs b/agora/Agora/Effect/GovernorMutation.hs index 4b929f2..fb28e33 100644 --- a/agora/Agora/Effect/GovernorMutation.hs +++ b/agora/Agora/Effect/GovernorMutation.hs @@ -116,7 +116,7 @@ instance PTryFrom PData (PAsData PMutateGovernorDatum) where {- | Validator for the governor mutation effect. This effect is implemented using the 'Agora.Effect.makeEffect' wrapper, - meaning that the burning of GAT is checked in the said wrapper. + meaning that the burning of GAT is checked in said wrapper. In order to locate the governor, the validator is parametrized with a 'Agora.Governor.Governor'.