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

254 lines
6.7 KiB
Haskell

module Sample.Proposal.PrivilegeEscalate (
Operation (..),
privilegeEscalate,
Validity (..),
mkTestTree,
) where
import Agora.Proposal (
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (UnlockStake, Vote),
ProposalStatus (VotingReady),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
emptyVotesFor,
)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (draftTime, votingTime),
)
import Agora.SafeMoney (GTTag)
import Agora.Stake (
ProposalAction (
Voted
),
ProposalLock (ProposalLock),
StakeDatum (..),
StakeRedeemer (PermitVote, RetractVotes),
)
import Data.Default (Default (def))
import Data.Map.Strict qualified as StrictMap
import Data.Tagged (Tagged, untag)
import Plutarch.Context (
input,
normalizeValue,
output,
script,
signedWith,
timeRange,
withDatum,
withRedeemer,
withRef,
withValue,
)
import Plutarch.Extra.AssetClass (assetClassValue)
import PlutusLedgerApi.V1 (Credential (PubKeyCredential))
import PlutusLedgerApi.V2 (PubKeyHash, TxOutRef (TxOutRef))
import Sample.Proposal.Shared (proposalTxRef, stakeTxRef)
import Sample.Shared (
minAda,
proposalAssetClass,
proposalScriptHash,
proposalValidator,
stakeAssetClass,
stakeScriptHash,
stakeValidator,
)
import Test.Specification (SpecificationTree, group, testValidator)
import Test.Util (CombinableBuilder, closedBoundedInterval, mkSpending, pubKeyHashes)
data Operation = Voting | RetractingVotes
data Validity = Validity
{ forStakeValidator :: Bool
, forProposalValidator :: Bool
}
wrap :: forall x y. Operation -> (x -> x -> y) -> x -> x -> y
wrap Voting = id
wrap RetractingVotes = flip
defStakeAmount :: Tagged GTTag Integer
defStakeAmount = 100000
defResultTag :: ResultTag
defResultTag = ResultTag 0
defProposalId :: ProposalId
defProposalId = ProposalId 0
mkProposalInputOutputDatum :: Operation -> (ProposalDatum, ProposalDatum)
mkProposalInputOutputDatum op =
let effects = StrictMap.singleton defResultTag StrictMap.empty
proposal =
ProposalDatum
{ proposalId = defProposalId
, effects = effects
, status = VotingReady
, cosigners = [] -- doesn't matter
, thresholds = def
, votes = emptyVotesFor effects
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
proposalWithVotes =
proposal
{ votes =
ProposalVotes $
StrictMap.singleton defResultTag (untag defStakeAmount)
}
in wrap op (,) proposal proposalWithVotes
mkProposalRedeemer :: Operation -> ProposalRedeemer
mkProposalRedeemer op = wrap op const (Vote defResultTag) UnlockStake
proposalRef :: TxOutRef
proposalRef = TxOutRef proposalTxRef 1
attacker :: PubKeyHash
attacker = head pubKeyHashes
mkStakeInputOutputDatums :: Operation -> ([StakeDatum], [StakeDatum])
mkStakeInputOutputDatums op =
let delegatee = pubKeyHashes !! 1
firstStake =
StakeDatum
{ stakedAmount = defStakeAmount
, owner = PubKeyCredential attacker
, delegatedTo = Just $ PubKeyCredential delegatee
, lockedBy = []
}
otherStakes =
(\pkh -> firstStake {owner = PubKeyCredential pkh})
<$> drop 2 pubKeyHashes
allStakes = take 10 $ firstStake : otherStakes
createdAt = (def :: ProposalTimingConfig).votingTime - 1
stakeWithLock =
( \stake ->
stake
{ lockedBy =
[ ProposalLock defProposalId $
Voted
defResultTag
createdAt
]
}
)
<$> allStakes
in wrap op (,) allStakes stakeWithLock
mkStakeRedeemer :: Operation -> StakeRedeemer
mkStakeRedeemer op = wrap op const PermitVote RetractVotes
mkStakeRef :: Integer -> TxOutRef
mkStakeRef o = TxOutRef stakeTxRef $ 1 + o
privilegeEscalate :: forall b. CombinableBuilder b => Operation -> b
privilegeEscalate op =
let sst = assetClassValue stakeAssetClass 1
stakeValue = normalizeValue $ minAda <> sst
(stakeInputDatums, stakeOutputDatums) = mkStakeInputOutputDatums op
stakeBuilder =
mconcat $
zipWith3
( \index stakeInput stakeOutput ->
mconcat @b
[ input $
mconcat
[ script stakeScriptHash
, withDatum stakeInput
, withValue stakeValue
, withRef $ mkStakeRef index
, withRedeemer $ mkStakeRedeemer op
]
, output $
mconcat
[ script stakeScriptHash
, withDatum stakeOutput
, withValue stakeValue
]
]
)
[1 ..]
stakeInputDatums
stakeOutputDatums
---
pst = assetClassValue proposalAssetClass 1
proposalValue = normalizeValue $ minAda <> pst
(proposalInput, proposalOutput) = mkProposalInputOutputDatum op
proposalBuilder =
mconcat @b
[ input $
mconcat
[ script proposalScriptHash
, withDatum proposalInput
, withRedeemer $ mkProposalRedeemer op
, withValue proposalValue
, withRef proposalRef
]
, output $
mconcat
[ script proposalScriptHash
, withDatum proposalOutput
, withValue proposalValue
]
]
---
validTimeRange =
closedBoundedInterval
((def :: ProposalTimingConfig).draftTime + 1)
((def :: ProposalTimingConfig).votingTime - 1)
miscBuilder =
mconcat @b
[ signedWith attacker
, timeRange validTimeRange
]
in mconcat
[ miscBuilder
, stakeBuilder
, proposalBuilder
]
mkTestTree :: String -> Operation -> Validity -> SpecificationTree
mkTestTree name op val = group name [proposal, stake]
where
spend = mkSpending privilegeEscalate op
proposal =
testValidator
val.forProposalValidator
"proposal"
proposalValidator
(fst $ mkProposalInputOutputDatum op)
(mkProposalRedeemer op)
(spend proposalRef)
stakeInputdDatum = head $ fst $ mkStakeInputOutputDatums op
stake =
testValidator
val.forStakeValidator
"stake"
stakeValidator
stakeInputdDatum
(mkStakeRedeemer op)
(spend $ mkStakeRef 1)