check stake upon the creation of a proposal

...documentation is updated as well
This commit is contained in:
fanghr 2022-04-26 19:41:04 +08:00
parent ec0c53db99
commit c67a4ea81b
2 changed files with 182 additions and 26 deletions

View file

@ -47,6 +47,7 @@ import Agora.Proposal (
PProposalId, PProposalId,
PProposalStatus (PFinished), PProposalStatus (PFinished),
PProposalThresholds, PProposalThresholds,
PResultTag,
Proposal (..), Proposal (..),
ProposalId, ProposalId,
ProposalStatus (Draft, Executable), ProposalStatus (Draft, Executable),
@ -56,6 +57,14 @@ import Agora.Proposal (
proposalPolicy, proposalPolicy,
proposalValidator, proposalValidator,
) )
import Agora.SafeMoney (GTTag)
import Agora.Stake (
PProposalLock (..),
PStakeDatum (..),
Stake (..),
stakePolicy,
stakeValidator,
)
import Agora.Utils ( import Agora.Utils (
findOutputsToAddress, findOutputsToAddress,
hasOnlyOneTokenOfCurrencySymbol, hasOnlyOneTokenOfCurrencySymbol,
@ -72,6 +81,7 @@ import Agora.Utils (
pisUxtoSpent, pisUxtoSpent,
pownCurrencySymbol, pownCurrencySymbol,
psymbolValueOf, psymbolValueOf,
ptxSignedBy,
pvalueSpent, pvalueSpent,
scriptHashFromAddress, scriptHashFromAddress,
) )
@ -105,9 +115,9 @@ import Plutarch.DataRepr (
PIsDataReprInstances (PIsDataReprInstances), PIsDataReprInstances (PIsDataReprInstances),
) )
import Plutarch.Lift (PUnsafeLiftDecl (..)) import Plutarch.Lift (PUnsafeLiftDecl (..))
import Plutarch.Map.Extra (plookup, plookup') import Plutarch.Map.Extra (pkeys, plookup, plookup')
import Plutarch.Monadic qualified as P import Plutarch.Monadic qualified as P
import Plutarch.SafeMoney (puntag) import Plutarch.SafeMoney (PDiscrete, Tagged (..), puntag, pvalueDiscrete)
import Plutarch.Unsafe (punsafeCoerce) import Plutarch.Unsafe (punsafeCoerce)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -176,6 +186,7 @@ PlutusTx.makeIsDataIndexed
data Governor = Governor data Governor = Governor
{ gstOutRef :: TxOutRef { gstOutRef :: TxOutRef
-- ^ Referenced utxo will be spent to mint the GST. -- ^ Referenced utxo will be spent to mint the GST.
, gtClassRef :: Tagged GTTag AssetClass
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -265,16 +276,25 @@ When the redeemer is 'CreateProposal', the script will check:
* 'nextProposalId' is advanced. * 'nextProposalId' is advanced.
* Nothing is changed other that that. * Nothing is changed other that that.
- Exactly one stake (the "input stake") must be provided in the input:
* At least 'Agora.Stake.stackedAmount' of GT must be spent in the transaction.
* The transaction must be signed by the stake owner.
- Exactly one new proposal state token is minted. - Exactly one new proposal state token is minted.
- Exactly one UTXO is sent to the proposal validator, this UTXO must: - An UTXO which holds the newly minted proposal state token is sent to the proposal validator.
This UTXO must have a valid datum of type 'Agora.Proposal.ProposalDatum', the datum must:
* Hold the newly minted proposal state token. * Copy its id and thresholds from the governor's state.
* Have a valid datum of type 'Agora.Proposal.ProposalDatum', the datum must: * Have status set to 'Proposal.Draft'.
* Have zero votes.
* Have exactly one cosigner - the stake owner
- Copy its id and thresholds from the governor's state. - An UTXO which holds the stake state token is sent back to the stake validator.
- Have status set to 'Proposal.Draft'. This UTXO must have a valid datum of type 'Agora.Stake.StakeDatum':
- Have zero votes.
- TODO: should we check cosigners? * The 'Agora.Stake.stakedAmount' and 'Agora.Stake.owner' should not be changed,
comparing to the input stake.
* The new proposal locks must be appended to the 'Agora.Stake.lockedBy'.
== Minting GATs == Minting GATs
@ -331,6 +351,10 @@ governorValidator gov =
txInfo' <- plet $ pfromData $ ctx.txInfo txInfo' <- plet $ pfromData $ ctx.txInfo
txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo' txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo'
datums <- plet $ pfromData $ pfield @"data" # txInfo'
valueSpent <- plet $ pvalueSpent # 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'
@ -381,7 +405,48 @@ governorValidator gov =
passert "Exactly one proposal token must be minted" $ passert "Exactly one proposal token must be minted" $
hasOnlyOneTokenOfCurrencySymbol # pproposalSymbol # txInfo.mint hasOnlyOneTokenOfCurrencySymbol # pproposalSymbol # txInfo.mint
filteredOutputs <- --
inputsFromStakeValidatorWithStateToken <-
plet $
pfilter
# ( phoistAcyclic $
plam
( \((pfield @"resolved" #) -> txOut') -> P.do
txOut <- pletFields @'["address", "value"] txOut'
txOut.address #== pdata pstakeValidatorAddress
#&& psymbolValueOf # pstakeStateSymbol # txOut.value #== 1
)
)
# pfromData txInfo.inputs
passert "Exactly one input from the stake validator" $
plength # inputsFromStakeValidatorWithStateToken #== 1
stakeInputDatumHash <-
plet $
pfield @"datumHash"
#$ pfield @"resolved"
#$ phead # inputsFromStakeValidatorWithStateToken
passert "Stake input must have datum" $
pisDJust # stakeInputDatumHash
let stakeInputDatum' = mustFindDatum' @PStakeDatum # stakeInputDatumHash # datums
stakeInputDatum <-
pletFields @["stakedAmount", "owner", "lockedBy"] stakeInputDatum'
passert "Required amount of stake GT should be spent" $
stakeInputDatum.stakedAmount #< (pgtValueOf # valueSpent)
passert "Tx should be signed by the stake owner" $
ptxSignedBy # txInfo' # stakeInputDatum.owner
--
outputsToProposalValidatorWithStateToken <-
plet $ plet $
pfilter pfilter
# ( phoistAcyclic $ # ( phoistAcyclic $
@ -395,10 +460,10 @@ governorValidator gov =
) )
# pfromData txInfo.outputs # pfromData txInfo.outputs
passert "Exactly one utxo with proposal state token should be sent to the proposal validator" $ passert "Exactly one UTXO with proposal state token should be sent to the proposal validator" $
plength # filteredOutputs #== 1 plength # outputsToProposalValidatorWithStateToken #== 1
outputDatumHash <- plet $ pfield @"datumHash" #$ phead # filteredOutputs outputDatumHash <- plet $ pfield @"datumHash" #$ phead # outputsToProposalValidatorWithStateToken
passert "The utxo paid to the proposal validator must have datum" $ passert "The utxo paid to the proposal validator must have datum" $
pisDJust # outputDatumHash pisDJust # outputDatumHash
@ -407,7 +472,7 @@ governorValidator gov =
plet $ plet $
mustFindDatum' @PProposalDatum mustFindDatum' @PProposalDatum
# outputDatumHash # outputDatumHash
# ctx.txInfo # datums
passert "Proposal datum must be valid" $ passert "Proposal datum must be valid" $
proposalDatumValid # outputProposalDatum' proposalDatumValid # outputProposalDatum'
@ -426,9 +491,75 @@ governorValidator gov =
passert "Initial proposal votes should be empty" $ passert "Initial proposal votes should be empty" $
pnull #$ pto $ pto $ pfromData outputProposalDatum.votes pnull #$ pto $ pto $ pfromData outputProposalDatum.votes
-- TODO: should we check cosigners here? passert "Proposal state should be draft" $
outputProposalDatum.status #== pconstantData Draft
passert "Proposal state should be draft" $ outputProposalDatum.status #== pconstantData Draft passert "Proposal should have only one cosigner" $
plength # pfromData outputProposalDatum.cosigners #== 1
let cosigner = phead # pfromData outputProposalDatum.cosigners
passert "Cosigner should be the stake owner" $
pdata stakeInputDatum.owner #== cosigner
--
outputToStakeValidatorWithStateToken <-
plet $
pfilter
# ( phoistAcyclic $
plam
( \(txOut') -> P.do
txOut <- pletFields @'["address", "value"] txOut'
txOut.address #== pdata pstakeValidatorAddress
#&& psymbolValueOf # pstakeStateSymbol # txOut.value #== 1
)
)
# pfromData txInfo.outputs
passert "Exactly one UTXO with stake state token should be sent to the stake validator" $
plength # outputToStakeValidatorWithStateToken #== 1
let stakeOutputDatumHash' =
pfield @"datumHash"
#$ pfromData
$ phead # outputToStakeValidatorWithStateToken
stakeOutputDatumHash = mustBePDJust # "Stake output should have datum" # stakeOutputDatumHash'
stakeOutputDatum =
pforgetData $
pdata $
mustBePJust # "Stake output not found" #$ pfindDatum # stakeOutputDatumHash # txInfo'
let possibleVoteResults = pkeys #$ pto $ pfromData outputProposalDatum.votes
mkProposalLock :: Term _ (PProposalId :--> PAsData PResultTag :--> PAsData PProposalLock)
mkProposalLock =
phoistAcyclic $
plam
( \pid rt' ->
let fields =
pdcons @"vote" # rt'
#$ pdcons @"proposalTag" # pdata pid # pdnil
in pdata $ pcon $ PProposalLock fields
)
expectedProposalLocks =
pconcat # stakeInputDatum.lockedBy
#$ pmap # (mkProposalLock # outputProposalDatum.id) # possibleVoteResults
expectedOutputDatum =
pforgetData $
pdata $
pcon $
PStakeDatum $
pdcons @"stakedAmount" # pdata stakeInputDatum.stakedAmount
#$ pdcons @"owner" # pdata stakeInputDatum.owner
#$ pdcons @"lockedBy" # pdata expectedProposalLocks # pdnil
passert "Unexpected stake output datum" $ expectedOutputDatum #== stakeOutputDatum
popaque $ pconstant () popaque $ pconstant ()
PMintGATs _ -> P.do PMintGATs _ -> P.do
@ -470,12 +601,12 @@ governorValidator gov =
plet $ plet $
mustFindDatum' @PProposalDatum mustFindDatum' @PProposalDatum
# proposalInputTxOut.datumHash # proposalInputTxOut.datumHash
# txInfo' # datums
outputProposalDatum' <- outputProposalDatum' <-
plet $ plet $
mustFindDatum' @PProposalDatum mustFindDatum' @PProposalDatum
# proposalOutputTxOut.datumHash # proposalOutputTxOut.datumHash
# txInfo' # datums
passert "Proposal datum must be valid" $ passert "Proposal datum must be valid" $
proposalDatumValid # inputProposalDatum' proposalDatumValid # inputProposalDatum'
@ -591,8 +722,8 @@ governorValidator gov =
stateTokenAssetClass :: AssetClass stateTokenAssetClass :: AssetClass
stateTokenAssetClass = gstAssetClass gov stateTokenAssetClass = gstAssetClass gov
proposalParameters :: Proposal outputProposalDatum :: Proposal
proposalParameters = outputProposalDatum =
Proposal Proposal
{ governorSTAssetClass = stateTokenAssetClass { governorSTAssetClass = stateTokenAssetClass
} }
@ -600,7 +731,7 @@ governorValidator gov =
proposalSymbol :: CurrencySymbol proposalSymbol :: CurrencySymbol
proposalSymbol = mintingPolicySymbol policy proposalSymbol = mintingPolicySymbol policy
where where
policy = mkMintingPolicy $ proposalPolicy proposalParameters policy = mkMintingPolicy $ proposalPolicy outputProposalDatum
pproposalSymbol :: Term s PCurrencySymbol pproposalSymbol :: Term s PCurrencySymbol
pproposalSymbol = phoistAcyclic $ pconstant proposalSymbol pproposalSymbol = phoistAcyclic $ pconstant proposalSymbol
@ -609,7 +740,7 @@ governorValidator gov =
proposalValidatorAddress = Address (ScriptCredential hash) Nothing proposalValidatorAddress = Address (ScriptCredential hash) Nothing
where where
hash = validatorHash validator hash = validatorHash validator
validator = mkValidator $ proposalValidator proposalParameters validator = mkValidator $ proposalValidator outputProposalDatum
pproposalValidatorAddress :: Term s PAddress pproposalValidatorAddress :: Term s PAddress
pproposalValidatorAddress = phoistAcyclic $ pconstant proposalValidatorAddress pproposalValidatorAddress = phoistAcyclic $ pconstant proposalValidatorAddress
@ -620,6 +751,29 @@ governorValidator gov =
pgatSymbol :: Term s PCurrencySymbol pgatSymbol :: Term s PCurrencySymbol
pgatSymbol = phoistAcyclic $ pconstant $ gatSymbol gov pgatSymbol = phoistAcyclic $ pconstant $ gatSymbol gov
stakeParameters :: Stake
stakeParameters = Stake gov.gtClassRef
stakeValidatorAddress :: Address
stakeValidatorAddress = Address (ScriptCredential hash) Nothing
where
validator = mkValidator $ stakeValidator stakeParameters
hash = validatorHash validator
stakeStateSymbol :: CurrencySymbol
stakeStateSymbol = mintingPolicySymbol policy
where
policy = mkMintingPolicy $ stakePolicy stakeParameters
pstakeValidatorAddress :: Term s PAddress
pstakeValidatorAddress = phoistAcyclic $ pconstant stakeValidatorAddress
pstakeStateSymbol :: Term s PCurrencySymbol
pstakeStateSymbol = phoistAcyclic $ pconstant stakeStateSymbol
pgtValueOf :: Term s (PValue :--> PDiscrete GTTag)
pgtValueOf = pvalueDiscrete gov.gtClassRef
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Get the assetclass of GST from governor parameters. -- | Get the assetclass of GST from governor parameters.

View file

@ -547,12 +547,14 @@ hasOnlyOneTokenOfCurrencySymbol = phoistAcyclic $
mustFindDatum' :: mustFindDatum' ::
forall (datum :: PType). forall (datum :: PType).
PIsData datum => PIsData datum =>
forall s. Term s (PMaybeData PDatumHash :--> PTxInfo :--> datum) forall s. Term s (PMaybeData PDatumHash :-->
(PBuiltinList (PAsData (PTuple PDatumHash PDatum)))
:--> datum)
mustFindDatum' = phoistAcyclic $ mustFindDatum' = phoistAcyclic $
plam $ \mdh info -> P.do plam $ \mdh datums -> P.do
PDJust ((pfield @"_0" #) -> dh) <- pmatch mdh PDJust ((pfield @"_0" #) -> dh) <- pmatch mdh
PJust dt <- pmatch $ pfindDatum # dh # info PJust dt <- pmatch $ plookupTuple # dh # datums
pfromData $ punsafeCoerce dt punsafeCoerce dt
{- | Extract the value stored in a PMaybe container. {- | Extract the value stored in a PMaybe container.
If there's no value, throw an error with the given message. If there's no value, throw an error with the given message.