agora/agora-specs/Sample/Proposal/Vote.hs
Seungheon Oh d2018afd4d Use liqwid-script-export
commit ec70bfd539fe2e27fd48f5f76395400287ac72d7
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Tue Oct 18 18:58:59 2022 -0500

    use LSE

commit 25fff9b3ad1f2dde4cd7cf36977530b06a87d23c
Merge: 01cd3aa 1821dd6
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Tue Oct 18 18:17:45 2022 -0500

    Merge branch 'staging' into seungheonoh/ply

commit 01cd3aa7a235e6fe6658246ca1026fa26dc71a83
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 12:02:03 2022 +0800

    update benchmark

commit a8513244892ce33cfdc9edf8cd501c4985ae8008
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 11:59:22 2022 +0800

    fix tests

commit 20ca40823485c2e2f78253643cf4453ac7b7ddd5
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 11:57:37 2022 +0800

    better import

commit a19fe49424210891bd03db71e4083fc1e0edfd98
Author: Hongrui Fang <chfanghr@gmail.com>
Date:   Tue Oct 11 11:08:20 2022 +0800

    update flake inputs

commit c93b21f1f9441e5c6f54525bf7c6a54757ec36cc
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 12:54:12 2022 -0500

    tried to make tests pass

commit 1046ae1237299a33c58b48661bdb6d325a22147e
Merge: 2bf4e36 363bd83
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 12:18:48 2022 -0500

    Merge branch 'staging' into seungheonoh/ply

commit 2bf4e3627c1b229f58078695082da85c80efd560
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 10:48:36 2022 -0500

    remove junkpile

commit a1dbc9ad9e531fe0d0a0480c4aef9cf9ffa90f1d
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Mon Oct 10 10:47:25 2022 -0500

    versions

commit 4542a06ac733858297d3a48c53368fad19dedc43
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Thu Oct 6 22:57:48 2022 -0500

    script exporting interface

commit 6bd8c1a1d57e4bf9dc25c3068a9c8eae6bf6a19d
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Thu Oct 6 22:58:41 2022 -0500

    fixed tests

commit d3ce2cf95633d336f3e621833677bd5bf10ee2c8
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sun Oct 2 00:55:18 2022 -0500

    fixed tests

commit 1ae64c9f692652b77b0506013853b2ba44267c65
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sat Oct 1 13:28:20 2022 -0500

    linker

commit db88cb75c7b74843141ad8ab4e6522b66d0dcfbc
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sat Oct 1 01:03:50 2022 -0500

    exporting scripts

commit 6389fce28e885a8a7f8669629c266f59c0edb51f
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Sat Oct 1 00:51:49 2022 -0500

    made scripts parameterized on the script level

commit aea1e518a8890550bdebd0e5251da11d915c53a9
Author: Seungheon Oh <seungheon.ooh@gmail.com>
Date:   Wed Sep 28 19:53:29 2022 -0500

    Use `TypedScriptEnvelope` for `Agora.Bootstrap`
2022-10-18 19:02:10 -05:00

554 lines
15 KiB
Haskell

{- |
Module : Sample.Proposal.Vote
Maintainer : connor@mlabs.city
Description: Generate sample data for testing the functionalities of voting on proposals.
Sample and utilities for testing the functionalities of voting on proposals.
-}
module Sample.Proposal.Vote (
ParameterBundle (..),
VoteParameters (..),
StakeParameters (..),
StakeInputParameters (..),
StakeOutputParameters (..),
NumProposals (..),
ProposalParameters (..),
TransactionParameters (..),
Validity (..),
vote,
mkTestTree,
mkValidOwnerVoteBundle,
mkValidDelegateeVoteBundle,
transparentAssets,
transactionNotAuthorized,
voteForNonexistentOutcome,
noProposal,
moreThanOneProposals,
invalidLocks,
destroyStakes,
insufficientAmount,
insufficientAmount1,
) where
import Agora.Governor (Governor (..))
import Agora.Proposal (
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (Vote),
ProposalStatus (VotingReady),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (draftTime, votingTime),
)
import Agora.Stake (
ProposalLock (Voted),
StakeDatum (..),
StakeRedeemer (Destroy, PermitVote),
)
import Data.Default (Default (def))
import Data.Map.Strict qualified as StrictMap
import Data.Maybe (catMaybes)
import Data.Tagged (untag)
import Plutarch.Context (
input,
mint,
normalizeValue,
output,
script,
signedWith,
timeRange,
withInlineDatum,
withRedeemer,
withRef,
withValue,
)
import PlutusLedgerApi.V1.Value qualified as Value
import PlutusLedgerApi.V2 (Credential (PubKeyCredential), PubKeyHash)
import PlutusLedgerApi.V2.Contexts (TxOutRef (TxOutRef))
import Sample.Proposal.Shared (proposalTxRef)
import Sample.Shared (
governor,
minAda,
proposalPolicySymbol,
proposalValidator,
proposalValidatorHash,
stakeAssetClass,
stakeValidator,
stakeValidatorHash,
)
import Test.Specification (SpecificationTree, group, testValidator)
import Test.Util (
CombinableBuilder,
closedBoundedInterval,
mkSpending,
pubKeyHashes,
)
data ParameterBundle = ParamerterBundle
{ voteParameters :: VoteParameters
, stakeParameters :: StakeParameters
, proposalParameters :: ProposalParameters
, transactionParameters :: TransactionParameters
}
newtype VoteParameters = VoteParameters {voteFor :: ResultTag}
data StakeParameters = StakeParameters
{ numStakes :: Integer
, stakeInputParameters :: StakeInputParameters
, stakeOutputParameters :: StakeOutputParameters
}
newtype StakeInputParameters = StakeInputParameters
{ perStakeGTs :: Integer
}
data StakeOutputParameters = StakeOutputParameters
{ burnStakes :: Bool
, dontAddNewLock :: Bool
, changeGTAmount :: Bool
, changeAdaAmount :: Bool
}
data NumProposals = NoProposal | OneProposal | MoreThanOneProposals
data ProposalParameters = ProposalParameters
{ wrongAddedVotes :: Bool
, numProposals :: NumProposals
}
data SignedBy = Owner | Delegatee | Unknown
newtype TransactionParameters = TransactionParameters
{ signedBy :: SignedBy
}
data Validity = Validity
{ forProposalValidator :: Bool
, forStakeValidator :: Bool
}
--------------------------------------------------------------------------------
stakeOwner :: PubKeyHash
stakeOwner = head pubKeyHashes
delegatee :: PubKeyHash
delegatee = pubKeyHashes !! 1
unknownSig :: PubKeyHash
unknownSig = pubKeyHashes !! 2
--------------------------------------------------------------------------------
initialVotes :: StrictMap.Map ResultTag Integer
initialVotes =
StrictMap.fromList
[ (ResultTag 0, 114)
, (ResultTag 1, 514)
]
proposalInputDatum :: ProposalDatum
proposalInputDatum =
ProposalDatum
{ proposalId = ProposalId 22
, effects =
StrictMap.fromList
[ (ResultTag 0, StrictMap.empty)
, (ResultTag 1, StrictMap.empty)
]
, status = VotingReady
, cosigners = [PubKeyCredential stakeOwner]
, thresholds = def
, votes = ProposalVotes initialVotes
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
mkProposalRedeemer :: VoteParameters -> ProposalRedeemer
mkProposalRedeemer v = Vote v.voteFor
mkProposalRef :: Integer -> TxOutRef
mkProposalRef = TxOutRef proposalTxRef
numProposals :: NumProposals -> Integer
numProposals NoProposal = 0
numProposals OneProposal = 1
numProposals MoreThanOneProposals = 2
--------------------------------------------------------------------------------
mkStakeRedeemer :: StakeOutputParameters -> StakeRedeemer
mkStakeRedeemer params =
if params.burnStakes
then Destroy
else PermitVote
mkStakeInputDatum :: StakeInputParameters -> StakeDatum
mkStakeInputDatum params =
StakeDatum
{ stakedAmount = fromInteger params.perStakeGTs
, owner = PubKeyCredential stakeOwner
, delegatedTo = Just (PubKeyCredential delegatee)
, lockedBy =
[ Voted (ProposalId 0) (ResultTag 0)
, Voted (ProposalId 1) (ResultTag 2)
]
}
mkStakeRef :: Integer -> Integer -> TxOutRef
mkStakeRef o i = TxOutRef proposalTxRef $ o + i
--------------------------------------------------------------------------------
vote :: forall b. CombinableBuilder b => ParameterBundle -> b
vote params =
let pst = Value.singleton proposalPolicySymbol "" 1
sst = Value.assetClassValue stakeAssetClass 1
---
stakeInputDatum =
mkStakeInputDatum
params.stakeParameters.stakeInputParameters
stakeInputValue =
normalizeValue $
sst
<> Value.assetClassValue
(untag governor.gtClassRef)
params.stakeParameters.stakeInputParameters.perStakeGTs
<> minAda
newLock =
Voted
proposalInputDatum.proposalId
params.voteParameters.voteFor
updatedLocks =
if params.stakeParameters.stakeOutputParameters.dontAddNewLock
then stakeInputDatum.lockedBy
else newLock : stakeInputDatum.lockedBy
stakeOutputDatum = stakeInputDatum {lockedBy = updatedLocks}
stakeOutputValue =
let changeAmount cond = if cond then (* 100) else id
gtAmount =
changeAmount
params.stakeParameters.stakeOutputParameters.changeGTAmount
params.stakeParameters.stakeInputParameters.perStakeGTs
adaAmount =
changeAmount
params.stakeParameters.stakeOutputParameters.changeAdaAmount
10_000_000
in normalizeValue $
sst
<> Value.assetClassValue
(untag governor.gtClassRef)
gtAmount
<> minAda
<> Value.singleton "" "" adaAmount
stakeRedeemer =
mkStakeRedeemer params.stakeParameters.stakeOutputParameters
stakeBuilder :: b
stakeBuilder =
foldMap
( \i ->
mconcat
[ input $
mconcat
[ script stakeValidatorHash
, withValue stakeInputValue
, withInlineDatum stakeInputDatum
, withRedeemer stakeRedeemer
, withRef $ mkStakeRef numProposals' i
]
, if params.stakeParameters.stakeOutputParameters.burnStakes
then mint $ Value.assetClassValue stakeAssetClass (-1)
else
output $
mconcat
[ script stakeValidatorHash
, withValue stakeOutputValue
, withInlineDatum stakeOutputDatum
]
]
)
[1 .. params.stakeParameters.numStakes]
--------------------------------------------------------------------------
numProposals' = numProposals params.proposalParameters.numProposals
updatedVotes =
StrictMap.adjust
( ( if params.proposalParameters.wrongAddedVotes
then (* 10)
else id
)
. ( +
params.stakeParameters.stakeInputParameters.perStakeGTs
* params.stakeParameters.numStakes
)
)
params.voteParameters.voteFor
initialVotes
proposalOutputDatum =
proposalInputDatum
{ votes = ProposalVotes updatedVotes
}
proposalRedeemer = mkProposalRedeemer params.voteParameters
proposalValue =
normalizeValue $
pst
<> minAda
proposalBuidler :: b
proposalBuidler =
foldMap
( \i ->
mconcat
[ input $
mconcat
[ script proposalValidatorHash
, withValue proposalValue
, withRedeemer proposalRedeemer
, withInlineDatum proposalInputDatum
, withRef $ mkProposalRef i
]
, output $
mconcat
[ script proposalValidatorHash
, withValue proposalValue
, withInlineDatum proposalOutputDatum
]
]
)
[1 .. numProposals']
--------------------------------------------------------------------------
sig = case params.transactionParameters.signedBy of
Owner -> stakeOwner
Delegatee -> delegatee
Unknown -> unknownSig
--------------------------------------------------------------------------
validTimeRange =
closedBoundedInterval
((def :: ProposalTimingConfig).draftTime + 1)
((def :: ProposalTimingConfig).votingTime - 1)
--------------------------------------------------------------------------
miscBuilder :: b
miscBuilder =
mconcat
[ signedWith sig
, timeRange validTimeRange
]
--------------------------------------------------------------------------
builder :: b
builder =
mconcat
[ stakeBuilder
, proposalBuidler
, miscBuilder
]
in builder
--------------------------------------------------------------------------------
mkTestTree :: String -> ParameterBundle -> Validity -> SpecificationTree
mkTestTree name ps val = group name $ catMaybes [proposal, stake]
where
spend = mkSpending vote ps
numProposals' = numProposals ps.proposalParameters.numProposals
proposal =
case ps.proposalParameters.numProposals of
NoProposal -> Nothing
_ ->
Just $
testValidator
val.forProposalValidator
"proposal"
proposalValidator
proposalInputDatum
(mkProposalRedeemer ps.voteParameters)
(spend $ mkProposalRef 1)
stake =
case ps.stakeParameters.numStakes of
0 -> error "At least one stake"
_ ->
let stakeRef = mkStakeRef numProposals' 1
in Just $
testValidator
val.forStakeValidator
"stake"
stakeValidator
(mkStakeInputDatum ps.stakeParameters.stakeInputParameters)
(mkStakeRedeemer ps.stakeParameters.stakeOutputParameters)
(spend stakeRef)
--------------------------------------------------------------------------------
-- TODO(Connor) Use optics
mkValidOwnerVoteBundle :: Integer -> ParameterBundle
mkValidOwnerVoteBundle stakes =
ParamerterBundle
{ voteParameters =
VoteParameters
{ voteFor = ResultTag 0
}
, stakeParameters =
StakeParameters
{ numStakes = stakes
, stakeInputParameters =
StakeInputParameters
{ perStakeGTs = 114514
}
, stakeOutputParameters =
StakeOutputParameters
{ burnStakes = False
, dontAddNewLock = False
, changeGTAmount = False
, changeAdaAmount = False
}
}
, proposalParameters =
ProposalParameters
{ wrongAddedVotes = False
, numProposals = OneProposal
}
, transactionParameters =
TransactionParameters
{ signedBy = Owner
}
}
mkValidDelegateeVoteBundle :: Integer -> ParameterBundle
mkValidDelegateeVoteBundle stakes =
let template = mkValidOwnerVoteBundle stakes
in template
{ transactionParameters =
template.transactionParameters
{ signedBy = Delegatee
}
}
ownerVoteWithSignleStake :: ParameterBundle
ownerVoteWithSignleStake = mkValidOwnerVoteBundle 1
transparentAssets :: ParameterBundle
transparentAssets =
ownerVoteWithSignleStake
{ stakeParameters =
ownerVoteWithSignleStake.stakeParameters
{ stakeOutputParameters =
ownerVoteWithSignleStake.stakeParameters.stakeOutputParameters
{ changeAdaAmount = True
}
}
}
transactionNotAuthorized :: ParameterBundle
transactionNotAuthorized =
ownerVoteWithSignleStake
{ transactionParameters =
ownerVoteWithSignleStake.transactionParameters
{ signedBy = Unknown
}
}
voteForNonexistentOutcome :: ParameterBundle
voteForNonexistentOutcome =
ownerVoteWithSignleStake
{ voteParameters =
ownerVoteWithSignleStake.voteParameters
{ voteFor = ResultTag 1919810
}
}
noProposal :: ParameterBundle
noProposal =
ownerVoteWithSignleStake
{ proposalParameters =
ownerVoteWithSignleStake.proposalParameters
{ numProposals = NoProposal
}
}
moreThanOneProposals :: ParameterBundle
moreThanOneProposals =
ownerVoteWithSignleStake
{ proposalParameters =
ownerVoteWithSignleStake.proposalParameters
{ numProposals = MoreThanOneProposals
}
}
ownerVoteWithMultipleStakes :: ParameterBundle
ownerVoteWithMultipleStakes = mkValidOwnerVoteBundle 5
invalidLocks :: ParameterBundle
invalidLocks =
ownerVoteWithMultipleStakes
{ stakeParameters =
ownerVoteWithMultipleStakes.stakeParameters
{ stakeOutputParameters =
ownerVoteWithMultipleStakes.stakeParameters.stakeOutputParameters
{ dontAddNewLock = True
}
}
}
destroyStakes :: ParameterBundle
destroyStakes =
ownerVoteWithMultipleStakes
{ stakeParameters =
ownerVoteWithMultipleStakes.stakeParameters
{ stakeOutputParameters =
ownerVoteWithMultipleStakes.stakeParameters.stakeOutputParameters
{ burnStakes = True
}
}
}
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
}
}
}