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`
729 lines
26 KiB
Haskell
729 lines
26 KiB
Haskell
{- |
|
|
Module : Agora.Proposal.Scripts
|
|
Maintainer : emi@haskell.fyi
|
|
Description: Plutus Scripts for Proposals.
|
|
|
|
Plutus Scripts for Proposals.
|
|
-}
|
|
module Agora.Proposal.Scripts (
|
|
proposalValidator,
|
|
proposalPolicy,
|
|
) where
|
|
|
|
import Agora.Proposal (
|
|
PProposalDatum (PProposalDatum),
|
|
PProposalRedeemer (PAdvanceProposal, PCosign, PUnlock, PVote),
|
|
PProposalStatus (PDraft, PFinished, PLocked, PVotingReady),
|
|
PProposalVotes (PProposalVotes),
|
|
ProposalStatus (Draft, Finished, Locked, VotingReady),
|
|
pretractVotes,
|
|
pwinner',
|
|
)
|
|
import Agora.Proposal.Time (
|
|
currentProposalTime,
|
|
isDraftPeriod,
|
|
isExecutionPeriod,
|
|
isLockingPeriod,
|
|
isVotingPeriod,
|
|
)
|
|
import Agora.Stake (
|
|
PStakeDatum,
|
|
pextractVoteOption,
|
|
pgetStakeRoles,
|
|
pisIrrelevant,
|
|
pisVoter,
|
|
)
|
|
import Agora.Utils (
|
|
pfromSingleton,
|
|
pinsertUniqueBy,
|
|
plistEqualsBy,
|
|
pmapMaybe,
|
|
)
|
|
import Plutarch.Api.V1 (PCredential, PCurrencySymbol)
|
|
import Plutarch.Api.V1.AssocMap (plookup)
|
|
import Plutarch.Api.V2 (
|
|
PMintingPolicy,
|
|
PScriptContext (PScriptContext),
|
|
PScriptPurpose (PMinting, PSpending),
|
|
PTxInInfo,
|
|
PTxInfo (PTxInfo),
|
|
PTxOut,
|
|
PValidator,
|
|
)
|
|
import Plutarch.Extra.AssetClass (PAssetClass, passetClass, passetClassValueOf)
|
|
import Plutarch.Extra.Category (PCategory (pidentity))
|
|
import Plutarch.Extra.Comonad (pextract)
|
|
import Plutarch.Extra.Field (pletAll, pletAllC)
|
|
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
|
|
import Plutarch.Extra.Map (pupdate)
|
|
import Plutarch.Extra.Maybe (
|
|
passertPJust,
|
|
pisJust,
|
|
pjust,
|
|
pmaybe,
|
|
pnothing,
|
|
)
|
|
import Plutarch.Extra.Ord (pfromOrdBy, psort)
|
|
import Plutarch.Extra.Record (mkRecordConstr, (.&), (.=))
|
|
import Plutarch.Extra.ScriptContext (
|
|
pfindTxInByTxOutRef,
|
|
pfromOutputDatum,
|
|
pisTokenSpent,
|
|
ptryFromOutputDatum,
|
|
)
|
|
import Plutarch.Extra.Sum (PSum (PSum))
|
|
import "liqwid-plutarch-extra" Plutarch.Extra.TermCont (
|
|
pguardC,
|
|
pletC,
|
|
pletFieldsC,
|
|
pmatchC,
|
|
ptryFromC,
|
|
)
|
|
import Plutarch.Extra.Traversable (pfoldMap)
|
|
import Plutarch.Extra.Value (psymbolValueOf)
|
|
import Plutarch.SafeMoney (PDiscrete (PDiscrete))
|
|
import Plutarch.Unsafe (punsafeCoerce)
|
|
|
|
{- | Policy for Proposals.
|
|
|
|
== What this policy does
|
|
|
|
=== For minting:
|
|
|
|
- Governor is happy with mint.
|
|
|
|
* The governor must do most of the checking for the validity of the
|
|
transaction. For example, the governor must check that the datum
|
|
is correct, and that the ST is correctly paid to the right validator.
|
|
|
|
- Exactly 1 token is minted.
|
|
|
|
=== For burning:
|
|
|
|
- This policy cannot be burned.
|
|
|
|
== Arguments
|
|
|
|
Following arguments should be provided(in this order):
|
|
1. The assetclass of GST, see 'Agora.Governor.Scripts.governorPolicy'.
|
|
|
|
@since 1.0.0
|
|
-}
|
|
proposalPolicy :: ClosedTerm (PAssetClass :--> PMintingPolicy)
|
|
proposalPolicy =
|
|
plam $ \gtAssetClass _redeemer ctx' -> unTermCont $ do
|
|
PScriptContext ctx' <- pmatchC ctx'
|
|
ctx <- pletAllC ctx'
|
|
PTxInfo txInfo' <- pmatchC $ pfromData ctx.txInfo
|
|
txInfo <- pletFieldsC @'["inputs", "mint"] txInfo'
|
|
|
|
PMinting ownSymbol' <- pmatchC $ pfromData ctx.purpose
|
|
let mintedProposalST =
|
|
passetClassValueOf
|
|
# pfromData txInfo.mint
|
|
# (passetClass # (pfield @"_0" # ownSymbol') # pconstant "")
|
|
|
|
pguardC "Governance state-thread token must move" $
|
|
pisTokenSpent
|
|
# gtAssetClass
|
|
# txInfo.inputs
|
|
|
|
pguardC "Minted exactly one proposal ST" $
|
|
mintedProposalST #== 1
|
|
|
|
pure $ popaque (pconstant ())
|
|
|
|
{- | Validation context for redeemers which witness multiple stake in the reference
|
|
inputs.
|
|
|
|
@since 1.0.0
|
|
-}
|
|
data PWitnessMultipleStakeContext (s :: S) = PWitnessMultipleStakeContext
|
|
{ totalAmount :: Term s PInteger
|
|
, orderedOwners :: Term s (PList PCredential)
|
|
}
|
|
deriving stock
|
|
( -- | @since 1.0.0
|
|
Generic
|
|
)
|
|
deriving anyclass
|
|
( -- | @since 1.0.0
|
|
PlutusType
|
|
)
|
|
|
|
-- | @since 1.0.0
|
|
instance DerivePlutusType PWitnessMultipleStakeContext where
|
|
type DPTStrat _ = PlutusTypeScott
|
|
|
|
-- | @since 1.0.0
|
|
newtype PStakeInputsContext (s :: S) = PStakeInputsContext
|
|
{ inputStakes :: Term s (PList PStakeDatum)
|
|
}
|
|
deriving stock
|
|
( -- | @since 1.0.0
|
|
Generic
|
|
)
|
|
deriving anyclass
|
|
( -- | @since 1.0.0
|
|
PlutusType
|
|
)
|
|
|
|
-- | @since 1.0.0
|
|
instance DerivePlutusType PStakeInputsContext where
|
|
type DPTStrat _ = PlutusTypeNewtype
|
|
|
|
{- | The validator for Proposals.
|
|
|
|
The documentation for various of the redeemers lives at 'Agora.Proposal.ProposalRedeemer'.
|
|
|
|
== What this validator does
|
|
|
|
=== Voting/unlocking
|
|
|
|
When voting and unlocking, the proposal must witness a state transition
|
|
occuring in the relevant Stake. This transition must place a lock on
|
|
the stake that is tagged with the right 'Agora.Proposal.ResultTag', and 'Agora.Proposal.ProposalId'.
|
|
Note that only one proposal per transaction is supported.
|
|
|
|
=== Periods
|
|
|
|
Most redeemers are time-sensitive.
|
|
|
|
A list of all time-sensitive redeemers and their requirements:
|
|
|
|
- 'Agora.Proposal.Vote' can only be used when both the status is in 'Agora.Proposal.VotingReady',
|
|
and 'Agora.Proposal.Time.isVotingPeriod' is true.
|
|
- 'Agora.Proposal.Cosign' can only be used when both the status is in 'Agora.Proposal.Draft',
|
|
and 'Agora.Proposal.Time.isDraftPeriod' is true.
|
|
- 'Agora.Proposal.AdvanceProposal' can only be used when the status can be advanced
|
|
(see 'Agora.Proposal.AdvanceProposal' docs).
|
|
- 'Agora.Proposal.Unlock' is always valid.
|
|
|
|
== Arguments
|
|
|
|
Following arguments should be provided(in this order):
|
|
1. stake ST assetclass
|
|
2. governor ST symbol
|
|
3. proposal ST symbol
|
|
4. maximum number of cosigners
|
|
|
|
@since 1.0.0
|
|
-}
|
|
proposalValidator ::
|
|
ClosedTerm
|
|
( PAssetClass
|
|
:--> PCurrencySymbol
|
|
:--> PCurrencySymbol
|
|
:--> PInteger
|
|
:--> PValidator
|
|
)
|
|
proposalValidator =
|
|
plam $ \sstClass gstSymbol pstSymbol maximumCosigners datum redeemer ctx -> unTermCont $ do
|
|
ctxF <- pletAllC ctx
|
|
|
|
txInfo <- pletC $ pfromData ctxF.txInfo
|
|
txInfoF <-
|
|
pletFieldsC
|
|
@'[ "referenceInputs"
|
|
, "inputs"
|
|
, "outputs"
|
|
, "mint"
|
|
, "datums"
|
|
, "signatories"
|
|
, "validRange"
|
|
]
|
|
txInfo
|
|
|
|
currentTime <- pletC $ currentProposalTime # txInfoF.validRange
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
PSpending ((pfield @"_0" #) -> propsalInputRef) <-
|
|
pmatchC $ pfromData ctxF.purpose
|
|
|
|
let proposalInput =
|
|
pfield @"resolved"
|
|
#$ passertPJust
|
|
# "Own input should present"
|
|
#$ pfindTxInByTxOutRef
|
|
# propsalInputRef
|
|
# txInfoF.inputs
|
|
|
|
proposalInputF <- pletFieldsC @'["address", "value"] proposalInput
|
|
|
|
proposalInputDatum <- pfromData . fst <$> ptryFromC @(PAsData PProposalDatum) datum
|
|
proposalInputDatumF <- pletAllC $ pto proposalInputDatum
|
|
|
|
thresholdsF <- pletAllC proposalInputDatumF.thresholds
|
|
currentStatus <- pletC $ pfromData $ proposalInputDatumF.status
|
|
|
|
-- Own output is an output that
|
|
-- * is sent to the address of the proposal validator
|
|
-- * has an PST
|
|
-- * has the same proposal id as the proposal input
|
|
--
|
|
-- We can handle only one proposal under current design.
|
|
proposalOutputDatum <-
|
|
pletC $
|
|
passertPJust # "proposal input should present"
|
|
#$ pfindJust
|
|
# plam
|
|
( flip pletAll $ \outputF ->
|
|
let isProposalUTxO =
|
|
foldl1
|
|
(#&&)
|
|
[ ptraceIfFalse "Own by proposal validator" $
|
|
outputF.address #== proposalInputF.address
|
|
, ptraceIfFalse "Has proposal ST" $
|
|
psymbolValueOf # pstSymbol # outputF.value #== 1
|
|
]
|
|
|
|
handleProposalUTxO =
|
|
-- Using inline datum to avoid O(n^2) lookup.
|
|
pfromData $
|
|
ptrace "Resolve proposal datum" $
|
|
pfromOutputDatum @(PAsData PProposalDatum)
|
|
# outputF.datum
|
|
# txInfoF.datums
|
|
in pif
|
|
isProposalUTxO
|
|
(pjust # handleProposalUTxO)
|
|
pnothing
|
|
)
|
|
# pfromData txInfoF.outputs
|
|
|
|
--------------------------------------------------------------------------
|
|
|
|
-- Handle stake inputs/outputs.
|
|
|
|
-- Reslove stake datum if the given UTxO is a stake UTxO.
|
|
getStakeDatum :: Term _ (PTxOut :--> PMaybe PStakeDatum) <-
|
|
pletC $
|
|
plam $
|
|
flip (pletFields @'["value", "datum"]) $ \txOutF ->
|
|
let isStakeUTxO =
|
|
-- A stake UTxO is a UTxO that carries SST.
|
|
passetClassValueOf
|
|
# txOutF.value
|
|
# sstClass
|
|
#== 1
|
|
|
|
stake =
|
|
pfromData $
|
|
-- If we can't resolve the stake datum, error out.
|
|
passertPJust # "Stake datum should present"
|
|
-- Use inline datum to avoid extra map lookup.
|
|
#$ ptryFromOutputDatum @(PAsData PStakeDatum)
|
|
# txOutF.datum
|
|
# txInfoF.datums
|
|
in pif isStakeUTxO (pjust # stake) pnothing
|
|
|
|
spendStakes' :: Term _ ((PStakeInputsContext :--> PUnit) :--> PUnit) <-
|
|
pletC $
|
|
plam $
|
|
let stakeInputs =
|
|
pmapMaybe
|
|
# plam ((getStakeDatum #) . (pfield @"resolved" #))
|
|
# pfromData txInfoF.inputs
|
|
|
|
ctx = pcon $ PStakeInputsContext stakeInputs
|
|
in (# ctx)
|
|
|
|
let spendStakes ::
|
|
( PStakeInputsContext _ ->
|
|
TermCont _ ()
|
|
) ->
|
|
Term _ POpaque
|
|
spendStakes c = popaque $
|
|
spendStakes' #$ plam $ \sctx ->
|
|
unTermCont $ pmatchC sctx >>= c >> pure (pconstant ())
|
|
|
|
-- Witness stakes in reference inputs.
|
|
witnessStakes' ::
|
|
Term
|
|
s
|
|
( (PWitnessMultipleStakeContext :--> PUnit) :--> PUnit
|
|
) <-
|
|
pletC $
|
|
let updateCtx = plam $ \ctx' stake -> unTermCont $ do
|
|
ctxF <- pmatchC ctx'
|
|
|
|
stakeF <-
|
|
pletFieldsC @'["stakedAmount", "owner"] $
|
|
pto stake
|
|
|
|
pure $
|
|
pcon $
|
|
PWitnessMultipleStakeContext
|
|
{ totalAmount =
|
|
ctxF.totalAmount
|
|
+ punsafeCoerce
|
|
(pfromData stakeF.stakedAmount)
|
|
, orderedOwners =
|
|
pcons # stakeF.owner
|
|
# ctxF.orderedOwners
|
|
}
|
|
|
|
f :: Term _ (_ :--> PTxInInfo :--> _)
|
|
f = plam $ \ctx' ((pfield @"resolved" #) -> txOut) ->
|
|
let stakeDatum = getStakeDatum # txOut
|
|
updateCtx' = updateCtx # ctx'
|
|
in pmaybe # ctx' # updateCtx' # stakeDatum
|
|
|
|
sortOwners =
|
|
plam $
|
|
flip pmatch $
|
|
\ctxF ->
|
|
pcon $
|
|
ctxF
|
|
{ orderedOwners = psort # ctxF.orderedOwners
|
|
}
|
|
|
|
initialCtx = pcon $ PWitnessMultipleStakeContext 0 pnil
|
|
|
|
ctx =
|
|
sortOwners
|
|
#$ pfoldl
|
|
# f
|
|
# initialCtx
|
|
# txInfoF.referenceInputs
|
|
in plam (# ctx)
|
|
|
|
let witnessStakes ::
|
|
( PWitnessMultipleStakeContext _ ->
|
|
TermCont _ ()
|
|
) ->
|
|
Term _ POpaque
|
|
witnessStakes c = popaque $
|
|
witnessStakes' #$ plam $ \sctxF ->
|
|
unTermCont $ pmatchC sctxF >>= c >> pure (pconstant ())
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
proposalRedeemer <- fst <$> ptryFromC @PProposalRedeemer redeemer
|
|
|
|
pure $
|
|
popaque $
|
|
pmatch proposalRedeemer $ \case
|
|
PCosign _ -> spendStakes $ \sctxF -> do
|
|
pguardC "Should be in draft state" $
|
|
currentStatus #== pconstant Draft
|
|
|
|
stakeF <-
|
|
pletFieldsC @'["owner", "stakedAmount"] $
|
|
ptrace "Exactly one stake input" $
|
|
pfromSingleton # sctxF.inputStakes
|
|
|
|
let newCosigner = stakeF.owner
|
|
|
|
updatedSigs <-
|
|
pletC $
|
|
ptrace "Update signature set" $
|
|
pinsertUniqueBy
|
|
# (pfromOrdBy # plam pfromData)
|
|
# newCosigner
|
|
# proposalInputDatumF.cosigners
|
|
|
|
pguardC "Less cosigners than maximum limit" $
|
|
plength # updatedSigs #< maximumCosigners
|
|
|
|
pguardC "Meet minimum GT requirement" $
|
|
pfromData thresholdsF.cosign #<= stakeF.stakedAmount
|
|
|
|
let expectedDatum =
|
|
mkRecordConstr
|
|
PProposalDatum
|
|
( #proposalId .= proposalInputDatumF.proposalId
|
|
.& #effects .= proposalInputDatumF.effects
|
|
.& #status .= proposalInputDatumF.status
|
|
.& #cosigners .= pdata updatedSigs
|
|
.& #thresholds .= proposalInputDatumF.thresholds
|
|
.& #votes .= proposalInputDatumF.votes
|
|
.& #timingConfig .= proposalInputDatumF.timingConfig
|
|
.& #startingTime .= proposalInputDatumF.startingTime
|
|
)
|
|
|
|
pguardC "Signatures are correctly added to cosignature list" $
|
|
proposalOutputDatum #== expectedDatum
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
PVote r -> spendStakes $ \sctxF -> do
|
|
totalStakeAmount <-
|
|
pletC $
|
|
pto $
|
|
pfoldMap
|
|
# plam
|
|
( \stake -> unTermCont $ do
|
|
stakeF <- pletFieldsC @'["stakedAmount", "lockedBy"] stake
|
|
|
|
pguardC "Same stake shouldn't vote on the same proposal twice" $
|
|
pnot
|
|
#$ pisVoter
|
|
#$ pgetStakeRoles
|
|
# proposalInputDatumF.proposalId
|
|
# stakeF.lockedBy
|
|
|
|
pure $ pcon $ PSum $ pfromData stakeF.stakedAmount
|
|
)
|
|
# sctxF.inputStakes
|
|
|
|
pguardC "Exceed minimum amount" $
|
|
thresholdsF.vote #< totalStakeAmount
|
|
|
|
pguardC "Input proposal must be in VotingReady state" $
|
|
currentStatus #== pconstant VotingReady
|
|
|
|
pguardC "Proposal time should be wthin the voting period" $
|
|
isVotingPeriod # proposalInputDatumF.timingConfig
|
|
# proposalInputDatumF.startingTime
|
|
#$ passertPJust
|
|
# "Should be able to get current time"
|
|
# currentTime
|
|
|
|
-- Ensure the transaction is voting to a valid 'ResultTag'(outcome).
|
|
PProposalVotes voteMap <- pmatchC proposalInputDatumF.votes
|
|
voteFor <- pletC $ pfromData $ pfield @"resultTag" # r
|
|
|
|
pguardC "Vote option should be valid" $
|
|
pisJust #$ plookup # voteFor # voteMap
|
|
|
|
let -- The amount of new votes should be the 'stakedAmount'.
|
|
-- Update the vote counter of the proposal, and leave other stuff as is.
|
|
expectedNewVotes =
|
|
pcon $
|
|
PProposalVotes $
|
|
pupdate
|
|
# plam
|
|
( \votes -> unTermCont $ do
|
|
PDiscrete v <- pmatchC totalStakeAmount
|
|
pure $ pcon $ PJust $ votes + (pextract # v)
|
|
)
|
|
# voteFor
|
|
# pto (pfromData proposalInputDatumF.votes)
|
|
|
|
expectedProposalOut =
|
|
mkRecordConstr
|
|
PProposalDatum
|
|
( #proposalId .= proposalInputDatumF.proposalId
|
|
.& #effects .= proposalInputDatumF.effects
|
|
.& #status .= proposalInputDatumF.status
|
|
.& #cosigners .= proposalInputDatumF.cosigners
|
|
.& #thresholds .= proposalInputDatumF.thresholds
|
|
.& #votes .= pdata expectedNewVotes
|
|
.& #timingConfig .= proposalInputDatumF.timingConfig
|
|
.& #startingTime .= proposalInputDatumF.startingTime
|
|
)
|
|
|
|
pguardC "Output proposal should be valid" $
|
|
proposalOutputDatum #== expectedProposalOut
|
|
|
|
-- Note that the output stake locks validation now happens in the
|
|
-- stake validator.
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
PUnlock _ -> spendStakes $ \sctxF -> do
|
|
let expectedVotes =
|
|
pfoldl
|
|
# plam
|
|
( \votes stake -> unTermCont $ do
|
|
stakeF <-
|
|
pletFieldsC
|
|
@'["stakedAmount", "lockedBy"]
|
|
stake
|
|
|
|
stakeRoles <-
|
|
pletC $
|
|
pgetStakeRoles
|
|
# proposalInputDatumF.proposalId
|
|
# stakeF.lockedBy
|
|
|
|
pguardC "Stake input should be relevant" $
|
|
pnot #$ pisIrrelevant # stakeRoles
|
|
|
|
let canRetractVotes =
|
|
pisVoter # stakeRoles
|
|
|
|
voteCount =
|
|
pextract
|
|
#$ pto
|
|
$ pfromData stakeF.stakedAmount
|
|
|
|
newVotes =
|
|
pretractVotes
|
|
# (pextractVoteOption # stakeRoles)
|
|
# voteCount
|
|
# votes
|
|
|
|
pure $ pif canRetractVotes newVotes votes
|
|
)
|
|
# proposalInputDatumF.votes
|
|
# sctxF.inputStakes
|
|
|
|
currentTime' =
|
|
passertPJust
|
|
# "Should be able to get current time"
|
|
# currentTime
|
|
|
|
inVotingPeriod =
|
|
isVotingPeriod # proposalInputDatumF.timingConfig
|
|
# proposalInputDatumF.startingTime
|
|
# currentTime'
|
|
|
|
-- The votes can only change when the proposal still allows voting.
|
|
shouldUpdateVotes =
|
|
currentStatus #== pconstant VotingReady
|
|
#&& inVotingPeriod
|
|
|
|
pguardC "Proposal output correct" $
|
|
pif
|
|
shouldUpdateVotes
|
|
( let -- Remove votes and leave other parts of the proposal as it.
|
|
expectedProposalOut =
|
|
mkRecordConstr
|
|
PProposalDatum
|
|
( #proposalId .= proposalInputDatumF.proposalId
|
|
.& #effects .= proposalInputDatumF.effects
|
|
.& #status .= proposalInputDatumF.status
|
|
.& #cosigners .= proposalInputDatumF.cosigners
|
|
.& #thresholds .= proposalInputDatumF.thresholds
|
|
.& #votes .= pdata expectedVotes
|
|
.& #timingConfig .= proposalInputDatumF.timingConfig
|
|
.& #startingTime .= proposalInputDatumF.startingTime
|
|
)
|
|
in ptraceIfFalse "Update votes" $
|
|
expectedProposalOut #== proposalOutputDatum
|
|
)
|
|
-- No change to the proposal is allowed.
|
|
( ptraceIfFalse "Proposal unchanged" $
|
|
proposalOutputDatum #== proposalInputDatum
|
|
)
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
PAdvanceProposal _ -> unTermCont $ do
|
|
currentTime' <-
|
|
pletC $
|
|
passertPJust
|
|
# "Should be able to get current time"
|
|
# currentTime
|
|
|
|
applyIs <- pletC $
|
|
plam $ \f ->
|
|
f
|
|
# proposalInputDatumF.timingConfig
|
|
# proposalInputDatumF.startingTime
|
|
# currentTime'
|
|
let inDraftPeriod = applyIs # isDraftPeriod
|
|
inVotingPeriod = applyIs # isVotingPeriod
|
|
inExecutionPeriod = applyIs # isExecutionPeriod
|
|
|
|
inLockedPeriod <- pletC $ applyIs # isLockingPeriod
|
|
|
|
proposalOutputStatus <-
|
|
pletC $
|
|
pfromData $
|
|
pfield @"status" # pto proposalOutputDatum
|
|
|
|
pguardC "Only status changes in the output proposal" $
|
|
let expectedProposalOutputDatum =
|
|
mkRecordConstr
|
|
PProposalDatum
|
|
( #proposalId .= proposalInputDatumF.proposalId
|
|
.& #effects .= proposalInputDatumF.effects
|
|
.& #status .= pdata proposalOutputStatus
|
|
.& #cosigners .= proposalInputDatumF.cosigners
|
|
.& #thresholds .= proposalInputDatumF.thresholds
|
|
.& #votes .= proposalInputDatumF.votes
|
|
.& #timingConfig .= proposalInputDatumF.timingConfig
|
|
.& #startingTime .= proposalInputDatumF.startingTime
|
|
)
|
|
in proposalOutputDatum #== expectedProposalOutputDatum
|
|
|
|
pure $
|
|
pmatch currentStatus $ \case
|
|
PDraft ->
|
|
witnessStakes $ \sctxF -> do
|
|
let notTooLate = inDraftPeriod
|
|
|
|
pmatchC notTooLate >>= \case
|
|
PTrue -> do
|
|
pguardC "More cosigns than minimum amount" $
|
|
punsafeCoerce (pfromData thresholdsF.toVoting) #< sctxF.totalAmount
|
|
|
|
pguardC "All new cosigners are witnessed by their Stake datums" $
|
|
plistEqualsBy
|
|
# plam (\x (pfromData -> y) -> x #== y)
|
|
# sctxF.orderedOwners
|
|
# proposalInputDatumF.cosigners
|
|
|
|
-- 'Draft' -> 'VotingReady'
|
|
pguardC "Proposal status set to VotingReady" $
|
|
proposalOutputStatus #== pconstant VotingReady
|
|
-- Too late: failed proposal, status set to 'Finished'.
|
|
PFalse ->
|
|
pguardC "Proposal should fail: not on time" $
|
|
proposalOutputStatus #== pconstant Finished
|
|
|
|
----------------------------------------------------------------
|
|
|
|
PVotingReady -> unTermCont $ do
|
|
let notTooLate = inLockedPeriod
|
|
notTooEarly = pnot # inVotingPeriod
|
|
|
|
pguardC "Cannot advance ahead of time" notTooEarly
|
|
|
|
pmatchC notTooLate >>= \case
|
|
PTrue -> do
|
|
-- 'VotingReady' -> 'Locked'
|
|
pguardC "Proposal status set to Locked" $
|
|
proposalOutputStatus #== pconstant Locked
|
|
|
|
pguardC "Winner outcome not found" $
|
|
pisJust #$ pwinner' # proposalInputDatumF.votes
|
|
#$ punsafeCoerce
|
|
$ pfromData thresholdsF.execute
|
|
-- Too late: failed proposal, status set to 'Finished'.
|
|
PFalse ->
|
|
pguardC "Proposal should fail: not on time" $
|
|
proposalOutputStatus #== pconstant Finished
|
|
|
|
pure $ popaque $ pconstant ()
|
|
|
|
----------------------------------------------------------------
|
|
|
|
PLocked -> unTermCont $ do
|
|
let notTooLate = inExecutionPeriod
|
|
notTooEarly = pnot # inLockedPeriod
|
|
|
|
pguardC "Not too early" notTooEarly
|
|
|
|
pguardC "Proposal status set to Finished" $
|
|
proposalOutputStatus #== pconstant Finished
|
|
|
|
let gstMoved =
|
|
pany
|
|
# plam
|
|
( \( (pfield @"value" #)
|
|
. (pfield @"resolved" #) ->
|
|
value
|
|
) ->
|
|
psymbolValueOf # gstSymbol # value #== 1
|
|
)
|
|
# pfromData txInfoF.inputs
|
|
|
|
pguardC "GST not moved if too late, moved otherwise" $
|
|
pif
|
|
notTooLate
|
|
-- Not too late: GST should moved
|
|
pidentity
|
|
-- Not too late: GST should not moved
|
|
pnot
|
|
# gstMoved
|
|
|
|
pure $ popaque $ pconstant ()
|
|
|
|
----------------------------------------------------------------
|
|
|
|
PFinished -> ptraceError "Finished proposals cannot be advanced"
|