From 2fda54dcafa5924538d741018acf0c227764cc17 Mon Sep 17 00:00:00 2001 From: Emily Martins Date: Fri, 13 May 2022 15:28:20 +0200 Subject: [PATCH 01/19] add safety pool draft document --- docs/tech-design/safety-pool.md | 86 +++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 docs/tech-design/safety-pool.md diff --git a/docs/tech-design/safety-pool.md b/docs/tech-design/safety-pool.md new file mode 100644 index 0000000..962dfc0 --- /dev/null +++ b/docs/tech-design/safety-pool.md @@ -0,0 +1,86 @@ +# Safety Pool functionality for the Staking Pool + +| Specification | Implementation | Last revision | +|:-----------:|:-----------:|:-------------:| +| WIP | WIP | 2022-05-13 | + +--- + +**Specification ownership:** [Emily Martins] + +**Authors**: + +- [Emily Martins] + +**Implementation ownership:** [Emily Martins] + +[Emily Martins]: https://github.com/emiflake + +[Jack Hodgkinson]: https://github.com/jhodgdev + +**Current status:** Initial draft in need of editing and review from [Jack Hodgkinson]. + +--- + +In order for Agora’s StakingPool to act as a *SafetyPool,* it needs to be able to support a workflow for slashing staked governance tokens to act as a safety mechanism. This document outlines the changes that Agora needs to receive in order to support this. + +### Motivation + +In the event of a protocol suffering loss of funds through a [shortfall event](https://docs.aave.com/aavenomics/safety-module#shortfall-events), slashing a percentage of locked GT can be used to attempt recovery. Ultimately, doing this is beneficial for the stakeholders because it allows the protocol to recover and eventually benefits them as well (even though they bear the initial cost). Striking a balance (in the form of the right % slashed) is important in order for the stakeholders to be willing to partake in their sacrifice. + +## Slashing functionality + +In order to allow an admin to withdraw a set percentage of the amount staked, we create a new effect. + +### The `SlashEffect` validator: + +- Mint a `SlashToken` and send it to a validator ("the `Slash` validator") with a datum encoding the details of the slashing. + + The `SlashDatum` may look like this: + + ```haskell + data SlashDatum = SlashDatum + { -- | Identify which slash event this datum belongs to. + slashId :: Integer + , -- | Represents how much is to be slashed (as a ratio of the full staked amount). + slashPercentage :: Rational + , -- | During what time is using this datum for Stakes. + slashTimeRange :: POSIXTimeRange + } + ``` + +- This information must be encoded in the datum the effect is given to, so ultimately encoded in the proposal. + The datum that is passed to the effect ought to contain this `SlashDatum` in some way. + +### The `SlashToken` policy: + +- Exclusively check for GAT burn. Delegated checking goes to the `SlashEffect` validator. +- This `SlashToken` policy needs to be "known" by the Stake validator, in order to allow transactions to take place. + +### The `Slash` validator: + +- This validator allows spending of a percentage of a `Stake`s GT, provided a few conditions are met: + - The `SlashToken` is present + - The slash ID is tagged onto the new stake datum + - The time range encoded in the `SlashDatum` includes the `txInfoValidRange`. +- What is done with the recovered GT is up to the admin to determine. Q: Is this what we want? + +Finally, we need to change `StakeDatum` to encode a list of slash IDs in order to prevent slashes happening twice. + +--- + +## Preventing opting out of slashing + +If this is where we call it quits, then users will each be able to just opt-out of this slashing event. GT holders are individually incentivized to do so, because it means they don’t forfeit their assets. Obviously, then, in order to make the safety pool work at all, we need to prevent this. + +### Time-locking stakes + +A simple solution is time-locking stake withdrawal upon any interaction with it for a set amount of days. This ought to be long enough for a full proposal to go through, but not too long for it to become annoying for users of the staking pool. This presents a big drawback in general for all stakeholders as their assets are actually locked even though no slashing necessarily will ever happen. However, this is also a very simple solution for solving the opt-out problem. It should be something we can enable/disable after the fact, as well as in initial configuration. + +### CIP-31 dependent central lock + +Provided we have reference-inputs ([CIP-31](https://cips.cardano.org/cips/cip31/)) by the time we implement this, an alternative approach is viable: + +- We create a script that manages a `StakeLockDatum`. The script (”`StakeLock` validator”) encodes whether or not `Stake`s are allowed to withdraw. Using reference-inputs, we are able to witness this datum without consuming it, allowing us to lose no throughput on withdrawals, while maintaining a centralized lock. +- The `StakeLock` validator can only set to lock through an admin-controlled multisig. The admin multisig should do this in the event that a proposal has been created for the shortfall event. +- The `StakeLock` utxo can be consumed by anyone after a set period of time, unlocking it again. This prevents admins from abusing the locking for whatever reason. From a174e11c5bceae3763e07e778bc004923a1e3ca6 Mon Sep 17 00:00:00 2001 From: Emily Martins Date: Tue, 17 May 2022 15:15:57 +0200 Subject: [PATCH 02/19] apply suggestions --- docs/tech-design/safety-pool.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/tech-design/safety-pool.md b/docs/tech-design/safety-pool.md index 962dfc0..40aedcc 100644 --- a/docs/tech-design/safety-pool.md +++ b/docs/tech-design/safety-pool.md @@ -18,15 +18,15 @@ [Jack Hodgkinson]: https://github.com/jhodgdev -**Current status:** Initial draft in need of editing and review from [Jack Hodgkinson]. +**Current status:** Early revision of the document with low technical specification due to feature being further in the timeline. Has been reviewed by [Jack Hodgkinson]. --- -In order for Agora’s StakingPool to act as a *SafetyPool,* it needs to be able to support a workflow for slashing staked governance tokens to act as a safety mechanism. This document outlines the changes that Agora needs to receive in order to support this. +In order for Agora’s staking pool to act as a *safety pool*, it needs to be able to support a workflow for slashing staked governance tokens (GT) to act as a safety mechanism. (Note: we use "slashing" to mean taking away some token from a particular user.) This document outlines the changes that need to be made to Agora in order to support this. ### Motivation -In the event of a protocol suffering loss of funds through a [shortfall event](https://docs.aave.com/aavenomics/safety-module#shortfall-events), slashing a percentage of locked GT can be used to attempt recovery. Ultimately, doing this is beneficial for the stakeholders because it allows the protocol to recover and eventually benefits them as well (even though they bear the initial cost). Striking a balance (in the form of the right % slashed) is important in order for the stakeholders to be willing to partake in their sacrifice. +In the event of a protocol suffering loss of funds through a [shortfall event](https://docs.aave.com/aavenomics/safety-module#shortfall-events), slashing a percentage of locked GT can be used to attempt a recovery. Ultimately, doing this is beneficial for the stakeholders because it allows the protocol to recover and eventually benefits them as well (even though they bear the initial cost). Striking a balance (in the form of the right percentage slashed) is important in order for stakeholders to want to vote in favour of a proposal that results in such a slashing. ## Slashing functionality @@ -36,7 +36,7 @@ In order to allow an admin to withdraw a set percentage of the amount staked, we - Mint a `SlashToken` and send it to a validator ("the `Slash` validator") with a datum encoding the details of the slashing. - The `SlashDatum` may look like this: + The `SlashDatum` could look like this: ```haskell data SlashDatum = SlashDatum @@ -44,13 +44,12 @@ In order to allow an admin to withdraw a set percentage of the amount staked, we slashId :: Integer , -- | Represents how much is to be slashed (as a ratio of the full staked amount). slashPercentage :: Rational - , -- | During what time is using this datum for Stakes. + , -- | The time range that must contain `txInfoValidRange` in order to slash. slashTimeRange :: POSIXTimeRange } ``` -- This information must be encoded in the datum the effect is given to, so ultimately encoded in the proposal. - The datum that is passed to the effect ought to contain this `SlashDatum` in some way. +- `SlashDatum` must, in some way, be present in the datum that is passed to the `SlashEffect` validator. This means that the `ProposalDatum` also indirectly contains `SlashDatum`. ### The `SlashToken` policy: @@ -71,11 +70,11 @@ Finally, we need to change `StakeDatum` to encode a list of slash IDs in order t ## Preventing opting out of slashing -If this is where we call it quits, then users will each be able to just opt-out of this slashing event. GT holders are individually incentivized to do so, because it means they don’t forfeit their assets. Obviously, then, in order to make the safety pool work at all, we need to prevent this. +If this is where we call it quits, then users will each be able to just opt-out of this slashing event. GT holders are individually incentivized to do so, because it means they don’t forfeit their own assets. Obviously, then, in order to make the safety pool work at all, we need to prevent this. ### Time-locking stakes -A simple solution is time-locking stake withdrawal upon any interaction with it for a set amount of days. This ought to be long enough for a full proposal to go through, but not too long for it to become annoying for users of the staking pool. This presents a big drawback in general for all stakeholders as their assets are actually locked even though no slashing necessarily will ever happen. However, this is also a very simple solution for solving the opt-out problem. It should be something we can enable/disable after the fact, as well as in initial configuration. +A simple solution is time-locking stake withdrawal upon any interaction with it for a set amount of days. This ought to be long enough for a full proposal to go through, but not too long for it to become annoying for users of the staking pool. This presents a big drawback in general for all stakeholders as their assets are actually locked even though no slashing necessarily will ever happen. However, this is also a very simple solution for solving the opt-out problem. It should be something we can enable or disable after the fact, as well as in initial configuration. ### CIP-31 dependent central lock From b4b85e623f7c6f9d1407830d6f13968d10cd30c4 Mon Sep 17 00:00:00 2001 From: Emily Martins Date: Wed, 18 May 2022 16:28:26 +0200 Subject: [PATCH 03/19] add size benchmarking for scripts to `agora-bench` --- agora-bench/Bench.hs | 33 +++++++++++++++++++++++++++++++++ agora-bench/Main.hs | 42 +++++++++++++++++++++++++++++++++++------- agora.cabal | 5 ++++- 3 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 agora-bench/Bench.hs diff --git a/agora-bench/Bench.hs b/agora-bench/Bench.hs new file mode 100644 index 0000000..0539f24 --- /dev/null +++ b/agora-bench/Bench.hs @@ -0,0 +1,33 @@ +module Bench (Benchmark (..), benchmarkSize) where + +import Codec.Serialise (serialise) +import Data.ByteString.Lazy qualified as LBS +import Data.ByteString.Short qualified as SBS +import Data.Set (Set) +import Data.Set qualified as Set +import Data.Text (Text) +import Plutus.V1.Ledger.Scripts qualified as Plutus + +-------------------------------------------------------------------------------- + +-- | Represents the benchmark of a plutus script. +data Benchmark = Benchmark + { name :: Text + -- ^ Human readable name describing script. + , size :: Int + -- ^ The on-chain size of a script. + } + deriving stock (Show, Eq, Ord) + +-- | Create a benchmark containing only the size of the script. +benchmarkSize :: Text -> Plutus.Script -> Set Benchmark +benchmarkSize name script = + Set.singleton $ + Benchmark + { name = name + , size = scriptSize script + } + +-- | Compute the size of a script on-chain. +scriptSize :: Plutus.Script -> Int +scriptSize = SBS.length . SBS.toShort . LBS.toStrict . serialise diff --git a/agora-bench/Main.hs b/agora-bench/Main.hs index 62046f7..95307ca 100644 --- a/agora-bench/Main.hs +++ b/agora-bench/Main.hs @@ -1,14 +1,42 @@ module Main (main) where +import Agora.AuthorityToken (authorityTokenPolicy) +import Agora.Effect.TreasuryWithdrawal (treasuryWithdrawalValidator) +import Agora.Governor (Governor (..)) +import Agora.Governor.Scripts (governorPolicy, governorValidator) +import Agora.Proposal.Scripts (proposalPolicy, proposalValidator) +import Agora.Stake.Scripts (stakePolicy, stakeValidator) +import Agora.Treasury (treasuryValidator) +import Bench +import Data.Foldable (for_) +import Plutus.V1.Ledger.Api (CurrencySymbol) +import Sample.Shared import Prelude -------------------------------------------------------------------------------- --------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- - main :: IO () -main = pure () +main = do + let benchmarks = + mconcat + [ -- GATs + benchmarkSize "authorityTokenPolicy" $ compile $ authorityTokenPolicy authorityToken + , -- Governor + benchmarkSize "governorValidator" $ compile $ governorValidator governor + , benchmarkSize "governorPolicy" $ compile $ governorPolicy governor + , -- Stake + benchmarkSize "stakeValidator" $ compile $ stakeValidator stake + , benchmarkSize "stakePolicy" $ compile $ stakePolicy governor.gtClassRef + , -- Proposal + benchmarkSize "proposalValidator" $ compile $ proposalValidator proposal + , benchmarkSize "proposalPolicy" $ compile $ proposalPolicy govAssetClass + , -- Treasury + benchmarkSize "treasuryValidator" $ compile $ treasuryValidator gatCS + , -- Effect validators + benchmarkSize "treasuryWithdrawalValidator" $ compile $ treasuryWithdrawalValidator gatCS + ] + + for_ benchmarks print + +gatCS :: CurrencySymbol +gatCS = "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" -- arbitrary CS diff --git a/agora.cabal b/agora.cabal index c8ce871..6d34d9c 100644 --- a/agora.cabal +++ b/agora.cabal @@ -198,8 +198,11 @@ benchmark agora-bench import: lang, deps hs-source-dirs: agora-bench main-is: Main.hs + other-modules: Bench type: exitcode-stdio-1.0 - build-depends: agora + build-depends: + , agora + , agora-sample executable agora-purescript-bridge import: lang, deps, exe-opts From 80496430ab05ea9061b29ddd4376ec067da731b5 Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 00:17:58 +0800 Subject: [PATCH 04/19] add uitls to update a `PMap` on-chain --- agora/Agora/Utils.hs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/agora/Agora/Utils.hs b/agora/Agora/Utils.hs index 558bc13..c594200 100644 --- a/agora/Agora/Utils.hs +++ b/agora/Agora/Utils.hs @@ -40,6 +40,8 @@ module Agora.Utils ( pmsortBy, pmsort, pnubSort, + pupdate, + pmapMaybe, -- * Functions which should (probably) not be upstreamed anyOutput, @@ -286,6 +288,42 @@ pmapUnionWith = phoistAcyclic $ # ys pure $ pcon (PMap $ pconcat # ls # rs) +-- | A special version of `pmap` which allows list elements to be thrown out. +pmapMaybe :: forall s a list. (PIsListLike list a) => Term s ((a :--> PMaybe a) :--> list a :--> list a) +pmapMaybe = phoistAcyclic $ + pfix #$ plam $ \self f l -> pif (pnull # l) pnil $ + unTermCont $ do + x <- tclet $ phead # l + xs <- tclet $ ptail # l + + pure $ + pmatch (f # x) $ \case + PJust ux -> pcons # ux #$ self # f # xs + _ -> self # f # xs + +-- | / O(n) /. Update the value at a given key in a `PMap`, have the same functionalities as 'Data.Map.update'. +pupdate :: forall s k v. (PIsData k, PIsData v) => Term s ((v :--> PMaybe v) :--> k :--> PMap k v :--> PMap k v) +pupdate = phoistAcyclic $ + plam $ \f (pdata -> tk) (pto -> (ps :: Term _ (PBuiltinList _))) -> + pcon $ + PMap $ + pmapMaybe + # plam + ( \kv -> + let k = pfstBuiltin # kv + v = pfromData $ psndBuiltin # kv + in pif + (k #== tk) + -- 'PBuiltinPair' doesn't have 'PFunctor', so: + ( pmatch (f # v) $ + \case + PJust uv -> pcon $ PJust $ ppairDataBuiltin # k # pdata uv + _ -> pcon PNothing + ) + (pcon $ PJust kv) + ) + # ps + -- | Add two 'PValue's together. paddValue :: forall s. Term s (PValue :--> PValue :--> PValue) paddValue = phoistAcyclic $ From 11743e0aace5c5e5a5545075cb0fdbf1b2e8661e Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 16:49:55 +0800 Subject: [PATCH 05/19] `PEq` instances for a bunch of on-chain structures --- agora/Agora/Proposal.hs | 4 ++-- agora/Agora/Stake.hs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/agora/Agora/Proposal.hs b/agora/Agora/Proposal.hs index 794ea0a..3c82fe3 100644 --- a/agora/Agora/Proposal.hs +++ b/agora/Agora/Proposal.hs @@ -303,7 +303,7 @@ data PProposalStatus (s :: S) deriving anyclass (Generic) deriving anyclass (PIsDataRepr) deriving - (PlutusType, PIsData) + (PlutusType, PIsData, PEq) via PIsDataReprInstances PProposalStatus instance PUnsafeLiftDecl PProposalStatus where type PLifted PProposalStatus = ProposalStatus @@ -361,7 +361,7 @@ newtype PProposalDatum (s :: S) = PProposalDatum deriving anyclass (Generic) deriving anyclass (PIsDataRepr) deriving - (PlutusType, PIsData, PDataFields) + (PlutusType, PIsData, PDataFields, PEq) via (PIsDataReprInstances PProposalDatum) -- TODO: Derive this. diff --git a/agora/Agora/Stake.hs b/agora/Agora/Stake.hs index a33f632..e62e20a 100644 --- a/agora/Agora/Stake.hs +++ b/agora/Agora/Stake.hs @@ -191,7 +191,7 @@ newtype PStakeDatum (s :: S) = PStakeDatum deriving anyclass (Generic) deriving anyclass (PIsDataRepr) deriving - (PlutusType, PIsData, PDataFields) + (PlutusType, PIsData, PDataFields, PEq) via (PIsDataReprInstances PStakeDatum) instance PTryFrom PData (PAsData PStakeDatum) where @@ -241,7 +241,7 @@ newtype PProposalLock (s :: S) = PProposalLock deriving anyclass (Generic) deriving anyclass (PIsDataRepr) deriving - (PlutusType, PIsData, PDataFields) + (PlutusType, PIsData, PDataFields, PEq) via (PIsDataReprInstances PProposalLock) deriving via From cf51d47a0d7d46cf3c2a4b5ba974fe20906f57fb Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 17:14:04 +0800 Subject: [PATCH 06/19] validation logic for voting --- agora/Agora/Proposal/Scripts.hs | 134 +++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index ce2dcbe..f09e90b 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -13,18 +13,24 @@ module Agora.Proposal.Scripts ( import Agora.Proposal ( PProposalDatum (PProposalDatum), PProposalRedeemer (..), + PProposalVotes (PProposalVotes), Proposal (governorSTAssetClass, stakeSTAssetClass), + ProposalStatus (VotingReady), ) import Agora.Record (mkRecordConstr, (.&), (.=)) -import Agora.Stake (findStakeOwnedBy) +import Agora.Stake (PProposalLock (..), PStakeDatum (..), findStakeOwnedBy) import Agora.Utils ( anyOutput, findTxOutByTxOutRef, getMintingPolicySymbol, + mustBePJust, + mustFindDatum', + pisJust, pisUniqBy, psymbolValueOf, ptokenSpent, ptxSignedBy, + pupdate, pvalueSpent, tcassert, tclet, @@ -39,6 +45,8 @@ import Plutarch.Api.V1 ( PValidator, ) import Plutarch.Api.V1.Extra (passetClass, passetClassValueOf) +import Plutarch.Map.Extra (plookup) +import Plutarch.SafeMoney (puntag) import Plutus.V1.Ledger.Value (AssetClass (AssetClass)) {- | Policy for Proposals. @@ -123,7 +131,7 @@ proposalValidator proposal = ctx <- tcont $ pletFields @'["txInfo", "purpose"] ctx' txInfo <- tclet $ pfromData ctx.txInfo PTxInfo txInfo' <- tcmatch txInfo - txInfoF <- tcont $ pletFields @'["inputs", "mint", "datums", "signatories"] txInfo' + txInfoF <- tcont $ pletFields @'["inputs", "outputs", "mint", "datums", "signatories"] txInfo' PSpending ((pfield @"_0" #) -> txOutRef) <- tcmatch $ pfromData ctx.purpose PJust txOut <- tcmatch $ findTxOutByTxOutRef # txOutRef # txInfoF.inputs @@ -165,7 +173,127 @@ proposalValidator proposal = pure $ pmatch proposalRedeemer $ \case - PVote _r -> popaque (pconstant ()) + PVote r -> unTermCont $ do + -- TODO: do we have to check the timing here? + tcassert "Input proposal must be in VotingReady state" $ + proposalF.status #== pconstant VotingReady + + -- Ensure the transaction is voting to a valid 'ResultTag'(outcome). + PProposalVotes voteMap <- tcmatch proposalF.votes + voteFor <- tclet $ pfromData $ pfield @"resultTag" # r + + tcassert "Invalid vote option" $ + pisJust #$ plookup # voteFor # voteMap + + -- Find the input stake, the amount of new votes should be the 'stakedAmount'. + let stakeInput = + pfield @"resolved" + #$ mustBePJust + # "Stake input not found" + #$ pfind + # plam + ( \(pfromData . (pfield @"value" #) . (pfield @"resolved" #) -> value) -> + passetClassValueOf # value # stakeSTAssetClass #== 1 + ) + # pfromData txInfoF.inputs + + stakeIn :: Term _ PStakeDatum + stakeIn = mustFindDatum' # (pfield @"datumHash" # stakeInput) # txInfoF.datums + + stakeInF <- tcont $ pletFields @'["stakedAmount", "lockedBy", "owner"] stakeIn + + -- Ensure that no lock with the current proposal id has been put on the stake. + tcassert "Cannot vote on the a proposal using the same stake twice" $ + pnot #$ pany + # plam + ( \((pfield @"proposalTag" #) . pfromData -> pid) -> + pid #== proposalF.proposalId + ) + # pfromData stakeInF.lockedBy + + -- TODO: maybe we can move this outside of the pmatch block. + -- Filter out own output with own address and PST. + ownOutput <- + tclet $ + mustBePJust # "Own output not found" #$ pfind + # plam + ( \input -> unTermCont $ do + inputF <- tcont $ pletFields @'["address", "value"] input + pure $ + inputF.address #== ownAddress + #&& psymbolValueOf # stCurrencySymbol # inputF.value #== 1 + ) + # pfromData txInfoF.outputs + + ownOutputF <- tcont $ pletFields @'["datumHash", "value"] ownOutput + + -- TODO: is this really necessary? + tcassert "Own output value should be correct" $ ownOutputF.value #== pdata txOutF.value + + let proposalOut :: Term _ PProposalDatum + proposalOut = mustFindDatum' # (pfield @"datumHash" # ownOutput) # txInfoF.datums + + let -- Update the vote counter of the proposal, and leave other stuff as is. + expectedNewVotes = pmatch (pfromData proposalF.votes) $ \(PProposalVotes m) -> + pcon $ + PProposalVotes $ + pupdate + # plam + ( \votes -> + pcon $ PJust $ votes + (puntag stakeInF.stakedAmount) + ) + # voteFor + # m + expectedProposalOut = + mkRecordConstr + PProposalDatum + ( #proposalId .= proposalF.proposalId + .& #effects .= proposalF.effects + .& #status .= proposalF.status + .& #cosigners .= proposalF.cosigners + .& #thresholds .= proposalF.thresholds + .& #votes .= pdata expectedNewVotes + ) + + tcassert "Invalid output proposal" $ proposalOut #== expectedProposalOut + + -- We validate the output stake datum here as well: We need the vote option + -- to create a proper 'ProposalLock'. However the vote option is encoded + -- in the proposal redeemer, which is invisible for the stake validator. + + let stakeOutput = + mustBePJust # "Stake output not found" + #$ pfind + # plam + ( \(pfromData . (pfield @"value" #) -> value) -> + passetClassValueOf # value # stakeSTAssetClass #== 1 + ) + # pfromData txInfoF.outputs + + stakeOut :: Term _ PStakeDatum + stakeOut = mustFindDatum' # (pfield @"datumHash" # stakeOutput) # txInfoF.datums + + let newProposalLock = + mkRecordConstr + PProposalLock + ( #vote .= pdata voteFor + .& #proposalTag .= proposalF.proposalId + ) + expectedProposalLocks = + pcons + # pdata newProposalLock + # pfromData stakeInF.lockedBy + expectedStakeOut = + mkRecordConstr + PStakeDatum + ( #stakedAmount .= stakeInF.stakedAmount + .& #owner .= stakeInF.owner + .& #lockedBy .= pdata expectedProposalLocks + ) + + tcassert "Output stake should be locked by the proposal" $ expectedStakeOut #== stakeOut + + pure $ popaque (pconstant ()) -------------------------------------------------------------------------- PCosign r -> unTermCont $ do newSigs <- tclet $ pfield @"newCosigners" # r From 92c64f8d7a2f6946bf3b02cf166dc48ce5f22ee5 Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 20:34:56 +0800 Subject: [PATCH 07/19] store timing config in the proposal datum .... and mock the value upon creation for now --- agora-sample/Sample/Governor.hs | 3 +++ agora-sample/Sample/Proposal.hs | 2 ++ agora-sample/Sample/Shared.hs | 13 +++++++++++++ agora-test/Spec/Proposal.hs | 3 ++- agora/Agora/Governor/Scripts.hs | 13 +++++++++++++ agora/Agora/Proposal.hs | 5 +++++ agora/Agora/Proposal/Scripts.hs | 3 +++ agora/Agora/Proposal/Time.hs | 28 +++++++++++++++++++++++++++- 8 files changed, 68 insertions(+), 2 deletions(-) diff --git a/agora-sample/Sample/Governor.hs b/agora-sample/Sample/Governor.hs index ff5f0bf..f1c8f38 100644 --- a/agora-sample/Sample/Governor.hs +++ b/agora-sample/Sample/Governor.hs @@ -73,6 +73,7 @@ import Sample.Shared ( gstUTXORef, minAda, proposalPolicySymbol, + proposalTimingConfig, proposalValidatorAddress, signer, signer2, @@ -234,6 +235,7 @@ createProposal = , cosigners = [signer] , thresholds = defaultProposalThresholds , votes = emptyVotesFor effects + , timingConfig = proposalTimingConfig } ) proposalOutput :: TxOut @@ -408,6 +410,7 @@ mintGATs = , cosigners = [signer, signer2] , thresholds = defaultProposalThresholds , votes = proposalVotes + , timingConfig = proposalTimingConfig } proposalInputDatum :: Datum proposalInputDatum = Datum $ toBuiltinData proposalInputDatum' diff --git a/agora-sample/Sample/Proposal.hs b/agora-sample/Sample/Proposal.hs index e778fca..7853606 100644 --- a/agora-sample/Sample/Proposal.hs +++ b/agora-sample/Sample/Proposal.hs @@ -74,6 +74,7 @@ proposalCreation = , cosigners = [signer] , thresholds = defaultProposalThresholds , votes = emptyVotesFor effects + , timingConfig = proposalTimingConfig } ) @@ -167,6 +168,7 @@ cosignProposal newSigners = , cosigners = [signer] , thresholds = defaultProposalThresholds , votes = emptyVotesFor effects + , timingConfig = proposalTimingConfig } stakeDatum :: StakeDatum stakeDatum = StakeDatum (Tagged 50_000_000) signer2 [] diff --git a/agora-sample/Sample/Shared.hs b/agora-sample/Sample/Shared.hs index c6f40a7..71f2192 100644 --- a/agora-sample/Sample/Shared.hs +++ b/agora-sample/Sample/Shared.hs @@ -36,6 +36,7 @@ module Sample.Shared ( proposalPolicySymbol, proposalValidatorHash, proposalValidatorAddress, + proposalTimingConfig, -- ** Authority authorityToken, @@ -74,6 +75,9 @@ import Agora.Proposal ( Proposal (..), ProposalThresholds (..), ) +import Agora.Proposal.Time ( + ProposalTimingConfig (..), + ) import Agora.Stake (Stake (..)) import Agora.Treasury (treasuryValidator) import Agora.Utils (validatorHashToTokenName) @@ -184,6 +188,15 @@ authorityToken = authorityTokenFromGovernor governor authorityTokenSymbol :: CurrencySymbol authorityTokenSymbol = authorityTokenSymbolFromGovernor governor +proposalTimingConfig :: ProposalTimingConfig +proposalTimingConfig = + ProposalTimingConfig + { draftTime = 0 + , votingTime = 1000 + , lockingTime = 2000 + , executingTime = 3000 + } + ------------------------------------------------------------------ treasuryOut :: TxOut diff --git a/agora-test/Spec/Proposal.hs b/agora-test/Spec/Proposal.hs index cb049ed..a9caafb 100644 --- a/agora-test/Spec/Proposal.hs +++ b/agora-test/Spec/Proposal.hs @@ -13,7 +13,7 @@ module Spec.Proposal (tests) where import Agora.Proposal ( Proposal (..), - ProposalDatum (ProposalDatum), + ProposalDatum (..), ProposalId (ProposalId), ProposalRedeemer (Cosign), ProposalStatus (Draft), @@ -77,6 +77,7 @@ tests = [ (ResultTag 0, AssocMap.empty) , (ResultTag 1, AssocMap.empty) ] + , timingConfig = Shared.proposalTimingConfig } ) (Cosign [signer2]) diff --git a/agora/Agora/Governor/Scripts.hs b/agora/Agora/Governor/Scripts.hs index 8389f1d..d424db5 100644 --- a/agora/Agora/Governor/Scripts.hs +++ b/agora/Agora/Governor/Scripts.hs @@ -126,6 +126,7 @@ import Plutarch.TryFrom (ptryFrom) -------------------------------------------------------------------------------- +import Agora.Proposal.Time (ProposalTimingConfig (..)) import Plutus.V1.Ledger.Api ( CurrencySymbol (..), MintingPolicy, @@ -576,6 +577,8 @@ governorValidator gov = .& #cosigners .= proposalInputDatumF.cosigners .& #thresholds .= proposalInputDatumF.thresholds .& #votes .= proposalInputDatumF.votes + -- FIXME: copy from the governor datum + .& #timingConfig .= pdata (pconstant tmpTimingConfig) ) tcassert "Unexpected output proposal datum" $ @@ -727,6 +730,16 @@ governorValidator gov = let sym = governorSTSymbolFromGovernor gov in phoistAcyclic $ pconstant sym + -- TODO: remove this. This is temperary. + tmpTimingConfig :: ProposalTimingConfig + tmpTimingConfig = + ProposalTimingConfig + { draftTime = 0 + , votingTime = 1000 + , lockingTime = 2000 + , executingTime = 3000 + } + -------------------------------------------------------------------------------- -- | Get the 'CurrencySymbol' of GST. diff --git a/agora/Agora/Proposal.hs b/agora/Agora/Proposal.hs index 3c82fe3..21407d3 100644 --- a/agora/Agora/Proposal.hs +++ b/agora/Agora/Proposal.hs @@ -44,6 +44,8 @@ import PlutusTx qualified import PlutusTx.AssocMap qualified as AssocMap -------------------------------------------------------------------------------- + +import Agora.Proposal.Time (PProposalTimingConfig, ProposalTimingConfig) import Agora.SafeMoney (GTTag) import Agora.Utils (pkeysEqual, pnotNull) import Control.Applicative (Const) @@ -186,6 +188,8 @@ data ProposalDatum = ProposalDatum -- ^ Thresholds copied over on initialization. , votes :: ProposalVotes -- ^ Vote tally on the proposal + , timingConfig :: ProposalTimingConfig + -- ^ Timing configuration copied over on initialization. } deriving stock (Eq, Show, GHC.Generic) @@ -354,6 +358,7 @@ newtype PProposalDatum (s :: S) = PProposalDatum , "cosigners" ':= PBuiltinList (PAsData PPubKeyHash) , "thresholds" ':= PProposalThresholds , "votes" ':= PProposalVotes + , "timingConfig" ':= PProposalTimingConfig ] ) } diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index f09e90b..d56e146 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -151,6 +151,7 @@ proposalValidator proposal = , "cosigners" , "thresholds" , "votes" + , "timingConfig" ] proposalDatum @@ -253,6 +254,7 @@ proposalValidator proposal = .& #cosigners .= proposalF.cosigners .& #thresholds .= proposalF.thresholds .& #votes .= pdata expectedNewVotes + .& #timingConfig .= proposalF.timingConfig ) tcassert "Invalid output proposal" $ proposalOut #== expectedProposalOut @@ -342,6 +344,7 @@ proposalValidator proposal = .& #cosigners .= pdata updatedSigs .& #thresholds .= proposalF.thresholds .& #votes .= proposalF.votes + .& #timingConfig .= proposalF.timingConfig ) ) in foldr1 diff --git a/agora/Agora/Proposal/Time.hs b/agora/Agora/Proposal/Time.hs index afc4339..54fed1a 100644 --- a/agora/Agora/Proposal/Time.hs +++ b/agora/Agora/Proposal/Time.hs @@ -39,7 +39,12 @@ import Plutarch.Api.V1 ( PPOSIXTimeRange, PUpperBound (PUpperBound), ) -import Plutarch.DataRepr (PDataFields, PIsDataReprInstances (..)) +import Plutarch.DataRepr (DerivePConstantViaData (..), PDataFields, PIsDataReprInstances (..)) +import Plutarch.Lift ( + DerivePConstantViaNewtype (..), + PConstantDecl, + PUnsafeLiftDecl (..), + ) import Plutarch.Numeric (AdditiveSemigroup ((+))) import Plutarch.Unsafe (punsafeCoerce) import Plutus.V1.Ledger.Time (POSIXTime) @@ -122,10 +127,24 @@ newtype PProposalTime (s :: S) (PlutusType, PIsData, PDataFields) via (PIsDataReprInstances PProposalTime) +instance PUnsafeLiftDecl PProposalTime where + type PLifted PProposalTime = ProposalTime +deriving via + (DerivePConstantViaData ProposalTime PProposalTime) + instance + (PConstantDecl ProposalTime) + -- | Plutarch-level version of 'ProposalStartingTime'. newtype PProposalStartingTime (s :: S) = PProposalStartingTime (Term s PPOSIXTime) deriving (PlutusType, PIsData, PEq, POrd) via (DerivePNewtype PProposalStartingTime PPOSIXTime) +instance PUnsafeLiftDecl PProposalStartingTime where + type PLifted PProposalStartingTime = ProposalStartingTime +deriving via + (DerivePConstantViaNewtype ProposalStartingTime PProposalStartingTime PPOSIXTime) + instance + (PConstantDecl ProposalStartingTime) + -- | Plutarch-level version of 'ProposalTimingConfig'. newtype PProposalTimingConfig (s :: S) = PProposalTimingConfig { getProposalTimingConfig :: @@ -146,6 +165,13 @@ newtype PProposalTimingConfig (s :: S) = PProposalTimingConfig (PlutusType, PIsData, PDataFields) via (PIsDataReprInstances PProposalTimingConfig) +instance PUnsafeLiftDecl PProposalTimingConfig where + type PLifted PProposalTimingConfig = ProposalTimingConfig +deriving via + (DerivePConstantViaData ProposalTimingConfig PProposalTimingConfig) + instance + (PConstantDecl ProposalTimingConfig) + -------------------------------------------------------------------------------- -- FIXME: Orphan instance, move this to plutarch-extra. From e8b87654f3cce9f8c3300061bcdc2c9a377b74ca Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 21:01:14 +0800 Subject: [PATCH 08/19] store starting time of the proposal in its datum ... hardcoded to 0 upon creation for now --- agora-sample/Sample/Governor.hs | 3 +++ agora-sample/Sample/Proposal.hs | 2 ++ agora-sample/Sample/Shared.hs | 6 ++++++ agora-test/Spec/Proposal.hs | 1 + agora/Agora/Governor/Scripts.hs | 8 +++++++- agora/Agora/Proposal.hs | 5 ++++- agora/Agora/Proposal/Scripts.hs | 3 +++ 7 files changed, 26 insertions(+), 2 deletions(-) diff --git a/agora-sample/Sample/Governor.hs b/agora-sample/Sample/Governor.hs index f1c8f38..27d867b 100644 --- a/agora-sample/Sample/Governor.hs +++ b/agora-sample/Sample/Governor.hs @@ -80,6 +80,7 @@ import Sample.Shared ( stake, stakeAddress, stakeAssetClass, + tmpProposalStartingTime, ) import Test.Util (datumPair, toDatumHash) @@ -236,6 +237,7 @@ createProposal = , thresholds = defaultProposalThresholds , votes = emptyVotesFor effects , timingConfig = proposalTimingConfig + , startingTime = tmpProposalStartingTime } ) proposalOutput :: TxOut @@ -411,6 +413,7 @@ mintGATs = , thresholds = defaultProposalThresholds , votes = proposalVotes , timingConfig = proposalTimingConfig + , startingTime = tmpProposalStartingTime } proposalInputDatum :: Datum proposalInputDatum = Datum $ toBuiltinData proposalInputDatum' diff --git a/agora-sample/Sample/Proposal.hs b/agora-sample/Sample/Proposal.hs index 7853606..329d862 100644 --- a/agora-sample/Sample/Proposal.hs +++ b/agora-sample/Sample/Proposal.hs @@ -75,6 +75,7 @@ proposalCreation = , thresholds = defaultProposalThresholds , votes = emptyVotesFor effects , timingConfig = proposalTimingConfig + , startingTime = tmpProposalStartingTime } ) @@ -169,6 +170,7 @@ cosignProposal newSigners = , thresholds = defaultProposalThresholds , votes = emptyVotesFor effects , timingConfig = proposalTimingConfig + , startingTime = tmpProposalStartingTime } stakeDatum :: StakeDatum stakeDatum = StakeDatum (Tagged 50_000_000) signer2 [] diff --git a/agora-sample/Sample/Shared.hs b/agora-sample/Sample/Shared.hs index 71f2192..1f30fb0 100644 --- a/agora-sample/Sample/Shared.hs +++ b/agora-sample/Sample/Shared.hs @@ -37,6 +37,7 @@ module Sample.Shared ( proposalValidatorHash, proposalValidatorAddress, proposalTimingConfig, + tmpProposalStartingTime, -- ** Authority authorityToken, @@ -76,6 +77,7 @@ import Agora.Proposal ( ProposalThresholds (..), ) import Agora.Proposal.Time ( + ProposalStartingTime (..), ProposalTimingConfig (..), ) import Agora.Stake (Stake (..)) @@ -197,6 +199,10 @@ proposalTimingConfig = , executingTime = 3000 } +-- FIXME: should be removed. +tmpProposalStartingTime :: ProposalStartingTime +tmpProposalStartingTime = ProposalStartingTime 0 + ------------------------------------------------------------------ treasuryOut :: TxOut diff --git a/agora-test/Spec/Proposal.hs b/agora-test/Spec/Proposal.hs index a9caafb..646d96e 100644 --- a/agora-test/Spec/Proposal.hs +++ b/agora-test/Spec/Proposal.hs @@ -78,6 +78,7 @@ tests = , (ResultTag 1, AssocMap.empty) ] , timingConfig = Shared.proposalTimingConfig + , startingTime = Shared.tmpProposalStartingTime } ) (Cosign [signer2]) diff --git a/agora/Agora/Governor/Scripts.hs b/agora/Agora/Governor/Scripts.hs index d424db5..d1b176e 100644 --- a/agora/Agora/Governor/Scripts.hs +++ b/agora/Agora/Governor/Scripts.hs @@ -126,7 +126,7 @@ import Plutarch.TryFrom (ptryFrom) -------------------------------------------------------------------------------- -import Agora.Proposal.Time (ProposalTimingConfig (..)) +import Agora.Proposal.Time (ProposalStartingTime (..), ProposalTimingConfig (..)) import Plutus.V1.Ledger.Api ( CurrencySymbol (..), MintingPolicy, @@ -579,6 +579,8 @@ governorValidator gov = .& #votes .= proposalInputDatumF.votes -- FIXME: copy from the governor datum .& #timingConfig .= pdata (pconstant tmpTimingConfig) + -- FIXME: calculate from 'txInfoValidRange' + .& #startingTime .= pdata (pconstant tmpProposalStartingTime) ) tcassert "Unexpected output proposal datum" $ @@ -740,6 +742,10 @@ governorValidator gov = , executingTime = 3000 } + -- TODO: remove this. + tmpProposalStartingTime :: ProposalStartingTime + tmpProposalStartingTime = ProposalStartingTime 0 + -------------------------------------------------------------------------------- -- | Get the 'CurrencySymbol' of GST. diff --git a/agora/Agora/Proposal.hs b/agora/Agora/Proposal.hs index 21407d3..0da7627 100644 --- a/agora/Agora/Proposal.hs +++ b/agora/Agora/Proposal.hs @@ -45,7 +45,7 @@ import PlutusTx.AssocMap qualified as AssocMap -------------------------------------------------------------------------------- -import Agora.Proposal.Time (PProposalTimingConfig, ProposalTimingConfig) +import Agora.Proposal.Time (PProposalStartingTime, PProposalTimingConfig, ProposalStartingTime, ProposalTimingConfig) import Agora.SafeMoney (GTTag) import Agora.Utils (pkeysEqual, pnotNull) import Control.Applicative (Const) @@ -190,6 +190,8 @@ data ProposalDatum = ProposalDatum -- ^ Vote tally on the proposal , timingConfig :: ProposalTimingConfig -- ^ Timing configuration copied over on initialization. + , startingTime :: ProposalStartingTime + -- ^ The time upon the creation of the proposal. } deriving stock (Eq, Show, GHC.Generic) @@ -359,6 +361,7 @@ newtype PProposalDatum (s :: S) = PProposalDatum , "thresholds" ':= PProposalThresholds , "votes" ':= PProposalVotes , "timingConfig" ':= PProposalTimingConfig + , "startingTime" ':= PProposalStartingTime ] ) } diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index d56e146..44ab890 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -152,6 +152,7 @@ proposalValidator proposal = , "thresholds" , "votes" , "timingConfig" + , "startingTime" ] proposalDatum @@ -255,6 +256,7 @@ proposalValidator proposal = .& #thresholds .= proposalF.thresholds .& #votes .= pdata expectedNewVotes .& #timingConfig .= proposalF.timingConfig + .& #startingTime .= proposalF.startingTime ) tcassert "Invalid output proposal" $ proposalOut #== expectedProposalOut @@ -345,6 +347,7 @@ proposalValidator proposal = .& #thresholds .= proposalF.thresholds .& #votes .= proposalF.votes .& #timingConfig .= proposalF.timingConfig + .& #startingTime .= proposalF.startingTime ) ) in foldr1 From b4ca5747572c876d633e1eff342983a94c767804 Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 21:11:55 +0800 Subject: [PATCH 09/19] ensure that the voting op is within a valid period --- agora/Agora/Proposal/Scripts.hs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index 44ab890..7656303 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -17,6 +17,7 @@ import Agora.Proposal ( Proposal (governorSTAssetClass, stakeSTAssetClass), ProposalStatus (VotingReady), ) +import Agora.Proposal.Time (currentProposalTime, isVotingPeriod) import Agora.Record (mkRecordConstr, (.&), (.=)) import Agora.Stake (PProposalLock (..), PStakeDatum (..), findStakeOwnedBy) import Agora.Utils ( @@ -131,7 +132,17 @@ proposalValidator proposal = ctx <- tcont $ pletFields @'["txInfo", "purpose"] ctx' txInfo <- tclet $ pfromData ctx.txInfo PTxInfo txInfo' <- tcmatch txInfo - txInfoF <- tcont $ pletFields @'["inputs", "outputs", "mint", "datums", "signatories"] txInfo' + txInfoF <- + tcont $ + pletFields + @'[ "inputs" + , "outputs" + , "mint" + , "datums" + , "signatories" + , "validRange" + ] + txInfo' PSpending ((pfield @"_0" #) -> txOutRef) <- tcmatch $ pfromData ctx.purpose PJust txOut <- tcmatch $ findTxOutByTxOutRef # txOutRef # txInfoF.inputs @@ -173,13 +184,17 @@ proposalValidator proposal = tcassert "ST at inputs must be 1" (spentST #== 1) + currentTime <- tclet $ currentProposalTime # txInfoF.validRange + pure $ pmatch proposalRedeemer $ \case PVote r -> unTermCont $ do - -- TODO: do we have to check the timing here? tcassert "Input proposal must be in VotingReady state" $ proposalF.status #== pconstant VotingReady + tcassert "Proposal time should be wthin the voting period" $ + isVotingPeriod # proposalF.timingConfig # proposalF.startingTime # currentTime + -- Ensure the transaction is voting to a valid 'ResultTag'(outcome). PProposalVotes voteMap <- tcmatch proposalF.votes voteFor <- tclet $ pfromData $ pfield @"resultTag" # r From 82201a6e1fdae1c47353eff76ad4d885479f5bda Mon Sep 17 00:00:00 2001 From: fanghr Date: Wed, 18 May 2022 21:33:42 +0800 Subject: [PATCH 10/19] add sample test && fix broken tests --- agora-sample/Sample/Proposal.hs | 157 +++++++++++++++++++++++++++++++- agora-sample/Sample/Shared.hs | 2 +- agora-test/Spec/Proposal.hs | 52 ++++++++++- agora-testlib/Test/Util.hs | 22 +++++ agora/Agora/Governor/Scripts.hs | 2 +- agora/Agora/Proposal/Scripts.hs | 28 +++--- 6 files changed, 238 insertions(+), 25 deletions(-) diff --git a/agora-sample/Sample/Proposal.hs b/agora-sample/Sample/Proposal.hs index 329d862..7763f42 100644 --- a/agora-sample/Sample/Proposal.hs +++ b/agora-sample/Sample/Proposal.hs @@ -11,6 +11,7 @@ module Sample.Proposal ( cosignProposal, proposalRef, stakeRef, + voteOnProposal, ) where -------------------------------------------------------------------------------- @@ -21,6 +22,7 @@ import Plutus.V1.Ledger.Api ( Address (Address), Credential (ScriptCredential), Datum (Datum), + POSIXTimeRange, PubKeyHash, ScriptContext (..), ScriptPurpose (..), @@ -43,14 +45,16 @@ import Agora.Proposal ( ProposalDatum (..), ProposalId (..), ProposalStatus (..), + ProposalVotes (..), ResultTag (..), emptyVotesFor, ) -import Agora.Stake (Stake (..), StakeDatum (StakeDatum)) +import Agora.Proposal.Time (ProposalTimingConfig (..)) +import Agora.Stake (ProposalLock (ProposalLock), Stake (..), StakeDatum (..)) import Plutarch.SafeMoney (Tagged (Tagged), untag) import PlutusTx.AssocMap qualified as AssocMap import Sample.Shared -import Test.Util (datumPair, toDatumHash) +import Test.Util (closedBoundedInterval, datumPair, toDatumHash, updateMap) -------------------------------------------------------------------------------- @@ -176,6 +180,11 @@ cosignProposal newSigners = stakeDatum = StakeDatum (Tagged 50_000_000) signer2 [] proposalAfter :: ProposalDatum proposalAfter = proposalBefore {cosigners = newSigners <> proposalBefore.cosigners} + validTimeRange :: POSIXTimeRange + validTimeRange = + closedBoundedInterval + 10 + (proposalTimingConfig.draftTime - 10) in TxInfo { txInfoInputs = [ TxInInfo @@ -227,7 +236,7 @@ cosignProposal newSigners = , txInfoMint = st , txInfoDCert = [] , txInfoWdrl = [] - , txInfoValidRange = Interval.always + , txInfoValidRange = validTimeRange , txInfoSignatories = newSigners , txInfoData = [ datumPair . Datum $ toBuiltinData proposalBefore @@ -236,3 +245,145 @@ cosignProposal newSigners = ] , txInfoId = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" } + +{- | A valid transaction of voting on a propsal. + + -- TODO: docs +-} +voteOnProposal :: ResultTag -> Integer -> TxInfo +voteOnProposal voteFor voteCount = + let pst = Value.singleton proposalPolicySymbol "" 1 + sst = Value.assetClassValue stakeAssetClass 1 + + --- + + stakeOwner = signer + + --- + + effects = + AssocMap.fromList + [ (ResultTag 0, AssocMap.empty) + , (ResultTag 1, AssocMap.empty) + ] + + --- + + initialVotes :: AssocMap.Map ResultTag Integer + initialVotes = + AssocMap.fromList + [ (ResultTag 0, 42) + , (ResultTag 1, 4242) + ] + + --- + + proposalInputDatum' :: ProposalDatum + proposalInputDatum' = + ProposalDatum + { proposalId = ProposalId 42 + , effects = effects + , status = VotingReady + , cosigners = [stakeOwner] + , thresholds = defaultProposalThresholds + , votes = ProposalVotes initialVotes + , timingConfig = proposalTimingConfig + , startingTime = tmpProposalStartingTime + } + proposalInputDatum :: Datum + proposalInputDatum = Datum $ toBuiltinData proposalInputDatum' + proposalInput :: TxOut + proposalInput = + TxOut + { txOutAddress = proposalValidatorAddress + , txOutValue = pst + , txOutDatumHash = Just $ toDatumHash proposalInputDatum + } + + --- + + existingLocks :: [ProposalLock] + existingLocks = + [ ProposalLock (ResultTag 0) (ProposalId 0) + , ProposalLock (ResultTag 2) (ProposalId 1) + ] + + --- + + stakeInputDatum' :: StakeDatum + stakeInputDatum' = + StakeDatum + { stakedAmount = Tagged voteCount + , owner = stakeOwner + , lockedBy = existingLocks + } + stakeInputDatum :: Datum + stakeInputDatum = Datum $ toBuiltinData stakeInputDatum' + stakeInput :: TxOut + stakeInput = + TxOut + { txOutAddress = stakeAddress + , txOutValue = sst <> Value.assetClassValue (untag stake.gtClassRef) voteCount + , txOutDatumHash = Just $ toDatumHash stakeInputDatum + } + + --- + + updatedVotes :: AssocMap.Map ResultTag Integer + updatedVotes = updateMap (Just . (+ voteCount)) voteFor initialVotes + + --- + + proposalOutputDatum' :: ProposalDatum + proposalOutputDatum' = + proposalInputDatum' + { votes = ProposalVotes updatedVotes + } + proposalOutputDatum :: Datum + proposalOutputDatum = Datum $ toBuiltinData proposalOutputDatum' + proposalOutput :: TxOut + proposalOutput = + proposalInput + { txOutDatumHash = Just $ toDatumHash proposalOutputDatum + } + + --- + + -- Off-chain code should do exactly like this: prepend new lock to the list. + updatedLocks :: [ProposalLock] + updatedLocks = ProposalLock voteFor proposalInputDatum'.proposalId : existingLocks + + --- + + stakeOutputDatum' :: StakeDatum + stakeOutputDatum' = + stakeInputDatum' + { lockedBy = updatedLocks + } + stakeOutputDatum :: Datum + stakeOutputDatum = Datum $ toBuiltinData stakeOutputDatum' + stakeOutput :: TxOut + stakeOutput = + stakeInput + { txOutDatumHash = Just $ toDatumHash stakeOutputDatum + } + + --- + + validTimeRange = + closedBoundedInterval (proposalTimingConfig.draftTime + 1) (proposalTimingConfig.votingTime - 1) + in TxInfo + { txInfoInputs = + [ TxInInfo proposalRef proposalInput + , TxInInfo stakeRef stakeInput + ] + , txInfoOutputs = [proposalOutput, stakeOutput] + , txInfoFee = Value.singleton "" "" 2 + , txInfoMint = mempty + , txInfoDCert = [] + , txInfoWdrl = [] + , txInfoValidRange = validTimeRange + , txInfoSignatories = [stakeOwner] + , txInfoData = datumPair <$> [proposalInputDatum, proposalOutputDatum, stakeInputDatum, stakeOutputDatum] + , txInfoId = "827598fb2d69a896bbd9e645bb14c307df907f422b39eecbe4d6329bc30b428c" + } diff --git a/agora-sample/Sample/Shared.hs b/agora-sample/Sample/Shared.hs index 1f30fb0..46cc0d3 100644 --- a/agora-sample/Sample/Shared.hs +++ b/agora-sample/Sample/Shared.hs @@ -193,7 +193,7 @@ authorityTokenSymbol = authorityTokenSymbolFromGovernor governor proposalTimingConfig :: ProposalTimingConfig proposalTimingConfig = ProposalTimingConfig - { draftTime = 0 + { draftTime = 50 , votingTime = 1000 , lockingTime = 2000 , executingTime = 3000 diff --git a/agora-test/Spec/Proposal.hs b/agora-test/Spec/Proposal.hs index 646d96e..3ed1e4e 100644 --- a/agora-test/Spec/Proposal.hs +++ b/agora-test/Spec/Proposal.hs @@ -15,8 +15,9 @@ import Agora.Proposal ( Proposal (..), ProposalDatum (..), ProposalId (ProposalId), - ProposalRedeemer (Cosign), - ProposalStatus (Draft), + ProposalRedeemer (Cosign, Vote), + ProposalStatus (Draft, VotingReady), + ProposalVotes (ProposalVotes), ResultTag (ResultTag), cosigners, effects, @@ -30,7 +31,11 @@ import Agora.Proposal.Scripts ( proposalPolicy, proposalValidator, ) -import Agora.Stake (StakeDatum (StakeDatum), StakeRedeemer (WitnessStake)) +import Agora.Stake ( + ProposalLock (ProposalLock), + StakeDatum (StakeDatum), + StakeRedeemer (PermitVote, WitnessStake), + ) import Agora.Stake.Scripts (stakeValidator) import Plutarch.SafeMoney (Tagged (Tagged)) import Plutus.V1.Ledger.Api (ScriptContext (..), ScriptPurpose (..)) @@ -90,5 +95,46 @@ tests = WitnessStake (ScriptContext (Proposal.cosignProposal [signer2]) (Spending Proposal.stakeRef)) ] + , testGroup + "voting" + [ validatorSucceedsWith + "proposal" + (proposalValidator Shared.proposal) + ( ProposalDatum + { proposalId = ProposalId 42 + , effects = + AssocMap.fromList + [ (ResultTag 0, AssocMap.empty) + , (ResultTag 1, AssocMap.empty) + ] + , status = VotingReady + , cosigners = [signer] + , thresholds = Shared.defaultProposalThresholds + , votes = + ProposalVotes + ( AssocMap.fromList + [ (ResultTag 0, 42) + , (ResultTag 1, 4242) + ] + ) + , timingConfig = Shared.proposalTimingConfig + , startingTime = Shared.tmpProposalStartingTime + } + ) + (Vote (ResultTag 0)) + (ScriptContext (Proposal.voteOnProposal (ResultTag 0) 27) (Spending Proposal.proposalRef)) + , validatorSucceedsWith + "stake" + (stakeValidator Shared.stake) + ( StakeDatum + (Tagged 27) + signer + [ ProposalLock (ResultTag 0) (ProposalId 0) + , ProposalLock (ResultTag 2) (ProposalId 1) + ] + ) + (PermitVote $ ProposalLock (ResultTag 0) (ProposalId 42)) + (ScriptContext (Proposal.voteOnProposal (ResultTag 0) 27) (Spending Proposal.stakeRef)) + ] ] ] diff --git a/agora-testlib/Test/Util.hs b/agora-testlib/Test/Util.hs index 74e8ac6..4d6f733 100644 --- a/agora-testlib/Test/Util.hs +++ b/agora-testlib/Test/Util.hs @@ -38,6 +38,8 @@ module Test.Util ( toDatum, toDatumHash, datumPair, + closedBoundedInterval, + updateMap, ) where -------------------------------------------------------------------------------- @@ -62,9 +64,12 @@ import Plutarch.Crypto (pblake2b_256) import Plutarch.Evaluate (evalScript) import Plutarch.Lift (PUnsafeLiftDecl (PLifted)) import Plutus.V1.Ledger.Contexts (ScriptContext) +import Plutus.V1.Ledger.Interval as PlutusTx import Plutus.V1.Ledger.Scripts (Datum (Datum), DatumHash (DatumHash), Script) +import PlutusTx.AssocMap qualified as AssocMap import PlutusTx.Builtins qualified as PlutusTx import PlutusTx.IsData qualified as PlutusTx +import PlutusTx.Ord qualified as PlutusTx -------------------------------------------------------------------------------- @@ -231,3 +236,20 @@ toDatumHash datum = plift $ pblake2b_256 # pconstant (ByteString.Lazy.toStrict $ serialise $ PlutusTx.toData datum) + +-------------------------------------------------------------------------------- + +-- | Create a closed bounded `Interval`. +closedBoundedInterval :: PlutusTx.Ord a => a -> a -> PlutusTx.Interval a +closedBoundedInterval from to = PlutusTx.intersection (PlutusTx.from from) (PlutusTx.to to) + +-------------------------------------------------------------------------------- + +updateMap :: Eq k => (v -> Maybe v) -> k -> AssocMap.Map k v -> AssocMap.Map k v +updateMap f k = + AssocMap.mapMaybeWithKey + ( \k' v -> + if k' == k + then f v + else Just v + ) diff --git a/agora/Agora/Governor/Scripts.hs b/agora/Agora/Governor/Scripts.hs index d1b176e..0b41352 100644 --- a/agora/Agora/Governor/Scripts.hs +++ b/agora/Agora/Governor/Scripts.hs @@ -736,7 +736,7 @@ governorValidator gov = tmpTimingConfig :: ProposalTimingConfig tmpTimingConfig = ProposalTimingConfig - { draftTime = 0 + { draftTime = 50 , votingTime = 1000 , lockingTime = 2000 , executingTime = 3000 diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index 7656303..73de60b 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -230,24 +230,18 @@ proposalValidator proposal = -- TODO: maybe we can move this outside of the pmatch block. -- Filter out own output with own address and PST. - ownOutput <- - tclet $ - mustBePJust # "Own output not found" #$ pfind - # plam - ( \input -> unTermCont $ do - inputF <- tcont $ pletFields @'["address", "value"] input - pure $ - inputF.address #== ownAddress - #&& psymbolValueOf # stCurrencySymbol # inputF.value #== 1 - ) - # pfromData txInfoF.outputs + let ownOutput = + mustBePJust # "Own output not found" #$ pfind + # plam + ( \input -> unTermCont $ do + inputF <- tcont $ pletFields @'["address", "value"] input + pure $ + inputF.address #== ownAddress + #&& psymbolValueOf # stCurrencySymbol # inputF.value #== 1 + ) + # pfromData txInfoF.outputs - ownOutputF <- tcont $ pletFields @'["datumHash", "value"] ownOutput - - -- TODO: is this really necessary? - tcassert "Own output value should be correct" $ ownOutputF.value #== pdata txOutF.value - - let proposalOut :: Term _ PProposalDatum + proposalOut :: Term _ PProposalDatum proposalOut = mustFindDatum' # (pfield @"datumHash" # ownOutput) # txInfoF.datums let -- Update the vote counter of the proposal, and leave other stuff as is. From dfe4bba15f66717e06a8f17131674b5ce15f3d24 Mon Sep 17 00:00:00 2001 From: fanghr Date: Thu, 19 May 2022 13:58:12 +0800 Subject: [PATCH 11/19] ensure the new proposal lock is placed on the stake --- agora/Agora/Proposal/Scripts.hs | 5 +++-- agora/Agora/Stake/Scripts.hs | 29 ++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index 73de60b..dc85b71 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -271,8 +271,8 @@ proposalValidator proposal = tcassert "Invalid output proposal" $ proposalOut #== expectedProposalOut -- We validate the output stake datum here as well: We need the vote option - -- to create a proper 'ProposalLock'. However the vote option is encoded - -- in the proposal redeemer, which is invisible for the stake validator. + -- to create a valid 'ProposalLock', however the vote option is encoded + -- in the proposal redeemer, which is invisible for the stake validator. let stakeOutput = mustBePJust # "Stake output not found" @@ -292,6 +292,7 @@ proposalValidator proposal = ( #vote .= pdata voteFor .& #proposalTag .= proposalF.proposalId ) + -- Prepend the new lock to existing locks expectedProposalLocks = pcons # pdata newProposalLock diff --git a/agora/Agora/Stake/Scripts.hs b/agora/Agora/Stake/Scripts.hs index 74494e8..aaf114b 100644 --- a/agora/Agora/Stake/Scripts.hs +++ b/agora/Agora/Stake/Scripts.hs @@ -7,6 +7,7 @@ Plutus Scripts for Stakes. -} module Agora.Stake.Scripts (stakePolicy, stakeValidator) where +import Agora.Record (mkRecordConstr, (.&), (.=)) import Agora.SafeMoney (GTTag) import Agora.Stake import Agora.Utils ( @@ -222,7 +223,7 @@ stakeValidator stake = -- TODO: Use PTryFrom let stakeDatum' :: Term _ PStakeDatum stakeDatum' = pfromData $ punsafeCoerce datum - stakeDatum <- tcont $ pletFields @'["owner", "stakedAmount"] stakeDatum' + stakeDatum <- tcont $ pletFields @'["owner", "stakedAmount", "lockedBy"] stakeDatum' PSpending txOutRef <- tcmatch $ pfromData ctx.purpose @@ -291,7 +292,7 @@ stakeValidator stake = pure $ popaque (pconstant ()) -------------------------------------------------------------------------- - PPermitVote _ -> unTermCont $ do + PPermitVote l -> unTermCont $ do tcassert "Owner signs this transaction" ownerSignsTransaction @@ -301,18 +302,40 @@ stakeValidator stake = tcassert "Proposal ST spent" $ spentProposalST #== 1 + -- Update the stake datum, but only the 'lockedBy' field. + + let -- We actually don't know whether the given lock is valid or not. + -- This is checked in the proposal validator. + newLock = pfield @"lock" # l + -- Prepend the new lock to the existing locks. + expectedLocks = pcons # newLock # stakeDatum.lockedBy + + expectedDatum <- + tclet $ + pdata $ + mkRecordConstr + PStakeDatum + ( #stakedAmount .= stakeDatum.stakedAmount + .& #owner .= stakeDatum.owner + .& #lockedBy .= pdata expectedLocks + ) + tcassert "A UTXO must exist with the correct output" $ + -- FIXME: no need to pass the whole txInfo to 'anyOutput'. anyOutput @PStakeDatum # txInfo #$ plam $ \value address newStakeDatum' -> let isScriptAddress = pdata address #== ownAddress - _correctOutputDatum = pdata newStakeDatum' #== pdata stakeDatum' + correctOutputDatum = pdata newStakeDatum' #== expectedDatum + -- TODO: Is this correct? I think We only need to ensure + -- correct amount of GT/SST in the continuing output. valueCorrect = pdata continuingValue #== pdata value in pif isScriptAddress ( foldl1 (#&&) [ ptraceIfFalse "valueCorrect" valueCorrect + , ptraceIfFalse "datumCorrect" correctOutputDatum ] ) (pcon PFalse) From 3f5707eb86e0c5316601b9149b4a2e40a34ea21a Mon Sep 17 00:00:00 2001 From: fanghr Date: Thu, 19 May 2022 17:10:33 +0800 Subject: [PATCH 12/19] some doc for the proposal voting sample --- agora-sample/Sample/Proposal.hs | 26 +++++++++++++++++--------- agora-test/Spec/Proposal.hs | 20 ++++++++++++++++++-- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/agora-sample/Sample/Proposal.hs b/agora-sample/Sample/Proposal.hs index 7763f42..c476ec3 100644 --- a/agora-sample/Sample/Proposal.hs +++ b/agora-sample/Sample/Proposal.hs @@ -12,6 +12,7 @@ module Sample.Proposal ( proposalRef, stakeRef, voteOnProposal, + VotingParameters (..), ) where -------------------------------------------------------------------------------- @@ -246,12 +247,19 @@ cosignProposal newSigners = , txInfoId = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" } -{- | A valid transaction of voting on a propsal. +-------------------------------------------------------------------------------- - -- TODO: docs --} -voteOnProposal :: ResultTag -> Integer -> TxInfo -voteOnProposal voteFor voteCount = +-- | Parameters for creating a voting transaction. +data VotingParameters = VotingParameters + { voteFor :: ResultTag + -- ^ The outcome the transaction is voting for. + , voteCount :: Integer + -- ^ The count of votes. + } + +-- | Create a valid transaction that votes on a propsal, given the parameters. +voteOnProposal :: VotingParameters -> TxInfo +voteOnProposal params = let pst = Value.singleton proposalPolicySymbol "" 1 sst = Value.assetClassValue stakeAssetClass 1 @@ -313,7 +321,7 @@ voteOnProposal voteFor voteCount = stakeInputDatum' :: StakeDatum stakeInputDatum' = StakeDatum - { stakedAmount = Tagged voteCount + { stakedAmount = Tagged params.voteCount , owner = stakeOwner , lockedBy = existingLocks } @@ -323,14 +331,14 @@ voteOnProposal voteFor voteCount = stakeInput = TxOut { txOutAddress = stakeAddress - , txOutValue = sst <> Value.assetClassValue (untag stake.gtClassRef) voteCount + , txOutValue = sst <> Value.assetClassValue (untag stake.gtClassRef) params.voteCount , txOutDatumHash = Just $ toDatumHash stakeInputDatum } --- updatedVotes :: AssocMap.Map ResultTag Integer - updatedVotes = updateMap (Just . (+ voteCount)) voteFor initialVotes + updatedVotes = updateMap (Just . (+ params.voteCount)) params.voteFor initialVotes --- @@ -351,7 +359,7 @@ voteOnProposal voteFor voteCount = -- Off-chain code should do exactly like this: prepend new lock to the list. updatedLocks :: [ProposalLock] - updatedLocks = ProposalLock voteFor proposalInputDatum'.proposalId : existingLocks + updatedLocks = ProposalLock params.voteFor proposalInputDatum'.proposalId : existingLocks --- diff --git a/agora-test/Spec/Proposal.hs b/agora-test/Spec/Proposal.hs index 3ed1e4e..a78d693 100644 --- a/agora-test/Spec/Proposal.hs +++ b/agora-test/Spec/Proposal.hs @@ -122,7 +122,15 @@ tests = } ) (Vote (ResultTag 0)) - (ScriptContext (Proposal.voteOnProposal (ResultTag 0) 27) (Spending Proposal.proposalRef)) + ( ScriptContext + ( Proposal.voteOnProposal + Proposal.VotingParameters + { Proposal.voteFor = ResultTag 0 + , Proposal.voteCount = 27 + } + ) + (Spending Proposal.proposalRef) + ) , validatorSucceedsWith "stake" (stakeValidator Shared.stake) @@ -134,7 +142,15 @@ tests = ] ) (PermitVote $ ProposalLock (ResultTag 0) (ProposalId 42)) - (ScriptContext (Proposal.voteOnProposal (ResultTag 0) 27) (Spending Proposal.stakeRef)) + ( ScriptContext + ( Proposal.voteOnProposal + Proposal.VotingParameters + { Proposal.voteFor = ResultTag 0 + , Proposal.voteCount = 27 + } + ) + (Spending Proposal.stakeRef) + ) ] ] ] From bce9b45c253fa7550d0630585f3199b370a84d2c Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 01:10:24 +0800 Subject: [PATCH 13/19] add missing doc strings --- agora-sample/Sample/Shared.hs | 5 ++++- agora-testlib/Test/Util.hs | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/agora-sample/Sample/Shared.hs b/agora-sample/Sample/Shared.hs index 46cc0d3..adaed30 100644 --- a/agora-sample/Sample/Shared.hs +++ b/agora-sample/Sample/Shared.hs @@ -199,7 +199,10 @@ proposalTimingConfig = , executingTime = 3000 } --- FIXME: should be removed. +{- | Hard coded starting time of every propoal. + This will be calculated by the governor in the future. + FIXME: Remove this. +-} tmpProposalStartingTime :: ProposalStartingTime tmpProposalStartingTime = ProposalStartingTime 0 diff --git a/agora-testlib/Test/Util.hs b/agora-testlib/Test/Util.hs index 4d6f733..624d728 100644 --- a/agora-testlib/Test/Util.hs +++ b/agora-testlib/Test/Util.hs @@ -245,6 +245,10 @@ closedBoundedInterval from to = PlutusTx.intersection (PlutusTx.from from) (Plut -------------------------------------------------------------------------------- +{- | / O(n) /. The expression @'updateMap' f k v@ will update the value @x@ at key @k@. + If @f x@ is Nothing, the key-value pair will be deleted from the map, otherwise the + value will be updated. +-} updateMap :: Eq k => (v -> Maybe v) -> k -> AssocMap.Map k v -> AssocMap.Map k v updateMap f k = AssocMap.mapMaybeWithKey From cdffbeffc97c40f2c104be330402275df9159b0b Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 03:15:21 +0800 Subject: [PATCH 14/19] add a property test for `pupdate` and `updateMap` --- agora-test/Spec/Utils.hs | 88 ++++++++++++++++++++++++++++++++++++-- agora-testlib/Test/Util.hs | 2 +- agora.cabal | 1 + 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/agora-test/Spec/Utils.hs b/agora-test/Spec/Utils.hs index e255cf2..fef48c4 100644 --- a/agora-test/Spec/Utils.hs +++ b/agora-test/Spec/Utils.hs @@ -9,17 +9,33 @@ module Spec.Utils (tests) where -------------------------------------------------------------------------------- -import Agora.Utils (phalve, pisUniq, pmergeBy, pmsort, pnubSort) +import Agora.Utils (phalve, pisUniq, pmergeBy, pmsort, pnubSort, pupdate) -------------------------------------------------------------------------------- import Data.List (nub, sort) -import Data.Set as S +import Data.Map qualified as M +import Data.Set qualified as S -------------------------------------------------------------------------------- +import Control.Monad.Cont (cont, runCont) import Test.Tasty (TestTree) -import Test.Tasty.QuickCheck (testProperty) +import Test.Tasty.QuickCheck ( + Arbitrary (arbitrary), + Property, + Testable (property), + elements, + forAll, + suchThat, + testProperty, + (.&&.), + ) +import Test.Util (updateMap) + +-------------------------------------------------------------------------------- + +import PlutusTx.AssocMap qualified as AssocMap -------------------------------------------------------------------------------- @@ -30,6 +46,7 @@ tests = , testProperty "'phalve' splits a list in half as expected" prop_halveCorrect , testProperty "'pnubSort' sorts a list and remove duplicate elements" prop_nubSortProperly , testProperty "'pisUniq' can tell whether all elements in a list are unique" prop_uniqueList + , testProperty "'pupdate' updates assoc maps as 'updateMap' does" prop_updateAssocMapParity ] -------------------------------------------------------------------------------- @@ -142,3 +159,68 @@ prop_uniqueList l = isUnique == expected -- isUnique = plift $ pisUniq # pconstant l + +{- | Test the parity between 'updateMap' and 'pupdate', + also ensure they both work correctly. +-} +prop_updateAssocMapParity :: Property +prop_updateAssocMapParity = + runCont + ( do + -- Generate a bunch unique keys. + keys <- + cont $ + forAll $ + arbitrary @(S.Set Integer) `suchThat` (not . S.null) + + -- Generate key-value pairs. + kvPairs <- cont $ forAll $ mapM (\k -> (k,) <$> (arbitrary @Integer)) $ S.toList keys + + let initialMap = AssocMap.fromList kvPairs + + pinitialMap :: Term _ _ + pinitialMap = phoistAcyclic $ pconstant initialMap + + referenceMap = M.fromList kvPairs + + let pupdatedValue :: Maybe Integer -> Term _ (PMaybe PInteger) + pupdatedValue updatedValue = phoistAcyclic $ case updatedValue of + Nothing -> pcon PNothing + Just v -> pcon $ PJust $ pconstant v + + -- Given the key and the updated value, test the parity + parity key updatedValue = + let native = updateMap (const updatedValue) key initialMap + + plutarch :: AssocMap.Map Integer Integer + plutarch = + plift $ + pupdate + # plam (\_ -> pupdatedValue updatedValue) + # pconstant key + # pinitialMap + + expected = + AssocMap.fromList $ + M.toList $ + M.update (const updatedValue) key referenceMap + in expected == native + && expected == plutarch + + -- Select a key, generate a maybe value. + -- The value at the key should be set to the new value or removed. + (targetKey, _) <- cont $ forAll $ elements kvPairs + updatedValue <- cont $ forAll $ arbitrary @(Maybe Integer) + + -- Now what if the key doesn't exist in our map? + nonexistentKey <- + cont $ + forAll $ + arbitrary @Integer `suchThat` (\k -> not $ S.member k keys) + + pure + ( property (parity targetKey updatedValue) + .&&. property (parity nonexistentKey updatedValue) + ) + ) + id diff --git a/agora-testlib/Test/Util.hs b/agora-testlib/Test/Util.hs index 624d728..ab750d1 100644 --- a/agora-testlib/Test/Util.hs +++ b/agora-testlib/Test/Util.hs @@ -246,7 +246,7 @@ closedBoundedInterval from to = PlutusTx.intersection (PlutusTx.from from) (Plut -------------------------------------------------------------------------------- {- | / O(n) /. The expression @'updateMap' f k v@ will update the value @x@ at key @k@. - If @f x@ is Nothing, the key-value pair will be deleted from the map, otherwise the + If @f x@ is Nothing, the key-value pair will be deleted from the map, otherwise the value will be updated. -} updateMap :: Eq k => (v -> Maybe v) -> k -> AssocMap.Map k v -> AssocMap.Map k v diff --git a/agora.cabal b/agora.cabal index 6d34d9c..7d01530 100644 --- a/agora.cabal +++ b/agora.cabal @@ -114,6 +114,7 @@ common test-deps , agora , apropos , apropos-tx + , mtl , QuickCheck , quickcheck-instances , tasty From 9549fae0df940adb7ec7adf0f7f8a08d4a2d29ca Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 16:02:06 +0800 Subject: [PATCH 15/19] fix misleading assert messages && add some comments --- agora-sample/Sample/Proposal.hs | 4 ++++ agora/Agora/Proposal/Scripts.hs | 12 ++++++------ agora/Agora/Stake/Scripts.hs | 2 -- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/agora-sample/Sample/Proposal.hs b/agora-sample/Sample/Proposal.hs index c476ec3..8e00803 100644 --- a/agora-sample/Sample/Proposal.hs +++ b/agora-sample/Sample/Proposal.hs @@ -374,6 +374,10 @@ voteOnProposal params = stakeOutput = stakeInput { txOutDatumHash = Just $ toDatumHash stakeOutputDatum + -- We won't include the minimum Ada in the output value + -- due to how we check the output value in the stake validator. + -- The implementation is correct though, it should work in a + -- real on-chain environment. } --- diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index dc85b71..3fe0244 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -199,14 +199,14 @@ proposalValidator proposal = PProposalVotes voteMap <- tcmatch proposalF.votes voteFor <- tclet $ pfromData $ pfield @"resultTag" # r - tcassert "Invalid vote option" $ + tcassert "Vote option should be valid" $ pisJust #$ plookup # voteFor # voteMap -- Find the input stake, the amount of new votes should be the 'stakedAmount'. let stakeInput = pfield @"resolved" #$ mustBePJust - # "Stake input not found" + # "Stake input should be present" #$ pfind # plam ( \(pfromData . (pfield @"value" #) . (pfield @"resolved" #) -> value) -> @@ -220,7 +220,7 @@ proposalValidator proposal = stakeInF <- tcont $ pletFields @'["stakedAmount", "lockedBy", "owner"] stakeIn -- Ensure that no lock with the current proposal id has been put on the stake. - tcassert "Cannot vote on the a proposal using the same stake twice" $ + tcassert "Same stake shouldn't vote on the same propsoal twice" $ pnot #$ pany # plam ( \((pfield @"proposalTag" #) . pfromData -> pid) -> @@ -231,7 +231,7 @@ proposalValidator proposal = -- TODO: maybe we can move this outside of the pmatch block. -- Filter out own output with own address and PST. let ownOutput = - mustBePJust # "Own output not found" #$ pfind + mustBePJust # "Own output should be present" #$ pfind # plam ( \input -> unTermCont $ do inputF <- tcont $ pletFields @'["address", "value"] input @@ -268,14 +268,14 @@ proposalValidator proposal = .& #startingTime .= proposalF.startingTime ) - tcassert "Invalid output proposal" $ proposalOut #== expectedProposalOut + tcassert "Output proposal should be valid" $ proposalOut #== expectedProposalOut -- We validate the output stake datum here as well: We need the vote option -- to create a valid 'ProposalLock', however the vote option is encoded -- in the proposal redeemer, which is invisible for the stake validator. let stakeOutput = - mustBePJust # "Stake output not found" + mustBePJust # "Stake output should be present" #$ pfind # plam ( \(pfromData . (pfield @"value" #) -> value) -> diff --git a/agora/Agora/Stake/Scripts.hs b/agora/Agora/Stake/Scripts.hs index aaf114b..da9da7f 100644 --- a/agora/Agora/Stake/Scripts.hs +++ b/agora/Agora/Stake/Scripts.hs @@ -327,8 +327,6 @@ stakeValidator stake = $ \value address newStakeDatum' -> let isScriptAddress = pdata address #== ownAddress correctOutputDatum = pdata newStakeDatum' #== expectedDatum - -- TODO: Is this correct? I think We only need to ensure - -- correct amount of GT/SST in the continuing output. valueCorrect = pdata continuingValue #== pdata value in pif isScriptAddress From db92986c6d06e57cc1d5f5bfeb4f6224b4c1b696 Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 16:34:03 +0800 Subject: [PATCH 16/19] pull own output filtering out of the pmatch block --- agora/Agora/Proposal/Scripts.hs | 40 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index 3fe0244..282ba26 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -186,6 +186,28 @@ proposalValidator proposal = currentTime <- tclet $ currentProposalTime # txInfoF.validRange + -- Filter out own output with own address and PST. + -- Delay the evaluation cause in some cases there won't be any continuing output. + ownOutputD <- + tclet $ + pdelay $ + mustBePJust # "Own output should be present" #$ pfind + # plam + ( \input -> unTermCont $ do + inputF <- tcont $ pletFields @'["address", "value"] input + pure $ + inputF.address #== ownAddress + #&& psymbolValueOf # stCurrencySymbol # inputF.value #== 1 + ) + # pfromData txInfoF.outputs + + proposalOutD <- + tclet $ + pdelay $ + mustFindDatum' @PProposalDatum + # (pfield @"datumHash" # pforce ownOutputD) + # txInfoF.datums + pure $ pmatch proposalRedeemer $ \case PVote r -> unTermCont $ do @@ -228,22 +250,6 @@ proposalValidator proposal = ) # pfromData stakeInF.lockedBy - -- TODO: maybe we can move this outside of the pmatch block. - -- Filter out own output with own address and PST. - let ownOutput = - mustBePJust # "Own output should be present" #$ pfind - # plam - ( \input -> unTermCont $ do - inputF <- tcont $ pletFields @'["address", "value"] input - pure $ - inputF.address #== ownAddress - #&& psymbolValueOf # stCurrencySymbol # inputF.value #== 1 - ) - # pfromData txInfoF.outputs - - proposalOut :: Term _ PProposalDatum - proposalOut = mustFindDatum' # (pfield @"datumHash" # ownOutput) # txInfoF.datums - let -- Update the vote counter of the proposal, and leave other stuff as is. expectedNewVotes = pmatch (pfromData proposalF.votes) $ \(PProposalVotes m) -> pcon $ @@ -268,7 +274,7 @@ proposalValidator proposal = .& #startingTime .= proposalF.startingTime ) - tcassert "Output proposal should be valid" $ proposalOut #== expectedProposalOut + tcassert "Output proposal should be valid" $ pforce proposalOutD #== expectedProposalOut -- We validate the output stake datum here as well: We need the vote option -- to create a valid 'ProposalLock', however the vote option is encoded From ccf56f58d1f367af79f081eeb2d842fe67b4f41a Mon Sep 17 00:00:00 2001 From: fanghr Date: Fri, 20 May 2022 17:09:22 +0800 Subject: [PATCH 17/19] add min ada to both stake input and output --- agora-sample/Sample/Proposal.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/agora-sample/Sample/Proposal.hs b/agora-sample/Sample/Proposal.hs index 8e00803..de52379 100644 --- a/agora-sample/Sample/Proposal.hs +++ b/agora-sample/Sample/Proposal.hs @@ -331,7 +331,12 @@ voteOnProposal params = stakeInput = TxOut { txOutAddress = stakeAddress - , txOutValue = sst <> Value.assetClassValue (untag stake.gtClassRef) params.voteCount + , txOutValue = + mconcat + [ sst + , Value.assetClassValue (untag stake.gtClassRef) params.voteCount + , minAda + ] , txOutDatumHash = Just $ toDatumHash stakeInputDatum } @@ -374,10 +379,6 @@ voteOnProposal params = stakeOutput = stakeInput { txOutDatumHash = Just $ toDatumHash stakeOutputDatum - -- We won't include the minimum Ada in the output value - -- due to how we check the output value in the stake validator. - -- The implementation is correct though, it should work in a - -- real on-chain environment. } --- From 1ba572240931b7b470c45cb83a9f89d37f1061cc Mon Sep 17 00:00:00 2001 From: Emily Martins Date: Fri, 20 May 2022 17:03:41 +0200 Subject: [PATCH 18/19] init `agora-scripts` for generating ScriptInfo for CTL --- agora-scripts/Options.hs | 45 +++++++++++ agora-scripts/Scripts.hs | 66 ++++++++++++++++ agora.cabal | 35 ++++++--- agora/Agora/Aeson/Orphans.hs | 146 +++++++++++++++++++++++++++++++++++ agora/Agora/SafeMoney.hs | 14 +++- agora/Agora/ScriptInfo.hs | 59 ++++++++++++++ 6 files changed, 353 insertions(+), 12 deletions(-) create mode 100644 agora-scripts/Options.hs create mode 100644 agora-scripts/Scripts.hs create mode 100644 agora/Agora/Aeson/Orphans.hs create mode 100644 agora/Agora/ScriptInfo.hs diff --git a/agora-scripts/Options.hs b/agora-scripts/Options.hs new file mode 100644 index 0000000..f6c4d10 --- /dev/null +++ b/agora-scripts/Options.hs @@ -0,0 +1,45 @@ +{- | +Module : Options +Maintainer : emi@haskell.fyi +Description: Command line options for 'agora-scripts'. + +Command line options for 'agora-scripts'. +-} +module Options (Options (..), parseOptions) where + +import Options.Applicative ((<**>)) +import Options.Applicative qualified as Opt + +data Options = Options + { config :: FilePath + , output :: FilePath + } + deriving stock (Show, Eq) + +opt :: Opt.Parser Options +opt = + Options + <$> Opt.strOption + ( Opt.long "config" + <> Opt.short 'c' + <> Opt.metavar "CONFIG_PATH" + <> Opt.value "./" + <> Opt.help "The path where the script configuration is." + ) + <*> Opt.strOption + ( Opt.long "output" + <> Opt.short 'o' + <> Opt.metavar "OUTPUT_PATH" + <> Opt.value "./" + <> Opt.help "Output where generated scripts will be." + ) + +parseOptions :: IO Options +parseOptions = Opt.execParser p + where + p = + Opt.info + (opt <**> Opt.helper) + ( Opt.fullDesc + <> Opt.progDesc "Generate Agora scripts for off-chain use." + ) diff --git a/agora-scripts/Scripts.hs b/agora-scripts/Scripts.hs new file mode 100644 index 0000000..7d171d1 --- /dev/null +++ b/agora-scripts/Scripts.hs @@ -0,0 +1,66 @@ +{- | +Module : Scripts +Maintainer : emi@haskell.fyi +Description: Export scripts given configuration. + +Export scripts given configuration. +-} +module Scripts (main) where + +import Agora.Governor (Governor (Governor)) +import Agora.Governor qualified as Governor +import Agora.Governor.Scripts (governorPolicy) +import Agora.SafeMoney (GTTag) +import Agora.ScriptInfo (PolicyInfo, mkPolicyInfo) +import Control.Monad ((>=>)) +import Data.Aeson qualified as Aeson +import GHC.Generics qualified as GHC +import Options (Options (..), parseOptions) +import Plutarch.SafeMoney (Tagged) +import Plutus.V1.Ledger.Api (TxOutRef) +import Plutus.V1.Ledger.Value (AssetClass) +import System.Exit (exitFailure) + +data ScriptsConfig = ScriptsConfig + { governorInitialSpend :: TxOutRef + , gtClassRef :: Tagged GTTag AssetClass + , maximumCosigners :: Integer + } + deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) + deriving stock (Show, Eq, GHC.Generic) + +data AgoraScripts = AgoraScripts + { governorPolicyInfo :: PolicyInfo + } + deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) + deriving stock (Show, Eq, GHC.Generic) + +main :: IO () +main = do + putStrLn "Hello, world!" + + options <- parseOptions + + params <- + Aeson.eitherDecodeFileStrict @ScriptsConfig options.config + >>= either (putStrLn >=> const exitFailure) pure + + let scripts = agoraScripts params + + print params + print scripts + + pure () + +agoraScripts :: ScriptsConfig -> AgoraScripts +agoraScripts config = + AgoraScripts + { governorPolicyInfo = mkPolicyInfo (governorPolicy governor) + } + where + governor = + Governor + { Governor.gstOutRef = config.governorInitialSpend + , Governor.gtClassRef = config.gtClassRef + , Governor.maximumCosigners = config.maximumCosigners + } diff --git a/agora.cabal b/agora.cabal index 7d01530..be3d748 100644 --- a/agora.cabal +++ b/agora.cabal @@ -146,18 +146,21 @@ library Agora.Utils Agora.Utils.Value + Agora.ScriptInfo + Agora.Aeson.Orphans + other-modules: hs-source-dirs: agora library pprelude + default-language: Haskell2010 + exposed-modules: PPrelude + hs-source-dirs: agora + build-depends: , base , plutarch - exposed-modules: PPrelude - hs-source-dirs: agora - default-language: Haskell2010 - library agora-testlib import: lang, deps, test-deps exposed-modules: Test.Util @@ -165,7 +168,6 @@ library agora-testlib library agora-sample import: lang, deps, test-deps - build-depends: agora-testlib exposed-modules: Sample.Effect.TreasuryWithdrawal Sample.Governor @@ -173,9 +175,10 @@ library agora-sample Sample.Shared Sample.Stake Sample.Treasury - hs-source-dirs: agora-sample + build-depends: agora-testlib + test-suite agora-test import: lang, deps, test-deps type: exitcode-stdio-1.0 @@ -205,16 +208,26 @@ benchmark agora-bench , agora , agora-sample +executable agora-scripts + import: lang, deps, exe-opts + main-is: Scripts.hs + hs-source-dirs: agora-scripts + other-modules: + Options + build-depends: + , agora + , optparse-applicative + executable agora-purescript-bridge import: lang, deps, exe-opts main-is: Bridge.hs + hs-source-dirs: agora-purescript-bridge + other-modules: + AgoraTypes + Options + build-depends: , agora , optparse-applicative , path , purescript-bridge - - hs-source-dirs: agora-purescript-bridge - other-modules: - AgoraTypes - Options diff --git a/agora/Agora/Aeson/Orphans.hs b/agora/Agora/Aeson/Orphans.hs new file mode 100644 index 0000000..522643a --- /dev/null +++ b/agora/Agora/Aeson/Orphans.hs @@ -0,0 +1,146 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +module Agora.Aeson.Orphans (AsBase16Bytes (..)) where + +-------------------------------------------------------------------------------- + +import Data.Coerce (Coercible, coerce) +import Prelude + +-------------------------------------------------------------------------------- + +import Codec.Serialise qualified as Codec +import Data.Aeson qualified as Aeson +import Data.Aeson.Types qualified as Aeson +import Data.ByteString.Lazy qualified as Lazy +import Data.Text qualified as T +import Data.Text.Encoding qualified as T + +-------------------------------------------------------------------------------- + +import Plutus.V1.Ledger.Api qualified as Plutus +import Plutus.V1.Ledger.Bytes qualified as Plutus +import Plutus.V1.Ledger.Value qualified as Plutus + +-------------------------------------------------------------------------------- + +newtype AsBase16Bytes a = AsBase16Bytes {unAsBase16Bytes :: a} +newtype AsBase16Codec a = AsBase16Codec {unAsBase16Codec :: a} + +deriving via + (Plutus.CurrencySymbol, Plutus.TokenName) + instance + Aeson.ToJSON Plutus.AssetClass + +deriving via + (Plutus.CurrencySymbol, Plutus.TokenName) + instance + Aeson.FromJSON Plutus.AssetClass + +deriving via + AsBase16Bytes Plutus.TxId + instance + Aeson.FromJSON Plutus.TxId + +deriving via + AsBase16Bytes Plutus.TxId + instance + Aeson.ToJSON Plutus.TxId + +deriving anyclass instance Aeson.FromJSON Plutus.TxOutRef +deriving anyclass instance Aeson.ToJSON Plutus.TxOutRef + +instance (Coercible a Plutus.LedgerBytes) => Aeson.ToJSON (AsBase16Bytes a) where + toJSON = + Aeson.String + . Plutus.encodeByteString + . Plutus.bytes + . coerce @(AsBase16Bytes a) @Plutus.LedgerBytes + +instance (Coercible Plutus.LedgerBytes a) => Aeson.FromJSON (AsBase16Bytes a) where + parseJSON v = + Aeson.parseJSON @T.Text v + >>= either (Aeson.parserThrowError []) (pure . coerce @_ @(AsBase16Bytes a)) + . Plutus.fromHex + . T.encodeUtf8 + +instance (Codec.Serialise a) => Aeson.ToJSON (AsBase16Codec a) where + toJSON = + Aeson.String + . Plutus.encodeByteString + . Lazy.toStrict + . Codec.serialise @a + . (.unAsBase16Codec) + +instance (Codec.Serialise a) => Aeson.FromJSON (AsBase16Codec a) where + parseJSON v = + Aeson.parseJSON @T.Text v + >>= either (Aeson.parserThrowError [] . show) (pure . AsBase16Codec) + . Codec.deserialiseOrFail + . Lazy.fromStrict + . T.encodeUtf8 + +-------------------------------------------------------------------------------- + +deriving via + (AsBase16Bytes Plutus.CurrencySymbol) + instance + (Aeson.ToJSON Plutus.CurrencySymbol) +deriving via + (AsBase16Bytes Plutus.CurrencySymbol) + instance + (Aeson.FromJSON Plutus.CurrencySymbol) + +deriving via + (AsBase16Bytes Plutus.TokenName) + instance + (Aeson.ToJSON Plutus.TokenName) +deriving via + (AsBase16Bytes Plutus.TokenName) + instance + (Aeson.FromJSON Plutus.TokenName) + +deriving via + (AsBase16Bytes Plutus.ValidatorHash) + instance + (Aeson.ToJSON Plutus.ValidatorHash) +deriving via + (AsBase16Bytes Plutus.ValidatorHash) + instance + (Aeson.FromJSON Plutus.ValidatorHash) + +deriving via + (AsBase16Codec Plutus.Validator) + instance + (Aeson.ToJSON Plutus.Validator) +deriving via + (AsBase16Codec Plutus.Validator) + instance + (Aeson.FromJSON Plutus.Validator) + +deriving via + (AsBase16Codec Plutus.MintingPolicy) + instance + (Aeson.ToJSON Plutus.MintingPolicy) +deriving via + (AsBase16Codec Plutus.MintingPolicy) + instance + (Aeson.FromJSON Plutus.MintingPolicy) + +deriving via + (AsBase16Codec Plutus.Script) + instance + (Aeson.ToJSON Plutus.Script) +deriving via + (AsBase16Codec Plutus.Script) + instance + (Aeson.FromJSON Plutus.Script) + +deriving via + Integer + instance + (Aeson.ToJSON Plutus.POSIXTime) +deriving via + Integer + instance + (Aeson.FromJSON Plutus.POSIXTime) diff --git a/agora/Agora/SafeMoney.hs b/agora/Agora/SafeMoney.hs index f94ae8d..2a469ea 100644 --- a/agora/Agora/SafeMoney.hs +++ b/agora/Agora/SafeMoney.hs @@ -8,6 +8,9 @@ Tags and extras for "Plutarch.SafeMoney". module Agora.SafeMoney ( ADATag, GTTag, + GovernorSTTag, + StakeSTTag, + ProposalSTTag, adaRef, ) where @@ -18,7 +21,7 @@ import Plutus.V1.Ledger.Value (AssetClass (AssetClass)) import Plutarch.SafeMoney -------------------------------------------------------------------------------- --- Example tags +-- Tags -- | Governance token. data GTTag @@ -26,6 +29,15 @@ data GTTag -- | ADA. data ADATag +-- | Governor ST token. +data GovernorSTTag + +-- | Stake ST token. +data StakeSTTag + +-- | Proposal ST token. +data ProposalSTTag + -------------------------------------------------------------------------------- -- | Resolves ada tags. diff --git a/agora/Agora/ScriptInfo.hs b/agora/Agora/ScriptInfo.hs new file mode 100644 index 0000000..54a709e --- /dev/null +++ b/agora/Agora/ScriptInfo.hs @@ -0,0 +1,59 @@ +{- | +Module : Agora.ScriptInfo +Maintainer : emi@haskell.fyi +Description: Exportable script bundles for off-chain consumption. + +Exportable script bundles for off-chain consumption. +-} +module Agora.ScriptInfo ( + -- * Types + PolicyInfo (..), + ValidatorInfo (..), + + -- * Introduction functions + mkValidatorInfo, + mkPolicyInfo, +) where + +import Agora.Aeson.Orphans () +import Data.Aeson qualified as Aeson +import GHC.Generics qualified as GHC +import Plutarch.Api.V1 (PMintingPolicy, PValidator, mintingPolicySymbol, mkMintingPolicy, mkValidator, validatorHash) +import Plutus.V1.Ledger.Api (MintingPolicy, Validator, ValidatorHash) +import Plutus.V1.Ledger.Value (CurrencySymbol) + +-- | Bundle containing a 'Validator' and its hash. +data ValidatorInfo = ValidatorInfo + { script :: Validator + , hash :: ValidatorHash + } + deriving stock (Show, Eq, GHC.Generic) + deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) + +-- | Create a 'ValidatorInfo' given a Plutarch term. +mkValidatorInfo :: ClosedTerm PValidator -> ValidatorInfo +mkValidatorInfo term = + ValidatorInfo + { script = validator + , hash = validatorHash validator + } + where + validator = mkValidator term + +-- | Bundle containing a 'MintingPolicy' and its symbol. +data PolicyInfo = PolicyInfo + { policy :: MintingPolicy + , currencySymbol :: CurrencySymbol + } + deriving stock (Show, Eq, GHC.Generic) + deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) + +-- | Create a 'PolicyInfo' given a Plutarch term. +mkPolicyInfo :: ClosedTerm PMintingPolicy -> PolicyInfo +mkPolicyInfo term = + PolicyInfo + { policy = policy + , currencySymbol = mintingPolicySymbol policy + } + where + policy = mkMintingPolicy term From effbcd1d6b51b33b4ca1f46351918b2c473c5d92 Mon Sep 17 00:00:00 2001 From: Emily Martins Date: Fri, 20 May 2022 18:08:31 +0200 Subject: [PATCH 19/19] add remaining scripts, export to json file, fix compile --- agora-scripts/Options.hs | 4 +- agora-scripts/Scripts.hs | 79 +++++++++++++++++++++++++------- agora-scripts/agora-params.json | 11 +++++ agora-scripts/agora-scripts.json | 1 + agora.cabal | 3 +- agora/Agora/ScriptInfo.hs | 4 ++ agora/Agora/Treasury.hs | 1 + 7 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 agora-scripts/agora-params.json create mode 100644 agora-scripts/agora-scripts.json diff --git a/agora-scripts/Options.hs b/agora-scripts/Options.hs index f6c4d10..e56d09a 100644 --- a/agora-scripts/Options.hs +++ b/agora-scripts/Options.hs @@ -23,14 +23,14 @@ opt = ( Opt.long "config" <> Opt.short 'c' <> Opt.metavar "CONFIG_PATH" - <> Opt.value "./" + <> Opt.value "./agora-scripts/agora-params.json" <> Opt.help "The path where the script configuration is." ) <*> Opt.strOption ( Opt.long "output" <> Opt.short 'o' <> Opt.metavar "OUTPUT_PATH" - <> Opt.value "./" + <> Opt.value "./agora-scripts/agora-scripts.json" <> Opt.help "Output where generated scripts will be." ) diff --git a/agora-scripts/Scripts.hs b/agora-scripts/Scripts.hs index 7d171d1..0544a1c 100644 --- a/agora-scripts/Scripts.hs +++ b/agora-scripts/Scripts.hs @@ -5,23 +5,40 @@ Description: Export scripts given configuration. Export scripts given configuration. -} -module Scripts (main) where +module Main (main) where +import Agora.AuthorityToken (AuthorityToken, authorityTokenPolicy) import Agora.Governor (Governor (Governor)) import Agora.Governor qualified as Governor -import Agora.Governor.Scripts (governorPolicy) +import Agora.Governor.Scripts ( + authorityTokenFromGovernor, + authorityTokenSymbolFromGovernor, + governorPolicy, + governorValidator, + proposalFromGovernor, + stakeFromGovernor, + ) +import Agora.Proposal (Proposal) +import Agora.Proposal.Scripts (proposalPolicy, proposalValidator) import Agora.SafeMoney (GTTag) -import Agora.ScriptInfo (PolicyInfo, mkPolicyInfo) +import Agora.ScriptInfo (PolicyInfo, ValidatorInfo, mkPolicyInfo, mkValidatorInfo) +import Agora.Stake (Stake) +import Agora.Stake.Scripts (stakePolicy, stakeValidator) +import Agora.Treasury (treasuryValidator) import Control.Monad ((>=>)) import Data.Aeson qualified as Aeson import GHC.Generics qualified as GHC import Options (Options (..), parseOptions) +import Plutarch.Api.V1 (mintingPolicySymbol, mkMintingPolicy) import Plutarch.SafeMoney (Tagged) import Plutus.V1.Ledger.Api (TxOutRef) -import Plutus.V1.Ledger.Value (AssetClass) +import Plutus.V1.Ledger.Value (AssetClass, CurrencySymbol) +import Plutus.V1.Ledger.Value qualified as Value import System.Exit (exitFailure) +import Text.Printf (printf) -data ScriptsConfig = ScriptsConfig +-- | Params required for creating script export. +data ScriptParams = ScriptParams { governorInitialSpend :: TxOutRef , gtClassRef :: Tagged GTTag AssetClass , maximumCosigners :: Integer @@ -29,38 +46,68 @@ data ScriptsConfig = ScriptsConfig deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) deriving stock (Show, Eq, GHC.Generic) +-- | Scripts that get exported. data AgoraScripts = AgoraScripts { governorPolicyInfo :: PolicyInfo + , governorValidatorInfo :: ValidatorInfo + , stakePolicyInfo :: PolicyInfo + , stakeValidatorInfo :: ValidatorInfo + , proposalPolicyInfo :: PolicyInfo + , proposalValidatorInfo :: ValidatorInfo + , treasuryValidatorInfo :: ValidatorInfo + , authorityTokenPolicyInfo :: PolicyInfo } deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) deriving stock (Show, Eq, GHC.Generic) main :: IO () main = do - putStrLn "Hello, world!" - options <- parseOptions params <- - Aeson.eitherDecodeFileStrict @ScriptsConfig options.config + Aeson.eitherDecodeFileStrict @ScriptParams options.config >>= either (putStrLn >=> const exitFailure) pure let scripts = agoraScripts params - print params - print scripts + Aeson.encodeFile options.output scripts - pure () + printf "Done! Wrote to %s\n" options.output -agoraScripts :: ScriptsConfig -> AgoraScripts -agoraScripts config = +-- | Create scripts from params. +agoraScripts :: ScriptParams -> AgoraScripts +agoraScripts params = AgoraScripts { governorPolicyInfo = mkPolicyInfo (governorPolicy governor) + , governorValidatorInfo = mkValidatorInfo (governorValidator governor) + , stakePolicyInfo = mkPolicyInfo (stakePolicy params.gtClassRef) + , stakeValidatorInfo = mkValidatorInfo (stakeValidator stake) + , proposalPolicyInfo = mkPolicyInfo (proposalPolicy governorSTAssetClass) + , proposalValidatorInfo = mkValidatorInfo (proposalValidator proposal) + , treasuryValidatorInfo = mkValidatorInfo (treasuryValidator authorityTokenSymbol) + , authorityTokenPolicyInfo = mkPolicyInfo (authorityTokenPolicy authorityToken) } where + governor :: Governor governor = Governor - { Governor.gstOutRef = config.governorInitialSpend - , Governor.gtClassRef = config.gtClassRef - , Governor.maximumCosigners = config.maximumCosigners + { Governor.gstOutRef = params.governorInitialSpend + , Governor.gtClassRef = params.gtClassRef + , Governor.maximumCosigners = params.maximumCosigners } + + authorityToken :: AuthorityToken + authorityToken = authorityTokenFromGovernor governor + + authorityTokenSymbol :: CurrencySymbol + authorityTokenSymbol = authorityTokenSymbolFromGovernor governor + + governorSTAssetClass :: AssetClass + governorSTAssetClass = + Value.assetClass (mintingPolicySymbol $ mkMintingPolicy $ governorPolicy governor) "" + + proposal :: Proposal + proposal = proposalFromGovernor governor + + stake :: Stake + stake = stakeFromGovernor governor diff --git a/agora-scripts/agora-params.json b/agora-scripts/agora-params.json new file mode 100644 index 0000000..cb036a3 --- /dev/null +++ b/agora-scripts/agora-params.json @@ -0,0 +1,11 @@ +{ + "governorInitialSpend": { + "txOutRefId": "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be", + "txOutRefIdx": 0 + }, + "gtClassRef": [ + "", + "" + ], + "maximumCosigners": 5 +} diff --git a/agora-scripts/agora-scripts.json b/agora-scripts/agora-scripts.json new file mode 100644 index 0000000..d02a671 --- /dev/null +++ b/agora-scripts/agora-scripts.json @@ -0,0 +1 @@ +{"authorityTokenPolicyInfo":{"currencySymbol":"6aa44a4ea34725d9bcf9b522babe6b6b94ae55eddac23b9ccaa94427","policy":"5904ad010000323232323232323232323232323232323232323222323232323253330153370e90000010a99980a99b8848000cc88c8ccc02c00cdd7180e0009bae301c301b001301c00137566032601e6030008664466e9520003300e37520046601c6ea400403cdd7180c8008070a99980a999119b8848000ccc88c054894ccc060004400c4cc010c07c004c008c07800488cdc00009991191998078019bae3020001375c6040603e00260400026eacc074c070c078c8c078c074004c078008011200000133223374a9000198071ba90023300e375200201e91011c3f57878004c69a4ebcad6907b12e8245e44fd85ebaff8c27dd9c2f4b0000e375860320082a66602a6644646602646464646464a66603c66e1d2000002133332233333301c22533301f0011225001153330223371e646eb8c09cc0a0004c098004010488c008c0a000c4c008c094004004888c008c09cc098c0a000c4894004888c00800c4894004dd718110009bac302232302230223022302230190013021007233330090073756604600a604600c002294054cc07d2401455061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5574696c732e68733a3536333a31352d323300163023002301e0013754603e603c002603a002603c0026eb0c070004c068c070008c0640148888cc88c8cccc88cccc068894ccc074004489400454ccc080cdd798111812000802091180118130018980118118008009111801181380189128009ba900337566040603e0024646464a66604066e1d2002002132533302100110011330224913e617574686f72697479546f6b656e7356616c6964496e3a2047415420546f6b656e4e616d6520646f65736e2774206d617463682053637269707448617368000013301b23375e60460026ea4dd718128011bab0041325333021001100113302249137617574686f72697479546f6b656e7356616c6964496e3a2047415420696e636f72726563746c79206c69766573206174205075624b6579000014a0604a00460400026ea8c084c088c08400852898100009bae301d005004149854cc059240120416c6c206f757470757473206f6e6c7920656d69742076616c696420474154730016153301649129506172656e7420746f6b656e20646964206e6f74206d6f766520696e206d696e74696e6720474154730016149854cc0592414d5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f417574686f72697479546f6b656e2e68733a3134343a372d32350016301a00230150013754602c602a004602c602a002602a00244466660080040064666600c0040024002290000a4000446666008466e3cdd7180880080180091118011bab3017003122500122333300323371e6eb8c04000400c004888c008dd6980b00189128009803111299980500089128008a99980698011808800891180118098018998018011808000aba04bd702441002300a300a0012300222533300500114a22a6600c60066018002260046016002464600446600400400246004466004004002aae7c88ccc01000800400c5282b9a5738aae755d12ba1230023754002aae781"},"governorPolicyInfo":{"currencySymbol":"3f57878004c69a4ebcad6907b12e8245e44fd85ebaff8c27dd9c2f4b","policy":"59054d01000032323232323232323232323232323232323222323232533300e3370e90000010991919299980899911919800925114a0664602c44a666032002244a00226660066038002444600400626004603a0024a66602866ebc00cc068c070004488c00800c489400400530127d8799fd8799f581c0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88beff00ff003758602e0062a666022a6601466e1ccc03cdd7180b8021bab3017001480084cdc39991191991199999809919b8f375c603800200600a44460046eacc08800c48940048cccccc0508cdc79bae301d0010030012223002375a6046006244a0024002290000a40006eb8c068004dd7180d180d800980d8009bab301700133223374a9000198059ba90023300b375200297ae0375c602e00891100480084c8c8c8c8c8c8c94cc044c94ccc06400440044cc07524012a45786563757465207468726573686f6c64206973206c657373207468616e206f7220657175616c20746f000013371290000018a9980899299980c800880089980ea481294472616674207468726573686f6c64206973206c657373207468616e206f7220657175616c20746f20000013371290000010a9980899299980c800880089980ea48128566f7465207468726573686f6c64206973206c657373207468616e206f7220657175616c20746f20000013371290000008a9980899299980c800880089980ea4812b4472616674207468726573686f6c64206973206c657373207468616e20766f7465207468726573686f6c640000133712004002264a666032002200226603a9212d45786563757465207468726573686f6c64206973206c657373207468616e20766f7465207468726573686f6c6400001337100020066eb4c074c07800cdd6980e0011bad301b002301b001301b3019301b00133223301049122446174756d206e6f7420666f756e6420696e20746865207472616e73616374696f6e003322333301822533301b0011225001153330183371e646eb8c07cc084004c078004010488c008c08000c4c008c07c004004888c008c07cc080c08400c4894004cc88c8c8c94ccc064cdc3a400000426eb8c07c00454cc07401458c084008c070004dd5000a4920476976656e2054784f757420646f6e657327742068617665206120646174756d000020013018300d301a001375860306460346034601c00260320046601a920119476f7665726e6f72206f7574707574206e6f7420666f756e64003300e23370e660206eb8c060014dd5980c180c980d000a40046eb0c05c00854cc05524012245786163746c79206f6e6520746f6b656e2073686f756c64206265206d696e746564001615330154911f5265666572656e636564207574786f2073686f756c64206265207370656e740016301730170013016001301630143016004153301249014f5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f476f7665726e6f722f536372697074732e68733a3137303a352d34340016301600230110013754602260246026002ae8088ccc02400800400c528118069806800911980090008a998050010b1802911299980480089128008a999803180118060008911801180700189980180118068009119998019ba9002001233300622337006eb4c040008005200037560022900011199980291299980400089128008a99980299baf300a300b0010041223002300d00313002300c0010012223002300f0031225001573444600644a66600c0022006266008601200260046014002464600446600400400246004466004004002aae7d5ce2ab9d5742ae888c008dd5000aab9e01"},"governorValidatorInfo":{"hash":"f975d9a89cf94e58ad4955c9d2a3ce21ce5351909bfc4de4857fc46c","script":"59193901000032323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323222232323232323232323253330563370e90010010991919299982c99b8733049010375660bc60ba0049001099299982d19b873032001480084c94ccc16ccdc3998258091bab3060001480084c94ccc170c8c8c8c8c94cc134c94ccc18800440044cc18d2412a45786563757465207468726573686f6c64206973206c657373207468616e206f7220657175616c20746f000013371290000018a998269929998310008800899831a481294472616674207468726573686f6c64206973206c657373207468616e206f7220657175616c20746f20000013371290000010a998269929998310008800899831a48128566f7465207468726573686f6c64206973206c657373207468616e206f7220657175616c20746f20000013371290000008a998269929998310008800899831a4812b4472616674207468726573686f6c64206973206c657373207468616e20766f7465207468726573686f6c640000133712004002264a6660c400220022660c69212d45786563757465207468726573686f6c64206973206c657373207468616e20766f7465207468726573686f6c6400001337100020066eb4c194c19000cdd698320011bad306300230610013062306130620011323232533305f3370e90010010a99982f99baf0040151533305f3370e6609e08a646660a44466446664446608c6604046466660b4002006466ec0008dd3198031bab30710033756002200460d400200466042460466605e466ebcc1ac004c1ac00800c00488cc114cc07c8c8cccc16400400c8cdd80011ba8337006eb4c1c000cdd680088011834800801198101181119817119baf306a001306a00200300100200137566460d060ce00260d06460d060ce00260d00040020380026eb0c190041200213232533306153304d3025002130250011323232323253330663375e60d600698103d87b8000153330663375e00c66e95200033062306b00533062306b004330623374a90030309983118358011983118358009983118359835000830899299983399b89375a6460da60dc00260d80046eb4c1b80044c8c94ccc1a4cdc39982c8151bab306e018001132533306a3370e608400200426660b844a6660d866e1ccc1700b4dd59838983818390012400020022a6660d866e1ccc1700b4dd5983898381839001240042a660b0002264a660b264a6660dc00220022660de92125474154206d7573742062652074616767656420627920746865206566666563742068617368000013302a02e003132533306e001100113306f49110556e657870656374656420646174756d000013371e660cc921224f757470757420746f206566666563742073686f756c64206861766520646174756d003072307130710013306a4901225265636569766572206973206e6f7420696e2074686520656666656374206c697374003322333305a23371e6eb8c1c800400c004888c008dd7183c8018912800998352491c474154207265636569766572206973206e6f74206120736372697074003232323253330713370e900100109118011bae3078003122500130770023071001375460e660e800260e400200c60e40042941289bac306f01a153306b4901244f75747075742047415473206973206d6f7265207468616e206d696e746564204741547300163306323371090001982d0159bab306f306e3070001375860dc0322a660d4920128526571756972656420616d6f756e74206f6620474154732073686f756c64206265206d696e74656400163040001332232330012001153306b491505061747465726e206d61746368206661696c75726520696e207175616c69666965642027646f2720626c6f636b20617420506c7574617263682f4d617962652f45787472612e68733a31353a352d31310016333305423370e6eb4c1b000400c004888c008dd5983980189128009bad3069001375660d800a2a660d0921314869676765737420766f746520646f65736e2774206d65657420746865206d696e696d756d20726571756972656d656e74001633063491124e6f2077696e6e696e67206f7574636f6d65003330582253330683300124a229404cc00494ccc1a4cdc41bad3070001375a60e0006244600400a20042a660d2921505061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f476f7665726e6f722f536372697074732e68733a3539343a33312d343000161223002004225001375660d660d40022a660ce920120556e6578706563746564206f75747075742070726f706f73616c20646174756d001615330674914850726f706f73616c206d75737420626520696e206c6f636b65642865786563757461626c652920737461746520696e206f7264657220746f20657865637574652065666665637473001630690013068001306700130660013067002153306204416330483065306330663305d4911950726f706f73616c206f7574707574206e6f7420666f756e64003304b23253304e3370e660a40906eacc19cc198005200213375e60ce00208c60ce0026eb0c194040dd61832983200719823983218311832991832983218330009982e2491850726f706f73616c20696e707574206e6f7420666f756e64003304a23253304d3370e660a208e6eacc198c194005200213375e60cc00208a60cc60ca60c860cc0026eb0c190040dd6183218318068a998302493454686520676f7665726e6f722063616e206f6e6c792070726f63657373206f6e652070726f706f73616c20617420612074696d650016153306049124476f7665726e6f722073746174652073686f756c64206e6f74206265206368616e67656400161533305f3370e90020010a998259929998300008800899830a493373696e676c65417574686f72697479546f6b656e4275726e65643a204d757374206275726e2065786163746c79203120474154000013370e6609e0406eacc190038cdc0a4000900109929998300008800899830a494673696e676c65417574686f72697479546f6b656e4275726e65643a20416c6c2047415420746f6b656e73206d7573742062652076616c69642061742074686520696e70757473000013301e23301d02132306630650013066001375860c860ca60c80242a6660be66ebc010cdd2a4000660b660c800e660b66ea0c8cdc0000a40046eb4c190c18c01c16854ccc17cc94cc130cdc399828023000a4004266e1cc0e00052002375660c801c26464a6660c2604860cc60ca00226464a6660c666e1cdd69834001181e1bab30680031533306333223306322533306600114a02a6660ce66ebcc1b000400c5288980118358008011bac3068011306800113253330643370e607800290010992999832981400089929998331815000899191919299983519baf306f004306f306e0121533306a3375e60de00260de0242a6660d466ebcc1bc00d30103d87980001533306a3370e60846eb0c1bc00920021533306a3375e6ea4dd7183780418379bac306f00213232533306c3370e6eb4c1c402cc114dd598388008a99983619baf3374a9000198341838805998341838805198341ba73304b375860e260e00146609a64466e9520003306a0013306a37500040d26eb4c1c4018c130dd598389838001833998348271983119832a491e5374616b65206f75747075742073686f756c64206861766520646174756d0030713070001375860e260e00342930a99836a4811d556e6578706563746564207374616b65206f757470757420646174756d0016153306d491315374616b6564204754732073686f756c642062652073656e74206261636b20746f207374616b652076616c696461746f720016306f30710013306704c330552325330583375e60e20020b4266e1ccc17017cdd598389838000a400460e20026eb0c1bc06854cc1ad240122436f7369676e65722073686f756c6420626520746865207374616b65206f776e65720016153306b4912650726f706f73616c2073686f756c642068617665206f6e6c79206f6e6520636f7369676e65720016153306b4911e50726f706f73616c2073746174652073686f756c642062652064726166740016153306b49124496e76616c6964207468726573686f6c647320696e2070726f706f73616c20646174756d0016153306b49125496e76616c69642070726f706f73616c20696420696e2070726f706f73616c20646174756d0016306d001306c001306b306b001306c0011533067049163304d001375860d460d20262a660cc920137546865207574786f207061696420746f207468652070726f706f73616c2076616c696461746f72206d757374206861766520646174756d001632306a3068306b001306900115330654915345786163746c79206f6e65205554584f20776974682070726f706f73616c20737461746520746f6b656e2073686f756c642062652073656e7420746f207468652070726f706f73616c2076616c696461746f7200163305d2325330513375e60d4002092266e1ccc15412cdd598351834800a400460d40026eb0c1a004c54cc1912412654782073686f756c64206265207369676e656420627920746865207374616b65206f776e65720016153306449130526571756972656420616d6f756e74206f66207374616b65204754732073686f756c642062652070726573656e7465640016306600130673304930663065001375860cc60ca01e2a660c49211e5374616b6520696e70757420646f65736e2774206861766520646174756d0016306430663065306430660013305c4901155374616b6520696e707574206e6f7420666f756e64003304a23253304d3375e60cc00209e266e1ccc144150dd598331832800a400460cc60ca60c860cc0026eb0c19004054cc1812412945786163746c79206f6e652070726f706f73616c20746f6b656e206d757374206265206d696e746564001615330604911f556e657870656374656420676f7665726e6f7220737461746520646174756d00163065002305f00137546464646402aa6660be66e1d20000021324994ccc1780045261533060054161533305f3370e900100109924ca6660bc0022930a9983002a0b0a99982f99b87480100084c926533305e001149854cc1801505854cc1812413f7265616368656420656e64206f662073756d207768696c65207374696c6c206e6f7420686176696e6720666f756e642074686520636f6e7374727563746f7200163065002305f00137540222a660ba9201164e657720646174756d206973206e6f742076616c6964001633058491244f7570757420676f7665726e6f7220737461746520646174756d206e6f7420666f756e64003305133054490122476f7665726e6f72206f757470757420646f65736e2774206861766520646174756d003060305f001375860c060be0122a660b89212d537461746520746f6b656e2073686f756c64207374617920617420676f7665726e6f72277320616464726573730016305e3060305f001153305b49012f45786163746c79206f6e65207574786f2073686f756c642062652073656e7420746f2074686520676f7665726e6f72001633223305523375e60c260c40020040046eb0c178024c17800854cc1692412d4f776e20696e7075742073686f756c6420686176652065786163746c79206f6e6520737461746520746f6b656e0016305e00e305d305c305b305d00133053491134f776e20696e707574206e6f7420666f756e64003322332305722533305a001122500113330033060001222300200313002305f001253330593375e00660bc60be0022446004006244a00200260b60026eb0c16c01c54cc15d24014f5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f476f7665726e6f722f536372697074732e68733a3239323a352d35390016305c0023056001375460b060ae00c6460ae60ac00260ac00260aa60aa00260a800260aa00260a600260a60020029111c3f57878004c69a4ebcad6907b12e8245e44fd85ebaff8c27dd9c2f4b002304622533304900110061330463003304f0013002304e001230452253330480011005132533304a30040011330460013003304e00213003304e002304e00123330460014a09452f5bded8c04464666606e6ea400cdd5982598250009191919299982519b87480080084c94ccc12c00440044cc130024004cc0248cdd798268009ba9375c60a00046eac0104c94ccc12c00440044cc13002c00528182800118250009baa304c304d304c00214a2609600292013e617574686f72697479546f6b656e7356616c6964496e3a2047415420546f6b656e4e616d6520646f65736e2774206d617463682053637269707448617368002304022533304300114a22a660606006609200226004609000292137617574686f72697479546f6b656e7356616c6964496e3a2047415420696e636f72726563746c79206c69766573206174205075624b6579000014891c6aa44a4ea34725d9bcf9b522babe6b6b94ae55eddac23b9ccaa9442700232323253330413370e90010010a5014a2608e00460820026ea80040048c8c94cc0a8c94ccc0fc00440044cc100014004cc014020dd598218010a9981519299981f800880089982000500098051bac3043001153302a32533303f001100113304000c00133712602c6eb0c10c005200a132533303f001100113304000d0013300d375660860046eacc10cc108c108004c104c104004c100c10800524013250726f706f73616c20686173206174206c65617374206f6e6520526573756c7454616720686173206e6f2065666665637473002303822533303b00114a02a660086006608200226004608000246607200200429448c034dd5981f000a4812250726f706f73616c20686173206174206c65617374206f6e6520636f7369676e6572002533303400114a029452412b50726f706f73616c2068617320666577657220636f7369676e657273207468616e20746865206c696d69740049013950726f706f73616c20766f74657320616e6420656666656374732061726520636f6d70617469626c6520776974682065616368206f746865720022323253330353370e601a004601a00226600a6600c0180046600c0180022940c050008c04c008c0b88894ccc0c800854ccc0c80045288a501533303200114a02a66606666ebcc0e0008c0e00044cc00cc0dc008c0dc00452818169112999819180400088168a9998191804181b00088008998020009119980480219802802001198028020009198010008009815911299981818030008919800816001899191929998199804801891980098040018010991998038010009111980099819803001801181b801981b002181b001981a000919814800814181491112999817980280108008a99981798028008801099191919299981999803802001099817802199804003801802899817801199804003803000981b002181b001981a001981a0012ba322337106eb4008dd6800998131112999815000880109980199b8000248008c0bc005200000123232333004003375c605c0026eb8c0b8c0b4004c0b8004cdd2a4000660466ea4014cc08cdd480281111119998020010019199980300100090008a400029000111999808919b8f375c605200200600244460046eacc0c000c489400488cccc0408cdc79bae30280010030012223002375a605e006244a00291010022330212253330240011003133021302a001300230290010022330020230012301e225333021001101d13301e3003302700130023026001491165374616b65206f7574707574206e6f7420666f756e640049011c50726f706f73616c20646174756d206d7573742062652076616c6964000014c129d8799fd87a9f581c44fd51f02679fe98ba45c64eea4b7169cad2f573439e542ef08cff3dffd87a80ff000014891c8fd3f732c764550a66a42f31e5936b6dc9f85eabb5c08c774cebd3cd002233017003330123301300400200149122446174756d206e6f7420666f756e6420696e20746865207472616e73616374696f6e00490120476976656e2054784f757420646f6e657327742068617665206120646174756d00301322253330170011225001153330183002301d0011223002301f003133003002301c0012233301600200100314a000298129d8799fd87a9f581c9aecaa97f19230ddfcd5c67fdab9a032783ed2aba422362b68a63253ffd87a80ff002233330033752004002466600a4466e00dd6980e001000a40006eac0045200022333300f2253330120011225001153330133375e602a60300020082446004603400626004602e00200244460046036006244a00244601c44a6660220022006266008602e0026004602c0020029111c55d252e7c81d25e68ed198a26f07832f838c4d1f078e7d28bd18c209004901186c697374206973206c6f6e676572207468616e207a65726f002233330030020012223002003122500100122333300822533300b00112250011533300c3371e646eb8c048c04c004c044004010488c008c04c00c4c008c040004004888c008c048c044c04c00c489400488c8c8c94ccc030cdc3a400000426eb8c04400454cc03401458c048008c030004dd500091802912999804000880209929998051802000899803000980198070010980198070011807000a5eb815d0111980090008a998030010b119180111980100100091801119801001000aab9f5734ae7155ce918011801000aba25742460046ea800555cf01"},"proposalPolicyInfo":{"currencySymbol":"8fd3f732c764550a66a42f31e5936b6dc9f85eabb5c08c774cebd3cd","policy":"5902d501000032323232323232323232323232323232223232323232533300e3370e90000010991919299980899b874800000854ccc044cc88cdc42400066644602644a66602c0022006266008603c0026004603a0024466e00004cc88c8ccc04800cdd7180f8009bae301f301e001301f001375660386036603a64603a6038002603a0040089000000999119ba548000cc050dd48011980a1ba90010154891c3f57878004c69a4ebcad6907b12e8245e44fd85ebaff8c27dd9c2f4b000143758603000e2a66602266e1ccc88c8ccc03800cdd7180d8009bae301b301a001301b001375660306460306030002602e00e664466e952000330143752004660286ea4004054dd7180c00080a240042930a9980aa4811e4d696e7465642065786163746c79206f6e652070726f706f73616c2053540016153301549127476f7665726e616e63652073746174652d74687265616420746f6b656e206d757374206d6f7665001615330154914e5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f50726f706f73616c2f536372697074732e68733a37373a352d32330016301900230140013754602a602800a2a6602492014e5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f50726f706f73616c2f536372697074732e68733a37323a352d323300163016002301100137546024602200460246022002602200244466660080040064666600c0040024002290000a4000446666008466e3cdd7180680080180091118011bab3013003122500122333300323371e6eb8c03000400c004888c008dd6980900189128009800911299980280089128008a99980318011806800891180118078018998018011806000919180111980100100091801119801001000aab9f5734ae812f5c0911005738aae755d12ba1230023754002aae781"},"proposalValidatorInfo":{"hash":"44fd51f02679fe98ba45c64eea4b7169cad2f573439e542ef08cff3d","script":"5909bf010000323232323232323232323232323232323232323232323232323232323232323232323232222323232323232323253330253370e9001001099999119999181291299981400089128008999801981900091118010018980118188009299981419baf003303030310011223002003122500100122230023032303130330031225001302d0013758605a00c4646464646464646464646464a66606466e1c0112002132323253330353370e9001001099299981b199119b873021001302153330383020001100113233330362222533303d30250011302400213232533303f3300500200413330060050040011330390043330060050020013045002304500175e6082002608000266606a444a6660766046002205e2a66607660466084002200226666070444a66607c604c00224660020660062646464a66608260520062466002605200600426466600e004002444660026607e00c0060046090006608e008608e006608a0020020024466660744444a666082605200420022a66608260520022004264646464a66608a6600e00800426607e00866601000e00600a26607e00466601000e00c00260960086096006609200660920040086600a0080046600a0080020040024466e40dd70011bae0010011533303633020005001153330363370e00c603e0022a66606c66040466666444666605a46464646464a66608066e1d200200214a02a66608066e1c01120021333302b375c6090002010466ebcc124c120c128004dd48050a5014a0609200460840026ea8c114c110008cc88c8ccc0bc00cdd718238009bae3047304600130470013756608800201a60846088646088608600260880020024646464a66607c66e1d2000002133029375c608c00200c244a002608e00460800026ea8c10cc104c110c10cc108c1100044894004dd70009bac303f303e0163758607e03049445280008a99981b1991191981a91919191919299981f99b874800800854cc1092401455061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5574696c732e68733a3533393a31352d323300161333302a375c608e0026eb0c11cc8c11cc11cc11cc114004c11801c8ccc024dd5982400298240030008a5030480023041001375460886086002608400260860026eb0c104004c0fcc104008c0f80648894cc09cc94ccc0e800440044cc0f524115446174756d206d75737420626520636f7272656374000013375e00266e95200033033304101233033304101133033304101033033374e6605e0086eb0c10403ccc0ccc104038cc0ccc104c1000380b454cc09cc94ccc0e800440044cc0f524011756616c75652073686f756c6420626520636f7272656374000013375e6e98dd5982098200099ba6003132533303a001100113303d4901224d7573742062652073656e7420746f2050726f706f73616c27732061646472657373000013375e01a0042930a9981ca481325369676e6174757265732061726520636f72726563746c7920616464656420746f20636f7369676e6174757265206c6973740016153303949135416c6c206e657720636f7369676e65727320617265207769746e6573736564206279207468656972205374616b6520646174756d7300161533039491254173206d616e79206e657720636f7369676e657273206173205374616b6520646174756d73001615330394911b5369676e656420627920616c6c206e657720636f7369676e6572730016153303949114436f7369676e6572732061726520756e6971756500163758607a002293181f001181b8009baa0171533035490116535420617420696e70757473206d757374206265203100163223303022533303300114a02a66606a66ebcc0f400400c52889801181e0008011bac303901032333021004375c60720026eb8c0e4c0e0004c0e4004cc88cdd2a4000660566ea4008cc0acdd4800812a451c55d252e7c81d25e68ed198a26f07832f838c4d1f078e7d28bd18c2090048811c9aecaa97f19230ddfcd5c67fdab9a032783ed2aba422362b68a6325300332233330283752004002466604c4466e00dd6981e801000a40006eac004520004891c8fd3f732c764550a66a42f31e5936b6dc9f85eabb5c08c774cebd3cd00001323330232233223332223302b3302c232333302f0010032337600046e98cc018dd598210019bab0011002303a0010023302e230313303223375e60760026076004006002446605466056464666605c002006466ec0008dd419b80375a60820066eb40044008c0e4004008cc0b48c0c0cc0c48cdd7981d000981d0010018008010009bab32303930380013039323039303800130390020010300013758606a01c606800c606400260620026060002605e002606001a605e0022a660509214f5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f50726f706f73616c2f536372697074732e68733a3132393a352d3135001615330284914f5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f50726f706f73616c2f536372697074732e68733a3132373a352d34340016302e002302700137546054605200a604c6050002604c604e0026050002604c002604c00246602800201cae8ccc0508894ccc06000440084cc00ccdc000124004604200290001180a11299980b8008a51153300730033021001130023020001223333003002001222300200312250010012233330122253330150011225001153330173371e646eb8c080c084004c07c004010488c008c08400c4c008c078004004888c008c080c07cc08400c489400488ccc05000800400c52811119998020010019199980300100090008a400029000111999802119b8f375c602c00200600244460046eacc07800c489400488cccc00c8cdc79bae30150010030012223002375a603a006244a0026014444a66601c002244a0022a6660206004603000224460046034006266006004602e00297ae022300a22533300d0011003133004301700130023016001223300922533300c00110031330083016001300230150010022300722533300a001100e133006300330140013002301300122333300722533300a00112250011533300c3375e602060280020082446004602c0062600460260020024446004602e006244a0024600a44a6660100022018264a666016600800226600a002600660240042600660240046024002ae808ccc018005282512300222533300500114a02a6600c6006601e00226004601c002464600446600400400246004466004004002aae7c8cc00800400d5cd0a514bd6f7b6302b9c5573a460066004002460046004002ae895d0918011baa0015573c1"},"stakePolicyInfo":{"currencySymbol":"55d252e7c81d25e68ed198a26f07832f838c4d1f078e7d28bd18c209","policy":"5906fa0100003232323232323232323232323232323232323232323232323232323232323232323232323232323232323232223232323232323232533302e3370e90000010991919299981899b884800000454ccc0c4cdc3801240002a66606266e1c00520021533303132323302f2323232323253330393370e90010010a9981e2481455061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5574696c732e68733a3533393a31352d3233001613333020375c607e0026eb0c0fcc08cc0f801c8ccc024dd5982000298200030008a503040002303b001375460786076002607400260760026eb0c0e4004c0dcc0e402c888c8c8c94ccc0dccdc3a400000429404c8c94cc09ccc88cc0e0894ccc0ec0045280a99981e99baf304300100314a22600460840020046eb0c0fcc098c0f803cc0fcc0f800854cc09ccc058020cc0b0c070dd6981f80119980f005800a40042a6604e6602c0106605860386eb4c0fc008ccc07802c0052002132337126660320020180046660320120180046605860386eb4c0fc008ccc07802c0052002375c607c004607c008607c00460720026ea8c0e8c0ec008526153303449012941205554584f206d75737420657869737420776974682074686520636f7272656374206f7574707574001615330344911b4d696e746564205354206d7573742062652065786163746c7920310016153303449116535420617420696e70757473206d75737420626520300016153330313370e00490010a99981899b87001337029000240042a66606264646605e46464646464a66607266e1d200000213333020375c607e0026eb0c0fcc8c0fcc090004c0f801c8ccc024dd5982000298200030008a50153303c4901455061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5574696c732e68733a3538393a31352d323300163040002303b00137546078607600260740026076646076607400260760026eb0c0e4004c0e402c8894cc088cdc399812803001a40042605e64a66606600229405289bac303a3024303b001149854cc0d12412a416e20756e6c6f636b656420696e707574206578697374656420636f6e7461696e696e6720616e20535400161533034491095354206275726e65640016153303449116535420617420696e70757473206d75737420626520310016330210023756606c00c6604000264666044446604a6eacc8c0e4c0e0004c0e4c8c0e4c0e0004c0e40080040c8004dd6181a8039bae3034001153303149014b5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5374616b652f536372697074732e68733a38313a352d323300163035002303000137546062606000a605e605e002605c002605e002605a002605a0024466e24c00c004c00c0088ccc00800401c01c888cccc01000800c8cccc01800800480045200014800088cccc0108cdc79bae302700100300122230023756605a006244a002446666006466e3cdd7181300080180091118011bad302c0031225001301b222533301f001122500115333021300230270011223002302900313300300230260012333003002002001489002223004337606ea400cdd3180219bb037520046ea00048cc05400407488cccc00c008004888c00800c489400400488cccc058894ccc064004489400454ccc06ccdc7991bae30223023001302100100412230023023003130023020001001222300230223021302300312250012301c30020012301b30020012301a30020012301930040012233301400200100314a04602c602c0024466660126ea40080048ccc01088cdc01bad301c00200148000dd58008a400044601a44a666020002200626600860300026004602e002446660064466600ce00008004008004888cc014cc0188c8cccc02400400c8cdd80011ba633006375660340066eac0044008c050004008cc0208c02ccc0308cdd7980a800980a80100180091119802198029191999804000801919bb000237506600c6eb4c06400cdd680088011809800801198039180519805919baf30140013014002003001223300922533300c00110031330083014001300230130010022300722533300a001100e133006300330120013002301100122333300722533300a00112250011533300c3375e60206024002008244600460280062600460220020024446004602a006244a0024600a44a6660100022018264a666016600800226600a002600660200042600660200046020002ae808ccc018005282512300222533300500114a02a6600c6006601a002260046018002464600446600400400246004466004004002aae7c8cc00800400d5cd0a514bd6f7b6302b9c5573aae895d0918011baa0015573c1"},"stakeValidatorInfo":{"hash":"9aecaa97f19230ddfcd5c67fdab9a032783ed2aba422362b68a63253","script":"590b7101000032323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232222323232323232323232533303e3370e90010010999991199181f91299982100089128008999801982480091118010018980118240009299982099baf003304730480011223002003122500100130440013758608800e464646464646464646464a66609266e1d2000002153330493370e00c90010a999824981f0020a9998248048a9998249980f009911191929981599299982780088008998292490f697353637269707441646472657373000013375e00801e2a6605664a66609e00220022660a405e002a6605666ebcc150c14c050c150c14c00854cc0accdc399b80375a60a80280026eb4c1500084cdc4a40006eb4c1500084c94ccc13c00440044cc1480b80054cc0accc074014cc0f0c8dd5982a982a182b000982a1829982a808180f8008a998159980e8029981e191bab305530543056001305430533055010301f00113233712660920980026609209800c66078646eacc154c150c158004c150c14cc154040c07c004dd6982980298298008a4c2a660980542c2a660980562c2a660980582c2a6609805a2c2a66609266e1d200400215333049009153330493370e00a90010a9998249980f009911299982619baf00200d132533304d001100113305002c0013375e6e98c8dd598299829182a0009829182898298071ba600314a02930a998260150b0a9982600e8b0a998260158b0a99982499b874801800854ccc12402454ccc124cdc3803240042a66609266e1c0152002153330493301e013222533304c3375e00401a264a66609a00220022660a005800266ebcdd3191bab30533052305400130523051305300e374c0062940526153304c02a16153304c01d16153304c02d16153304c02b16153330493370e90040010a99982499b870064800854ccc1254cc1000244cc88cdc42400066608e4466e00004cc88c8ccc0dc00cdd7182b0009bae305630550013056001375660a660a460a86460a860a600260a8004008900000099ba548000cc0f4dd481a9981e9ba90360343758609e0242a6660926603c026444a66609866ebc00803454cc0a4c94ccc13400440044cc1400b0004cdd79ba632375660a660a460a800260a460a260a601c6e9800c4c94ccc13400440044cc1400b4004cdd780080d0a50149854cc1300a85854cc1312401374f776e6572207369676e732074686973207472616e73616374696f6e204f522070726f706f73616c20746f6b656e206973207370656e740016153304c02d16153330493370e00c90010a99982499b87008337029000240042a666092607c0082a6660920122930a998260158b0a998260160b0a9982624810e53686f756c64206275726e2053540016153304c02d163050002304b00137546464646402ea66609266e1d20000021323232324994ccc12c004526153304f031163050003375a002609e0022a66609266e1d20020021324994ccc120004526153304c02e16153330493370e9002001099191924ca6660940022930a998270180b182780118278008a99982499b87480180084c8c8c8c926533304b001149854cc13c0c458c14000ccc8c124894ccc13000440e04cc104c00cc14c004c008c1480048c8c8c8c80154ccc138cdc3a40000042646464646464649329998298008a4c2a660ae0722c60b00066eb4004c15c004c15400cdd6800982a0008a9982881a0b182a80118280009baa0013758002609e0022a66609266e1d20080021324994ccc120004526153304c02e16153304c02f163050002304b001375402664a66608a00229405289bac304b3047304c013323233302d004375c60980026eb8c130c12c004c130004cdd2a4000660706ea40c0cc0e0dd48188179981e8200009919981f91198199bab32304c304b001304c32304c304b001304c00200103c001375860900166607607c6eacc11c020cc88cc100894ccc10c0045280a99982219baf304a00100314a22600460920020046eb0c118c104c11401cc118c114018c8c118c11c004c114c110c11800454cc10524014c5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5374616b652f536372697074732e68733a3232393a352d3138001615330414914c5061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5374616b652f536372697074732e68733a3232373a352d323200163045002304000137546082608000c6082010607c607c002607a002607c002607800260780024466e24c00c004c00c0088ccc0600040780788ccc008074074004888c010cdd81ba9003374c600866ec0dd48011ba800123302200102749011150726f706f73616c205354207370656e74002232330242323232323253330343370e90010010a9981b8050b099998059bae303a00137586074601c607200e46660126eacc0ec014c0ec018004528181d801181b0009baa30373036001303500130360013758606800260646068004921455061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f5574696c732e68733a3533393a31352d3233002233330030020012223002003122500100122333302722533302a00112250011533302b3371e646eb8c0c8c0cc004c0c4004010488c008c0cc00c4c008c0c0004004888c008c0c8c0c4c0cc00c48940048c0b0c0080048c0acc0080048c0a8c09800488ccc09400800400c5282490c76616c7565436f727265637400490112636f72726563744f7574707574446174756d0049012941205554584f206d75737420657869737420776974682074686520636f7272656374206f75747075740049011c4f776e6572207369676e732074686973207472616e73616374696f6e0049010e5374616b6520756e6c6f636b656400490116535420617420696e70757473206d7573742062652031004901186c697374206973206c6f6e676572207468616e207a65726f0049013f7265616368656420656e64206f662073756d207768696c65207374696c6c206e6f7420686176696e6720666f756e642074686520636f6e7374727563746f720022233330040020032333300600200120011480005200022333300423371e6eb8c07c00400c004888c008dd598128018912800911999801919b8f375c603c00200600244460046eb4c09000c4894004c0508894ccc060004489400454ccc064c008c07c004488c008c08400c4cc00c008c0780052f5c091011c8fd3f732c764550a66a42f31e5936b6dc9f85eabb5c08c774cebd3cd0048810022333003223330067000040020040024446600a6600c4646666022002006466ec0008dd3198031bab301f0033756002200460320020046600e4601466016466ebcc068004c06800800c004888cc010cc0148c8cccc04000400c8cdd80011ba833006375a603c0066eb40044008c060004008cc0188c024cc0288cdd7980c800980c80100180091198079129998090008801899803980c8009801180c0008011180691299980800088050998029801980b8009801180b000918061129998078008804899299980898020008998028009801980b00109801980b001180b000aba0233300c0014a09448c024894ccc0300045280a99802180198098008980118090009198050008010a514bd6f7b6301119998019ba9002001233300522337006eb4c04c008005200037560022900011199980291299980400089128008a99980499baf300d300f0010041223002301100313002300e0010012223002301200312250012230042253330070011003133004300e0013002300d00148811c55d252e7c81d25e68ed198a26f07832f838c4d1f078e7d28bd18c209002323002233002002001230022330020020015573eae688c014c0080048c010c0100055ce2ab9d5744ae848c008dd5000aab9e1"},"treasuryValidatorInfo":{"hash":"39ad85486d8624a49f080636324f80938febfb91552709c70a3ac00e","script":"590338010000323232323232323232323232323222232323232533300e3370e90000010992999807a99806992999808000880089980924813373696e676c65417574686f72697479546f6b656e4275726e65643a204d757374206275726e2065786163746c79203120474154000013370e6466660146ea40440048ccc88c03c894ccc048004400c4cc010c064004c008c06800488cdc01bad301a00200148000dd58008a40006eacc04cc8c054c054c054004c054004cdc0a400090010992999808000880089980924814673696e676c65417574686f72697479546f6b656e4275726e65643a20416c6c2047415420746f6b656e73206d7573742062652076616c69642061742074686520696e70757473000013300a23232333300c37520266eacc058c05c0048c8c8c94ccc058cdc3a4004004264a66602e00220022660329213e617574686f72697479546f6b656e7356616c6964496e3a2047415420546f6b656e4e616d6520646f65736e2774206d617463682053637269707448617368000013301123375e60340026ea4dd7180d8011bab0041325333017001100113301949137617574686f72697479546f6b656e7356616c6964496e3a2047415420696e636f72726563746c79206c69766573206174205075624b6579000014a06038004602e0026ea8c05cc064c05c008528980b80099180a980b000980b0009bac30133015001149854cc04524128412073696e676c6520617574686f7269747920746f6b656e20686173206265656e206275726e65640016301200415330104901465061747465726e206d61746368206661696c75726520696e2027646f2720626c6f636b2061742061676f72612f41676f72612f54726561737572792e68733a38353a332d313200163014002300f0013754601e6020002602000244666600844a66600e002244a0022a66601466ebcc034c038004010488c008c04000c4c008c03c004004888c008c04800c48940048c008894ccc0140045288a9980318019806000898011806800919180111980100100091801119801001000aab9f2233300400200100314a0ae692211c6aa44a4ea34725d9bcf9b522babe6b6b94ae55eddac23b9ccaa94427005738aae755d0aba2230023754002aae781"}} \ No newline at end of file diff --git a/agora.cabal b/agora.cabal index be3d748..3d83793 100644 --- a/agora.cabal +++ b/agora.cabal @@ -145,11 +145,10 @@ library Agora.Treasury Agora.Utils Agora.Utils.Value - Agora.ScriptInfo - Agora.Aeson.Orphans other-modules: + Agora.Aeson.Orphans hs-source-dirs: agora library pprelude diff --git a/agora/Agora/ScriptInfo.hs b/agora/Agora/ScriptInfo.hs index 54a709e..f9643ae 100644 --- a/agora/Agora/ScriptInfo.hs +++ b/agora/Agora/ScriptInfo.hs @@ -25,7 +25,9 @@ import Plutus.V1.Ledger.Value (CurrencySymbol) -- | Bundle containing a 'Validator' and its hash. data ValidatorInfo = ValidatorInfo { script :: Validator + -- ^ The validator script. , hash :: ValidatorHash + -- ^ Hash of the validator. } deriving stock (Show, Eq, GHC.Generic) deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) @@ -43,7 +45,9 @@ mkValidatorInfo term = -- | Bundle containing a 'MintingPolicy' and its symbol. data PolicyInfo = PolicyInfo { policy :: MintingPolicy + -- ^ The minting policy. , currencySymbol :: CurrencySymbol + -- ^ The symbol given by the minting policy. } deriving stock (Show, Eq, GHC.Generic) deriving anyclass (Aeson.ToJSON, Aeson.FromJSON) diff --git a/agora/Agora/Treasury.hs b/agora/Agora/Treasury.hs index f472243..475c936 100644 --- a/agora/Agora/Treasury.hs +++ b/agora/Agora/Treasury.hs @@ -72,6 +72,7 @@ deriving via do so in a valid manner. -} treasuryValidator :: + -- | Governance Authority Token that can unlock this validator. CurrencySymbol -> ClosedTerm PValidator treasuryValidator gatCs' = plam $ \_datum redeemer ctx' -> unTermCont $ do