Merge pull request #100 from Liqwid-Labs/connor/advance-proposals

Advance proposals
This commit is contained in:
方泓睿 2022-05-31 00:37:34 +08:00 committed by GitHub
commit 55b0669d41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 831 additions and 78 deletions

View file

@ -45,7 +45,6 @@ import Plutus.V1.Ledger.Value qualified as Value
import Sample.Shared (
authorityTokenSymbol,
defaultProposalThresholds,
govAssetClass,
govValidatorAddress,
governor,
@ -113,7 +112,7 @@ mkEffectTxInfo newGovDatum =
governorInputDatum' :: GovernorDatum
governorInputDatum' =
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 0
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -175,7 +174,7 @@ mkEffectTxInfo newGovDatum =
validNewGovernorDatum :: GovernorDatum
validNewGovernorDatum =
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 42
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -185,7 +184,7 @@ invalidNewGovernorDatum :: GovernorDatum
invalidNewGovernorDatum =
GovernorDatum
{ proposalThresholds =
defaultProposalThresholds
def
{ countVoting = Tagged (-1)
}
, nextProposalId = ProposalId 42

View file

@ -70,7 +70,6 @@ import Agora.Stake (
import Sample.Shared (
authorityTokenSymbol,
defaultProposalThresholds,
govAssetClass,
govSymbol,
govValidatorAddress,
@ -119,7 +118,7 @@ mintGST =
governorOutputDatum' :: GovernorDatum
governorOutputDatum' =
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 0
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -214,7 +213,7 @@ createProposal =
governorInputDatum' :: GovernorDatum
governorInputDatum' =
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = thisProposalId
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -245,7 +244,7 @@ createProposal =
, effects = effects
, status = Draft
, cosigners = [signer]
, thresholds = defaultProposalThresholds
, thresholds = def
, votes = emptyVotesFor effects
, timingConfig = def
, startingTime = proposalStartingTimeFromTimeRange validTimeRange
@ -392,7 +391,7 @@ mintGATs =
governorInputDatum' :: GovernorDatum
governorInputDatum' =
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 5
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -428,7 +427,7 @@ mintGATs =
, effects = effects
, status = Locked
, cosigners = [signer, signer2]
, thresholds = defaultProposalThresholds
, thresholds = def
, votes = proposalVotes
, timingConfig = def
, startingTime = ProposalStartingTime 10
@ -587,7 +586,7 @@ mutateState =
governorInputDatum' :: GovernorDatum
governorInputDatum' =
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 5
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def

View file

@ -13,6 +13,12 @@ module Sample.Proposal (
stakeRef,
voteOnProposal,
VotingParameters (..),
advanceProposalSuccess,
advanceProposalFailureTimeout,
TransitionParameters (..),
advanceFinishedPropsoal,
advanceProposalInsufficientVotes,
advancePropsoalWithsStake,
) where
--------------------------------------------------------------------------------
@ -27,6 +33,8 @@ import Plutus.V1.Ledger.Api (
Address (Address),
Credential (ScriptCredential),
Datum (Datum),
DatumHash,
POSIXTime,
POSIXTimeRange,
PubKeyHash,
ScriptContext (..),
@ -36,6 +44,7 @@ import Plutus.V1.Ledger.Api (
TxInfo (..),
TxOut (TxOut, txOutAddress, txOutDatumHash, txOutValue),
TxOutRef (TxOutRef),
ValidatorHash,
)
import Plutus.V1.Ledger.Value qualified as Value
import PlutusTx.AssocMap qualified as AssocMap
@ -50,6 +59,7 @@ import Agora.Proposal (
ProposalDatum (..),
ProposalId (..),
ProposalStatus (..),
ProposalThresholds (..),
ProposalVotes (..),
ResultTag (..),
emptyVotesFor,
@ -84,7 +94,7 @@ proposalCreation =
, effects = effects
, status = Draft
, cosigners = [signer]
, thresholds = defaultProposalThresholds
, thresholds = def
, votes = emptyVotesFor effects
, timingConfig = def
, startingTime = proposalStartingTimeFromTimeRange validTimeRange
@ -96,7 +106,7 @@ proposalCreation =
Datum
( toBuiltinData $
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 0
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -107,7 +117,7 @@ proposalCreation =
Datum
( toBuiltinData $
GovernorDatum
{ proposalThresholds = defaultProposalThresholds
{ proposalThresholds = def
, nextProposalId = ProposalId 1
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
@ -185,7 +195,7 @@ cosignProposal newSigners =
, effects = effects
, status = Draft
, cosigners = [signer]
, thresholds = defaultProposalThresholds
, thresholds = def
, votes = emptyVotesFor effects
, timingConfig = def
, startingTime = ProposalStartingTime 0
@ -306,7 +316,7 @@ voteOnProposal params =
, effects = effects
, status = VotingReady
, cosigners = [stakeOwner]
, thresholds = defaultProposalThresholds
, thresholds = def
, votes = ProposalVotes initialVotes
, timingConfig = def
, startingTime = ProposalStartingTime 0
@ -375,7 +385,7 @@ voteOnProposal params =
---
-- Off-chain code should do exactly like this: prepend new lock to the list.
-- Off-chain code should do exactly like this: prepend new lock toStatus the list.
updatedLocks :: [ProposalLock]
updatedLocks = ProposalLock params.voteFor proposalInputDatum'.proposalId : existingLocks
@ -415,3 +425,398 @@ voteOnProposal params =
, txInfoData = datumPair <$> [proposalInputDatum, proposalOutputDatum, stakeInputDatum, stakeOutputDatum]
, txInfoId = "827598fb2d69a896bbd9e645bb14c307df907f422b39eecbe4d6329bc30b428c"
}
--------------------------------------------------------------------------------
-- | Parameters for state transition of proposals.
data TransitionParameters = TransitionParameters
{ -- The initial status of the propsoal.
initialProposalStatus :: ProposalStatus
, -- The starting time of the propsoal.
proposalStartingTime :: ProposalStartingTime
}
-- | Create a 'TxInfo' that update the status of a proposal.
mkTransitionTxInfo ::
-- | Initial state of the proposal.
ProposalStatus ->
-- | Next state of the proposal.
ProposalStatus ->
-- | Effects.
AssocMap.Map ResultTag (AssocMap.Map ValidatorHash DatumHash) ->
-- | Votes.
ProposalVotes ->
-- | Starting time of the proposal.
ProposalStartingTime ->
-- | Valid time range of the transaction.
POSIXTimeRange ->
TxInfo
mkTransitionTxInfo from to effects votes startingTime timeRange =
let pst = Value.singleton proposalPolicySymbol "" 1
---
proposalInputDatum' :: ProposalDatum
proposalInputDatum' =
ProposalDatum
{ proposalId = ProposalId 0
, effects = effects
, status = from
, cosigners = [signer]
, thresholds = def
, votes = votes
, timingConfig = def
, startingTime = startingTime
}
proposalInputDatum :: Datum
proposalInputDatum = Datum $ toBuiltinData proposalInputDatum'
proposalInput :: TxOut
proposalInput =
TxOut
{ txOutAddress = proposalValidatorAddress
, txOutValue = pst
, txOutDatumHash = Just $ toDatumHash proposalInputDatum
}
---
proposalOutputDatum' :: ProposalDatum
proposalOutputDatum' =
proposalInputDatum'
{ status = to
}
proposalOutputDatum :: Datum
proposalOutputDatum = Datum $ toBuiltinData proposalOutputDatum'
proposalOutput :: TxOut
proposalOutput =
proposalInput
{ txOutValue = proposalInput.txOutValue <> minAda
, txOutDatumHash = Just $ toDatumHash proposalOutputDatum
}
in TxInfo
{ txInfoInputs = [TxInInfo proposalRef proposalInput]
, txInfoOutputs = [proposalOutput]
, txInfoFee = Value.singleton "" "" 2
, txInfoMint = mempty
, txInfoDCert = []
, txInfoWdrl = []
, txInfoValidRange = timeRange
, txInfoSignatories = [signer]
, txInfoData = datumPair <$> [proposalInputDatum, proposalOutputDatum]
, txInfoId = "95ba4015e30aef16a3461ea97a779f814aeea6b8009d99a94add4b8293be737a"
}
{- | Create a valid 'TxInfo' that advances a proposal, given the parameters.
Note that 'TransitionParameters.initialProposalStatus' should not be 'Finished'.
-}
advanceProposalSuccess :: TransitionParameters -> TxInfo
advanceProposalSuccess params =
let -- Status of the output proposal.
toStatus :: ProposalStatus
toStatus = case params.initialProposalStatus of
Draft -> VotingReady
VotingReady -> Locked
Locked -> Finished
Finished -> error "Cannot advance 'Finished' proposal"
effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
emptyVotes@(ProposalVotes emptyVotes') = emptyVotesFor effects
-- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
updateMap
(\_ -> Just $ untag (def :: ProposalThresholds).countVoting + 1)
(ResultTag 0)
emptyVotes'
votes :: ProposalVotes
votes = case params.initialProposalStatus of
Draft -> emptyVotes
-- With sufficient votes
_ -> outcome0WinningVotes
proposalStartingTime :: POSIXTime
proposalStartingTime =
let (ProposalStartingTime startingTime) = params.proposalStartingTime
in startingTime
timeRange :: POSIXTimeRange
timeRange = case params.initialProposalStatus of
-- [S + 1, S + D - 1]
Draft ->
closedBoundedInterval
(proposalStartingTime + 1)
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime - 1)
-- [S + D + V + 1, S + D + V + L - 1]
VotingReady ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
- 1
)
-- [S + D + V + L + 1, S + + D + V + L + E - 1]
Locked ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime - 1
)
Finished -> error "Cannot advance 'Finished' proposal"
in mkTransitionTxInfo
params.initialProposalStatus
toStatus
effects
votes
params.proposalStartingTime
timeRange
{- | Create a valid 'TxInfo' that advances a proposal to failed state, given the parameters.
The reason why the proposal fails is the proposal has ran out of time.
Note that 'TransitionParameters.initialProposalStatus' should not be 'Finished'.
-}
advanceProposalFailureTimeout :: TransitionParameters -> TxInfo
advanceProposalFailureTimeout params =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
emptyVotes@(ProposalVotes emptyVotes') = emptyVotesFor effects
-- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
updateMap
(\_ -> Just $ untag (def :: ProposalThresholds).countVoting + 1)
(ResultTag 0)
emptyVotes'
votes :: ProposalVotes
votes = case params.initialProposalStatus of
Draft -> emptyVotes
-- With sufficient votes
_ -> outcome0WinningVotes
proposalStartingTime :: POSIXTime
proposalStartingTime =
let (ProposalStartingTime startingTime) = params.proposalStartingTime
in startingTime
timeRange :: POSIXTimeRange
timeRange = case params.initialProposalStatus of
-- [S + D + 1, S + D + V - 1]
Draft ->
closedBoundedInterval
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime + 1)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime - 1
)
-- [S + D + V + L + 1, S + D + V + L + E -1]
VotingReady ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
- 1
)
-- [S + D + V + L + E + 1, S + D + V + L + E + 100]
Locked ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
+ 100
)
Finished -> error "Cannot advance 'Finished' proposal"
in mkTransitionTxInfo
params.initialProposalStatus
Finished
effects
votes
params.proposalStartingTime
timeRange
-- | An invalid 'TxInfo' that tries to advance a 'VotingReady' proposal without sufficient votes.
advanceProposalInsufficientVotes :: TxInfo
advanceProposalInsufficientVotes =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
-- Insufficient votes.
votes = emptyVotesFor effects
proposalStartingTime = 0
-- Valid time range.
-- [S + D + 1, S + V - 1]
timeRange =
closedBoundedInterval
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime + 1)
(proposalStartingTime + (def :: ProposalTimingConfig).votingTime - 1)
in mkTransitionTxInfo
VotingReady
Locked
effects
votes
(ProposalStartingTime proposalStartingTime)
timeRange
-- | An invalid 'TxInfo' that tries to advance a 'Finished' proposal.
advanceFinishedPropsoal :: TxInfo
advanceFinishedPropsoal =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
-- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
AssocMap.fromList
[ (ResultTag 0, untag (def :: ProposalThresholds).countVoting + 1)
, (ResultTag 1, 0)
]
---
timeRange =
closedBoundedInterval
((def :: ProposalTimingConfig).lockingTime + 1)
((def :: ProposalTimingConfig).executingTime - 1)
in mkTransitionTxInfo
Finished
Finished
effects
outcome0WinningVotes
(ProposalStartingTime 0)
timeRange
{- | An illegal 'TxInfo' that tries to use 'AdvanceProposal' with a stake.
From the perspective of stake validator, the transition is valid,
so the proposal validator should reject this.
-}
advancePropsoalWithsStake :: TxInfo
advancePropsoalWithsStake =
let templateTxInfo =
advanceProposalSuccess
TransitionParameters
{ initialProposalStatus = VotingReady
, proposalStartingTime = ProposalStartingTime 0
}
---
-- Now we create a new lock on an arbitrary stake
sst = Value.assetClassValue stakeAssetClass 1
---
stakeOwner = signer
stakedAmount = 200
---
existingLocks :: [ProposalLock]
existingLocks =
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
]
---
stakeInputDatum' :: StakeDatum
stakeInputDatum' =
StakeDatum
{ stakedAmount = Tagged stakedAmount
, owner = stakeOwner
, lockedBy = existingLocks
}
stakeInputDatum :: Datum
stakeInputDatum = Datum $ toBuiltinData stakeInputDatum'
stakeInput :: TxOut
stakeInput =
TxOut
{ txOutAddress = stakeAddress
, txOutValue =
mconcat
[ sst
, Value.assetClassValue (untag stake.gtClassRef) stakedAmount
, minAda
]
, txOutDatumHash = Just $ toDatumHash stakeInputDatum
}
---
updatedLocks :: [ProposalLock]
updatedLocks = ProposalLock (ResultTag 42) (ProposalId 27) : existingLocks
---
stakeOutputDatum' :: StakeDatum
stakeOutputDatum' =
stakeInputDatum'
{ lockedBy = updatedLocks
}
stakeOutputDatum :: Datum
stakeOutputDatum = Datum $ toBuiltinData stakeOutputDatum'
stakeOutput :: TxOut
stakeOutput =
stakeInput
{ txOutDatumHash = Just $ toDatumHash stakeOutputDatum
}
in templateTxInfo
{ txInfoInputs = TxInInfo stakeRef stakeInput : templateTxInfo.txInfoInputs
, txInfoOutputs = stakeOutput : templateTxInfo.txInfoOutputs
, txInfoData =
(datumPair <$> [stakeInputDatum, stakeOutputDatum])
<> templateTxInfo.txInfoData
, txInfoSignatories = [stakeOwner]
}

View file

@ -33,7 +33,6 @@ module Sample.Shared (
gstUTXORef,
-- ** Proposal
defaultProposalThresholds,
proposal,
proposalPolicySymbol,
proposalValidatorHash,
@ -184,13 +183,16 @@ proposalValidatorHash = proposalValidatorHashFromGovernor governor
proposalValidatorAddress :: Address
proposalValidatorAddress = scriptHashAddress proposalValidatorHash
defaultProposalThresholds :: ProposalThresholds
defaultProposalThresholds =
ProposalThresholds
{ countVoting = Tagged 1000
, create = Tagged 1
, startVoting = Tagged 10
}
{- | Default value of 'Agora.Proposal.ProposalThresholds'.
For testing purpose only.
-}
instance Default ProposalThresholds where
def =
ProposalThresholds
{ countVoting = Tagged 1000
, create = Tagged 1
, startVoting = Tagged 10
}
authorityToken :: AuthorityToken
authorityToken = authorityTokenFromGovernor governor

View file

@ -34,7 +34,7 @@ specs =
"governor validator should pass"
(governorValidator Shared.governor)
( GovernorDatum
Shared.defaultProposalThresholds
def
(ProposalId 0)
def
def
@ -56,7 +56,7 @@ specs =
"governor validator should fail"
(governorValidator Shared.governor)
( GovernorDatum
Shared.defaultProposalThresholds
def
(ProposalId 0)
def
def

View file

@ -44,7 +44,7 @@ specs =
"proposal creation"
(governorValidator Shared.governor)
( GovernorDatum
Shared.defaultProposalThresholds
def
(ProposalId 0)
def
def
@ -55,7 +55,7 @@ specs =
"GATs minting"
(governorValidator Shared.governor)
( GovernorDatum
Shared.defaultProposalThresholds
def
(ProposalId 5)
def
def
@ -66,7 +66,7 @@ specs =
"mutate governor state"
(governorValidator Shared.governor)
( GovernorDatum
Shared.defaultProposalThresholds
def
(ProposalId 5)
def
def

View file

@ -15,8 +15,9 @@ import Agora.Proposal (
Proposal (..),
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (Cosign, Vote),
ProposalStatus (Draft, VotingReady),
ProposalRedeemer (..),
ProposalStatus (..),
ProposalThresholds (..),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
cosigners,
@ -39,7 +40,7 @@ import Agora.Stake (
)
import Agora.Stake.Scripts (stakeValidator)
import Data.Default.Class (Default (def))
import Data.Tagged (Tagged (Tagged))
import Data.Tagged (Tagged (Tagged), untag)
import Plutus.V1.Ledger.Api (ScriptContext (..), ScriptPurpose (..))
import PlutusTx.AssocMap qualified as AssocMap
import Sample.Proposal qualified as Proposal
@ -49,6 +50,7 @@ import Spec.Specification (
SpecificationTree,
group,
policySucceedsWith,
validatorFailsWith,
validatorSucceedsWith,
)
@ -81,7 +83,7 @@ specs =
]
, status = Draft
, cosigners = [signer]
, thresholds = Shared.defaultProposalThresholds
, thresholds = def
, votes =
emptyVotesFor $
AssocMap.fromList
@ -115,7 +117,7 @@ specs =
]
, status = VotingReady
, cosigners = [signer]
, thresholds = Shared.defaultProposalThresholds
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
@ -158,5 +160,189 @@ specs =
(Spending Proposal.stakeRef)
)
]
, group
"advancing"
[ group "successfully advance to next state" $
map
( \(name, initialState) ->
validatorSucceedsWith
name
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = initialState
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[
( ResultTag 0
, case initialState of
Draft -> 0
_ -> untag (def :: ProposalThresholds).countVoting + 1
)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
( Proposal.advanceProposalSuccess
Proposal.TransitionParameters
{ Proposal.initialProposalStatus = initialState
, Proposal.proposalStartingTime = ProposalStartingTime 0
}
)
(Spending Proposal.proposalRef)
)
)
[ ("Draft -> VotringReady", Draft)
, ("VotingReady -> Locked", VotingReady)
, ("Locked -> Finished", Locked)
]
, group "successfully advance to failed state: timeout" $
map
( \(name, initialState) ->
validatorSucceedsWith
name
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = initialState
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[
( ResultTag 0
, case initialState of
Draft -> 0
_ -> untag (def :: ProposalThresholds).countVoting + 1
)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
( Proposal.advanceProposalFailureTimeout
Proposal.TransitionParameters
{ Proposal.initialProposalStatus = initialState
, Proposal.proposalStartingTime = ProposalStartingTime 0
}
)
(Spending Proposal.proposalRef)
)
)
[ ("Draft -> Finished", Draft)
, ("VotingReady -> Finished", VotingReady)
, ("Locked -> Finished", Locked)
]
, validatorFailsWith
"illegal: insufficient votes"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = VotingReady
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 0)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
Proposal.advanceProposalInsufficientVotes
(Spending Proposal.proposalRef)
)
, validatorFailsWith
"illegal: initial state is Finished"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = Finished
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, untag (def :: ProposalThresholds).countVoting + 1)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
Proposal.advanceFinishedPropsoal
(Spending Proposal.proposalRef)
)
, validatorFailsWith
"illegal: with stake input"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = VotingReady
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 0)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
Proposal.advancePropsoalWithsStake
(Spending Proposal.proposalRef)
)
]
]
]

View file

@ -52,7 +52,9 @@ import Agora.Proposal (
Proposal (..),
ProposalStatus (Draft, Locked),
pemptyVotesFor,
pneutralOption,
proposalDatumValid,
pwinner,
)
import Agora.Proposal.Scripts (
proposalPolicy,
@ -77,7 +79,6 @@ import Agora.Utils (
mustFindDatum',
pfindTxInByTxOutRef,
pisDJust,
pisJust,
pisUTXOSpent,
psymbolValueOf,
ptryFindDatum,
@ -114,13 +115,11 @@ import Plutarch.Api.V1.AssetClass (
passetClass,
passetClassValueOf,
)
import Plutarch.Extra.Comonad (pextract)
import Plutarch.Extra.Map (
pkeys,
plookup,
plookup',
)
import Plutarch.Extra.TermCont (pmatchC)
import Plutarch.SafeMoney (PDiscrete (..), pvalueDiscrete')
import Plutarch.TryFrom ()
@ -605,35 +604,9 @@ governorValidator gov =
-- TODO: anything else to check here?
-- Find the highest votes and the corresponding tag.
let highestVoteFolder =
phoistAcyclic $
plam
( \pair last' ->
pif
(pisJust # last')
( unTermCont $ do
PJust last <- tcmatch last'
let lastHighestVote = pfromData $ psndBuiltin # last
thisVote = pfromData $ psndBuiltin # pair
pure $ pif (lastHighestVote #< thisVote) (pcon $ PJust pair) last'
)
(pcon $ PJust pair)
)
votesList = pto $ pto $ pfromData proposalInputDatumF.votes
maybeWinner =
pfoldr # highestVoteFolder # pcon PNothing # votesList
winner <- tclet $ mustBePJust # "No winning outcome" # maybeWinner
PDiscrete minimumVotes' <- pmatchC $ pfromData $ pfield @"execute" # proposalInputDatumF.thresholds
let highestVote = pfromData $ psndBuiltin # winner
minimumVotes = pextract # minimumVotes'
tcassert "Higgest vote doesn't meet the minimum requirement" $ minimumVotes #<= highestVote
let finalResultTag = pfromData $ pfstBuiltin # winner
let quorum = pto $ pto $ pfromData $ pfield @"execute" # proposalInputDatumF.thresholds
neutralOption = pneutralOption # proposalInputDatumF.effects
finalResultTag = pwinner # proposalInputDatumF.votes # quorum # neutralOption
-- The effects of the winner outcome.
effectGroup <- tclet $ plookup' # finalResultTag #$ proposalInputDatumF.effects

View file

@ -31,6 +31,8 @@ module Agora.Proposal (
-- * Plutarch helpers
proposalDatumValid,
pemptyVotesFor,
pwinner,
pneutralOption,
) where
import GHC.Generics qualified as GHC
@ -48,7 +50,7 @@ import PlutusTx.AssocMap qualified as AssocMap
import Agora.Proposal.Time (PProposalStartingTime, PProposalTimingConfig, ProposalStartingTime, ProposalTimingConfig)
import Agora.SafeMoney (GTTag)
import Agora.Utils (pkeysEqual, pmapMap, pnotNull)
import Agora.Utils (mustBePJust, pkeysEqual, pmapMap, pnotNull, tclet)
import Control.Applicative (Const)
import Control.Arrow (first)
import Data.Tagged (Tagged)
@ -448,3 +450,98 @@ proposalDatumValid proposal =
, ptraceIfFalse "Proposal has fewer cosigners than the limit" $ plength # (pfromData datum.cosigners) #<= pconstant proposal.maximumCosigners
, ptraceIfFalse "Proposal votes and effects are compatible with each other" $ pkeysEqual # datum.effects # pto (pfromData datum.votes)
]
{- Find the winner result tag, given the votes, the quorum the "neutral" result tag.
The winner should be unambiguous, meaning that if two options have the same highest votes,
the "neutral" option will be the winner.
-}
pwinner ::
Term
s
( PProposalVotes
:--> PInteger
:--> PResultTag
:--> PResultTag
)
pwinner = phoistAcyclic $
plam $ \votes quorum neutral -> unTermCont $ do
winner <- tclet $ phighestVotes # votes
winnerResultTag <- tclet $ pfromData $ pfstBuiltin # winner
highestVotes <- tclet $ pfromData $ psndBuiltin # winner
let l :: Term _ (PBuiltinList _)
l = pto $ pto votes
f ::
Term
_
( PBuiltinPair (PAsData PResultTag) (PAsData PInteger)
:--> PInteger
:--> PInteger
)
f = plam $ \(pfromData . (psndBuiltin #) -> thisVotes) i ->
pif
(thisVotes #== highestVotes)
(i + 1)
i
noDuplicateHighestVotes =
ptraceIfFalse "Ambiguous winner" $
pfoldr # f # 0 # l #== 1
exceedQuorum =
ptraceIfFalse "Highest vote count should exceed the minimum threshold" $
quorum #< highestVotes
pure $
pif
(noDuplicateHighestVotes #&& exceedQuorum)
winnerResultTag
neutral
-- | Find the winning outcome (and the corresponding vote count) given the votes.
phighestVotes ::
Term
s
( PProposalVotes
:--> PBuiltinPair (PAsData PResultTag) (PAsData PInteger)
)
phighestVotes = phoistAcyclic $
plam $ \votes ->
let l :: Term _ (PBuiltinList _)
l = pto $ pto votes
f ::
Term
_
( PBuiltinPair (PAsData PResultTag) (PAsData PInteger)
:--> PBuiltinPair (PAsData PResultTag) (PAsData PInteger)
:--> PBuiltinPair (PAsData PResultTag) (PAsData PInteger)
)
f = phoistAcyclic $
plam $ \this last ->
let lastVotes = pfromData $ psndBuiltin # last
thisVotes = pfromData $ psndBuiltin # this
in pif (lastVotes #< thisVotes) this last
in pfoldr # f # (phead # l) # l
-- | Find the "neutral" option (a dummy outcome with no effect) given the effects.
pneutralOption ::
Term
s
( PMap PResultTag (PMap PValidatorHash PDatumHash)
:--> PResultTag
)
pneutralOption = phoistAcyclic $
plam $ \effects ->
let l :: Term _ (PBuiltinList (PBuiltinPair (PAsData PResultTag) _))
l = pto effects
f :: Term _ (PBuiltinPair (PAsData PResultTag) (PAsData (PMap _ _)) :--> PBool)
f = phoistAcyclic $
plam $ \((pfromData . (psndBuiltin #) -> el)) ->
let el' :: Term _ (PBuiltinList _)
el' = pto el
in pnull # el'
in pfromData $ pfstBuiltin #$ mustBePJust # "No neutral option" #$ pfind # f # l

View file

@ -13,11 +13,18 @@ module Agora.Proposal.Scripts (
import Agora.Proposal (
PProposalDatum (PProposalDatum),
PProposalRedeemer (..),
PProposalStatus (..),
PProposalVotes (PProposalVotes),
Proposal (governorSTAssetClass, stakeSTAssetClass),
ProposalStatus (VotingReady),
ProposalStatus (..),
)
import Agora.Proposal.Time (
currentProposalTime,
isDraftPeriod,
isExecutionPeriod,
isLockingPeriod,
isVotingPeriod,
)
import Agora.Proposal.Time (currentProposalTime, isVotingPeriod)
import Agora.Stake (PProposalLock (..), PStakeDatum (..), findStakeOwnedBy)
import Agora.Utils (
findTxOutByTxOutRef,
@ -368,5 +375,84 @@ proposalValidator proposal =
PUnlock _r ->
popaque (pconstant ())
--------------------------------------------------------------------------
PAdvanceProposal _r ->
popaque (pconstant ())
PAdvanceProposal _r -> unTermCont $ do
tcassert "No stake input is allowed" $ spentStakeST #== 0
currentTime <- tclet $ currentProposalTime # txInfoF.validRange
proposalOutStatus <- tclet $ pfield @"status" # proposalOut
let -- Only the status of proposals should be updated in this case.
templateProposalOut =
mkRecordConstr
PProposalDatum
( #proposalId .= proposalF.proposalId
.& #effects .= proposalF.effects
.& #status .= proposalOutStatus
.& #cosigners .= proposalF.cosigners
.& #thresholds .= proposalF.thresholds
.& #votes .= proposalF.votes
.& #timingConfig .= proposalF.timingConfig
.& #startingTime .= proposalF.startingTime
)
tcassert "Only status changes in the output proposal" $
templateProposalOut #== proposalOut
inDraftPeriod <- tclet $ isDraftPeriod # proposalF.timingConfig # proposalF.startingTime # currentTime
inVotingPeriod <- tclet $ isVotingPeriod # proposalF.timingConfig # proposalF.startingTime # currentTime
inLockedPeriod <- tclet $ isLockingPeriod # proposalF.timingConfig # proposalF.startingTime # currentTime
inExecutionPeriod <- tclet $ isExecutionPeriod # proposalF.timingConfig # proposalF.startingTime # currentTime
-- Check the timings.
let isFinished = proposalF.status #== pconstantData Finished
notTooLate = pmatch (pfromData proposalF.status) $ \case
PDraft _ -> inDraftPeriod
-- Can only advance after the voting period is over.
PVotingReady _ -> inLockedPeriod
PLocked _ -> inExecutionPeriod
_ -> pconstant False
notTooEarly = pmatch (pfromData proposalF.status) $ \case
PVotingReady _ -> pnot # inVotingPeriod
PLocked _ -> pnot # inLockedPeriod
_ -> pconstant True
tcassert "Cannot advance ahead of time" notTooEarly
tcassert "Finished proposals cannot be advanced" $ pnot # isFinished
pure $
pif
notTooLate
-- On time: advance to next status.
( pmatch (pfromData proposalF.status) $ \case
PDraft _ -> unTermCont $ do
-- TODO: Perform other necessary checks.
-- 'Draft' -> 'VotingReady'
tcassert "Proposal status set to VotingReady" $
proposalOutStatus #== pconstantData VotingReady
pure $ popaque (pconstant ())
PVotingReady _ -> unTermCont $ do
-- 'VotingReady' -> 'Locked'
tcassert "Proposal status set to Locked" $
proposalOutStatus #== pconstantData Locked
pure $ popaque (pconstant ())
PLocked _ -> unTermCont $ do
-- 'Locked' -> 'Finished'
tcassert "Proposal status set to Finished" $
proposalOutStatus #== pconstantData Finished
-- TODO: Perform other necessary checks.
pure $ popaque (pconstant ())
_ -> popaque (pconstant ())
)
-- Too late: failed proposal, status set to 'Finished'.
( popaque $
ptraceIfFalse "Proposal should fail: not on time" $
proposalOutStatus #== pconstantData Finished
-- TODO: Should check that the GST is not moved
-- if the proposal is in 'Locked' state.
)

View file

@ -2,22 +2,28 @@ name,cpu,mem,size
Agora/Effects/Treasury Withdrawal Effect/effect/Simple,340268715,724428,3050
Agora/Effects/Treasury Withdrawal Effect/effect/Simple with multiple treasuries ,570029812,1211300,3377
Agora/Effects/Treasury Withdrawal Effect/effect/Mixed Assets,502351827,1071087,3242
Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/governor validator should pass,103830462,228928,7628
Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/governor validator should pass,103830462,228928,7629
Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/effect validator should pass,127968605,266935,3358
Agora/Stake/policy/stakeCreation,59776675,126049,2116
Agora/Stake/validator/stakeDepositWithdraw deposit,276249331,599197,4024
Agora/Stake/validator/stakeDepositWithdraw withdraw,276249331,599197,4016
Agora/Proposal/policy/proposalCreation,34784356,68894,1523
Agora/Proposal/validator/cosignature/proposal,241204796,510319,4812
Agora/Proposal/validator/cosignature/proposal,241651391,511819,5644
Agora/Proposal/validator/cosignature/stake,186332635,402961,4561
Agora/Proposal/validator/voting/proposal,239645722,489368,4820
Agora/Proposal/validator/voting/proposal,240181636,491168,5652
Agora/Proposal/validator/voting/stake,154223940,328703,4614
Agora/Proposal/validator/advancing/successfully advance to next state/Draft -> VotringReady,131365724,260351,5030
Agora/Proposal/validator/advancing/successfully advance to next state/VotingReady -> Locked,130643392,258848,5039
Agora/Proposal/validator/advancing/successfully advance to next state/Locked -> Finished,132128827,262454,5039
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Draft -> Finished,129853757,257621,5032
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/VotingReady -> Finished,128636280,254916,5039
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Locked -> Finished,129626570,257320,5039
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,25177457,55883,806
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,40266637,88241,900
Agora/Treasury/Validator/Positive/Allows for effect changes,37343572,79744,1841
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,25177457,55883,806
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,40266637,88241,900
Agora/Governor/policy/GST minting,57978053,120125,1833
Agora/Governor/validator/proposal creation,330344593,681815,8143
Agora/Governor/validator/GATs minting,431952116,934409,8266
Agora/Governor/validator/mutate governor state,101019422,223202,7685
Agora/Governor/validator/proposal creation,330344593,681815,8145
Agora/Governor/validator/GATs minting,442720585,955552,8268
Agora/Governor/validator/mutate governor state,101019422,223202,7686

1 name cpu mem size
2 Agora/Effects/Treasury Withdrawal Effect/effect/Simple 340268715 724428 3050
3 Agora/Effects/Treasury Withdrawal Effect/effect/Simple with multiple treasuries 570029812 1211300 3377
4 Agora/Effects/Treasury Withdrawal Effect/effect/Mixed Assets 502351827 1071087 3242
5 Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/governor validator should pass 103830462 228928 7628 7629
6 Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/effect validator should pass 127968605 266935 3358
7 Agora/Stake/policy/stakeCreation 59776675 126049 2116
8 Agora/Stake/validator/stakeDepositWithdraw deposit 276249331 599197 4024
9 Agora/Stake/validator/stakeDepositWithdraw withdraw 276249331 599197 4016
10 Agora/Proposal/policy/proposalCreation 34784356 68894 1523
11 Agora/Proposal/validator/cosignature/proposal 241204796 241651391 510319 511819 4812 5644
12 Agora/Proposal/validator/cosignature/stake 186332635 402961 4561
13 Agora/Proposal/validator/voting/proposal 239645722 240181636 489368 491168 4820 5652
14 Agora/Proposal/validator/voting/stake 154223940 328703 4614
15 Agora/Proposal/validator/advancing/successfully advance to next state/Draft -> VotringReady 131365724 260351 5030
16 Agora/Proposal/validator/advancing/successfully advance to next state/VotingReady -> Locked 130643392 258848 5039
17 Agora/Proposal/validator/advancing/successfully advance to next state/Locked -> Finished 132128827 262454 5039
18 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Draft -> Finished 129853757 257621 5032
19 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/VotingReady -> Finished 128636280 254916 5039
20 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Locked -> Finished 129626570 257320 5039
21 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple 25177457 55883 806
22 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 40266637 88241 900
23 Agora/Treasury/Validator/Positive/Allows for effect changes 37343572 79744 1841
24 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple 25177457 55883 806
25 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 40266637 88241 900
26 Agora/Governor/policy/GST minting 57978053 120125 1833
27 Agora/Governor/validator/proposal creation 330344593 681815 8143 8145
28 Agora/Governor/validator/GATs minting 431952116 442720585 934409 955552 8266 8268
29 Agora/Governor/validator/mutate governor state 101019422 223202 7685 7686