agora/agora-specs/Sample/Proposal/Unlock.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

542 lines
14 KiB
Haskell

{- |
Module : Sample.Proposal.UnlockStake
Maintainer : connor@mlabs.city
Description: Generate sample data for testing the functionalities of unlocking stake and retracting votes
Sample and utilities for testing the functionalities of unlocking stake and retracting votes
-}
module Sample.Proposal.Unlock (
ParameterBundle (..),
StakeRole (..),
TimeRange (..),
SignedBy (..),
TransactionParameters (..),
ProposalParameters (..),
StakeParameters (..),
Validity (..),
unlock,
mkTestTree,
mkValidVoterRetractVotes,
mkValidDelegateeRetractVotes,
mkValidVoterCreatorRetractVotes,
mkValidCreatorRemoveLock,
mkValidVoterRemoveLockAfterVoting,
mkRetractVotesWhileNotVoting,
mkUnockIrrelevantStakes,
mkRemoveCreatorLockBeforeFinished,
mkCreatorRetractVotes,
mkChangeOutputStakeValue,
) where
--------------------------------------------------------------------------------
import Agora.Governor (Governor (..))
import Agora.Proposal (
ProposalDatum (..),
ProposalEffectGroup,
ProposalId (..),
ProposalRedeemer (Unlock),
ProposalStatus (..),
ProposalVotes (..),
ResultTag (..),
)
import Agora.Proposal.Time (ProposalStartingTime (ProposalStartingTime), ProposalTimingConfig (..))
import Agora.Stake (
ProposalLock (..),
StakeDatum (..),
StakeRedeemer (RetractVotes),
)
import Data.Default.Class (Default (def))
import Data.Map.Strict qualified as StrictMap
import Data.Tagged (Tagged (Tagged), untag)
import Plutarch.Context (
input,
normalizeValue,
output,
script,
signedWith,
timeRange,
txId,
withDatum,
withRedeemer,
withRef,
withValue,
)
import Plutarch.SafeMoney (Discrete (Discrete))
import PlutusLedgerApi.V1.Value qualified as Value
import PlutusLedgerApi.V2 (
Credential (PubKeyCredential),
PubKeyHash,
TxOutRef (..),
)
import Sample.Proposal.Shared (stakeTxRef)
import Sample.Shared (
governor,
minAda,
proposalPolicySymbol,
proposalValidator,
proposalValidatorHash,
stakeAssetClass,
stakeValidator,
stakeValidatorHash,
)
import Test.Specification (SpecificationTree, group, testValidator)
import Test.Util (CombinableBuilder, closedBoundedInterval, mkSpending, pubKeyHashes)
--------------------------------------------------------------------------------
votesTemplate :: ProposalVotes
votesTemplate =
ProposalVotes $
StrictMap.fromList
[ (ResultTag 0, 0)
, (ResultTag 1, 0)
]
-- | Create empty effects for every result tag given the votes.
emptyEffectFor ::
ProposalVotes ->
StrictMap.Map ResultTag ProposalEffectGroup
emptyEffectFor (ProposalVotes vs) =
StrictMap.fromList $
map (,StrictMap.empty) (StrictMap.keys vs)
-- | The default vote option that will be used by functions in this module.
defVoteFor :: ResultTag
defVoteFor = ResultTag 0
-- | The default number of GTs the stake will have.
defStakedGTs :: Integer
defStakedGTs = 100000
alteredStakedGTs :: Integer
alteredStakedGTs = 100
-- | Default owner of the stakes.
defOwner :: PubKeyHash
defOwner = pubKeyHashes !! 1
defDelegatee :: PubKeyHash
defDelegatee = pubKeyHashes !! 2
defUnknown :: PubKeyHash
defUnknown = pubKeyHashes !! 3
defProposalId :: ProposalId
defProposalId = ProposalId 0
defStartingTime :: ProposalStartingTime
defStartingTime = ProposalStartingTime 0
--------------------------------------------------------------------------------
data ParameterBundle = ParameterBundle
{ proposalParameters :: ProposalParameters
, stakeParameters :: StakeParameters
, transactionParameters :: TransactionParameters
}
data SignedBy = Owner | Delegatee | Unknown
data TimeRange = WhileVoting | AfterVoting
data TransactionParameters = TransactionParameters
{ signedBy :: SignedBy
, timeRange :: TimeRange
}
data ProposalParameters = ProposalParameters
{ proposalStatus :: ProposalStatus
, retractVotes :: Bool
}
-- | How a stake has been used on a particular proposal.
data StakeRole
= -- | The stake was spent to vote for a paraticular option.
Voter
| -- | The stake was used to create the proposal.
Creator
| -- | The stake was used to both create and vote for the proposal.
Both
| -- | The stake has nothing to do with the proposal.
Irrelevant
deriving stock (Bounded, Enum, Show)
data StakeParameters = StakeParameters
{ numStakes :: Integer
, stakeRole :: StakeRole
, removeVoterLock :: Bool
, removeCreatorLock :: Bool
, alterOutputValue :: Bool
}
data Validity = Validity
{ forProposalValidator :: Bool
, forStakeValidator :: Bool
}
--------------------------------------------------------------------------------
mkStakeRef :: Integer -> TxOutRef
mkStakeRef = TxOutRef stakeTxRef
stakeRedeemer :: StakeRedeemer
stakeRedeemer = RetractVotes
mkStakeInputDatum :: StakeParameters -> StakeDatum
mkStakeInputDatum ps =
StakeDatum
{ stakedAmount = Discrete $ Tagged defStakedGTs
, owner = PubKeyCredential defOwner
, delegatedTo = Just $ PubKeyCredential defDelegatee
, lockedBy = stakeLocks
}
where
stakeLocks = mkStakeLocks' ps.stakeRole
mkStakeLocks' Voter = [Voted defProposalId defVoteFor]
mkStakeLocks' Creator = [Created defProposalId]
mkStakeLocks' Both = mkStakeLocks' Voter <> mkStakeLocks' Creator
mkStakeLocks' Irrelevant =
let ProposalId pid = defProposalId
ResultTag vid = defVoteFor
in [ Voted (ProposalId $ pid + 1) (ResultTag $ vid + 1)
, Created (ProposalId $ pid + 1)
]
--------------------------------------------------------------------------------
proposalRef :: TxOutRef
proposalRef = TxOutRef stakeTxRef 0
proposalRedeemer :: ProposalRedeemer
proposalRedeemer = Unlock
mkProposalInputDatum ::
StakeParameters ->
ProposalParameters ->
ProposalDatum
mkProposalInputDatum sps pps =
ProposalDatum
{ proposalId = defProposalId
, effects = emptyEffectFor votesTemplate
, status = pps.proposalStatus
, cosigners = [PubKeyCredential $ head pubKeyHashes]
, thresholds = def
, votes = updatVotes votesTemplate
, timingConfig = def
, startingTime = defStartingTime
}
where
updatVotes (ProposalVotes vt) =
ProposalVotes $
StrictMap.adjust
(+ sps.numStakes * defStakedGTs)
defVoteFor
vt
--------------------------------------------------------------------------------
unlock :: forall b. CombinableBuilder b => ParameterBundle -> b
unlock ps = builder
where
pst = Value.singleton proposalPolicySymbol "" 1
proposalInputDatum =
mkProposalInputDatum
ps.stakeParameters
ps.proposalParameters
proposalOutputDatum =
if ps.proposalParameters.retractVotes
then proposalInputDatum {votes = votesTemplate}
else proposalInputDatum
proposalValue = normalizeValue $ pst <> minAda
proposalBuilder :: b
proposalBuilder =
mconcat
[ input $
mconcat
[ script proposalValidatorHash
, withValue proposalValue
, withDatum proposalInputDatum
, withRef proposalRef
, withRedeemer proposalRedeemer
]
, output $
mconcat
[ script proposalValidatorHash
, withValue proposalValue
, withDatum proposalOutputDatum
]
]
---
sst = Value.assetClassValue stakeAssetClass 1
stakeInputDatum = mkStakeInputDatum ps.stakeParameters
removeLocks v c =
filter $
not
. ( \case
Created pid -> c && pid == defProposalId
Cosigned pid -> c && pid == defProposalId
Voted pid _ -> v && pid == defProposalId
)
stakeOutputDatum =
stakeInputDatum
{ lockedBy =
removeLocks
ps.stakeParameters.removeVoterLock
ps.stakeParameters.removeCreatorLock
stakeInputDatum.lockedBy
}
mkStakeValue gt =
normalizeValue $
mconcat
[ minAda
, sst
, Value.assetClassValue
(untag governor.gtClassRef)
gt
]
stakeInputValue = mkStakeValue defStakedGTs
stakeOutputValue =
mkStakeValue $
if ps.stakeParameters.alterOutputValue
then alteredStakedGTs
else defStakedGTs
stakeBuilder :: b
stakeBuilder =
foldMap
( \i ->
mconcat
[ input $
mconcat
[ script stakeValidatorHash
, withValue stakeInputValue
, withDatum stakeInputDatum
, withRef $ mkStakeRef i
]
, output $
mconcat
[ script stakeValidatorHash
, withValue stakeOutputValue
, withDatum stakeOutputDatum
]
]
)
[1 .. ps.stakeParameters.numStakes]
---
time = case ps.transactionParameters.timeRange of
WhileVoting ->
closedBoundedInterval
((def :: ProposalTimingConfig).draftTime + 1)
((def :: ProposalTimingConfig).votingTime - 1)
AfterVoting ->
closedBoundedInterval
((def :: ProposalTimingConfig).votingTime + 1)
((def :: ProposalTimingConfig).lockingTime - 1)
sig = case ps.transactionParameters.signedBy of
Unknown -> defUnknown
Owner -> defOwner
Delegatee -> defDelegatee
---
builder =
mconcat
[ txId "388bc0b897b3dadcd479da4c88291de4113a50b72ddbed001faf7fc03f11bc52"
, proposalBuilder
, stakeBuilder
, signedWith sig
, timeRange time
]
--------------------------------------------------------------------------------
{- | Create a test tree that runs both the stake validator and the proposal
validator.
-}
mkTestTree :: String -> ParameterBundle -> Validity -> SpecificationTree
mkTestTree name ps val = group name [stake, proposal]
where
spend = mkSpending unlock ps
stake =
testValidator
val.forStakeValidator
"stake"
stakeValidator
(mkStakeInputDatum ps.stakeParameters)
stakeRedeemer
(spend $ mkStakeRef 1)
proposal =
testValidator
val.forProposalValidator
"proposal"
proposalValidator
(mkProposalInputDatum ps.stakeParameters ps.proposalParameters)
proposalRedeemer
(spend proposalRef)
--------------------------------------------------------------------------------
mkValidVoterRetractVotes :: Integer -> ParameterBundle
mkValidVoterRetractVotes i =
ParameterBundle
{ proposalParameters =
ProposalParameters
{ proposalStatus = VotingReady
, retractVotes = True
}
, stakeParameters =
StakeParameters
{ numStakes = i
, stakeRole = Voter
, removeVoterLock = True
, removeCreatorLock = False
, alterOutputValue = False
}
, transactionParameters =
TransactionParameters
{ signedBy = Owner
, timeRange =
WhileVoting
}
}
mkValidDelegateeRetractVotes :: Integer -> ParameterBundle
mkValidDelegateeRetractVotes i =
let template = mkValidVoterRetractVotes i
in template
{ transactionParameters =
template.transactionParameters
{ signedBy = Delegatee
}
}
mkValidVoterCreatorRetractVotes :: Integer -> ParameterBundle
mkValidVoterCreatorRetractVotes i =
let template = mkValidVoterRetractVotes i
in template
{ stakeParameters =
template.stakeParameters
{ stakeRole = Both
}
}
mkValidCreatorRemoveLock :: Integer -> ParameterBundle
mkValidCreatorRemoveLock i =
let template = mkValidVoterRetractVotes i
in template
{ proposalParameters =
template.proposalParameters
{ proposalStatus = Finished
, retractVotes = False
}
, stakeParameters =
template.stakeParameters
{ stakeRole = Creator
, removeCreatorLock = True
}
, transactionParameters =
template.transactionParameters
{ timeRange = AfterVoting
}
}
mkValidVoterRemoveLockAfterVoting :: Integer -> ParameterBundle
mkValidVoterRemoveLockAfterVoting i =
let template = mkValidVoterRetractVotes i
in template
{ proposalParameters =
template.proposalParameters
{ proposalStatus = Finished
, retractVotes = False
}
, transactionParameters =
template.transactionParameters
{ timeRange = AfterVoting
}
}
mkRetractVotesWhileNotVoting :: Integer -> [ParameterBundle]
mkRetractVotesWhileNotVoting i =
let template = mkValidVoterRetractVotes i
in map
( \s ->
template
{ proposalParameters =
template.proposalParameters
{ proposalStatus = s
}
}
)
[Draft, Locked, Finished]
mkUnockIrrelevantStakes :: Integer -> ParameterBundle
mkUnockIrrelevantStakes i =
let template = mkValidVoterRetractVotes i
in template
{ stakeParameters =
template.stakeParameters
{ stakeRole = Irrelevant
, removeCreatorLock = True
}
}
mkRemoveCreatorLockBeforeFinished :: Integer -> [ParameterBundle]
mkRemoveCreatorLockBeforeFinished i =
let template = mkValidCreatorRemoveLock i
in map
( \s ->
template
{ proposalParameters =
template.proposalParameters
{ proposalStatus = s
}
}
)
[Draft, VotingReady, Locked]
mkCreatorRetractVotes :: Integer -> ParameterBundle
mkCreatorRetractVotes i =
let template = mkValidVoterRetractVotes i
in template
{ proposalParameters =
template.proposalParameters
{ proposalStatus = VotingReady
}
, stakeParameters =
template.stakeParameters
{ stakeRole = Creator
}
, transactionParameters =
template.transactionParameters
{ timeRange = WhileVoting
}
}
mkChangeOutputStakeValue :: Integer -> ParameterBundle
mkChangeOutputStakeValue i =
let template = mkValidVoterRetractVotes i
in template
{ stakeParameters =
template.stakeParameters
{ alterOutputValue = True
}
}