move Proposal impl. to Agora.Proposal.Scripts to avoid cyclic deps
This commit is contained in:
parent
cee9eaec79
commit
deaa5cf6f1
6 changed files with 253 additions and 197 deletions
|
|
@ -129,6 +129,7 @@ library
|
||||||
Agora.Governor
|
Agora.Governor
|
||||||
Agora.MultiSig
|
Agora.MultiSig
|
||||||
Agora.Proposal
|
Agora.Proposal
|
||||||
|
Agora.Proposal.Scripts
|
||||||
Agora.Proposal.Time
|
Agora.Proposal.Time
|
||||||
Agora.Record
|
Agora.Record
|
||||||
Agora.SafeMoney
|
Agora.SafeMoney
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ module Agora.Effect.NoOp (noOpValidator, PNoOp) where
|
||||||
import Control.Applicative (Const)
|
import Control.Applicative (Const)
|
||||||
|
|
||||||
import Agora.Effect (makeEffect)
|
import Agora.Effect (makeEffect)
|
||||||
import Plutarch (popaque)
|
|
||||||
import Plutarch.Api.V1 (PValidator)
|
import Plutarch.Api.V1 (PValidator)
|
||||||
import Plutarch.TryFrom (PTryFrom (..))
|
import Plutarch.TryFrom (PTryFrom (..))
|
||||||
import Plutus.V1.Ledger.Value (CurrencySymbol)
|
import Plutus.V1.Ledger.Value (CurrencySymbol)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import Generics.SOP (Generic, I (I))
|
||||||
|
|
||||||
import Agora.Effect (makeEffect)
|
import Agora.Effect (makeEffect)
|
||||||
import Agora.Utils (findTxOutByTxOutRef, paddValue, passert)
|
import Agora.Utils (findTxOutByTxOutRef, paddValue, passert)
|
||||||
import Plutarch (popaque)
|
|
||||||
import Plutarch.Api.V1 (
|
import Plutarch.Api.V1 (
|
||||||
PCredential (..),
|
PCredential (..),
|
||||||
PTuple,
|
PTuple,
|
||||||
|
|
@ -34,7 +33,7 @@ import Plutarch.DataRepr (
|
||||||
PDataFields,
|
PDataFields,
|
||||||
PIsDataReprInstances (..),
|
PIsDataReprInstances (..),
|
||||||
)
|
)
|
||||||
import Plutarch.Lift (PUnsafeLiftDecl (..))
|
import Plutarch.Lift (PConstantDecl, PUnsafeLiftDecl (..))
|
||||||
import Plutarch.Monadic qualified as P
|
import Plutarch.Monadic qualified as P
|
||||||
import Plutarch.TryFrom (PTryFrom (..))
|
import Plutarch.TryFrom (PTryFrom (..))
|
||||||
import Plutus.V1.Ledger.Credential (Credential)
|
import Plutus.V1.Ledger.Credential (Credential)
|
||||||
|
|
@ -69,10 +68,11 @@ newtype PTreasuryWithdrawalDatum (s :: S)
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PTreasuryWithdrawalDatum where
|
instance PUnsafeLiftDecl PTreasuryWithdrawalDatum where
|
||||||
type PLifted PTreasuryWithdrawalDatum = TreasuryWithdrawalDatum
|
type PLifted PTreasuryWithdrawalDatum = TreasuryWithdrawalDatum
|
||||||
|
|
||||||
deriving via
|
deriving via
|
||||||
(DerivePConstantViaData TreasuryWithdrawalDatum PTreasuryWithdrawalDatum)
|
(DerivePConstantViaData TreasuryWithdrawalDatum PTreasuryWithdrawalDatum)
|
||||||
instance
|
instance
|
||||||
(PConstant TreasuryWithdrawalDatum)
|
(PConstantDecl TreasuryWithdrawalDatum)
|
||||||
|
|
||||||
instance PTryFrom PData PTreasuryWithdrawalDatum where
|
instance PTryFrom PData PTreasuryWithdrawalDatum where
|
||||||
type PTryFromExcess PData PTreasuryWithdrawalDatum = Const ()
|
type PTryFromExcess PData PTreasuryWithdrawalDatum = Const ()
|
||||||
|
|
@ -99,7 +99,7 @@ treasuryWithdrawalValidator currSymbol = makeEffect currSymbol $
|
||||||
\_cs (datum' :: Term _ PTreasuryWithdrawalDatum) txOutRef' txInfo' -> P.do
|
\_cs (datum' :: Term _ PTreasuryWithdrawalDatum) txOutRef' txInfo' -> P.do
|
||||||
datum <- pletFields @'["receivers", "treasuries"] datum'
|
datum <- pletFields @'["receivers", "treasuries"] datum'
|
||||||
txInfo <- pletFields @'["outputs", "inputs"] txInfo'
|
txInfo <- pletFields @'["outputs", "inputs"] txInfo'
|
||||||
PJust txOut <- pmatch $ findTxOutByTxOutRef # txOutRef' # pfromData txInfo'
|
PJust txOut <- pmatch $ findTxOutByTxOutRef # txOutRef' # pfromData txInfo.inputs
|
||||||
effInput <- pletFields @'["address", "value"] $ txOut
|
effInput <- pletFields @'["address", "value"] $ txOut
|
||||||
outputValues <-
|
outputValues <-
|
||||||
plet $
|
plet $
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,6 @@ module Agora.Proposal (
|
||||||
PProposalVotes (..),
|
PProposalVotes (..),
|
||||||
PProposalId (..),
|
PProposalId (..),
|
||||||
PResultTag (..),
|
PResultTag (..),
|
||||||
|
|
||||||
-- * Scripts
|
|
||||||
proposalValidator,
|
|
||||||
proposalPolicy,
|
|
||||||
proposalDatumValid,
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import GHC.Generics qualified as GHC
|
import GHC.Generics qualified as GHC
|
||||||
|
|
@ -38,47 +33,26 @@ import Generics.SOP (Generic, I (I))
|
||||||
import Plutarch.Api.V1 (
|
import Plutarch.Api.V1 (
|
||||||
PDatumHash,
|
PDatumHash,
|
||||||
PMap,
|
PMap,
|
||||||
PMintingPolicy,
|
|
||||||
PPubKeyHash,
|
PPubKeyHash,
|
||||||
PScriptContext (PScriptContext),
|
|
||||||
PScriptPurpose (PMinting, PSpending),
|
|
||||||
PTxInfo (PTxInfo),
|
|
||||||
PValidator,
|
|
||||||
PValidatorHash,
|
PValidatorHash,
|
||||||
mintingPolicySymbol,
|
|
||||||
mkMintingPolicy,
|
|
||||||
)
|
)
|
||||||
import Plutarch.DataRepr (
|
|
||||||
DerivePConstantViaData (..),
|
|
||||||
PDataFields,
|
|
||||||
PIsDataReprInstances (PIsDataReprInstances),
|
|
||||||
)
|
|
||||||
import Plutus.V1.Ledger.Api (DatumHash, PubKeyHash, ValidatorHash)
|
|
||||||
import PlutusTx qualified
|
import PlutusTx qualified
|
||||||
import PlutusTx.AssocMap qualified as AssocMap
|
import PlutusTx.AssocMap qualified as AssocMap
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
import Agora.Record (mkRecordConstr, (.&), (.=))
|
|
||||||
import Agora.SafeMoney (GTTag)
|
import Agora.SafeMoney (GTTag)
|
||||||
import Agora.Utils (
|
|
||||||
anyOutput,
|
|
||||||
findTxOutByTxOutRef,
|
|
||||||
passert,
|
|
||||||
pnotNull,
|
|
||||||
psymbolValueOf,
|
|
||||||
ptokenSpent,
|
|
||||||
ptxSignedBy,
|
|
||||||
pvalueSpent,
|
|
||||||
)
|
|
||||||
import Control.Arrow (first)
|
import Control.Arrow (first)
|
||||||
import Plutarch.Api.V1.Extra (passetClass, passetClassValueOf)
|
import Plutarch.DataRepr (DerivePConstantViaData (..), PDataFields, PIsDataReprInstances (..))
|
||||||
import Plutarch.Builtin (PBuiltinMap)
|
import Plutarch.Lift (
|
||||||
import Plutarch.Lift (DerivePConstantViaNewtype (..), PConstantDecl, PUnsafeLiftDecl (..))
|
DerivePConstantViaNewtype (..),
|
||||||
import Plutarch.Monadic qualified as P
|
PConstantDecl,
|
||||||
|
PUnsafeLiftDecl (..),
|
||||||
|
)
|
||||||
import Plutarch.SafeMoney (PDiscrete, Tagged)
|
import Plutarch.SafeMoney (PDiscrete, Tagged)
|
||||||
import Plutarch.TryFrom (PTryFrom (PTryFromExcess, ptryFrom'))
|
import Plutarch.TryFrom (PTryFrom (PTryFromExcess, ptryFrom'))
|
||||||
import Plutarch.Unsafe (punsafeCoerce)
|
import Plutarch.Unsafe (punsafeCoerce)
|
||||||
import Plutus.V1.Ledger.Value (AssetClass (AssetClass))
|
import Plutus.V1.Ledger.Api (DatumHash, PubKeyHash, ValidatorHash)
|
||||||
|
import Plutus.V1.Ledger.Value (AssetClass)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Haskell-land
|
-- Haskell-land
|
||||||
|
|
@ -395,161 +369,3 @@ data PProposalRedeemer (s :: S)
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PProposalRedeemer where type PLifted PProposalRedeemer = ProposalRedeemer
|
instance PUnsafeLiftDecl PProposalRedeemer where type PLifted PProposalRedeemer = ProposalRedeemer
|
||||||
deriving via (DerivePConstantViaData ProposalRedeemer PProposalRedeemer) instance (PConstantDecl ProposalRedeemer)
|
deriving via (DerivePConstantViaData ProposalRedeemer PProposalRedeemer) instance (PConstantDecl ProposalRedeemer)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
{- | Policy for Proposals.
|
|
||||||
This needs to perform two checks:
|
|
||||||
- Governor is happy with mint.
|
|
||||||
- Exactly 1 token is minted.
|
|
||||||
|
|
||||||
NOTE: The governor needs to check that the datum is correct
|
|
||||||
and sent to the right address.
|
|
||||||
-}
|
|
||||||
proposalPolicy :: Proposal -> ClosedTerm PMintingPolicy
|
|
||||||
proposalPolicy proposal =
|
|
||||||
plam $ \_redeemer ctx' -> P.do
|
|
||||||
PScriptContext ctx' <- pmatch ctx'
|
|
||||||
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
|
||||||
PTxInfo txInfo' <- pmatch $ pfromData ctx.txInfo
|
|
||||||
txInfo <- pletFields @'["inputs", "mint"] txInfo'
|
|
||||||
PMinting _ownSymbol <- pmatch $ pfromData ctx.purpose
|
|
||||||
|
|
||||||
let inputs = txInfo.inputs
|
|
||||||
mintedValue = pfromData txInfo.mint
|
|
||||||
AssetClass (govCs, govTn) = proposal.governorSTAssetClass
|
|
||||||
|
|
||||||
PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose
|
|
||||||
let mintedProposalST = passetClassValueOf # mintedValue # (passetClass # (pfield @"_0" # ownSymbol') # pconstant "")
|
|
||||||
|
|
||||||
passert "Governance state-thread token must move" $
|
|
||||||
ptokenSpent
|
|
||||||
# (passetClass # pconstant govCs # pconstant govTn)
|
|
||||||
# inputs
|
|
||||||
|
|
||||||
passert "Minted exactly one proposal ST" $
|
|
||||||
mintedProposalST #== 1
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
|
|
||||||
-- | Validator for Proposals.
|
|
||||||
proposalValidator :: Proposal -> ClosedTerm PValidator
|
|
||||||
proposalValidator proposal =
|
|
||||||
plam $ \datum redeemer ctx' -> P.do
|
|
||||||
PScriptContext ctx' <- pmatch ctx'
|
|
||||||
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
|
||||||
txInfo <- plet $ pfromData ctx.txInfo
|
|
||||||
PTxInfo txInfo' <- pmatch txInfo
|
|
||||||
txInfoF <- pletFields @'["inputs", "mint"] txInfo'
|
|
||||||
PSpending ((pfield @"_0" #) -> txOutRef) <- pmatch $ pfromData ctx.purpose
|
|
||||||
|
|
||||||
PJust txOut <- pmatch $ findTxOutByTxOutRef # txOutRef # txInfoF.inputs
|
|
||||||
txOutF <- pletFields @'["address", "value"] $ txOut
|
|
||||||
|
|
||||||
let proposalDatum :: Term _ PProposalDatum
|
|
||||||
proposalDatum = pfromData $ punsafeCoerce datum
|
|
||||||
proposalRedeemer :: Term _ PProposalRedeemer
|
|
||||||
proposalRedeemer = pfromData $ punsafeCoerce redeemer
|
|
||||||
|
|
||||||
proposalF <-
|
|
||||||
pletFields
|
|
||||||
@'[ "proposalId"
|
|
||||||
, "effects"
|
|
||||||
, "status"
|
|
||||||
, "cosigners"
|
|
||||||
, "thresholds"
|
|
||||||
, "votes"
|
|
||||||
]
|
|
||||||
proposalDatum
|
|
||||||
|
|
||||||
ownAddress <- plet $ txOutF.address
|
|
||||||
|
|
||||||
stCurrencySymbol <- plet $ pconstant $ mintingPolicySymbol $ mkMintingPolicy (proposalPolicy proposal)
|
|
||||||
valueSpent <- plet $ pvalueSpent # txInfoF.inputs
|
|
||||||
spentST <- plet $ psymbolValueOf # stCurrencySymbol #$ valueSpent
|
|
||||||
let AssetClass (stakeSym, stakeTn) = proposal.stakeSTAssetClass
|
|
||||||
spentStakeST <- plet $ passetClassValueOf # valueSpent # (passetClass # pconstant stakeSym # pconstant stakeTn)
|
|
||||||
|
|
||||||
pmatch proposalRedeemer $ \case
|
|
||||||
PVote _r -> P.do
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
--------------------------------------------------------------------------
|
|
||||||
PCosign r -> P.do
|
|
||||||
newSigs <- plet $ pfield @"newCosigners" # r
|
|
||||||
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
|
|
||||||
passert "Signed by all new cosigners" $
|
|
||||||
pall # plam (\sig -> ptxSignedBy # ctx.txInfo # sig) # newSigs
|
|
||||||
|
|
||||||
passert "As many new cosigners as Stake datums" $
|
|
||||||
spentStakeST #== plength # newSigs
|
|
||||||
|
|
||||||
passert "Signatures are correctly added to cosignature list" $
|
|
||||||
anyOutput @PProposalDatum # ctx.txInfo
|
|
||||||
#$ plam
|
|
||||||
$ \newValue address newProposalDatum -> P.do
|
|
||||||
-- This is a little sad. Can we do better by
|
|
||||||
-- building a new ProposalDatum and then comparing?
|
|
||||||
let correctDatum =
|
|
||||||
pdata newProposalDatum
|
|
||||||
#== pdata
|
|
||||||
( mkRecordConstr
|
|
||||||
PProposalDatum
|
|
||||||
( #proposalId .= proposalF.proposalId
|
|
||||||
.& #effects .= proposalF.effects
|
|
||||||
.& #status .= proposalF.status
|
|
||||||
.& #cosigners .= pdata (pconcat # newSigs # proposalF.cosigners)
|
|
||||||
.& #thresholds .= proposalF.thresholds
|
|
||||||
.& #votes .= proposalF.votes
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
foldr1
|
|
||||||
(#&&)
|
|
||||||
[ pcon PTrue
|
|
||||||
, ptraceIfFalse "Datum must be correct" correctDatum
|
|
||||||
, ptraceIfFalse "Value should be correct" $ pdata txOutF.value #== pdata newValue
|
|
||||||
, ptraceIfFalse "Must be sent to Proposal's address" $ ownAddress #== pdata address
|
|
||||||
]
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
--------------------------------------------------------------------------
|
|
||||||
PUnlock _r -> P.do
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
--------------------------------------------------------------------------
|
|
||||||
PAdvanceProposal _r -> P.do
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
|
|
||||||
{- | Check for various invariants a proposal must uphold.
|
|
||||||
This can be used to check both upopn creation and
|
|
||||||
upon any following state transitions in the proposal.
|
|
||||||
-}
|
|
||||||
proposalDatumValid :: Term s (PProposalDatum :--> PBool)
|
|
||||||
proposalDatumValid =
|
|
||||||
phoistAcyclic $
|
|
||||||
plam $ \datum' -> P.do
|
|
||||||
datum <- pletFields @'["effects", "cosigners"] $ datum'
|
|
||||||
|
|
||||||
let effects :: Term _ (PBuiltinMap PResultTag (PBuiltinMap PValidatorHash PDatumHash))
|
|
||||||
effects = punsafeCoerce datum.effects
|
|
||||||
|
|
||||||
atLeastOneNegativeResult :: Term _ PBool
|
|
||||||
atLeastOneNegativeResult =
|
|
||||||
pany # plam (\pair -> pnull #$ pfromData $ psndBuiltin # pair) # effects
|
|
||||||
|
|
||||||
foldr1
|
|
||||||
(#&&)
|
|
||||||
[ ptraceIfFalse "Proposal has at least one ResultTag has no effects" atLeastOneNegativeResult
|
|
||||||
, ptraceIfFalse "Proposal has at least one cosigner" $ pnotNull # pfromData datum.cosigners
|
|
||||||
]
|
|
||||||
|
|
|
||||||
227
agora/Agora/Proposal/Scripts.hs
Normal file
227
agora/Agora/Proposal/Scripts.hs
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
module Agora.Proposal.Scripts (
|
||||||
|
proposalValidator,
|
||||||
|
proposalPolicy,
|
||||||
|
proposalDatumValid,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Agora.Proposal
|
||||||
|
import Agora.Record (mkRecordConstr, (.&), (.=))
|
||||||
|
import Agora.Stake (PStakeDatum)
|
||||||
|
import Agora.Utils (
|
||||||
|
anyOutput,
|
||||||
|
findTxOutByTxOutRef,
|
||||||
|
passert,
|
||||||
|
pfindDatum',
|
||||||
|
pnotNull,
|
||||||
|
psymbolValueOf,
|
||||||
|
ptokenSpent,
|
||||||
|
ptxSignedBy,
|
||||||
|
pvalueSpent,
|
||||||
|
)
|
||||||
|
import Plutarch.Api.V1 (
|
||||||
|
PDatumHash,
|
||||||
|
PMaybeData (PDJust, PDNothing),
|
||||||
|
PMintingPolicy,
|
||||||
|
PPubKeyHash,
|
||||||
|
PScriptContext (PScriptContext),
|
||||||
|
PScriptPurpose (PMinting, PSpending),
|
||||||
|
PTxInInfo (PTxInInfo),
|
||||||
|
PTxInfo (PTxInfo),
|
||||||
|
PTxOut (PTxOut),
|
||||||
|
PValidator,
|
||||||
|
PValidatorHash,
|
||||||
|
mintingPolicySymbol,
|
||||||
|
mkMintingPolicy,
|
||||||
|
)
|
||||||
|
import Plutarch.Api.V1.Extra (passetClass, passetClassValueOf)
|
||||||
|
import Plutarch.Builtin (PBuiltinMap)
|
||||||
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.Unsafe (punsafeCoerce)
|
||||||
|
import Plutus.V1.Ledger.Value (AssetClass (AssetClass))
|
||||||
|
|
||||||
|
{- | Policy for Proposals.
|
||||||
|
This needs to perform two checks:
|
||||||
|
- Governor is happy with mint.
|
||||||
|
- Exactly 1 token is minted.
|
||||||
|
|
||||||
|
NOTE: The governor needs to check that the datum is correct
|
||||||
|
and sent to the right address.
|
||||||
|
-}
|
||||||
|
proposalPolicy :: Proposal -> ClosedTerm PMintingPolicy
|
||||||
|
proposalPolicy proposal =
|
||||||
|
plam $ \_redeemer ctx' -> P.do
|
||||||
|
PScriptContext ctx' <- pmatch ctx'
|
||||||
|
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
||||||
|
PTxInfo txInfo' <- pmatch $ pfromData ctx.txInfo
|
||||||
|
txInfo <- pletFields @'["inputs", "mint"] txInfo'
|
||||||
|
PMinting _ownSymbol <- pmatch $ pfromData ctx.purpose
|
||||||
|
|
||||||
|
let inputs = txInfo.inputs
|
||||||
|
mintedValue = pfromData txInfo.mint
|
||||||
|
AssetClass (govCs, govTn) = proposal.governorSTAssetClass
|
||||||
|
|
||||||
|
PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose
|
||||||
|
let mintedProposalST = passetClassValueOf # mintedValue # (passetClass # (pfield @"_0" # ownSymbol') # pconstant "")
|
||||||
|
|
||||||
|
passert "Governance state-thread token must move" $
|
||||||
|
ptokenSpent
|
||||||
|
# (passetClass # pconstant govCs # pconstant govTn)
|
||||||
|
# inputs
|
||||||
|
|
||||||
|
passert "Minted exactly one proposal ST" $
|
||||||
|
mintedProposalST #== 1
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
|
||||||
|
-- | Validator for Proposals.
|
||||||
|
proposalValidator :: Proposal -> ClosedTerm PValidator
|
||||||
|
proposalValidator proposal =
|
||||||
|
plam $ \datum redeemer ctx' -> P.do
|
||||||
|
PScriptContext ctx' <- pmatch ctx'
|
||||||
|
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
||||||
|
txInfo <- plet $ pfromData ctx.txInfo
|
||||||
|
PTxInfo txInfo' <- pmatch txInfo
|
||||||
|
txInfoF <- pletFields @'["inputs", "mint"] txInfo'
|
||||||
|
PSpending ((pfield @"_0" #) -> txOutRef) <- pmatch $ pfromData ctx.purpose
|
||||||
|
|
||||||
|
PJust txOut <- pmatch $ findTxOutByTxOutRef # txOutRef # txInfoF.inputs
|
||||||
|
txOutF <- pletFields @'["address", "value"] $ txOut
|
||||||
|
|
||||||
|
let proposalDatum :: Term _ PProposalDatum
|
||||||
|
proposalDatum = pfromData $ punsafeCoerce datum
|
||||||
|
proposalRedeemer :: Term _ PProposalRedeemer
|
||||||
|
proposalRedeemer = pfromData $ punsafeCoerce redeemer
|
||||||
|
|
||||||
|
proposalF <-
|
||||||
|
pletFields
|
||||||
|
@'[ "proposalId"
|
||||||
|
, "effects"
|
||||||
|
, "status"
|
||||||
|
, "cosigners"
|
||||||
|
, "thresholds"
|
||||||
|
, "votes"
|
||||||
|
]
|
||||||
|
proposalDatum
|
||||||
|
|
||||||
|
ownAddress <- plet $ txOutF.address
|
||||||
|
|
||||||
|
stCurrencySymbol <- plet $ pconstant $ mintingPolicySymbol $ mkMintingPolicy (proposalPolicy proposal)
|
||||||
|
valueSpent <- plet $ pvalueSpent # txInfoF.inputs
|
||||||
|
spentST <- plet $ psymbolValueOf # stCurrencySymbol #$ valueSpent
|
||||||
|
let AssetClass (stakeSym, stakeTn) = proposal.stakeSTAssetClass
|
||||||
|
stakeSTAssetClass <- plet $ passetClass # pconstant stakeSym # pconstant stakeTn
|
||||||
|
spentStakeST <- plet $ passetClassValueOf # valueSpent # stakeSTAssetClass
|
||||||
|
|
||||||
|
pmatch proposalRedeemer $ \case
|
||||||
|
PVote _r -> P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PCosign r -> P.do
|
||||||
|
newSigs <- plet $ pfield @"newCosigners" # r
|
||||||
|
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
passert "Signed by all new cosigners" $
|
||||||
|
pall # plam (\sig -> ptxSignedBy # ctx.txInfo # sig) # newSigs
|
||||||
|
|
||||||
|
passert "As many new cosigners as Stake datums" $
|
||||||
|
spentStakeST #== plength # newSigs
|
||||||
|
|
||||||
|
let stakeDatumOwnedBy :: Term _ (PPubKeyHash :--> PStakeDatum :--> PBool)
|
||||||
|
stakeDatumOwnedBy =
|
||||||
|
phoistAcyclic $
|
||||||
|
plam $ \pk stakeDatum -> P.do
|
||||||
|
stakeDatumF <- pletFields @'["owner"] $ pto stakeDatum
|
||||||
|
stakeDatumF.owner #== pdata pk
|
||||||
|
|
||||||
|
-- Does the input have a `Stake` owned by a particular PK?
|
||||||
|
let isInputStakeOwnedBy :: Term _ (PAsData PPubKeyHash :--> PAsData PTxInInfo :--> PBool)
|
||||||
|
isInputStakeOwnedBy =
|
||||||
|
plam $ \ss txInInfo' -> P.do
|
||||||
|
PTxInInfo ((pfield @"resolved" #) -> txOut) <- pmatch $ pfromData txInInfo'
|
||||||
|
PTxOut txOut' <- pmatch txOut
|
||||||
|
txOutF <- pletFields @'["value", "datumHash"] txOut'
|
||||||
|
outStakeST <- plet $ passetClassValueOf # txOutF.value # stakeSTAssetClass
|
||||||
|
pmatch txOutF.datumHash $ \case
|
||||||
|
PDNothing _ -> pcon PFalse
|
||||||
|
PDJust ((pfield @"_0" #) -> datumHash) ->
|
||||||
|
pif
|
||||||
|
(outStakeST #== 1)
|
||||||
|
-- TODO: use 'ptryFindDatum' instead in the future
|
||||||
|
( pmatch (pfindDatum' # datumHash # txInfo) $ \case
|
||||||
|
PNothing -> pcon PFalse
|
||||||
|
PJust v -> stakeDatumOwnedBy # pfromData ss # pfromData v
|
||||||
|
)
|
||||||
|
(pcon PFalse)
|
||||||
|
|
||||||
|
passert "All new cosigners are witnessed by their Stake datums" $
|
||||||
|
pall
|
||||||
|
# plam (\sig -> pany # (isInputStakeOwnedBy # sig) # txInfoF.inputs)
|
||||||
|
# newSigs
|
||||||
|
|
||||||
|
passert "Signatures are correctly added to cosignature list" $
|
||||||
|
anyOutput @PProposalDatum # ctx.txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \newValue address newProposalDatum -> P.do
|
||||||
|
let correctDatum =
|
||||||
|
pdata newProposalDatum
|
||||||
|
#== pdata
|
||||||
|
( mkRecordConstr
|
||||||
|
PProposalDatum
|
||||||
|
( #proposalId .= proposalF.proposalId
|
||||||
|
.& #effects .= proposalF.effects
|
||||||
|
.& #status .= proposalF.status
|
||||||
|
.& #cosigners .= pdata (pconcat # newSigs # proposalF.cosigners)
|
||||||
|
.& #thresholds .= proposalF.thresholds
|
||||||
|
.& #votes .= proposalF.votes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ pcon PTrue
|
||||||
|
, ptraceIfFalse "Datum must be correct" correctDatum
|
||||||
|
, ptraceIfFalse "Value should be correct" $ pdata txOutF.value #== pdata newValue
|
||||||
|
, ptraceIfFalse "Must be sent to Proposal's address" $ ownAddress #== pdata address
|
||||||
|
]
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PUnlock _r -> P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PAdvanceProposal _r -> P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
|
||||||
|
{- | Check for various invariants a proposal must uphold.
|
||||||
|
This can be used to check both upopn creation and
|
||||||
|
upon any following state transitions in the proposal.
|
||||||
|
-}
|
||||||
|
proposalDatumValid :: Term s (PProposalDatum :--> PBool)
|
||||||
|
proposalDatumValid =
|
||||||
|
phoistAcyclic $
|
||||||
|
plam $ \datum' -> P.do
|
||||||
|
datum <- pletFields @'["effects", "cosigners"] $ datum'
|
||||||
|
|
||||||
|
let effects :: Term _ (PBuiltinMap PResultTag (PBuiltinMap PValidatorHash PDatumHash))
|
||||||
|
effects = punsafeCoerce datum.effects
|
||||||
|
|
||||||
|
atLeastOneNegativeResult :: Term _ PBool
|
||||||
|
atLeastOneNegativeResult =
|
||||||
|
pany # plam (\pair -> pnull #$ pfromData $ psndBuiltin # pair) # effects
|
||||||
|
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ ptraceIfFalse "Proposal has at least one ResultTag has no effects" atLeastOneNegativeResult
|
||||||
|
, ptraceIfFalse "Proposal has at least one cosigner" $ pnotNull # pfromData datum.cosigners
|
||||||
|
]
|
||||||
|
|
@ -11,6 +11,7 @@ module Agora.Utils (
|
||||||
pfind',
|
pfind',
|
||||||
pfindDatum,
|
pfindDatum,
|
||||||
pfindDatum',
|
pfindDatum',
|
||||||
|
ptryFindDatum,
|
||||||
pvalueSpent,
|
pvalueSpent,
|
||||||
ptxSignedBy,
|
ptxSignedBy,
|
||||||
paddValue,
|
paddValue,
|
||||||
|
|
@ -67,6 +68,7 @@ import Plutarch.Api.V1.Value (PValue (PValue))
|
||||||
import Plutarch.Builtin (ppairDataBuiltin)
|
import Plutarch.Builtin (ppairDataBuiltin)
|
||||||
import Plutarch.Internal (punsafeCoerce)
|
import Plutarch.Internal (punsafeCoerce)
|
||||||
import Plutarch.Monadic qualified as P
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.TryFrom (PTryFrom, ptryFrom)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Validator-level utility functions
|
-- Validator-level utility functions
|
||||||
|
|
@ -82,6 +84,17 @@ pfindDatum = phoistAcyclic $
|
||||||
PTxInfo txInfo' <- pmatch txInfo''
|
PTxInfo txInfo' <- pmatch txInfo''
|
||||||
plookupTuple # datumHash #$ pfield @"datums" # txInfo'
|
plookupTuple # datumHash #$ pfield @"datums" # txInfo'
|
||||||
|
|
||||||
|
-- | Find a datum with the given hash, and `ptryFrom` it.
|
||||||
|
ptryFindDatum :: PTryFrom PData a => Term s (PDatumHash :--> PTxInfo :--> PMaybe a)
|
||||||
|
ptryFindDatum = phoistAcyclic $
|
||||||
|
plam $ \datumHash txInfo'' -> P.do
|
||||||
|
PTxInfo txInfo' <- pmatch txInfo''
|
||||||
|
pmatch (plookupTuple # datumHash #$ pfield @"datums" # txInfo') $ \case
|
||||||
|
PNothing -> pcon PNothing
|
||||||
|
PJust datum -> P.do
|
||||||
|
(datum', _) <- ptryFrom $ pto datum
|
||||||
|
pcon (PJust datum')
|
||||||
|
|
||||||
{- | Find a datum with the given hash.
|
{- | Find a datum with the given hash.
|
||||||
NOTE: this is unsafe in the sense that, if the data layout is wrong, this is UB.
|
NOTE: this is unsafe in the sense that, if the data layout is wrong, this is UB.
|
||||||
-}
|
-}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue