allow spending more than one stakes in one tx

This commit is contained in:
Hongrui Fang 2022-09-23 14:41:58 +08:00
parent 8e91a7ef2d
commit 1bc60a48e5
6 changed files with 397 additions and 325 deletions

View file

@ -25,15 +25,21 @@ import Agora.Stake (
PNoProposal,
PWithProposalRedeemer
),
PSigContext (
PSigContext (PSigContext),
PSignedBy (
PSignedByDelegate,
PSignedByOwner,
PUnknownSig
),
PStakeDatum,
PStakeInputContext (PStakeInput),
PStakeOutputContext (PStakeBurnt, PStakeOutput),
PStakeRedeemer (PClearDelegate, PDelegateTo, PDepositWithdraw, PDestroy, PPermitVote, PRetractVotes),
PStakeRedeemer (
PClearDelegate,
PDelegateTo,
PDepositWithdraw,
PDestroy,
PPermitVote,
PRetractVotes
),
PStakeRedeemerContext (
PDepositWithdrawDelta,
PNoMetadata,
@ -57,15 +63,19 @@ import Agora.Stake.Redeemers (
)
import Data.Tagged (Tagged (Tagged))
import Plutarch.Api.V1 (
KeyGuarantees (Sorted),
PCredential (PPubKeyCredential, PScriptCredential),
PTokenName,
)
import Plutarch.Api.V1.AssocMap (plookup)
import Plutarch.Api.V1.Value (PValue)
import Plutarch.Api.V2 (
AmountGuarantees,
PMintingPolicy,
PScriptPurpose (PMinting, PSpending),
PTxInInfo,
PTxInfo,
PTxOut,
PTxOutRef,
PValidator,
)
@ -75,8 +85,9 @@ import Plutarch.Extra.AssetClass (
pvalueOf,
)
import Plutarch.Extra.Bind (PBind ((#>>=)))
import Plutarch.Extra.Field (pletAllC)
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust)
import Plutarch.Extra.Category (PSemigroupoid ((#>>>)))
import Plutarch.Extra.Functor (PFunctor (pfmap))
import "liqwid-plutarch-extra" Plutarch.Extra.List (pfindJust, pmapMaybe)
import Plutarch.Extra.Maybe (
passertPJust,
pjust,
@ -100,6 +111,7 @@ import Plutarch.Extra.Value (
psymbolValueOf,
)
import Plutarch.SafeMoney (
pvalueDiscrete,
pvalueDiscrete',
)
import Plutarch.Unsafe (punsafeCoerce)
@ -235,8 +247,14 @@ mkStakeValidator
impl
as
(Tagged (AssetClass (gtSym, gtTn))) =
plam $ \datum redeemer ctx -> unTermCont $ do
gtAssetClass <- pletC $ passetClass # pconstant gtSym # pconstant gtTn
plam $ \_datum redeemer ctx -> unTermCont $ do
let sstValueOf ::
( forall (ag :: AmountGuarantees) (s :: S).
Term s (PValue 'Sorted ag :--> PInteger)
)
sstValueOf =
phoistAcyclic $
psymbolValueOf # pconstant (stakeSTSymbol as)
--------------------------------------------------------------------------
@ -257,118 +275,138 @@ mkStakeValidator
--------------------------------------------------------------------------
-- Assemble the stake input context.
stakeInputDatum <- pfromData . fst <$> ptryFromC datum
stakeInputDatumF <- pletAllC $ pto stakeInputDatum
PSpending stakeInputRef <- pmatchC $ pfromData ctxF.purpose
-- The UTxO we are validating, which is also the input stake.
stakeInput <-
pletC $
pfield @"resolved"
#$ passertPJust # "Malformed script context: own input not found"
#$ pfindTxInByTxOutRef
# (pfield @"_0" # stakeInputRef)
# txInfoF.inputs
let validatedInput =
pfield @"resolved"
#$ passertPJust
# "Malformed script context: validated input not found"
#$ pfindTxInByTxOutRef
# (pfield @"_0" # stakeInputRef)
# txInfoF.inputs
stakeInputF <- pletFieldsC @'["address", "value"] stakeInput
stakeInputContext <-
pletC $
pcon $
PStakeInput
stakeInputDatum
stakeInputF.value
stakeValidatorAddress = pfield @"address" # validatedInput
--------------------------------------------------------------------------
-- Assemble the signature context.
getStakeDatum :: Term _ (PTxOut :--> PMaybe PStakeDatum) <-
pletC $
plam $ \txOut -> unTermCont $ do
txOutF <- pletFieldsC @'["value", "datum", "address"] txOut
signedBy <- pletC $ pauthorizedBy # authorizationContext txInfoF
let isStakeUTxO =
foldl1
(#&&)
[ ptraceIfFalse "Carries SST" $
sstValueOf # txOutF.value #== 1
, ptraceIfFalse "Owned by stake validator" $
txOutF.address #== stakeValidatorAddress
]
let ownerSignsTransaction = signedBy # stakeInputDatumF.owner
datum =
ptrace "Resolve stake datum" $
pfromData $
pfromOutputDatum @(PAsData PStakeDatum)
# txOutF.datum
# txInfoF.datums
pure $ pif isStakeUTxO (pjust # datum) pnothing
--------------------------------------------------------------------------
stakeInputDatums <-
pletC $
pmapMaybe
# ((pfield @"resolved") #>>> getStakeDatum)
# pfromData txInfoF.inputs
--------------------------------------------------------------------------
firstStakeInputDatumF <-
pletFieldsC @'["owner", "delegatedTo"] $
phead # stakeInputDatums
restOfStakeInputDatums <- pletC $ ptail # stakeInputDatums
pguardC "All input stakes have the same owner or delegate" $
let allHaveSameOwner =
pall
# ( (pfield @"owner")
#>>> plam (#== firstStakeInputDatumF.owner)
)
# restOfStakeInputDatums
allHaveSameDelegate =
pall
# ( (pfield @"delegatedTo")
#>>> plam (#== firstStakeInputDatumF.delegatedTo)
)
# restOfStakeInputDatums
in allHaveSameOwner #|| allHaveSameDelegate
authorizedBy <- pletC $ pauthorizedBy # authorizationContext txInfoF
let ownerSignsTransaction = authorizedBy # firstStakeInputDatumF.owner
delegateSignsTransaction =
pmaybeData
# pconstant False
# plam ((signedBy #) . pfromData)
# pfromData stakeInputDatumF.delegatedTo
# plam ((authorizedBy #) . pfromData)
# pfromData firstStakeInputDatumF.delegatedTo
signedBy =
pif
ownerSignsTransaction
(pcon PSignedByOwner)
$ pif
delegateSignsTransaction
(pcon PSignedByDelegate)
$ pcon PUnknownSig
sigContext <-
pletC $
pif ownerSignsTransaction (pcon PSignedByOwner) $
pif delegateSignsTransaction (pcon PSignedByDelegate) $
pcon PUnknownSig
pcon $
PSigContext
firstStakeInputDatumF.owner
firstStakeInputDatumF.delegatedTo
signedBy
--------------------------------------------------------------------------
stCurrencySymbol <- pletC $ pconstant $ stakeSTSymbol as
mintedST <- pletC $ psymbolValueOf # stCurrencySymbol # txInfoF.mint
valueSpent <- pletC $ pvalueSpent # txInfoF.inputs
spentST <- pletC $ psymbolValueOf # stCurrencySymbol #$ valueSpent
let gtAssetClass = passetClass # pconstant gtSym # pconstant gtTn
-- The stake validator can only handle one stake in one transaction.
pguardC "ST at inputs must be 1" $
spentST #== 1
let oneStakeBurnt =
ptraceIfFalse "Exactly one stake st burnt" $
mintedST #== (-1)
--------------------------------------------------------------------------
-- Assemble the stake output context.
let -- Look for the output stake.
stakeOutput =
pfindJust
# plam
( \output -> unTermCont $ do
outputF <-
pletFieldsC @'["address", "value", "datum"]
output
let isStakeOutput =
-- The stake should be owned by the stake validator.
outputF.address #== stakeInputF.address
#&&
-- The stake UTxO carries the state thread token.
psymbolValueOf
# stCurrencySymbol
# outputF.value #== 1
stakeOutputDatum =
pfromOutputDatum
# outputF.datum
# txInfoF.datums
context =
pcon $
PStakeOutput
(pfromData stakeOutputDatum)
outputF.value
pure $
pif
isStakeOutput
(pjust # context)
pnothing
)
# pfromData txInfoF.outputs
stakeOutputContext <-
stakeOutputDatums <-
pletC $
pmatch stakeOutput $ \case
-- Stake output found.
PJust stakeOutput' -> stakeOutput'
-- Stake output not found, meaning the input stake should be burnt.
PNothing -> unTermCont $ do
pguardC "One stake should be burnt" oneStakeBurnt
pmapMaybe
# plam
( \output ->
let validateGT = plam $ \stakeDatum ->
let expected = pfield @"stakedAmount" # stakeDatum
actual =
pvalueDiscrete
# gtAssetClass
# (pfield @"value" # output)
in pif
(expected #== actual)
stakeDatum
(ptraceError "Unmatched GT value")
in pfmap
# validateGT
# (getStakeDatum # output)
)
# pfromData txInfoF.outputs
pure $ pcon PStakeBurnt
--------------------------------------------------------------------------
mintedST <- pletC $ sstValueOf # txInfoF.mint
pguardC "No new SST minted" $
foldl1
(#||)
[ ptraceIfFalse "All stakes burnt" $
mintedST #< 0 #&& pnull # stakeOutputDatums
, ptraceIfFalse "Nothing burnt" $
mintedST #== 0
]
--------------------------------------------------------------------------
@ -427,12 +465,11 @@ mkStakeValidator
plam $ \redeemerContext ->
pcon $
PStakeRedeemerHandlerContext
stakeInputContext
stakeOutputContext
stakeInputDatums
stakeOutputDatums
redeemerContext
sigContext
proposalContext
gtAssetClass
txInfo
noMetadataContext <-