check minimum stake amount while voting

This commit is contained in:
Hongrui Fang 2022-10-04 22:01:04 +08:00
parent 1b3ec260a3
commit 270c984f8d
10 changed files with 88 additions and 49 deletions

View file

@ -63,6 +63,7 @@ import Test.Tasty.QuickCheck (
data GovernorDatumCases data GovernorDatumCases
= ExecuteLE0 = ExecuteLE0
| CreateLE0 | CreateLE0
| ToVotingLE0
| VoteLE0 | VoteLE0
| Correct | Correct
deriving stock (Eq, Show) deriving stock (Eq, Show)
@ -88,9 +89,10 @@ governorDatumValidProperty =
classifiedPropertyNative gen (const []) expected classifier pisGovernorDatumValid classifiedPropertyNative gen (const []) expected classifier pisGovernorDatumValid
where where
classifier :: GovernorDatum -> GovernorDatumCases classifier :: GovernorDatum -> GovernorDatumCases
classifier ((.proposalThresholds) -> ProposalThresholds e c v) classifier ((.proposalThresholds) -> ProposalThresholds e c tv v)
| e < 0 = ExecuteLE0 | e < 0 = ExecuteLE0
| c < 0 = CreateLE0 | c < 0 = CreateLE0
| tv < 0 = ToVotingLE0
| v < 0 = VoteLE0 | v < 0 = VoteLE0
| otherwise = Correct | otherwise = Correct
@ -110,24 +112,27 @@ governorDatumValidProperty =
let validGT = taggedInteger (0, 1000000000) let validGT = taggedInteger (0, 1000000000)
execute <- validGT execute <- validGT
create <- validGT create <- validGT
toVoting <- validGT
vote <- validGT vote <- validGT
le0 <- taggedInteger (-1000, -1) le0 <- taggedInteger (-1000, -1)
case c of case c of
ExecuteLE0 -> ExecuteLE0 ->
-- execute < 0 -- execute < 0
return $ ProposalThresholds le0 create vote return $ ProposalThresholds le0 create toVoting vote
CreateLE0 -> CreateLE0 ->
-- c < 0 -- c < 0
return $ ProposalThresholds execute le0 vote return $ ProposalThresholds execute le0 toVoting vote
ToVotingLE0 ->
return $ ProposalThresholds execute create le0 vote
VoteLE0 -> VoteLE0 ->
-- vote < 0 -- vote < 0
return $ ProposalThresholds execute create le0 return $ ProposalThresholds execute create toVoting le0
Correct -> do Correct -> do
-- c <= vote < execute -- c <= vote < execute
nv <- taggedInteger (0, untag execute - 1) nv <- taggedInteger (0, untag execute - 1)
nc <- taggedInteger (0, untag nv) nc <- taggedInteger (0, untag nv)
return $ ProposalThresholds execute nc nv return $ ProposalThresholds execute nc toVoting nv
data GovernorPolicyCases data GovernorPolicyCases
= ReferenceUTXONotSpent = ReferenceUTXONotSpent

View file

@ -195,7 +195,7 @@ invalidNewGovernorDatum =
GovernorDatum GovernorDatum
{ proposalThresholds = { proposalThresholds =
def def
{ vote = Tagged (-1) { toVoting = Tagged (-1)
} }
, nextProposalId = ProposalId 42 , nextProposalId = ProposalId 42
, proposalTimings = def , proposalTimings = def

View file

@ -93,7 +93,7 @@ validGovernorOutputDatum =
} }
invalidProposalThresholds :: ProposalThresholds invalidProposalThresholds :: ProposalThresholds
invalidProposalThresholds = ProposalThresholds (-1) (-1) (-1) invalidProposalThresholds = ProposalThresholds (-1) (-1) (-1) (-1)
invalidMaxTimeRangeWidth :: MaxTimeRangeWidth invalidMaxTimeRangeWidth :: MaxTimeRangeWidth
invalidMaxTimeRangeWidth = MaxTimeRangeWidth 0 invalidMaxTimeRangeWidth = MaxTimeRangeWidth 0

View file

@ -120,6 +120,7 @@ mkGovernorOutputDatum ValueInvalid =
ProposalThresholds ProposalThresholds
{ execute = -1 { execute = -1
, create = -1 , create = -1
, toVoting = -1
, vote = -1 , vote = -1
} }
in Just $ in Just $

View file

@ -679,7 +679,7 @@ getNextState = \case
-- | Calculate the number of GTs per stake in order to exceed the minimum limit. -- | Calculate the number of GTs per stake in order to exceed the minimum limit.
compPerStakeGTsForDraft :: NumStake -> Integer compPerStakeGTsForDraft :: NumStake -> Integer
compPerStakeGTsForDraft nCosigners = compPerStakeGTsForDraft nCosigners =
untag (def :: ProposalThresholds).vote untag (def :: ProposalThresholds).toVoting
`div` fromIntegral nCosigners + 1 `div` fromIntegral nCosigners + 1
dummyDatum :: () dummyDatum :: ()
@ -944,7 +944,7 @@ mkInsufficientCosignsBundle nCosigners nEffects =
} }
where where
insuffcientPerStakeGTs = insuffcientPerStakeGTs =
untag (def :: ProposalThresholds).vote untag (def :: ProposalThresholds).toVoting
`div` fromIntegral nCosigners - 1 `div` fromIntegral nCosigners - 1
template = mkValidToNextStateBundle nCosigners nEffects False Draft template = mkValidToNextStateBundle nCosigners nEffects False Draft

View file

@ -25,6 +25,8 @@ module Sample.Proposal.Vote (
moreThanOneProposals, moreThanOneProposals,
invalidLocks, invalidLocks,
destroyStakes, destroyStakes,
insufficientAmount,
insufficientAmount1,
) where ) where
import Agora.Governor (Governor (..)) import Agora.Governor (Governor (..))
@ -526,3 +528,27 @@ destroyStakes =
} }
} }
} }
insufficientAmount :: ParameterBundle
insufficientAmount =
ownerVoteWithSignleStake
{ stakeParameters =
ownerVoteWithSignleStake.stakeParameters
{ stakeInputParameters =
ownerVoteWithSignleStake.stakeParameters.stakeInputParameters
{ perStakeGTs = 1
}
}
}
insufficientAmount1 :: ParameterBundle
insufficientAmount1 =
ownerVoteWithMultipleStakes
{ stakeParameters =
ownerVoteWithMultipleStakes.stakeParameters
{ stakeInputParameters =
ownerVoteWithMultipleStakes.stakeParameters.stakeInputParameters
{ perStakeGTs = 1
}
}
}

View file

@ -189,6 +189,7 @@ instance Default ProposalThresholds where
ProposalThresholds ProposalThresholds
{ execute = Tagged 1000 { execute = Tagged 1000
, create = Tagged 1 , create = Tagged 1
, toVoting = Tagged 100
, vote = Tagged 100 , vote = Tagged 100
} }

View file

@ -176,6 +176,14 @@ specs =
"attempt to burn stakes" "attempt to burn stakes"
Vote.destroyStakes Vote.destroyStakes
(Vote.Validity True False) (Vote.Validity True False)
, Vote.mkTestTree
"insufficient staked amount"
Vote.insufficientAmount
(Vote.Validity False True)
, Vote.mkTestTree
"insufficient staked amount"
Vote.insufficientAmount1
(Vote.Validity False True)
] ]
] ]
, group , group

View file

@ -67,8 +67,7 @@ import Plutarch.DataRepr (
), ),
PDataFields, PDataFields,
) )
import Plutarch.Extra.Comonad (pextract) import Plutarch.Extra.Field (pletAll)
import Plutarch.Extra.Field (pletAllC)
import Plutarch.Extra.Function (pbuiltinUncurry) import Plutarch.Extra.Function (pbuiltinUncurry)
import Plutarch.Extra.IsData ( import Plutarch.Extra.IsData (
DerivePConstantViaDataList (DerivePConstantViaDataList), DerivePConstantViaDataList (DerivePConstantViaDataList),
@ -81,14 +80,14 @@ import Plutarch.Extra.IsData (
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust) import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
import Plutarch.Extra.Map qualified as PM import Plutarch.Extra.Map qualified as PM
import Plutarch.Extra.Maybe (pfromJust) import Plutarch.Extra.Maybe (pfromJust)
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC, pmatchC) import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (pguardC, pletC)
import Plutarch.Lift ( import Plutarch.Lift (
DerivePConstantViaNewtype (DerivePConstantViaNewtype), DerivePConstantViaNewtype (DerivePConstantViaNewtype),
PConstantDecl, PConstantDecl,
PUnsafeLiftDecl (type PLifted), PUnsafeLiftDecl (type PLifted),
) )
import Plutarch.Orphans () import Plutarch.Orphans ()
import Plutarch.SafeMoney (PDiscrete (PDiscrete)) import Plutarch.SafeMoney (PDiscrete)
import PlutusLedgerApi.V2 (Credential, DatumHash, ScriptHash, ValidatorHash) import PlutusLedgerApi.V2 (Credential, DatumHash, ScriptHash, ValidatorHash)
import PlutusTx qualified import PlutusTx qualified
@ -222,7 +221,7 @@ data ProposalStatus
This data is stored centrally (in the 'Agora.Governor.Governor') and copied over This data is stored centrally (in the 'Agora.Governor.Governor') and copied over
to 'Proposal's when they are created. to 'Proposal's when they are created.
@since 0.1.0 @since 1.0.0
-} -}
data ProposalThresholds = ProposalThresholds data ProposalThresholds = ProposalThresholds
{ execute :: Tagged GTTag Integer { execute :: Tagged GTTag Integer
@ -232,9 +231,10 @@ data ProposalThresholds = ProposalThresholds
-- --
-- It is recommended this be a high enough amount, in order to prevent DOS from bad -- It is recommended this be a high enough amount, in order to prevent DOS from bad
-- actors. -- actors.
, toVoting :: Tagged GTTag Integer
-- ^ How much GT required to to move into 'Locked'.
, vote :: Tagged GTTag Integer , vote :: Tagged GTTag Integer
-- ^ How much GT required to allow voting to happen. -- ^ How much GT required to vote on a outcome.
-- (i.e. to move into 'VotingReady')
} }
deriving stock deriving stock
( -- | @since 0.1.0 ( -- | @since 0.1.0
@ -553,7 +553,7 @@ deriving via (DerivePConstantViaEnum ProposalStatus PProposalStatus) instance (P
{- | Plutarch-level version of 'ProposalThresholds'. {- | Plutarch-level version of 'ProposalThresholds'.
@since 0.1.0 @since 1.0.0
-} -}
newtype PProposalThresholds (s :: S) = PProposalThresholds newtype PProposalThresholds (s :: S) = PProposalThresholds
{ getProposalThresholds :: { getProposalThresholds ::
@ -562,6 +562,7 @@ newtype PProposalThresholds (s :: S) = PProposalThresholds
( PDataRecord ( PDataRecord
'[ "execute" ':= PDiscrete GTTag '[ "execute" ':= PDiscrete GTTag
, "create" ':= PDiscrete GTTag , "create" ':= PDiscrete GTTag
, "toVoting" ':= PDiscrete GTTag
, "vote" ':= PDiscrete GTTag , "vote" ':= PDiscrete GTTag
] ]
) )
@ -951,23 +952,18 @@ pneutralOption = phoistAcyclic $
-} -}
pisProposalThresholdsValid :: forall (s :: S). Term s (PProposalThresholds :--> PBool) pisProposalThresholdsValid :: forall (s :: S). Term s (PProposalThresholds :--> PBool)
pisProposalThresholdsValid = phoistAcyclic $ pisProposalThresholdsValid = phoistAcyclic $
plam $ \thresholds -> unTermCont $ do plam $
thresholdsF <- pletAllC thresholds flip pletAll $ \thresholdsF ->
PDiscrete execute' <- pmatchC thresholdsF.execute
PDiscrete draft' <- pmatchC thresholdsF.create
PDiscrete vote' <- pmatchC thresholdsF.vote
execute <- pletC $ pextract # execute'
draft <- pletC $ pextract # draft'
vote <- pletC $ pextract # vote'
pure $
foldr1 foldr1
(#&&) (#&&)
[ ptraceIfFalse "Execute threshold is less than or equal to 0" $ 0 #<= execute [ ptraceIfFalse "Execute threshold is less than or equal to 0" $
, ptraceIfFalse "Draft threshold is less than or equal to 0" $ 0 #<= draft 0 #<= pfromData thresholdsF.execute
, ptraceIfFalse "Vote threshold is less than or equal to 0" $ 0 #<= vote , ptraceIfFalse "Create threshold is less than or equal to 0" $
0 #<= pfromData thresholdsF.create
, ptraceIfFalse "toVoting threshold is less than or equal to 0" $
0 #<= pfromData thresholdsF.toVoting
, ptraceIfFalse "Vote threshold is less than or equal to 0" $
0 #<= pfromData thresholdsF.vote
] ]
{- | Retract votes given the option and the amount of votes. {- | Retract votes given the option and the amount of votes.

View file

@ -458,25 +458,27 @@ proposalValidator as maximumCosigners =
---------------------------------------------------------------------- ----------------------------------------------------------------------
PVote r -> spendStakes $ \sctxF -> do PVote r -> spendStakes $ \sctxF -> do
let totalStakeAmount = totalStakeAmount <-
pto $ pletC $
pfoldMap pto $
# plam pfoldMap
( \stake -> unTermCont $ do # plam
stakeF <- pletFieldsC @'["stakedAmount", "lockedBy"] stake ( \stake -> unTermCont $ do
stakeF <- pletFieldsC @'["stakedAmount", "lockedBy"] stake
pguardC "Same stake shouldn't vote on the same proposal twice" $ pguardC "Same stake shouldn't vote on the same proposal twice" $
pnot pnot
#$ pisVoter #$ pisVoter
#$ pgetStakeRole #$ pgetStakeRole
# proposalInputDatumF.proposalId # proposalInputDatumF.proposalId
# stakeF.lockedBy # stakeF.lockedBy
pure $ pcon $ PSum $ pfromData stakeF.stakedAmount pure $ pcon $ PSum $ pfromData stakeF.stakedAmount
) )
# sctxF.inputStakes # sctxF.inputStakes
-- TODO(Connor): check minimum stake amount? pguardC "Exceed minimum amount" $
thresholdsF.vote #< totalStakeAmount
pguardC "Input proposal must be in VotingReady state" $ pguardC "Input proposal must be in VotingReady state" $
currentStatus #== pconstant VotingReady currentStatus #== pconstant VotingReady
@ -657,7 +659,7 @@ proposalValidator as maximumCosigners =
pmatchC notTooLate >>= \case pmatchC notTooLate >>= \case
PTrue -> do PTrue -> do
pguardC "More cosigns than minimum amount" $ pguardC "More cosigns than minimum amount" $
punsafeCoerce (pfromData thresholdsF.vote) #< sctxF.totalAmount punsafeCoerce (pfromData thresholdsF.toVoting) #< sctxF.totalAmount
pguardC "All new cosigners are witnessed by their Stake datums" $ pguardC "All new cosigners are witnessed by their Stake datums" $
plistEqualsBy plistEqualsBy