From 2865f2f093a6e10df38033d016de942e4030d260 Mon Sep 17 00:00:00 2001 From: Emily Martins Date: Wed, 27 Apr 2022 14:43:55 +0200 Subject: [PATCH] be more consistent in use of "period", "state", etc wrt. proposals - specify maximum cosigners requirement in spec. - remove silly qualified names in Proposal impl. --- agora-test/Spec/Sample/Shared.hs | 1 + agora/Agora/Proposal.hs | 18 +++++++++++------- agora/Agora/Proposal/Scripts.hs | 30 +++++++++++++++--------------- docs/tech-design/proposals.md | 24 ++++++++++++------------ 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/agora-test/Spec/Sample/Shared.hs b/agora-test/Spec/Sample/Shared.hs index 37b1afc..516435b 100644 --- a/agora-test/Spec/Sample/Shared.hs +++ b/agora-test/Spec/Sample/Shared.hs @@ -107,6 +107,7 @@ proposal = Value.assetClass govSymbol "" , stakeSTAssetClass = Value.assetClass stakeSymbol "" + , maximumCosigners = 6 } proposalPolicySymbol :: CurrencySymbol diff --git a/agora/Agora/Proposal.hs b/agora/Agora/Proposal.hs index a383b00..d7c6e35 100644 --- a/agora/Agora/Proposal.hs +++ b/agora/Agora/Proposal.hs @@ -84,8 +84,10 @@ newtype ResultTag = ResultTag {getResultTag :: Integer} deriving stock (Eq, Show, Ord) deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData) -{- | The "status" of the proposal. This is only useful for state transitions, - as opposed to time-based "phases". +{- | The "status" of the proposal. This is only useful for state transitions that + need to happen as a result of a transaction as opposed to time-based "periods". + + See the note on wording & the state machine in the tech-design. If the proposal is 'VotingReady', for instance, that doesn't necessarily mean that voting is possible, as this also requires the timing to be right. @@ -220,7 +222,7 @@ data ProposalRedeemer -- === @* -> 'Finished'@: -- -- If the proposal has run out of time for the current 'ProposalStatus', it will always be possible - -- to transition into 'Finished' state, because it has expired (and failed). + -- to transition into 'Finished' status, because it has expired (and failed). AdvanceProposal deriving stock (Eq, Show, GHC.Generic) @@ -236,6 +238,8 @@ PlutusTx.makeIsDataIndexed data Proposal = Proposal { governorSTAssetClass :: AssetClass , stakeSTAssetClass :: AssetClass + , maximumCosigners :: Integer + -- ^ Arbitrary limit for maximum amount of cosigners on a proposal. } deriving stock (Show, Eq) @@ -395,11 +399,11 @@ deriving via (DerivePConstantViaData ProposalRedeemer PProposalRedeemer) instanc -------------------------------------------------------------------------------- {- | Check for various invariants a proposal must uphold. - This can be used to check both upopn creation and + This can be used to check both upon creation and upon any following state transitions in the proposal. -} -proposalDatumValid :: Term s (Agora.Proposal.PProposalDatum :--> PBool) -proposalDatumValid = +proposalDatumValid :: Proposal -> Term s (Agora.Proposal.PProposalDatum :--> PBool) +proposalDatumValid proposal = phoistAcyclic $ plam $ \datum' -> P.do datum <- pletFields @'["effects", "cosigners"] $ datum' @@ -420,5 +424,5 @@ proposalDatumValid = (#&&) [ ptraceIfFalse "Proposal has at least one ResultTag has no effects" atLeastOneNegativeResult , ptraceIfFalse "Proposal has at least one cosigner" $ pnotNull # pfromData datum.cosigners - , ptraceIfFalse "Proposal has at most five cosigners" $ plength # (pfromData datum.cosigners) #< 6 + , ptraceIfFalse "Proposal has at most five cosigners" $ plength # (pfromData datum.cosigners) #<= pconstant proposal.maximumCosigners ] diff --git a/agora/Agora/Proposal/Scripts.hs b/agora/Agora/Proposal/Scripts.hs index 44612be..3535260 100644 --- a/agora/Agora/Proposal/Scripts.hs +++ b/agora/Agora/Proposal/Scripts.hs @@ -48,20 +48,20 @@ import Plutus.V1.Ledger.Value (AssetClass (AssetClass)) NOTE: The governor needs to check that the datum is correct and sent to the right address. -} -proposalPolicy :: Agora.Proposal.Proposal -> ClosedTerm Plutarch.Api.V1.PMintingPolicy +proposalPolicy :: Proposal -> ClosedTerm PMintingPolicy proposalPolicy proposal = plam $ \_redeemer ctx' -> P.do - Plutarch.Api.V1.PScriptContext ctx' <- pmatch ctx' + PScriptContext ctx' <- pmatch ctx' ctx <- pletFields @'["txInfo", "purpose"] ctx' - Plutarch.Api.V1.PTxInfo txInfo' <- pmatch $ pfromData ctx.txInfo + PTxInfo txInfo' <- pmatch $ pfromData ctx.txInfo txInfo <- pletFields @'["inputs", "mint"] txInfo' - Plutarch.Api.V1.PMinting _ownSymbol <- pmatch $ pfromData ctx.purpose + PMinting _ownSymbol <- pmatch $ pfromData ctx.purpose let inputs = txInfo.inputs mintedValue = pfromData txInfo.mint AssetClass (govCs, govTn) = proposal.governorSTAssetClass - Plutarch.Api.V1.PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose + PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose let mintedProposalST = passetClassValueOf # mintedValue # (passetClass # (pfield @"_0" # ownSymbol') # pconstant "") passert "Governance state-thread token must move" $ @@ -75,15 +75,15 @@ proposalPolicy proposal = popaque (pconstant ()) -- | Validator for Proposals. -proposalValidator :: Agora.Proposal.Proposal -> ClosedTerm Plutarch.Api.V1.PValidator +proposalValidator :: Proposal -> ClosedTerm PValidator proposalValidator proposal = plam $ \datum redeemer ctx' -> P.do - Plutarch.Api.V1.PScriptContext ctx' <- pmatch ctx' + PScriptContext ctx' <- pmatch ctx' ctx <- pletFields @'["txInfo", "purpose"] ctx' txInfo <- plet $ pfromData ctx.txInfo - Plutarch.Api.V1.PTxInfo txInfo' <- pmatch txInfo + PTxInfo txInfo' <- pmatch txInfo txInfoF <- pletFields @'["inputs", "mint", "datums", "signatories"] txInfo' - Plutarch.Api.V1.PSpending ((pfield @"_0" #) -> txOutRef) <- pmatch $ pfromData ctx.purpose + PSpending ((pfield @"_0" #) -> txOutRef) <- pmatch $ pfromData ctx.purpose PJust txOut <- pmatch $ findTxOutByTxOutRef # txOutRef # txInfoF.inputs txOutF <- pletFields @'["address", "value"] $ txOut @@ -114,13 +114,13 @@ proposalValidator proposal = signedBy <- plet $ ptxSignedBy # txInfoF.signatories pmatch proposalRedeemer $ \case - Agora.Proposal.PVote _r -> P.do + PVote _r -> P.do passert "ST at inputs must be 1" $ spentST #== 1 popaque (pconstant ()) -------------------------------------------------------------------------- - Agora.Proposal.PCosign r -> P.do + PCosign r -> P.do newSigs <- plet $ pfield @"newCosigners" # r passert "ST at inputs must be 1" $ @@ -143,14 +143,14 @@ proposalValidator proposal = # newSigs passert "Signatures are correctly added to cosignature list" $ - anyOutput @Agora.Proposal.PProposalDatum # ctx.txInfo + anyOutput @PProposalDatum # ctx.txInfo #$ plam $ \newValue address newProposalDatum -> P.do let correctDatum = pdata newProposalDatum #== pdata ( mkRecordConstr - Agora.Proposal.PProposalDatum + PProposalDatum ( #proposalId .= proposalF.proposalId .& #effects .= proposalF.effects .& #status .= proposalF.status @@ -170,13 +170,13 @@ proposalValidator proposal = popaque (pconstant ()) -------------------------------------------------------------------------- - Agora.Proposal.PUnlock _r -> P.do + PUnlock _r -> P.do passert "ST at inputs must be 1" $ spentST #== 1 popaque (pconstant ()) -------------------------------------------------------------------------- - Agora.Proposal.PAdvanceProposal _r -> P.do + PAdvanceProposal _r -> P.do passert "ST at inputs must be 1" $ spentST #== 1 diff --git a/docs/tech-design/proposals.md b/docs/tech-design/proposals.md index 6098795..2be2a23 100644 --- a/docs/tech-design/proposals.md +++ b/docs/tech-design/proposals.md @@ -35,9 +35,9 @@ Initiating a proposal requires the proposer to have more than a certain amount o ### Voting stages -The life-cycle of a proposal is neatly represented by a state machine, with the 'draft' phase being the initial state, and 'executed' and 'failed' being the terminating states. +The life-cycle of a proposal is neatly represented by a state machine, with the 'draft' state being the initial state, and 'executed' and 'failed' being the terminating states. -**Please note that this state-machine representation is purely conceptual and should not be expected to reflect technical implementation.** This is because some state transitions in the state machine representation don't need to happen in the actual implementation as a transaction. A key example is going from the "lock" phase to the "execution" phase. The only thing that needs to happen is that time goes by. So under the hood, they are represented the same in the Proposal's datum. +**Please note that this state-machine representation is purely conceptual and should not be expected to reflect technical implementation.** This is because some state transitions in the state machine representation don't need to happen in the actual implementation as a transaction. A key example is going from the "lock" phase to the "execution" phase. The only thing that needs to happen is that time goes by. So under the hood, they are represented the same in the Proposal's datum. Furthermore, in order to make our wording consistent, we use _"period"_ to mean a time-based, and _"status"_ to mean what is encoded in the datum. "State", then, refers to the more vague notion of what the state machine would look like. > Emily 2022-04-27: This is quite confusing still, I feel. @Jack, could you try to reword this and make it more clear? @@ -54,21 +54,21 @@ Consider the following 'stages' of a proposal: - `L`: the length of the locking period. - `E`: the length of the execution period. -| Action | Valid POSIXTimeRange | Valid _stored_ state(s) | -|-------------------------------------|-------------------------------------|-------------------------| -| Witness | \[S, ∞) | \* | -| Cosign | \[S, S + D) | Draft | -| AdvanceProposal | \[S, S + D) | Draft | -| Vote | \[S + D, S + D + V) | Voting | -| Unlock | \[S + D, ∞) | \* | -| CountVotes | \[S + D + V, S + D + V + L) | Voting | -| ExecuteProposal (if quorum reached) | \[S + D + V + L, S + D + V + L + E) | Voting | +| Action | Valid POSIXTimeRange | Valid _stored_ status(es) | +|-------------------------------------|-------------------------------------|---------------------------| +| Witness | \[S, ∞) | \* | +| Cosign | \[S, S + D) | Draft | +| AdvanceProposal | \[S, S + D) | Draft | +| Vote | \[S + D, S + D + V) | Voting | +| Unlock | \[S + D, ∞) | \* | +| CountVotes | \[S + D + V, S + D + V + L) | Voting | +| ExecuteProposal (if quorum reached) | \[S + D + V + L, S + D + V + L + E) | Voting | > Jack 2022-02-02: I will consider revising this table further at a later time. #### Draft phase -During the draft phase, a new UTXO at the proposal script has been created. At this stage, only votes in favor of co-signing the draft are counted. For the proposal to transition to the voting phase, a threshold of GT will have to be staked backing the proposal. This threshold will be determined on a per-system basis and could itself be a 'governable' parameter. It's important to note that cosignatures are not locking votes. Cosignatures are more like a delegated approval to a proposal. The sum of all cosignatures must tally to the threshold, and all cosigner stake datums must fit into a single transaction to witness their size. +During the draft phase, a new UTXO at the proposal script has been created. At this stage, only votes in favor of co-signing the draft are counted. For the proposal to transition to the voting phase, a threshold of GT will have to be staked backing the proposal. This threshold will be determined on a per-system basis and could itself be a 'governable' parameter. It's important to note that cosignatures are not locking votes. Cosignatures are more like a delegated approval to a proposal. The sum of all cosignatures must tally to the threshold, and all cosigner stake datums must fit into a single transaction to witness their size. A limit on the maximum amount of cosigners is placed in order to prevent a situation where the stake datums no longer fit in the transaction. The number doesn't matter and may be expressed in a parametrized way. #### Voting phase