add samples and tests for unlocking stakes

This commit is contained in:
fanghr 2022-06-01 21:35:57 +08:00 committed by Hongrui Fang
parent a1c5d0e339
commit 034e55c34f
No known key found for this signature in database
GPG key ID: 1C4711FFF64C0254
3 changed files with 541 additions and 5 deletions

View file

@ -19,6 +19,14 @@ module Sample.Proposal (
advanceFinishedPropsoal,
advanceProposalInsufficientVotes,
advancePropsoalWithInvalidOutputStake,
voterUnlockStakeAndRetractVotesWhile,
voterUnlockStakeWhile,
creatorRetractVotesWhile,
creatorUnlockStakeWhile,
unlockStakeAndRetractVotesUsingIrrelevantStakeWhile,
unlockStakeUsingIrrelevantStakeWhile,
unlockStakeProposalId,
unlockStake,
) where
import Agora.Governor (GovernorDatum (..))
@ -78,17 +86,14 @@ import PlutusLedgerApi.V1.Value qualified as Value (
assetClassValue,
singleton,
)
import PlutusTx.AssocMap qualified as AssocMap (
Map,
empty,
fromList,
)
import PlutusTx.AssocMap qualified as AssocMap
import Sample.Shared (
govValidatorHash,
minAda,
proposal,
proposalPolicySymbol,
proposalStartingTimeFromTimeRange,
proposalValidatorAddress,
proposalValidatorHash,
signer,
signer2,
@ -808,3 +813,280 @@ advancePropsoalWithInvalidOutputStake =
<> templateTxInfo.txInfoData
, txInfoSignatories = [stakeOwner]
}
--------------------------------------------------------------------------------
-- | Create empty effects for every result tag given the votes.
emptyEffectFor ::
ProposalVotes ->
AssocMap.Map ResultTag (AssocMap.Map ValidatorHash DatumHash)
emptyEffectFor (ProposalVotes vs) =
AssocMap.fromList $
map (,AssocMap.empty) (AssocMap.keys vs)
-- | The proposal id shared by all the samples relate to unlocking stake.
unlockStakeProposalId :: ProposalId
unlockStakeProposalId = ProposalId 0
-- | A 'ProposalVotes' that has only two options, serves as a template for unlokcing stake samples.
unlockStakePropsoalVotesTemplate :: ProposalVotes
unlockStakePropsoalVotesTemplate =
ProposalVotes $
AssocMap.fromList
[ (ResultTag 0, 0)
, (ResultTag 1, 0)
]
-- | Create a 'TxInfo' that unlocks a stake from a proposal. For internal use only.
mkUnlockStakeTxInfo ::
-- | The current state of the proposal.
ProposalStatus ->
-- | The votes of the input propsoal
ProposalVotes ->
-- | The votes of the output proposal.
ProposalVotes ->
-- | Stake amount.
Integer ->
-- | Retract from option.
[ProposalLock] ->
-- | The locks of output stake.
[ProposalLock] ->
TxInfo
mkUnlockStakeTxInfo
status
votesBefore
votesAfter
stakedAmount
locksBefore
locksAfter =
let stakeOwner = signer
stakeInputDatum' :: StakeDatum
stakeInputDatum' =
StakeDatum
{ stakedAmount = Tagged stakedAmount
, owner = stakeOwner
, lockedBy = locksBefore
}
stakeOutputDatum' :: StakeDatum
stakeOutputDatum' =
stakeInputDatum'
{ lockedBy = locksAfter
}
---
effects = emptyEffectFor votesBefore
proposalInputDatum' :: ProposalDatum
proposalInputDatum' =
ProposalDatum
{ proposalId = unlockStakeProposalId
, effects = effects
, status = status
, cosigners = [signer]
, thresholds = def
, votes = votesBefore
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
proposalOutputDatum' :: ProposalDatum
proposalOutputDatum' =
proposalInputDatum'
{ votes = votesAfter
}
---
sst = Value.assetClassValue stakeAssetClass 1
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
}
stakeOutputDatum :: Datum
stakeOutputDatum = Datum $ toBuiltinData stakeOutputDatum'
stakeOutput :: TxOut
stakeOutput =
stakeInput
{ txOutDatumHash = Just $ toDatumHash stakeOutputDatum
}
---
pst = Value.singleton proposalPolicySymbol "" 1
proposalInputDatum :: Datum
proposalInputDatum = Datum $ toBuiltinData proposalInputDatum'
proposalInput :: TxOut
proposalInput =
TxOut
{ txOutAddress = proposalValidatorAddress
, txOutValue = pst
, txOutDatumHash = Just $ toDatumHash proposalInputDatum
}
---
proposalOutputDatum :: Datum
proposalOutputDatum = Datum $ toBuiltinData proposalOutputDatum'
proposalOutput :: TxOut
proposalOutput =
proposalInput
{ txOutValue = proposalInput.txOutValue <> minAda
, txOutDatumHash = Just $ toDatumHash proposalOutputDatum
}
in TxInfo
{ txInfoInputs = [TxInInfo proposalRef proposalInput, TxInInfo stakeRef stakeInput]
, txInfoOutputs = [proposalOutput, stakeOutput]
, txInfoFee = Value.singleton "" "" 2
, txInfoMint = mempty
, txInfoDCert = []
, txInfoWdrl = []
, -- Time doesn't matter int this case.
txInfoValidRange = closedBoundedInterval 0 100
, txInfoSignatories = [signer]
, txInfoData = datumPair <$> [proposalInputDatum, proposalOutputDatum, stakeInputDatum, stakeOutputDatum]
, txInfoId = "95ba4015e30aef16a3461ea97a779f814aeea6b8009d99a94add4b8293be737a"
}
-- | How a stake has been used on a particular proposal.
data StakeUsage
= -- | The stake was spent to vote for a paraticular option.
VotedFor ResultTag
| -- | The stake was used to created the proposal.
Created
| -- | The stake has nothing to do with the proposal.
DidNothing
-- | Create a bunch of 'ProposalLock' given the 'StakeUsgae'.
mkStakeLocks :: StakeUsage -> [ProposalLock]
mkStakeLocks (VotedFor rt) = [ProposalLock rt unlockStakeProposalId]
mkStakeLocks Created =
map (`ProposalLock` unlockStakeProposalId) $
AssocMap.keys $ getProposalVotes unlockStakePropsoalVotesTemplate
mkStakeLocks _ = []
-- | Assemble the votes of the input propsoal based on 'unlockStakePropsoalVotesTemplate'.
mkVotesBefore ::
StakeUsage ->
-- | The staked amount/votes.
Integer ->
ProposalVotes
mkVotesBefore (VotedFor rt) vc =
ProposalVotes $
updateMap (Just . const vc) rt $
getProposalVotes unlockStakePropsoalVotesTemplate
mkVotesBefore _ vc = mkVotesBefore (VotedFor $ ResultTag 0) vc
{- | Create a 'TxInfo' that unlocks the stake from the proposal.
The last parameter controls whether votes should be retracted or not.
-}
unlockStake ::
-- | The status of both the input and output propsoals.
ProposalStatus ->
StakeUsage ->
-- | Staked amount/vote count.
Integer ->
-- | Should we retract votes?
Bool ->
TxInfo
unlockStake ps su staked shouldRetract =
let votesBefore = mkVotesBefore su staked
votesAfter =
if shouldRetract
then unlockStakePropsoalVotesTemplate
else votesBefore
locksBefore = mkStakeLocks su
locksAfter = []
in mkUnlockStakeTxInfo
ps
votesBefore
votesAfter
staked
locksBefore
locksAfter
{- | Create a 'TxInfo' that unlocks a stake which is used to vote on the proposal.
Correct count of votes is also retracted. The 'TxInfo' is valid only if the given
proposal status is 'VotingReady'.
-}
voterUnlockStakeAndRetractVotesWhile :: ProposalStatus -> TxInfo
voterUnlockStakeAndRetractVotesWhile ps =
unlockStake
ps
(VotedFor $ ResultTag 0)
42
True
{- | Create a 'TxInfo' that unlocks a stake which is used to vote on the proposal
without retracting the votes, given the status of the proposal.
The 'TxInfo' is valid only if the status of the propsoal is either 'Locked'
or 'Finished'.
-}
voterUnlockStakeWhile :: ProposalStatus -> TxInfo
voterUnlockStakeWhile ps =
unlockStake
ps
(VotedFor $ ResultTag 0)
42
False
{- | Create an invalid 'TxInfo' that retracts votes using the stake
that is used to create the proposal.
-}
creatorRetractVotesWhile :: ProposalStatus -> TxInfo
creatorRetractVotesWhile ps =
unlockStake
ps
Created
42
True
{- | Create a 'TxInfo' to unlock the stake that is used to create the propsoal.
The 'TxInfo' is valid only if the given proposal status is 'Finished'.
-}
creatorUnlockStakeWhile :: ProposalStatus -> TxInfo
creatorUnlockStakeWhile ps =
unlockStake
ps
Created
42
False
{- | Create an invalid 'TxInfo' that tries to retract votes and also unlock a stake
which is not locked by the proposal, given the status of the proposal.
-}
unlockStakeAndRetractVotesUsingIrrelevantStakeWhile :: ProposalStatus -> TxInfo
unlockStakeAndRetractVotesUsingIrrelevantStakeWhile ps =
unlockStake
ps
DidNothing
42
True
{- | Create an invalid 'TxInfo' that tries to unlock a stake which is not locked by the proposal,
given the status of the proposal.
-}
unlockStakeUsingIrrelevantStakeWhile :: ProposalStatus -> TxInfo
unlockStakeUsingIrrelevantStakeWhile ps =
unlockStake
ps
DidNothing
42
False

View file

@ -53,10 +53,16 @@ import Sample.Proposal qualified as Proposal (
advanceProposalSuccess,
advancePropsoalWithInvalidOutputStake,
cosignProposal,
creatorRetractVotesWhile,
creatorUnlockStakeWhile,
proposalCreation,
proposalRef,
stakeRef,
unlockStakeAndRetractVotesUsingIrrelevantStakeWhile,
unlockStakeUsingIrrelevantStakeWhile,
voteOnProposal,
voterUnlockStakeAndRetractVotesWhile,
voterUnlockStakeWhile,
)
import Sample.Shared (signer, signer2)
import Sample.Shared qualified as Shared (proposal, stake)
@ -356,5 +362,249 @@ specs =
(Spending Proposal.proposalRef)
)
]
, group
"unlocking"
[ group
"legal"
[ validatorSucceedsWith
"retract votes and unlock stake while voting"
(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, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(Proposal.voterUnlockStakeAndRetractVotesWhile VotingReady)
(Spending Proposal.proposalRef)
)
, validatorSucceedsWith
"unlock the stake that has been used to create the proposal"
(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, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(Proposal.creatorUnlockStakeWhile Finished)
(Spending Proposal.proposalRef)
)
, group "unlock stake after voting" $
map
( \ps ->
validatorSucceedsWith
(show ps)
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = ps
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(Proposal.voterUnlockStakeWhile ps)
(Spending Proposal.proposalRef)
)
)
[Locked, Finished]
]
, group
"illegal"
[ group "retract votes while the proposal is not voting ready" $
map
( \ps ->
validatorFailsWith
(show ps)
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = ps
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(Proposal.voterUnlockStakeAndRetractVotesWhile ps)
(Spending Proposal.proposalRef)
)
)
[Draft, Locked, Finished]
, group
"irrelevant stake"
$ foldMap
( \(f, s) ->
map
( \ps ->
validatorFailsWith
(s <> " (" <> show ps <> ")")
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = ps
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(f ps)
(Spending Proposal.proposalRef)
)
)
[Draft, VotingReady, Locked, Finished]
)
[ (Proposal.unlockStakeAndRetractVotesUsingIrrelevantStakeWhile, "unlock stake + retract votes")
, (Proposal.unlockStakeUsingIrrelevantStakeWhile, "unlock stake")
]
, group "unlock stake that has been used to create the proposal before finished" $
map
( \ps ->
validatorFailsWith
(show ps)
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = ps
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(Proposal.creatorUnlockStakeWhile ps)
(Spending Proposal.proposalRef)
)
)
[Draft, VotingReady, Locked]
, group "creator stake retract votes" $
map
( \ps ->
validatorFailsWith
(show ps)
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = ps
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Unlock (ResultTag 0))
( ScriptContext
(Proposal.creatorRetractVotesWhile ps)
(Spending Proposal.proposalRef)
)
)
[Draft, VotingReady, Locked, Finished]
]
]
]
]

View file

@ -18,6 +18,10 @@ Agora/Proposal/validator/advancing/successfully advance to next state/Locked ->
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Draft -> Finished,160888965,431112,6394
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/VotingReady -> Finished,159480054,428407,6395
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Locked -> Finished,160611032,430811,6395
Agora/Proposal/validator/unlocking/legal/retract votes and unlock stake while voting,171592676,462566,6467
Agora/Proposal/validator/unlocking/legal/unlock the stake that has been used to create the proposal,149988973,407906,6474
Agora/Proposal/validator/unlocking/legal/unlock stake after voting/Locked,149056062,408201,6468
Agora/Proposal/validator/unlocking/legal/unlock stake after voting/Finished,149056062,408201,6468
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,21017788,55883,806
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,33204186,88241,900
Agora/Treasury/Validator/Positive/Allows for effect changes,29938856,79744,1390

1 name cpu mem size
18 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Draft -> Finished 160888965 431112 6394
19 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/VotingReady -> Finished 159480054 428407 6395
20 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Locked -> Finished 160611032 430811 6395
21 Agora/Proposal/validator/unlocking/legal/retract votes and unlock stake while voting 171592676 462566 6467
22 Agora/Proposal/validator/unlocking/legal/unlock the stake that has been used to create the proposal 149988973 407906 6474
23 Agora/Proposal/validator/unlocking/legal/unlock stake after voting/Locked 149056062 408201 6468
24 Agora/Proposal/validator/unlocking/legal/unlock stake after voting/Finished 149056062 408201 6468
25 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple 21017788 55883 806
26 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 33204186 88241 900
27 Agora/Treasury/Validator/Positive/Allows for effect changes 29938856 79744 1390