agora/agora-specs/Sample/Proposal/Advance.hs
2022-12-08 17:28:26 +01:00

1125 lines
33 KiB
Haskell

{- |
Module : Sample.Proposal.Advance
Maintainer : connor@mlabs.city
Description: Generate sample data for testing the functionalities of advancing proposals
Sample and utilities for testing the functionalities of advancing proposals.
-}
module Sample.Proposal.Advance (
-- * Parameters
ParameterBundle (..),
GovernorParameters (..),
AuthorityTokenParameters (..),
ProposalParameters (..),
StakeParameters (..),
Winner (..),
-- * Testing Utilities
Validity (..),
advance,
mkTestTree,
mkTestTree',
-- * Parameter Bundles
mkValidToNextStateBundle,
mkValidToNextStateBundles,
mkValidToFailedStateBundles,
mkInsufficientVotesBundle,
mkAmbiguousWinnerBundle,
mkFromFinishedBundles,
mkInsufficientCosignsBundle,
mkToNextStateTooLateBundles,
mkMintGATsForWrongEffectsBundle,
mkNoGATMintedBundle,
mkGATsWithWrongDatumBundle,
mkMintGATsWithoutTagBundle,
mkBadGovernorOutputDatumBundle,
mkUnexpectedOutputStakeBundles,
mkFastforwardToFinishBundles,
mkBadGovernorRedeemerBundle,
) where
import Agora.Governor (
Governor (..),
GovernorDatum (..),
GovernorRedeemer (CreateProposal, MintGATs),
)
import Agora.Proposal (
ProposalDatum (..),
ProposalEffectGroup,
ProposalEffectMetadata (ProposalEffectMetadata),
ProposalId (ProposalId),
ProposalRedeemer (AdvanceProposal),
ProposalStatus (..),
ProposalThresholds (..),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
emptyVotesFor,
)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (
draftTime,
executingTime,
lockingTime,
votingTime
),
)
import Agora.SafeMoney (AuthorityTokenTag, GTTag)
import Agora.Stake (
StakeDatum (..),
)
import Control.Applicative (liftA2)
import Control.Monad.State (execState, modify, when)
import Data.Default (def)
import Data.List (singleton, sort)
import Data.Map.Strict qualified as StrictMap
import Data.Maybe (fromJust)
import Data.Tagged (Tagged (Tagged), untag)
import Plutarch.Context (
input,
mint,
output,
referenceInput,
script,
signedWith,
timeRange,
withDatum,
withInlineDatum,
withRedeemer,
withRef,
withValue,
)
import Plutarch.Extra.AssetClass (AssetClass (AssetClass), assetClassValue)
import Plutarch.Extra.ScriptContext (scriptHashToTokenName)
import Plutarch.Lift (PLifted, PUnsafeLiftDecl)
import PlutusLedgerApi.V2 (
Credential (PubKeyCredential),
DatumHash,
POSIXTime,
POSIXTimeRange,
PubKeyHash,
ScriptHash,
TxOutRef (TxOutRef),
)
import PlutusTx qualified
import Sample.Proposal.Shared (
governorTxRef,
proposalTxRef,
stakeTxRef,
)
import Sample.Shared (
authorityTokenPolicy,
authorityTokenSymbol,
governor,
governorAssetClass,
governorScriptHash,
governorValidator,
minAda,
proposalAssetClass,
proposalScriptHash,
proposalValidator,
signer,
stakeAssetClass,
stakeScriptHash,
)
import Test.Specification (
SpecificationTree,
group,
testPolicy,
testValidator,
)
import Test.Util (
CombinableBuilder,
closedBoundedInterval,
datumHash,
groupsOfN,
mkMinting,
mkSpending,
pubKeyHashes,
scriptHashes,
sortValue,
toDatum,
validatorHashes,
)
{- | A bunch of parameters that control the generation of the transaction
context.
-}
data ParameterBundle = ParameterBundle
{ proposalParameters :: ProposalParameters
-- ^ Parameters related to the the advancing proposal.
, stakeParameters :: StakeParameters
-- ^ Parameters related to stakes.
, governorParameters :: Maybe GovernorParameters
-- ^ Parameters related to GST moving. If set to 'Nothing', the GST won't
-- be moved, thus the governor validator won't be run in 'mkTestTree'.
, authorityTokenParameters :: [AuthorityTokenParameters]
-- ^ Parameters related to GAT minting. If set to 'Nothing', no GAT will
-- be minted, thus the GAT minting policy won't be run in 'mkTestTree'.
, transactionTimeRange :: POSIXTimeRange
-- ^ The value of 'TxInfo.txInfoValidRange', valid range of the generated
-- transaction.
, extraSignature :: Maybe PubKeyHash
-- ^ An extra signator. Intended to be used when
-- 'StakeParametersstakeParameters.transactionSignedByOwners' is set to
-- false.
}
-- | Everything about the generated governor stuff.
data GovernorParameters = forall
(redeemer :: Type)
(predeemer :: PType).
( PUnsafeLiftDecl predeemer
, PLifted predeemer ~ redeemer
, PIsData predeemer
, PlutusTx.ToData redeemer
) =>
GovernorParameters
{ invalidGovernorOutputDatum :: Bool
-- ^ The output governor datum will be changed.
, governorRedeemer :: redeemer
}
-- | Everything about the generated authority token stuff.
data AuthorityTokenParameters = forall
(datum :: Type)
(pdatum :: S -> Type).
( PUnsafeLiftDecl pdatum
, PLifted pdatum ~ datum
, PIsData pdatum
) =>
AuthorityTokenParameters
{ mintGATsFor :: ScriptHash
-- ^ GATs will be minted and sent to the given group of effects.
, carryDatum :: Maybe datum
-- ^ The datum that GAT UTxOs will be carrying.
, carryAuthScript :: Maybe ScriptHash
-- ^ The authentication script that GAT UTxOs link to through their token name.
, invalidTokenName :: Bool
-- ^ If set to true, GATs won't be tagged by their corresponding effect
-- hashes.
}
-- | Represent the winning effect group(s).
data Winner
= -- | Only one effect at the given index has the highest votes.
EffectAt Index
| -- | All the effects have the same highest votes.
All
-- | Everything about the generated proposal stuff.
data ProposalParameters = ProposalParameters
{ fromStatus :: ProposalStatus
-- ^ What status is the proposal advancing from
, toStatus :: ProposalStatus
-- ^ What status is the proposal advancing to
, effectList :: [ProposalEffectGroup]
-- ^ The effect groups of the proposal. A neutral effect group is not
-- required here.
, winnerAndVotes :: Maybe (Winner, Integer)
-- ^ Specify the effect group(s) that have the highest votes, and the value
-- of the highest votes.
, numCosigners :: NumStake
-- ^ The number of cosigners.
, invalidProposalOutputDatum :: Bool
-- ^ Whether to make the proposal output datum invalid or not.
}
-- | Everything about the generated stake stuff.
data StakeParameters = StakeParameters
{ numStake :: NumStake
, perStakeGTs :: Tagged GTTag Integer
, transactionSignedByOwners :: Bool
}
-- | Represent the number of stakes or the number of the cosigners.
type NumStake = Int
-- | Represent an index.
type Index = Int
{- | The validity of the generated transaction for variuos componets.
'True' means valid, 'False' means invalid.
-}
data Validity = Validity
{ forProposalValidator :: Bool
, forStakeValidator :: Bool
, forGovernorValidator :: Maybe Bool
, forAuthorityTokenPolicy :: Maybe Bool
}
--------------------------------------------------------------------------------
-- * Proposal
-- | Mock cosigners.
mkCosigners :: NumStake -> [Credential]
mkCosigners = sort . fmap PubKeyCredential . flip take pubKeyHashes
-- | Allocate the result tag for the effect at the given index.
outcomeIdxToResultTag :: Index -> ResultTag
outcomeIdxToResultTag = ResultTag . fromIntegral
-- | Add a neutral effect group and allocate result tags for the effect groups.
mkEffects ::
ProposalParameters ->
StrictMap.Map ResultTag ProposalEffectGroup
mkEffects ps =
let resultTags = map ResultTag [0 ..]
neutralEffect = StrictMap.empty
finalEffects = ps.effectList <> [neutralEffect]
in StrictMap.fromList $ zip resultTags finalEffects
-- | Set the votes of the winning group(s).
setWinner :: (Winner, Integer) -> ProposalVotes -> ProposalVotes
setWinner (All, votes) (ProposalVotes m) =
ProposalVotes $ StrictMap.mapMaybe (const $ Just votes) m
setWinner (EffectAt winnerIdx, votes) (ProposalVotes m) =
let winnerResultTag = outcomeIdxToResultTag winnerIdx
in ProposalVotes $ StrictMap.adjust (const votes) winnerResultTag m
-- | Mock votes for the proposal, given the parameters.
mkVotes ::
ProposalParameters ->
ProposalVotes
mkVotes ps =
let effects = mkEffects ps
emptyVotes = emptyVotesFor effects
in maybe emptyVotes (`setWinner` emptyVotes) (ps.winnerAndVotes)
-- | The starting time of every generated proposal.
proposalStartingTime :: POSIXTime
proposalStartingTime = 100
-- | Create the input proposal datum given the parameters.
mkProposalInputDatum :: ProposalParameters -> ProposalDatum
mkProposalInputDatum ps =
let effects = mkEffects ps
votes = mkVotes ps
st = ProposalStartingTime proposalStartingTime
in ProposalDatum
{ proposalId = ProposalId 0
, effects = effects
, status = ps.fromStatus
, cosigners = mkCosigners ps.numCosigners
, thresholds = def
, votes = votes
, timingConfig = def
, startingTime = st
}
-- | Create the output proposal datum given the parameters.
mkProposalOutputDatum :: ProposalParameters -> ProposalDatum
mkProposalOutputDatum ps =
let inputDatum = mkProposalInputDatum ps
outputCosigners =
if ps.invalidProposalOutputDatum
then []
else inputDatum.cosigners
in inputDatum
{ status = ps.toStatus
, cosigners = outputCosigners
}
-- | Reference to the proposal UTXO.
proposalRef :: TxOutRef
proposalRef = TxOutRef proposalTxRef 1
{- | Create a context builder that contains all the information about the
input/output of the proposal validator, given the paramters.
-}
mkProposalBuilder :: forall b. CombinableBuilder b => ProposalParameters -> b
mkProposalBuilder ps =
let pst = assetClassValue proposalAssetClass 1
value = sortValue $ minAda <> pst
in mconcat
[ input $
mconcat
[ script proposalScriptHash
, withRef proposalRef
, withDatum (mkProposalInputDatum ps)
, withValue value
]
, output $
mconcat
[ script proposalScriptHash
, withDatum (mkProposalOutputDatum ps)
, withValue value
]
]
{- | The proposal redeemer used to spend the proposal UTXO, which is always
'AdvanceProposal' in this case.
-}
proposalRedeemer :: ProposalRedeemer
proposalRedeemer = AdvanceProposal
--------------------------------------------------------------------------------
-- * Stake
-- Mock owners of the stakes.
mkStakeOwners :: NumStake -> [Credential]
mkStakeOwners = mkCosigners
-- | Create the input stake datums given the parameters.
mkStakeInputDatums :: StakeParameters -> [StakeDatum]
mkStakeInputDatums ps =
let template =
StakeDatum
{ stakedAmount = ps.perStakeGTs
, owner = PubKeyCredential ""
, delegatedTo = Nothing
, lockedBy = []
}
in (\owner -> template {owner = owner})
<$> mkStakeOwners ps.numStake
-- | Create the reference to a particular stake UTXO.
mkStakeRef :: Index -> TxOutRef
mkStakeRef = TxOutRef stakeTxRef . (+ 3) . fromIntegral
{- | Create a context builder that contains all the inputs/outputs of the
stake validator.
-}
mkStakeBuilder :: forall b. CombinableBuilder b => StakeParameters -> b
mkStakeBuilder ps =
let perStakeValue =
sortValue $
minAda
<> assetClassValue stakeAssetClass 1
<> assetClassValue
governor.gtClassRef
ps.perStakeGTs
perStake idx i =
let withSig =
case (i.owner, ps.transactionSignedByOwners) of
(PubKeyCredential owner, True) -> signedWith owner
_ -> mempty
in mconcat
[ withSig
, referenceInput $
mconcat
[ script stakeScriptHash
, withRef (mkStakeRef idx)
, withValue perStakeValue
, withInlineDatum i
]
]
in mconcat $
zipWith
perStake
[0 :: Index ..]
(mkStakeInputDatums ps)
--------------------------------------------------------------------------------
-- * Governor
-- | The input governor datum.
governorInputDatum :: GovernorDatum
governorInputDatum =
GovernorDatum
{ proposalThresholds = def
, nextProposalId = ProposalId 42
, proposalTimings = def
, createProposalTimeRangeMaxWidth = def
, maximumCreatedProposalsPerStake = 3
}
-- | Create the output governor datum given the parameters.
mkGovernorOutputDatum :: GovernorParameters -> GovernorDatum
mkGovernorOutputDatum ps =
if ps.invalidGovernorOutputDatum
then governorInputDatum {maximumCreatedProposalsPerStake = 15}
else governorInputDatum
-- | Reference to the governor UTXO.
governorRef :: TxOutRef
governorRef = TxOutRef governorTxRef 2
{- | Create a context builder that contains the input and the output of the
governor validator.
-}
mkGovernorBuilder :: forall b. CombinableBuilder b => GovernorParameters -> b
mkGovernorBuilder ps@(GovernorParameters _ redeemer) =
let gst = assetClassValue governorAssetClass 1
value = sortValue $ gst <> minAda
in mconcat
[ input $
mconcat
[ script governorScriptHash
, withValue value
, withRef governorRef
, withDatum governorInputDatum
, withRedeemer redeemer
]
, output $
mconcat
[ script governorScriptHash
, withValue value
, withRef governorRef
, withDatum (mkGovernorOutputDatum ps)
]
]
--------------------------------------------------------------------------------
-- * Authority Token
{- | Create a context builder that contains the infomation about the minted
authority tokens and where they're sent to.
-}
mkAuthorityTokenBuilder ::
forall b.
CombinableBuilder b =>
AuthorityTokenParameters ->
b
mkAuthorityTokenBuilder ps@AuthorityTokenParameters {carryDatum} =
let tn =
case (ps.invalidTokenName, ps.carryAuthScript) of
(True, Just _) -> "deadbeef"
(True, Nothing) -> "deadbeef"
(False, Just as) -> scriptHashToTokenName as
(False, Nothing) -> ""
ac = Tagged @AuthorityTokenTag $ AssetClass authorityTokenSymbol tn
minted = assetClassValue ac 1
value = sortValue $ minAda <> minted
in mconcat
[ mint minted
, output $
mconcat
[ script ps.mintGATsFor
, maybe mempty withDatum carryDatum
, withValue value
]
]
-- | The redeemer used while running the authority token policy.
authorityTokenRedeemer :: ()
authorityTokenRedeemer = ()
--------------------------------------------------------------------------------
-- | Create a 'TxInfo' that update the status of a proposal.
advance ::
forall b.
CombinableBuilder b =>
ParameterBundle ->
b
advance pb =
let mkBuilderMaybe = maybe mempty
in mconcat
[ mkProposalBuilder pb.proposalParameters
, mkStakeBuilder pb.stakeParameters
, mkBuilderMaybe mkGovernorBuilder pb.governorParameters
, foldMap mkAuthorityTokenBuilder pb.authorityTokenParameters
, timeRange pb.transactionTimeRange
, maybe mempty signedWith pb.extraSignature
]
--------------------------------------------------------------------------------
{- | Create a test tree that runs the relavant componets to test the advancing
functionalities.
-}
mkTestTree ::
String ->
ParameterBundle ->
Validity ->
SpecificationTree
mkTestTree name pb val =
group name $ mconcat [proposal, governor, authority]
where
spend = mkSpending advance pb
proposal =
let proposalInputDatum = mkProposalInputDatum pb.proposalParameters
in singleton $
testValidator
val.forProposalValidator
"proposal"
proposalValidator
proposalInputDatum
proposalRedeemer
(spend proposalRef)
governor =
maybe
[]
( singleton
. ( \(GovernorParameters _ governorRedeemer) ->
testValidator
(fromJust val.forGovernorValidator)
"governor"
governorValidator
governorInputDatum
governorRedeemer
(spend governorRef)
)
)
(pb.governorParameters)
authority = case pb.authorityTokenParameters of
[] -> []
_ ->
singleton
( testPolicy
(fromJust val.forAuthorityTokenPolicy)
"authority"
authorityTokenPolicy
authorityTokenRedeemer
(mkMinting advance pb authorityTokenSymbol)
)
{- | Create a test tree that runs a bunch of parameter bundles. These bundles
should have the same validity.
-}
mkTestTree' ::
String ->
(ParameterBundle -> String) ->
[ParameterBundle] ->
Validity ->
SpecificationTree
mkTestTree' groupName mkCaseName bundles val =
group groupName $
(\b -> mkTestTree (mkCaseName b) b val)
<$> bundles
--------------------------------------------------------------------------------
-- Utilities for creating parameter bundles
{- | Given the proposal status, create a time range that is in time for
advacing to the next state.
-}
mkInTimeTimeRange :: ProposalStatus -> POSIXTimeRange
mkInTimeTimeRange advanceFrom =
case advanceFrom 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"
{- | Given the proposal status, create a time range that is too time for
advacing to the next state.
-}
mkTooLateTimeRange :: ProposalStatus -> POSIXTimeRange
mkTooLateTimeRange advanceFrom =
case advanceFrom 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"
-- | Next state of the given proposal status.
getNextState :: ProposalStatus -> ProposalStatus
getNextState = \case
Draft -> VotingReady
VotingReady -> Locked
Locked -> Finished
Finished -> error "Cannot advance 'Finished' proposal"
-- | Calculate the number of GTs per stake in order to exceed the minimum limit.
compPerStakeGTsForDraft :: NumStake -> Tagged GTTag Integer
compPerStakeGTsForDraft nCosigners =
Tagged $
untag (def :: ProposalThresholds).toVoting
`div` fromIntegral nCosigners
+ 1
dummyDatum :: ()
dummyDatum = ()
dummyDatumHash :: DatumHash
dummyDatumHash = datumHash $ toDatum dummyDatum
-- | Create given number of effect groups. Each group will have 3 effects.
mkMockEffects :: Bool -> Int -> [ProposalEffectGroup]
mkMockEffects useAuthScript n = effects
where
effectsPerGroup = 3
mkAuthScripts True = Just <$> scriptHashes
mkAuthScripts False = repeat Nothing
authScripts = mkAuthScripts useAuthScript
datums = repeat dummyDatumHash
effectMetadata = zipWith ProposalEffectMetadata datums authScripts
effectScripts = validatorHashes
effects =
take n $
StrictMap.fromList
<$> groupsOfN
effectsPerGroup
(zip effectScripts effectMetadata)
numberOfVotesThatJustMeetsTheMinimumRequirement :: Integer
numberOfVotesThatJustMeetsTheMinimumRequirement =
untag (def @ProposalThresholds).execute
mkWinnerVotes :: Index -> (Winner, Integer)
mkWinnerVotes idx =
( EffectAt idx
, numberOfVotesThatJustMeetsTheMinimumRequirement
)
ambiguousWinnerVotes :: (Winner, Integer)
ambiguousWinnerVotes =
( All
, numberOfVotesThatJustMeetsTheMinimumRequirement
)
--------------------------------------------------------------------------------
-- * Parameter Bundles
---
-- * Legal
defaultWinnerIdx :: Index
defaultWinnerIdx = 0
{- | Advance a proposal to the next state, perfectly valid for all the
componets.
-}
mkValidToNextStateBundle ::
-- | Number of cosigners.
Word ->
-- | Number of effects.
Word ->
-- | Toggle the referenc script in GAT UTXO.
Bool ->
-- | The initial proposal state, should not be 'Finished'.
ProposalStatus ->
ParameterBundle
mkValidToNextStateBundle _ _ _ Finished =
error "Cannot advance from Finished"
mkValidToNextStateBundle nCosigners nEffects authScript from =
let next = getNextState from
effects = mkMockEffects authScript $ fromIntegral nEffects
winner = defaultWinnerIdx
template =
ParameterBundle
{ proposalParameters =
ProposalParameters
{ fromStatus = from
, toStatus = next
, effectList = effects
, winnerAndVotes = Nothing
, numCosigners = fromIntegral nCosigners
, invalidProposalOutputDatum = False
}
, stakeParameters =
StakeParameters
{ numStake = 0
, perStakeGTs =
compPerStakeGTsForDraft $
fromIntegral nCosigners
, transactionSignedByOwners = False
}
, governorParameters = Nothing
, authorityTokenParameters = []
, transactionTimeRange = mkInTimeTimeRange from
, extraSignature = Just signer
}
-- This is my favourite part of the test suite, lol.
modifyTemplate = do
when (from == Draft) $
modify $ \b ->
b
{ stakeParameters =
b.stakeParameters
{ transactionSignedByOwners = True
, numStake = fromIntegral nCosigners
}
, extraSignature = Nothing
}
when (from == VotingReady || from == Locked) $
modify $ \b ->
b
{ proposalParameters =
b.proposalParameters
{ winnerAndVotes = Just $ mkWinnerVotes winner
}
}
when (from == Locked) $
modify $ \b ->
let aut =
StrictMap.elems $
StrictMap.mapWithKey
( \vh (ProposalEffectMetadata _ authScript) ->
AuthorityTokenParameters
{ mintGATsFor = vh
, carryDatum = Just dummyDatum
, carryAuthScript = authScript
, invalidTokenName = False
}
)
(effects !! winner)
gov =
GovernorParameters
{ invalidGovernorOutputDatum = False
, governorRedeemer = MintGATs
}
in b
{ governorParameters = Just gov
, authorityTokenParameters = aut
}
in execState modifyTemplate template
mkValidToNextStateBundles ::
-- | Number of cosigners
Word ->
-- | Number of effects
Word ->
[ParameterBundle]
mkValidToNextStateBundles nCosigners nEffects =
liftA2
(mkValidToNextStateBundle nCosigners nEffects)
[True, False]
[Draft, VotingReady, Locked]
mkValidToFailedStateBundles ::
-- | Number of cosigners
Word ->
-- | Number of effects
Word ->
[ParameterBundle]
mkValidToFailedStateBundles nCosigners nEffects =
liftA2
mkBundle
[True, False]
[Draft, VotingReady, Locked]
where
mkBundle authScript from =
let next = Finished
effects = mkMockEffects authScript $ fromIntegral nEffects
in ParameterBundle
{ proposalParameters =
ProposalParameters
{ fromStatus = from
, toStatus = next
, effectList = effects
, winnerAndVotes = Nothing
, numCosigners = fromIntegral nCosigners
, invalidProposalOutputDatum = False
}
, stakeParameters =
StakeParameters
{ numStake = 0
, perStakeGTs =
compPerStakeGTsForDraft $
fromIntegral nCosigners
, transactionSignedByOwners = False
}
, governorParameters = Nothing
, authorityTokenParameters = []
, transactionTimeRange = mkTooLateTimeRange from
, extraSignature = Just signer
}
-- * Illegal
mkFromFinishedBundles ::
-- | Number of cosigners
Word ->
-- | Number of effects
Word ->
[ParameterBundle]
mkFromFinishedBundles nCosigners nEffects =
liftA2
mkBundle
[True, False]
[Draft, VotingReady, Locked]
where
mkBundle authScript from =
let template = mkValidToNextStateBundle nCosigners nEffects authScript from
in template
{ proposalParameters =
template.proposalParameters
{ fromStatus = Finished
, toStatus = Finished
}
}
mkToNextStateTooLateBundles :: Word -> Word -> [ParameterBundle]
mkToNextStateTooLateBundles nCosigners nEffects =
liftA2
mkBundle
[True, False]
[Draft, VotingReady, Locked]
where
mkBundle authScript from =
let template = mkValidToNextStateBundle nCosigners nEffects authScript from
in template
{ transactionTimeRange = mkTooLateTimeRange from
}
mkUnexpectedOutputStakeBundles :: Word -> Word -> [ParameterBundle]
mkUnexpectedOutputStakeBundles nCosigners nEffects =
liftA2
mkBundle
[True, False]
[VotingReady, Locked]
where
mkBundle authScript from =
let template = mkValidToNextStateBundle nCosigners nEffects authScript from
in template
{ stakeParameters =
template.stakeParameters
{ numStake = 1
}
}
-- * From Draft
mkInsufficientCosignsBundle :: Word -> Word -> ParameterBundle
mkInsufficientCosignsBundle nCosigners nEffects =
template
{ stakeParameters =
template.stakeParameters
{ perStakeGTs = insuffcientPerStakeGTs
}
}
where
insuffcientPerStakeGTs =
Tagged $
untag (def :: ProposalThresholds).toVoting
`div` fromIntegral nCosigners
- 1
template = mkValidToNextStateBundle nCosigners nEffects False Draft
-- * From VotingReady
setWinnerAndVotes ::
ParameterBundle ->
Maybe (Winner, Integer) ->
ParameterBundle
setWinnerAndVotes pb wv =
pb
{ proposalParameters =
pb.proposalParameters
{ winnerAndVotes = wv
}
}
mkInsufficientVotesBundle ::
Word ->
Word ->
ParameterBundle
mkInsufficientVotesBundle nCosigners nEffects =
mkValidToNextStateBundle nCosigners nEffects False VotingReady
`setWinnerAndVotes` Nothing
mkAmbiguousWinnerBundle ::
Word ->
Word ->
ParameterBundle
mkAmbiguousWinnerBundle nCosigners nEffects =
mkValidToNextStateBundle nCosigners nEffects False VotingReady
`setWinnerAndVotes` Just ambiguousWinnerVotes
-- * From Locked
mkValidFromLockedBundle :: Word -> Word -> ParameterBundle
mkValidFromLockedBundle nCosigners nEffects =
mkValidToNextStateBundle nCosigners nEffects False Locked
mkMintGATsForWrongEffectsBundle ::
Word ->
Word ->
ParameterBundle
mkMintGATsForWrongEffectsBundle nCosigners nEffects =
template
{ authorityTokenParameters =
take 4 $
zipWith
(\a i -> a {mintGATsFor = validatorHashes !! i})
template.authorityTokenParameters
[1, 3 ..]
}
where
template = mkValidFromLockedBundle nCosigners nEffects
mkNoGATMintedBundle ::
Word ->
Word ->
ParameterBundle
mkNoGATMintedBundle nCosigners nEffects =
template
{ authorityTokenParameters = []
}
where
template = mkValidFromLockedBundle nCosigners nEffects
mkMintGATsWithoutTagBundle ::
Word ->
Word ->
ParameterBundle
mkMintGATsWithoutTagBundle nCosigners nEffects =
template
{ authorityTokenParameters =
( \aut ->
aut
{ invalidTokenName = True
}
)
<$> template.authorityTokenParameters
}
where
template = mkValidFromLockedBundle nCosigners nEffects
mkGATsWithWrongDatumBundle ::
Word ->
Word ->
ParameterBundle
mkGATsWithWrongDatumBundle nCosigners nEffects =
template
{ authorityTokenParameters = newAut
}
where
template = mkValidFromLockedBundle nCosigners nEffects
newAut =
( \aut ->
AuthorityTokenParameters
aut.mintGATsFor
(Just (1 :: Integer))
aut.carryAuthScript
False
)
<$> template.authorityTokenParameters
mkBadGovernorOutputDatumBundle ::
Word ->
Word ->
ParameterBundle
mkBadGovernorOutputDatumBundle nCosigners nEffects =
template
{ governorParameters = Just gov
}
where
template = mkValidFromLockedBundle nCosigners nEffects
gov = GovernorParameters True MintGATs
mkBadGovernorRedeemerBundle ::
Word ->
Word ->
ParameterBundle
mkBadGovernorRedeemerBundle nCosigners nEffects =
template
{ governorParameters = Just gov
}
where
template = mkValidFromLockedBundle nCosigners nEffects
gov = GovernorParameters False CreateProposal
mkFastforwardToFinishBundles ::
Word ->
Word ->
[ParameterBundle]
mkFastforwardToFinishBundles nCosigners nEffects = updateTemplate <$> templates
where
templates = mkValidToFailedStateBundles nCosigners nEffects
mkMaliciousTimRange =
let lb = proposalStartingTime - 1
dub =
1
+ proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
vub =
dub
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
lub =
vub
+ (def :: ProposalTimingConfig).executingTime
go Draft = (lb, dub)
go VotingReady = (lb, vub)
go Locked = (lb, lub)
go Finished = error "cannot advance from Finished"
in uncurry closedBoundedInterval . go
updateTemplate template =
template
{ transactionTimeRange =
mkMaliciousTimRange template.proposalParameters.fromStatus
}