add validation logic for GAT minting; add doc string for MintGATs

This commit is contained in:
fanghr 2022-04-20 23:29:47 +08:00
parent 793f038182
commit c7ae47d487
3 changed files with 122 additions and 31 deletions

View file

@ -16,12 +16,12 @@ import Plutarch.Api.V1 (
PAddress (..), PAddress (..),
PCredential (..), PCredential (..),
PCurrencySymbol (..), PCurrencySymbol (..),
PMintingPolicy,
PScriptContext (..), PScriptContext (..),
PScriptPurpose (..), PScriptPurpose (..),
PTxInInfo (PTxInInfo), PTxInInfo (PTxInInfo),
PTxInfo (..), PTxInfo (..),
PTxOut (..), PTxOut (..),
PMintingPolicy
) )
import Plutarch.Api.V1.AssocMap (PMap (PMap)) import Plutarch.Api.V1.AssocMap (PMap (PMap))
import Plutarch.Api.V1.Value (PValue (PValue)) import Plutarch.Api.V1.Value (PValue (PValue))

View file

@ -36,6 +36,7 @@ import Generics.SOP (Generic, I (I))
import Agora.AuthorityToken ( import Agora.AuthorityToken (
AuthorityToken (..), AuthorityToken (..),
authorityTokenPolicy, authorityTokenPolicy,
authorityTokensValidIn,
singleAuthorityTokenBurned, singleAuthorityTokenBurned,
) )
import Agora.Proposal ( import Agora.Proposal (
@ -43,10 +44,12 @@ import Agora.Proposal (
PProposalId, PProposalId,
PProposalStatus (PDraft), PProposalStatus (PDraft),
PProposalThresholds, PProposalThresholds,
PResultTag,
Proposal (..), Proposal (..),
ProposalId, ProposalId,
ProposalThresholds, ProposalThresholds,
pnextProposalId, pnextProposalId,
proposalDatumValid,
proposalPolicy, proposalPolicy,
proposalValidator, proposalValidator,
) )
@ -61,18 +64,24 @@ import Agora.Utils (
pfindTxInByTxOutRef, pfindTxInByTxOutRef,
pisDJust, pisDJust,
pisUxtoSpent, pisUxtoSpent,
plookup,
pownCurrencySymbol, pownCurrencySymbol,
psymbolValueOf, psymbolValueOf,
scriptHashFromAddress,
) )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Plutarch (popaque) import Plutarch (popaque)
import Plutarch.Api.V1 ( import Plutarch.Api.V1 (
PAddress,
PCurrencySymbol, PCurrencySymbol,
PDatumHash,
PMaybeData (PDJust),
PMintingPolicy, PMintingPolicy,
PScriptPurpose (PSpending), PScriptPurpose (PSpending),
PValidator, PValidator,
PValidatorHash,
PValue, PValue,
mintingPolicySymbol, mintingPolicySymbol,
mkMintingPolicy, mkMintingPolicy,
@ -91,7 +100,7 @@ import Plutarch.Unsafe (punsafeCoerce)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Plutarch.Builtin (pforgetData) import Plutarch.Builtin (PBuiltinMap, pforgetData)
import Plutus.V1.Ledger.Api ( import Plutus.V1.Ledger.Api (
Address (Address), Address (Address),
Credential (ScriptCredential), Credential (ScriptCredential),
@ -240,11 +249,12 @@ governorPolicy params =
For 'MintGATs' redeemer, it will check: For 'MintGATs' redeemer, it will check:
- State datum is not changed. - State datum is not changed.
- Exactly one GAT is minted. - Exactly one proposal is being processed.
- The GAT is properly tagged. (Should we do this?) - Mint one GAT for every effect.
- The GAT is sent to the appropraite effect. (Should we do this?) - The GATs is properly tagged. (Should we do this?)
- The GATs are sent to the appropraite effects. (Should we do this?)
For 'PMutateGovernor', it will check: For 'MutateGovernor', it will check:
- A GAT is burnt. - A GAT is burnt.
- Said GAT must be tagged by the effect that is spending it. - Said GAT must be tagged by the effect that is spending it.
-} -}
@ -255,12 +265,13 @@ governorValidator params =
redeemer <- pmatch $ pfromData @PGovernorRedeemer $ punsafeCoerce redeemer' redeemer <- pmatch $ pfromData @PGovernorRedeemer $ punsafeCoerce redeemer'
ctx <- pletFields @'["txInfo", "purpose"] ctx' ctx <- pletFields @'["txInfo", "purpose"] ctx'
txInfo <- plet $ pfromData $ ctx.txInfo txInfo' <- plet $ pfromData $ ctx.txInfo
txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo'
PSpending ((pfield @"_0" #) -> txOutRef') <- pmatch $ pfromData ctx.purpose PSpending ((pfield @"_0" #) -> txOutRef') <- pmatch $ pfromData ctx.purpose
let txOutRef = pfromData txOutRef' let txOutRef = pfromData txOutRef'
PJust ((pfield @"resolved" #) -> ownInput') <- pmatch $ pfindTxInByTxOutRef # txOutRef # txInfo PJust ((pfield @"resolved" #) -> ownInput') <- pmatch $ pfindTxInByTxOutRef # txOutRef # txInfo'
ownInput <- pletFields @'["address", "value"] ownInput' ownInput <- pletFields @'["address", "value"] ownInput'
let selfAddress = pfromData $ ownInput.address let selfAddress = pfromData $ ownInput.address
@ -272,7 +283,7 @@ governorValidator params =
passert "Own input should have exactly one state token" $ passert "Own input should have exactly one state token" $
ownInputDatumNFTAmount #== 1 ownInputDatumNFTAmount #== 1
ownOutputs <- plet $ findOutputsToAddress # txInfo # selfAddress ownOutputs <- plet $ findOutputsToAddress # txInfo' # selfAddress
passert "Exactly one utxo should be sent to the governor" $ passert "Exactly one utxo should be sent to the governor" $
plength # ownOutputs #== 1 plength # ownOutputs #== 1
@ -287,17 +298,13 @@ governorValidator params =
newDatum' <- plet $ mustFindDatum' @PGovernorDatum # ownOutput.datumHash # ctx.txInfo newDatum' <- plet $ mustFindDatum' @PGovernorDatum # ownOutput.datumHash # ctx.txInfo
newParams <- pletFields @'["proposalThresholds", "nextProposalId"] newDatum' newParams <- pletFields @'["proposalThresholds", "nextProposalId"] newDatum'
mint <- plet $ pfromData $ pfield @"mint" # txInfo
case redeemer of case redeemer of
PCreateProposal _ -> P.do PCreateProposal _ -> P.do
pSym <- plet $ pconstant proposalSymbol
passert "Proposal id should be advanced by 1" $ passert "Proposal id should be advanced by 1" $
pnextProposalId # oldParams.nextProposalId #== newParams.nextProposalId pnextProposalId # oldParams.nextProposalId #== newParams.nextProposalId
passert "Exactly one proposal token must be minted" $ passert "Exactly one proposal token must be minted" $
hasOnlyOneTokenOfCurrencySymbol # pSym # mint hasOnlyOneTokenOfCurrencySymbol # pProposalSym # txInfo.mint
outputs <- plet $ findOutputsToAddress # ctx.txInfo # pconstant proposalValidatorAddress outputs <- plet $ findOutputsToAddress # ctx.txInfo # pconstant proposalValidatorAddress
passert "Exactly one utxo should be sent to the proposal validator" $ passert "Exactly one utxo should be sent to the proposal validator" $
@ -305,7 +312,7 @@ governorValidator params =
output <- pletFields @'["value", "datumHash"] $ phead # outputs output <- pletFields @'["value", "datumHash"] $ phead # outputs
passert "The proposal state token must be sent to the proposal validator" $ passert "The proposal state token must be sent to the proposal validator" $
psymbolValueOf # pSym # output.value #== 1 psymbolValueOf # pProposalSym # output.value #== 1
passert "The utxo paid to the proposal validator must have datum" $ passert "The utxo paid to the proposal validator must have datum" $
pisDJust # output.datumHash pisDJust # output.datumHash
@ -335,8 +342,8 @@ governorValidator params =
PDraft _ -> pconstant True PDraft _ -> pconstant True
_ -> pconstant False _ -> pconstant False
passert "Initial proposal cosigners should be empty" $ passert "Proposal datum must be valid" $
pnull #$ pfromData proposalParams.cosigners proposalDatumValid # proposalDatum'
-- TODO: proposal impl not done yet -- TODO: proposal impl not done yet
ptraceError "Not implemented yet" ptraceError "Not implemented yet"
@ -345,23 +352,95 @@ governorValidator params =
-- FIXME: There should be a better way to do this -- FIXME: There should be a better way to do this
(pforgetData $ pdata newDatum') #== datum' (pforgetData $ pdata newDatum') #== datum'
passert "Exactly one GAT should be minted" $
hasOnlyOneTokenOfCurrencySymbol # pGATSym # mint
passert "No token should be minted other than GAT" $
containsSingleCurrencySymbol # mint
-- TODO: any need to check the proposal datum here? -- TODO: any need to check the proposal datum here?
-- check exactly one(?) authority token is minted inputsWithProposalStateToken <-
plet $
pfilter
# ( plam $ \(((pfield @"value" #) . (pfield @"resolved" #)) -> value) ->
0 #< psymbolValueOf # pProposalSym # value
)
#$ pfromData txInfo.inputs
-- TODO: waiting for impl of proposal passert "One proposal at a time" $
ptraceError "Not implemented yet" plength # inputsWithProposalStateToken #== 1
proposalInputTxOut <-
pletFields @'["address", "value", "datumHash"] $
pfield @"resolved" #$ phead # inputsWithProposalStateToken
proposalDatum' <- plet $ mustFindDatum' @PProposalDatum # proposalInputTxOut.datumHash # txInfo'
passert "Proposal datum must be valid" $
proposalDatumValid # proposalDatum'
proposalDatum <- pletFields @'["id", "effects", "status", "cosigners", "thresholds", "votes"] proposalDatum'
let effects' :: Term _ (PBuiltinMap PResultTag (PBuiltinMap PValidatorHash PDatumHash))
effects' = punsafeCoerce proposalDatum.effects
effectMapList <-
plet $
pfoldr
# ( plam $ \m l -> P.do
let theMap = pfromData $ psndBuiltin # m
pconcat # theMap # l
)
# pcon PNil
# effects'
gatCount <- plet $ plength # effectMapList
passert "Required amount of GATs should be minted" $
psymbolValueOf # pProposalSym # txInfo.mint #== gatCount
passert "No token should be minted other than GAT" $
containsSingleCurrencySymbol # txInfo.mint
outputsWithGAT <-
plet $
pfilter
# ( plam $ \((pfield @"value" #) -> value) ->
0 #< psymbolValueOf # pGATSym # value
)
#$ pfromData txInfo.outputs
passert "Minted GAT amount should equal to amount of output GAT" $
plength # outputsWithGAT #== gatCount
passert "All GAT must be properly tagged" $
pall
# ( plam $ \(pfromData -> outInfo) ->
authorityTokensValidIn # pGATSym # outInfo
)
# outputsWithGAT
popaque $
pfoldr
# ( plam $ \(pfromData -> outputInfo') _ -> P.do
outputInfo <- pletFields @'["address", "datumHash"] $ outputInfo'
passert "GAT must be properly tagged" $ authorityTokensValidIn # pGATSym # outputInfo'
passert "Output to the effect should have datum" $ pisDJust # outputInfo.datumHash
PDJust ((pfield @"_0" #) -> datumHash) <- pmatch outputInfo.datumHash
let scriptHash = scriptHashFromAddress' # outputInfo.address
expectedDatumHash' <- pmatch $ plookup # (pdata scriptHash) # effectMapList
case expectedDatumHash' of
PJust expectedDatumHash ->
passert "An unexpected datum hash is found sent to the effect" $
datumHash #== expectedDatumHash
_ -> passert "A GAT is not sent to an effect" $ pconstant False
pconstant ()
)
# pconstant ()
# outputsWithGAT
-- TODO: check proposal votes and timing
-- TODO: waiting for impl of proposal
PMutateGovernor _ -> P.do PMutateGovernor _ -> P.do
passert "No token should be burnt other than GAT" $ passert "No token should be burnt other than GAT" $
containsSingleCurrencySymbol # mint containsSingleCurrencySymbol # txInfo.mint
popaque $ singleAuthorityTokenBurned pGATSym ctx.txInfo mint popaque $ singleAuthorityTokenBurned pGATSym ctx.txInfo txInfo.mint
where where
stateTokenAssetClass :: AssetClass stateTokenAssetClass :: AssetClass
stateTokenAssetClass = governorStateTokenAssetClass params stateTokenAssetClass = governorStateTokenAssetClass params
@ -378,6 +457,9 @@ governorValidator params =
policy :: MintingPolicy policy :: MintingPolicy
policy = mkMintingPolicy $ proposalPolicy proposalParams policy = mkMintingPolicy $ proposalPolicy proposalParams
pProposalSym :: Term s PCurrencySymbol
pProposalSym = phoistAcyclic $ pconstant proposalSymbol
proposalValidatorAddress :: Address proposalValidatorAddress :: Address
proposalValidatorAddress = Address (ScriptCredential hash) Nothing proposalValidatorAddress = Address (ScriptCredential hash) Nothing
where where
@ -405,6 +487,14 @@ governorValidator params =
pGATSym :: Term s PCurrencySymbol pGATSym :: Term s PCurrencySymbol
pGATSym = phoistAcyclic $ pconstant authorityTokenSymbol pGATSym = phoistAcyclic $ pconstant authorityTokenSymbol
scriptHashFromAddress' :: Term s (PAddress :--> PValidatorHash)
scriptHashFromAddress' = phoistAcyclic $
plam $ \addr -> P.do
mh <- pmatch $ scriptHashFromAddress # addr
case mh of
PJust vh -> vh
_ -> ptraceError "Not a valid validator address"
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
governorStateTokenAssetClass :: Governor -> AssetClass governorStateTokenAssetClass :: Governor -> AssetClass

View file

@ -47,7 +47,7 @@ module Agora.Utils (
hasOnlyOneTokenOfAssetClass', hasOnlyOneTokenOfAssetClass',
hasOnlyOneTokenOfCurrencySymbol, hasOnlyOneTokenOfCurrencySymbol,
mustFindDatum', mustFindDatum',
containsSingleCurrencySymbol containsSingleCurrencySymbol,
) where ) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -553,5 +553,6 @@ mustFindDatum' = phoistAcyclic $
pfromData $ punsafeCoerce dt pfromData $ punsafeCoerce dt
containsSingleCurrencySymbol :: Term s (PValue :--> PBool) containsSingleCurrencySymbol :: Term s (PValue :--> PBool)
containsSingleCurrencySymbol = phoistAcyclic $ plam $ \v -> P.do containsSingleCurrencySymbol = phoistAcyclic $
(plength #$ pto $ pto $ pto v) #== 1 plam $ \v -> P.do
(plength #$ pto $ pto $ pto v) #== 1