Merge pull request #59 from Liqwid-Labs/emiflake/proposal-impl
Proposal validator implementation
This commit is contained in:
commit
297b6a59ef
28 changed files with 2144 additions and 682 deletions
5
Makefile
5
Makefile
|
|
@ -10,6 +10,7 @@ usage:
|
||||||
@echo " hoogle -- Start local hoogle"
|
@echo " hoogle -- Start local hoogle"
|
||||||
@echo " format -- Format the project"
|
@echo " format -- Format the project"
|
||||||
@echo " haddock -- Generate Haddock docs for project"
|
@echo " haddock -- Generate Haddock docs for project"
|
||||||
|
@echo " tag -- Generate CTAGS and ETAGS files for project"
|
||||||
|
|
||||||
hoogle:
|
hoogle:
|
||||||
pkill hoogle || true
|
pkill hoogle || true
|
||||||
|
|
@ -35,3 +36,7 @@ format_check:
|
||||||
|
|
||||||
haddock:
|
haddock:
|
||||||
cabal haddock --haddock-html --haddock-hoogle --builddir=haddock
|
cabal haddock --haddock-html --haddock-hoogle --builddir=haddock
|
||||||
|
|
||||||
|
tag:
|
||||||
|
hasktags -x agora agora-bench agora-test
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import Test.Tasty (defaultMain, testGroup)
|
||||||
import Spec.AuthorityToken qualified as AuthorityToken
|
import Spec.AuthorityToken qualified as AuthorityToken
|
||||||
import Spec.Effect.TreasuryWithdrawal qualified as TreasuryWithdrawal
|
import Spec.Effect.TreasuryWithdrawal qualified as TreasuryWithdrawal
|
||||||
import Spec.Model.MultiSig qualified as MultiSig
|
import Spec.Model.MultiSig qualified as MultiSig
|
||||||
|
import Spec.Proposal qualified as Proposal
|
||||||
import Spec.Stake qualified as Stake
|
import Spec.Stake qualified as Stake
|
||||||
|
|
||||||
-- | The Agora test suite.
|
-- | The Agora test suite.
|
||||||
|
|
@ -28,6 +29,9 @@ main =
|
||||||
, testGroup
|
, testGroup
|
||||||
"Stake tests"
|
"Stake tests"
|
||||||
Stake.tests
|
Stake.tests
|
||||||
|
, testGroup
|
||||||
|
"Proposal tests"
|
||||||
|
Proposal.tests
|
||||||
, testGroup
|
, testGroup
|
||||||
"Multisig tests"
|
"Multisig tests"
|
||||||
[ testGroup
|
[ testGroup
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@ This module tests the Treasury Withdrawal Effect.
|
||||||
-}
|
-}
|
||||||
module Spec.Effect.TreasuryWithdrawal (tests) where
|
module Spec.Effect.TreasuryWithdrawal (tests) where
|
||||||
|
|
||||||
|
import Agora.Effect.TreasuryWithdrawal (
|
||||||
|
TreasuryWithdrawalDatum (TreasuryWithdrawalDatum),
|
||||||
|
treasuryWithdrawalValidator,
|
||||||
|
)
|
||||||
|
import Plutus.V1.Ledger.Value qualified as Value
|
||||||
import Spec.Sample.Effect.TreasuryWithdrawal (
|
import Spec.Sample.Effect.TreasuryWithdrawal (
|
||||||
buildReceiversOutputFromDatum,
|
buildReceiversOutputFromDatum,
|
||||||
buildScriptContext,
|
buildScriptContext,
|
||||||
|
|
@ -20,15 +25,7 @@ import Spec.Sample.Effect.TreasuryWithdrawal (
|
||||||
treasuries,
|
treasuries,
|
||||||
users,
|
users,
|
||||||
)
|
)
|
||||||
|
|
||||||
import Agora.Effect.TreasuryWithdrawal (
|
|
||||||
TreasuryWithdrawalDatum (TreasuryWithdrawalDatum),
|
|
||||||
treasuryWithdrawalValidator,
|
|
||||||
)
|
|
||||||
|
|
||||||
import Plutus.V1.Ledger.Value qualified as Value
|
|
||||||
import Spec.Util (effectFailsWith, effectSucceedsWith)
|
import Spec.Util (effectFailsWith, effectSucceedsWith)
|
||||||
|
|
||||||
import Test.Tasty (TestTree, testGroup)
|
import Test.Tasty (TestTree, testGroup)
|
||||||
|
|
||||||
tests :: [TestTree]
|
tests :: [TestTree]
|
||||||
|
|
|
||||||
91
agora-test/Spec/Proposal.hs
Normal file
91
agora-test/Spec/Proposal.hs
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Module : Spec.Proposal
|
||||||
|
Maintainer : emi@haskell.fyi
|
||||||
|
Description: Tests for Proposal policy and validator
|
||||||
|
|
||||||
|
Tests for Proposal policy and validator
|
||||||
|
-}
|
||||||
|
module Spec.Proposal (tests) where
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import Agora.Proposal (
|
||||||
|
ProposalDatum (ProposalDatum),
|
||||||
|
ProposalId (ProposalId),
|
||||||
|
ProposalRedeemer (Cosign),
|
||||||
|
ProposalStatus (Draft),
|
||||||
|
ResultTag (ResultTag),
|
||||||
|
cosigners,
|
||||||
|
effects,
|
||||||
|
emptyVotesFor,
|
||||||
|
proposalId,
|
||||||
|
status,
|
||||||
|
thresholds,
|
||||||
|
votes,
|
||||||
|
)
|
||||||
|
import Agora.Proposal.Scripts (
|
||||||
|
proposalPolicy,
|
||||||
|
proposalValidator,
|
||||||
|
)
|
||||||
|
import Agora.Stake (StakeDatum (StakeDatum), StakeRedeemer (WitnessStake))
|
||||||
|
import Agora.Stake.Scripts (stakeValidator)
|
||||||
|
import Plutarch.SafeMoney (Tagged (Tagged))
|
||||||
|
import Plutus.V1.Ledger.Api (ScriptContext (..), ScriptPurpose (..))
|
||||||
|
import PlutusTx.AssocMap qualified as AssocMap
|
||||||
|
import Spec.Sample.Proposal qualified as Proposal
|
||||||
|
import Spec.Sample.Shared (signer, signer2)
|
||||||
|
import Spec.Sample.Shared qualified as Shared
|
||||||
|
import Spec.Util (policySucceedsWith, validatorSucceedsWith)
|
||||||
|
import Test.Tasty (TestTree, testGroup)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Stake tests.
|
||||||
|
tests :: [TestTree]
|
||||||
|
tests =
|
||||||
|
[ testGroup
|
||||||
|
"policy"
|
||||||
|
[ policySucceedsWith
|
||||||
|
"proposalCreation"
|
||||||
|
(proposalPolicy Shared.proposal)
|
||||||
|
()
|
||||||
|
Proposal.proposalCreation
|
||||||
|
]
|
||||||
|
, testGroup
|
||||||
|
"validator"
|
||||||
|
[ testGroup
|
||||||
|
"cosignature"
|
||||||
|
[ validatorSucceedsWith
|
||||||
|
"proposal"
|
||||||
|
(proposalValidator Shared.proposal)
|
||||||
|
( ProposalDatum
|
||||||
|
{ proposalId = ProposalId 0
|
||||||
|
, effects =
|
||||||
|
AssocMap.fromList
|
||||||
|
[ (ResultTag 0, [])
|
||||||
|
, (ResultTag 1, [])
|
||||||
|
]
|
||||||
|
, status = Draft
|
||||||
|
, cosigners = [signer]
|
||||||
|
, thresholds = Shared.defaultProposalThresholds
|
||||||
|
, votes =
|
||||||
|
emptyVotesFor $
|
||||||
|
AssocMap.fromList
|
||||||
|
[ (ResultTag 0, [])
|
||||||
|
, (ResultTag 1, [])
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Cosign [signer2])
|
||||||
|
(ScriptContext (Proposal.cosignProposal [signer2]) (Spending Proposal.proposalRef))
|
||||||
|
, validatorSucceedsWith
|
||||||
|
"stake"
|
||||||
|
(stakeValidator Shared.stake)
|
||||||
|
(StakeDatum (Tagged 50_000_000) signer2 [])
|
||||||
|
WitnessStake
|
||||||
|
(ScriptContext (Proposal.cosignProposal [signer2]) (Spending Proposal.stakeRef))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
234
agora-test/Spec/Sample/Proposal.hs
Normal file
234
agora-test/Spec/Sample/Proposal.hs
Normal file
|
|
@ -0,0 +1,234 @@
|
||||||
|
{- |
|
||||||
|
Module : Spec.Sample.Proposal
|
||||||
|
Maintainer : emi@haskell.fyi
|
||||||
|
Description: Sample based testing for Proposal utxos
|
||||||
|
|
||||||
|
This module tests primarily the happy path for Proposal interactions
|
||||||
|
-}
|
||||||
|
module Spec.Sample.Proposal (
|
||||||
|
-- * Script contexts
|
||||||
|
proposalCreation,
|
||||||
|
cosignProposal,
|
||||||
|
proposalRef,
|
||||||
|
stakeRef,
|
||||||
|
) where
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
import Plutarch.Api.V1 (
|
||||||
|
validatorHash,
|
||||||
|
)
|
||||||
|
import Plutus.V1.Ledger.Api (
|
||||||
|
Address (Address),
|
||||||
|
Credential (ScriptCredential),
|
||||||
|
Datum (Datum),
|
||||||
|
PubKeyHash,
|
||||||
|
ScriptContext (..),
|
||||||
|
ScriptPurpose (..),
|
||||||
|
ToData (toBuiltinData),
|
||||||
|
TxInInfo (TxInInfo),
|
||||||
|
TxInfo (..),
|
||||||
|
TxOut (TxOut, txOutAddress, txOutDatumHash, txOutValue),
|
||||||
|
TxOutRef (TxOutRef),
|
||||||
|
)
|
||||||
|
import Plutus.V1.Ledger.Interval qualified as Interval
|
||||||
|
import Plutus.V1.Ledger.Value qualified as Value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
import Agora.Governor (
|
||||||
|
GovernorDatum (GovernorDatum, nextProposalId, proposalThresholds),
|
||||||
|
)
|
||||||
|
import Agora.Proposal (
|
||||||
|
Proposal (..),
|
||||||
|
ProposalDatum (..),
|
||||||
|
ProposalId (..),
|
||||||
|
ProposalStatus (..),
|
||||||
|
ResultTag (..),
|
||||||
|
emptyVotesFor,
|
||||||
|
)
|
||||||
|
import Agora.Stake (Stake (..), StakeDatum (StakeDatum))
|
||||||
|
import Plutarch.SafeMoney (Tagged (Tagged), untag)
|
||||||
|
import PlutusTx.AssocMap qualified as AssocMap
|
||||||
|
import Spec.Sample.Shared
|
||||||
|
import Spec.Util (datumPair, toDatumHash)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | This script context should be a valid transaction.
|
||||||
|
proposalCreation :: ScriptContext
|
||||||
|
proposalCreation =
|
||||||
|
let st = Value.singleton proposalPolicySymbol "" 1 -- Proposal ST
|
||||||
|
effects =
|
||||||
|
AssocMap.fromList
|
||||||
|
[ (ResultTag 0, [])
|
||||||
|
, (ResultTag 1, [])
|
||||||
|
]
|
||||||
|
proposalDatum :: Datum
|
||||||
|
proposalDatum =
|
||||||
|
Datum
|
||||||
|
( toBuiltinData $
|
||||||
|
ProposalDatum
|
||||||
|
{ proposalId = ProposalId 0
|
||||||
|
, effects = effects
|
||||||
|
, status = Draft
|
||||||
|
, cosigners = [signer]
|
||||||
|
, thresholds = defaultProposalThresholds
|
||||||
|
, votes = emptyVotesFor effects
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
govBefore :: Datum
|
||||||
|
govBefore =
|
||||||
|
Datum
|
||||||
|
( toBuiltinData $
|
||||||
|
GovernorDatum
|
||||||
|
{ proposalThresholds = defaultProposalThresholds
|
||||||
|
, nextProposalId = ProposalId 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
govAfter :: Datum
|
||||||
|
govAfter =
|
||||||
|
Datum
|
||||||
|
( toBuiltinData $
|
||||||
|
GovernorDatum
|
||||||
|
{ proposalThresholds = defaultProposalThresholds
|
||||||
|
, nextProposalId = ProposalId 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
in ScriptContext
|
||||||
|
{ scriptContextTxInfo =
|
||||||
|
TxInfo
|
||||||
|
{ txInfoInputs =
|
||||||
|
[ TxInInfo
|
||||||
|
(TxOutRef "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" 1)
|
||||||
|
TxOut
|
||||||
|
{ txOutAddress = Address (ScriptCredential $ validatorHash govValidator) Nothing
|
||||||
|
, txOutValue = Value.assetClassValue proposal.governorSTAssetClass 1
|
||||||
|
, txOutDatumHash = Just (toDatumHash govBefore)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, txInfoOutputs =
|
||||||
|
[ TxOut
|
||||||
|
{ txOutAddress = Address (ScriptCredential proposalValidatorHash) Nothing
|
||||||
|
, txOutValue =
|
||||||
|
mconcat
|
||||||
|
[ st
|
||||||
|
, Value.singleton "" "" 10_000_000
|
||||||
|
]
|
||||||
|
, txOutDatumHash = Just (toDatumHash proposalDatum)
|
||||||
|
}
|
||||||
|
, TxOut
|
||||||
|
{ txOutAddress = Address (ScriptCredential $ validatorHash govValidator) Nothing
|
||||||
|
, txOutValue =
|
||||||
|
mconcat
|
||||||
|
[ Value.assetClassValue proposal.governorSTAssetClass 1
|
||||||
|
, Value.singleton "" "" 10_000_000
|
||||||
|
]
|
||||||
|
, txOutDatumHash = Just (toDatumHash govAfter)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, txInfoFee = Value.singleton "" "" 2
|
||||||
|
, txInfoMint = st
|
||||||
|
, txInfoDCert = []
|
||||||
|
, txInfoWdrl = []
|
||||||
|
, txInfoValidRange = Interval.always
|
||||||
|
, txInfoSignatories = [signer]
|
||||||
|
, txInfoData =
|
||||||
|
[ datumPair proposalDatum
|
||||||
|
, datumPair govBefore
|
||||||
|
, datumPair govAfter
|
||||||
|
]
|
||||||
|
, txInfoId = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be"
|
||||||
|
}
|
||||||
|
, scriptContextPurpose = Minting proposalPolicySymbol
|
||||||
|
}
|
||||||
|
|
||||||
|
proposalRef :: TxOutRef
|
||||||
|
proposalRef = TxOutRef "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" 1
|
||||||
|
|
||||||
|
stakeRef :: TxOutRef
|
||||||
|
stakeRef = TxOutRef "0ca36f3a357bc69579ab2531aecd1e7d3714d993c7820f40b864be15" 0
|
||||||
|
|
||||||
|
-- | This script context should be a valid transaction.
|
||||||
|
cosignProposal :: [PubKeyHash] -> TxInfo
|
||||||
|
cosignProposal newSigners =
|
||||||
|
let st = Value.singleton proposalPolicySymbol "" 1 -- Proposal ST
|
||||||
|
effects =
|
||||||
|
AssocMap.fromList
|
||||||
|
[ (ResultTag 0, [])
|
||||||
|
, (ResultTag 1, [])
|
||||||
|
]
|
||||||
|
proposalBefore :: ProposalDatum
|
||||||
|
proposalBefore =
|
||||||
|
ProposalDatum
|
||||||
|
{ proposalId = ProposalId 0
|
||||||
|
, effects = effects
|
||||||
|
, status = Draft
|
||||||
|
, cosigners = [signer]
|
||||||
|
, thresholds = defaultProposalThresholds
|
||||||
|
, votes = emptyVotesFor effects
|
||||||
|
}
|
||||||
|
stakeDatum :: StakeDatum
|
||||||
|
stakeDatum = StakeDatum (Tagged 50_000_000) signer2 []
|
||||||
|
proposalAfter :: ProposalDatum
|
||||||
|
proposalAfter = proposalBefore {cosigners = newSigners <> proposalBefore.cosigners}
|
||||||
|
in TxInfo
|
||||||
|
{ txInfoInputs =
|
||||||
|
[ TxInInfo
|
||||||
|
proposalRef
|
||||||
|
TxOut
|
||||||
|
{ txOutAddress = proposalValidatorAddress
|
||||||
|
, txOutValue =
|
||||||
|
mconcat
|
||||||
|
[ st
|
||||||
|
, Value.singleton "" "" 10_000_000
|
||||||
|
]
|
||||||
|
, txOutDatumHash = Just (toDatumHash proposalBefore)
|
||||||
|
}
|
||||||
|
, TxInInfo
|
||||||
|
stakeRef
|
||||||
|
TxOut
|
||||||
|
{ txOutAddress = stakeAddress
|
||||||
|
, txOutValue =
|
||||||
|
mconcat
|
||||||
|
[ Value.singleton "" "" 10_000_000
|
||||||
|
, Value.assetClassValue (untag stake.gtClassRef) 50_000_000
|
||||||
|
, Value.singleton stakeSymbol "" 1
|
||||||
|
]
|
||||||
|
, txOutDatumHash = Just (toDatumHash stakeDatum)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, txInfoOutputs =
|
||||||
|
[ TxOut
|
||||||
|
{ txOutAddress = Address (ScriptCredential proposalValidatorHash) Nothing
|
||||||
|
, txOutValue =
|
||||||
|
mconcat
|
||||||
|
[ st
|
||||||
|
, Value.singleton "" "" 10_000_000
|
||||||
|
]
|
||||||
|
, txOutDatumHash = Just (toDatumHash . Datum $ toBuiltinData proposalAfter)
|
||||||
|
}
|
||||||
|
, TxOut
|
||||||
|
{ txOutAddress = stakeAddress
|
||||||
|
, txOutValue =
|
||||||
|
mconcat
|
||||||
|
[ Value.singleton "" "" 10_000_000
|
||||||
|
, Value.assetClassValue (untag stake.gtClassRef) 50_000_000
|
||||||
|
, Value.singleton stakeSymbol "" 1
|
||||||
|
]
|
||||||
|
, txOutDatumHash = Just (toDatumHash stakeDatum)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, txInfoFee = Value.singleton "" "" 2
|
||||||
|
, txInfoMint = st
|
||||||
|
, txInfoDCert = []
|
||||||
|
, txInfoWdrl = []
|
||||||
|
, txInfoValidRange = Interval.always
|
||||||
|
, txInfoSignatories = newSigners
|
||||||
|
, txInfoData =
|
||||||
|
[ datumPair . Datum $ toBuiltinData proposalBefore
|
||||||
|
, datumPair . Datum $ toBuiltinData proposalAfter
|
||||||
|
, datumPair . Datum $ toBuiltinData stakeDatum
|
||||||
|
]
|
||||||
|
, txInfoId = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be"
|
||||||
|
}
|
||||||
133
agora-test/Spec/Sample/Shared.hs
Normal file
133
agora-test/Spec/Sample/Shared.hs
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
{- |
|
||||||
|
Module : Spec.Sample.Shared
|
||||||
|
Maintainer : emi@haskell.fyi
|
||||||
|
Description: Shared useful values for creating Samples for testing.
|
||||||
|
|
||||||
|
Shared useful values for creating Samples for testing.
|
||||||
|
-}
|
||||||
|
module Spec.Sample.Shared (
|
||||||
|
-- * Misc
|
||||||
|
signer,
|
||||||
|
signer2,
|
||||||
|
|
||||||
|
-- * Components
|
||||||
|
|
||||||
|
-- ** Stake
|
||||||
|
stake,
|
||||||
|
stakeSymbol,
|
||||||
|
stakeValidatorHash,
|
||||||
|
stakeAddress,
|
||||||
|
|
||||||
|
-- ** Governor
|
||||||
|
governor,
|
||||||
|
govPolicy,
|
||||||
|
govValidator,
|
||||||
|
govSymbol,
|
||||||
|
|
||||||
|
-- ** Proposal
|
||||||
|
defaultProposalThresholds,
|
||||||
|
proposal,
|
||||||
|
proposalPolicySymbol,
|
||||||
|
proposalValidatorHash,
|
||||||
|
proposalValidatorAddress,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Agora.Governor (
|
||||||
|
Governor (Governor),
|
||||||
|
governorPolicy,
|
||||||
|
governorValidator,
|
||||||
|
)
|
||||||
|
import Agora.Proposal (
|
||||||
|
Proposal (..),
|
||||||
|
ProposalThresholds (..),
|
||||||
|
)
|
||||||
|
import Agora.Proposal.Scripts (
|
||||||
|
proposalPolicy,
|
||||||
|
proposalValidator,
|
||||||
|
)
|
||||||
|
import Agora.Stake (Stake (..))
|
||||||
|
import Agora.Stake.Scripts (stakePolicy, stakeValidator)
|
||||||
|
import Plutarch.Api.V1 (
|
||||||
|
mintingPolicySymbol,
|
||||||
|
mkMintingPolicy,
|
||||||
|
mkValidator,
|
||||||
|
validatorHash,
|
||||||
|
)
|
||||||
|
import Plutarch.SafeMoney
|
||||||
|
import Plutus.V1.Ledger.Address (scriptHashAddress)
|
||||||
|
import Plutus.V1.Ledger.Api (
|
||||||
|
Address (Address),
|
||||||
|
Credential (ScriptCredential),
|
||||||
|
CurrencySymbol,
|
||||||
|
MintingPolicy (..),
|
||||||
|
PubKeyHash,
|
||||||
|
)
|
||||||
|
import Plutus.V1.Ledger.Scripts (Validator, ValidatorHash)
|
||||||
|
import Plutus.V1.Ledger.Value qualified as Value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stake :: Stake
|
||||||
|
stake =
|
||||||
|
Stake
|
||||||
|
{ gtClassRef =
|
||||||
|
Tagged $
|
||||||
|
Value.assetClass
|
||||||
|
"da8c30857834c6ae7203935b89278c532b3995245295456f993e1d24"
|
||||||
|
"LQ"
|
||||||
|
, proposalSTClass = Value.assetClass proposalPolicySymbol ""
|
||||||
|
}
|
||||||
|
|
||||||
|
stakeSymbol :: CurrencySymbol
|
||||||
|
stakeSymbol = mintingPolicySymbol $ mkMintingPolicy $ stakePolicy stake.gtClassRef
|
||||||
|
|
||||||
|
stakeValidatorHash :: ValidatorHash
|
||||||
|
stakeValidatorHash = validatorHash $ mkValidator (stakeValidator stake)
|
||||||
|
|
||||||
|
stakeAddress :: Address
|
||||||
|
stakeAddress = Address (ScriptCredential stakeValidatorHash) Nothing
|
||||||
|
|
||||||
|
governor :: Governor
|
||||||
|
governor = Governor
|
||||||
|
|
||||||
|
govPolicy :: MintingPolicy
|
||||||
|
govPolicy = mkMintingPolicy (governorPolicy governor)
|
||||||
|
|
||||||
|
govValidator :: Validator
|
||||||
|
govValidator = mkValidator (governorValidator governor)
|
||||||
|
|
||||||
|
govSymbol :: CurrencySymbol
|
||||||
|
govSymbol = mintingPolicySymbol govPolicy
|
||||||
|
|
||||||
|
proposal :: Proposal
|
||||||
|
proposal =
|
||||||
|
Proposal
|
||||||
|
{ governorSTAssetClass = Value.assetClass govSymbol ""
|
||||||
|
, stakeSTAssetClass = Value.assetClass stakeSymbol ""
|
||||||
|
, maximumCosigners = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
proposalPolicySymbol :: CurrencySymbol
|
||||||
|
proposalPolicySymbol = mintingPolicySymbol $ mkMintingPolicy (proposalPolicy proposal)
|
||||||
|
|
||||||
|
-- | A sample 'PubKeyHash'.
|
||||||
|
signer :: PubKeyHash
|
||||||
|
signer = "8a30896c4fd5e79843e4ca1bd2cdbaa36f8c0bc3be7401214142019c"
|
||||||
|
|
||||||
|
-- | Another sample 'PubKeyHash'.
|
||||||
|
signer2 :: PubKeyHash
|
||||||
|
signer2 = "8a30896c4fd5e79843e4ca1bd2cdbaa36f8c0bc3be74012141420192"
|
||||||
|
|
||||||
|
proposalValidatorHash :: ValidatorHash
|
||||||
|
proposalValidatorHash = validatorHash (mkValidator $ proposalValidator proposal)
|
||||||
|
|
||||||
|
proposalValidatorAddress :: Address
|
||||||
|
proposalValidatorAddress = scriptHashAddress proposalValidatorHash
|
||||||
|
|
||||||
|
defaultProposalThresholds :: ProposalThresholds
|
||||||
|
defaultProposalThresholds =
|
||||||
|
ProposalThresholds
|
||||||
|
{ countVoting = Tagged 1000
|
||||||
|
, create = Tagged 1
|
||||||
|
, startVoting = Tagged 10
|
||||||
|
}
|
||||||
|
|
@ -7,8 +7,7 @@ This module tests primarily the happy path for Stake creation
|
||||||
-}
|
-}
|
||||||
module Spec.Sample.Stake (
|
module Spec.Sample.Stake (
|
||||||
stake,
|
stake,
|
||||||
policy,
|
stakeSymbol,
|
||||||
policySymbol,
|
|
||||||
validatorHashTN,
|
validatorHashTN,
|
||||||
signer,
|
signer,
|
||||||
|
|
||||||
|
|
@ -22,19 +21,14 @@ module Spec.Sample.Stake (
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
import Plutarch.Api.V1 (
|
import Plutarch.Api.V1 (
|
||||||
mintingPolicySymbol,
|
|
||||||
mkMintingPolicy,
|
|
||||||
mkValidator,
|
mkValidator,
|
||||||
validatorHash,
|
validatorHash,
|
||||||
)
|
)
|
||||||
import Plutus.V1.Ledger.Api (
|
import Plutus.V1.Ledger.Api (
|
||||||
Address (Address),
|
Address (Address),
|
||||||
Credential (ScriptCredential),
|
Credential (ScriptCredential),
|
||||||
CurrencySymbol,
|
|
||||||
Datum (Datum),
|
Datum (Datum),
|
||||||
DatumHash (DatumHash),
|
DatumHash (DatumHash),
|
||||||
MintingPolicy (..),
|
|
||||||
PubKeyHash,
|
|
||||||
ScriptContext (..),
|
ScriptContext (..),
|
||||||
ScriptPurpose (..),
|
ScriptPurpose (..),
|
||||||
ToData (toBuiltinData),
|
ToData (toBuiltinData),
|
||||||
|
|
@ -45,55 +39,28 @@ import Plutus.V1.Ledger.Api (
|
||||||
)
|
)
|
||||||
import Plutus.V1.Ledger.Contexts (TxOut (TxOut), TxOutRef (TxOutRef))
|
import Plutus.V1.Ledger.Contexts (TxOut (TxOut), TxOutRef (TxOutRef))
|
||||||
import Plutus.V1.Ledger.Interval qualified as Interval
|
import Plutus.V1.Ledger.Interval qualified as Interval
|
||||||
import Plutus.V1.Ledger.Scripts (Validator)
|
import Plutus.V1.Ledger.Value (TokenName (TokenName))
|
||||||
import Plutus.V1.Ledger.Value (AssetClass (AssetClass), TokenName (TokenName))
|
|
||||||
import Plutus.V1.Ledger.Value qualified as Value
|
import Plutus.V1.Ledger.Value qualified as Value
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
import Agora.SafeMoney (GTTag)
|
import Agora.SafeMoney (GTTag)
|
||||||
import Agora.Stake
|
import Agora.Stake
|
||||||
|
import Agora.Stake.Scripts (stakeValidator)
|
||||||
import Plutarch.SafeMoney
|
import Plutarch.SafeMoney
|
||||||
|
import Spec.Sample.Shared
|
||||||
import Spec.Util (datumPair, toDatumHash)
|
import Spec.Util (datumPair, toDatumHash)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | 'Stake' parameters for 'LQ'.
|
|
||||||
stake :: Stake
|
|
||||||
stake =
|
|
||||||
Stake
|
|
||||||
{ gtClassRef =
|
|
||||||
Tagged
|
|
||||||
( AssetClass
|
|
||||||
( "da8c30857834c6ae7203935b89278c532b3995245295456f993e1d24"
|
|
||||||
, "LQ"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
-- | 'Stake' policy instance.
|
|
||||||
policy :: MintingPolicy
|
|
||||||
policy = mkMintingPolicy (stakePolicy stake)
|
|
||||||
|
|
||||||
policySymbol :: CurrencySymbol
|
|
||||||
policySymbol = mintingPolicySymbol policy
|
|
||||||
|
|
||||||
-- | A sample 'PubKeyHash'.
|
|
||||||
signer :: PubKeyHash
|
|
||||||
signer = "8a30896c4fd5e79843e4ca1bd2cdbaa36f8c0bc3be7401214142019c"
|
|
||||||
|
|
||||||
-- | 'Stake' validator instance.
|
|
||||||
validator :: Validator
|
|
||||||
validator = mkValidator (stakeValidator stake)
|
|
||||||
|
|
||||||
-- | 'TokenName' that represents the hash of the 'Stake' validator.
|
-- | 'TokenName' that represents the hash of the 'Stake' validator.
|
||||||
validatorHashTN :: TokenName
|
validatorHashTN :: TokenName
|
||||||
validatorHashTN = let ValidatorHash vh = validatorHash validator in TokenName vh
|
validatorHashTN = let ValidatorHash vh = validatorHash (mkValidator $ stakeValidator stake) in TokenName vh
|
||||||
|
|
||||||
-- | This script context should be a valid transaction.
|
-- | This script context should be a valid transaction.
|
||||||
stakeCreation :: ScriptContext
|
stakeCreation :: ScriptContext
|
||||||
stakeCreation =
|
stakeCreation =
|
||||||
let st = Value.singleton policySymbol validatorHashTN 1 -- Stake ST
|
let st = Value.singleton stakeSymbol validatorHashTN 1 -- Stake ST
|
||||||
datum :: Datum
|
datum :: Datum
|
||||||
datum = Datum (toBuiltinData $ StakeDatum 424242424242 signer [])
|
datum = Datum (toBuiltinData $ StakeDatum 424242424242 signer [])
|
||||||
in ScriptContext
|
in ScriptContext
|
||||||
|
|
@ -102,7 +69,7 @@ stakeCreation =
|
||||||
{ txInfoInputs = []
|
{ txInfoInputs = []
|
||||||
, txInfoOutputs =
|
, txInfoOutputs =
|
||||||
[ TxOut
|
[ TxOut
|
||||||
{ txOutAddress = Address (ScriptCredential $ validatorHash validator) Nothing
|
{ txOutAddress = Address (ScriptCredential stakeValidatorHash) Nothing
|
||||||
, txOutValue = st <> Value.singleton "da8c30857834c6ae7203935b89278c532b3995245295456f993e1d24" "LQ" 424242424242
|
, txOutValue = st <> Value.singleton "da8c30857834c6ae7203935b89278c532b3995245295456f993e1d24" "LQ" 424242424242
|
||||||
, txOutDatumHash = Just (DatumHash "")
|
, txOutDatumHash = Just (DatumHash "")
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +83,7 @@ stakeCreation =
|
||||||
, txInfoData = [("", datum)]
|
, txInfoData = [("", datum)]
|
||||||
, txInfoId = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be"
|
, txInfoId = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be"
|
||||||
}
|
}
|
||||||
, scriptContextPurpose = Minting policySymbol
|
, scriptContextPurpose = Minting stakeSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | This ScriptContext should fail because the datum has too much GT.
|
-- | This ScriptContext should fail because the datum has too much GT.
|
||||||
|
|
@ -126,7 +93,7 @@ stakeCreationWrongDatum =
|
||||||
datum = Datum (toBuiltinData $ StakeDatum 4242424242424242 signer []) -- Too much GT
|
datum = Datum (toBuiltinData $ StakeDatum 4242424242424242 signer []) -- Too much GT
|
||||||
in ScriptContext
|
in ScriptContext
|
||||||
{ scriptContextTxInfo = stakeCreation.scriptContextTxInfo {txInfoData = [("", datum)]}
|
{ scriptContextTxInfo = stakeCreation.scriptContextTxInfo {txInfoData = [("", datum)]}
|
||||||
, scriptContextPurpose = Minting policySymbol
|
, scriptContextPurpose = Minting stakeSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
-- | This ScriptContext should fail because the datum has too much GT.
|
-- | This ScriptContext should fail because the datum has too much GT.
|
||||||
|
|
@ -137,7 +104,7 @@ stakeCreationUnsigned =
|
||||||
stakeCreation.scriptContextTxInfo
|
stakeCreation.scriptContextTxInfo
|
||||||
{ txInfoSignatories = []
|
{ txInfoSignatories = []
|
||||||
}
|
}
|
||||||
, scriptContextPurpose = Minting policySymbol
|
, scriptContextPurpose = Minting stakeSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -153,7 +120,7 @@ data DepositWithdrawExample = DepositWithdrawExample
|
||||||
-- | Create a ScriptContext that deposits or withdraws, given the config for it.
|
-- | Create a ScriptContext that deposits or withdraws, given the config for it.
|
||||||
stakeDepositWithdraw :: DepositWithdrawExample -> ScriptContext
|
stakeDepositWithdraw :: DepositWithdrawExample -> ScriptContext
|
||||||
stakeDepositWithdraw config =
|
stakeDepositWithdraw config =
|
||||||
let st = Value.singleton policySymbol validatorHashTN 1 -- Stake ST
|
let st = Value.singleton stakeSymbol validatorHashTN 1 -- Stake ST
|
||||||
stakeBefore :: StakeDatum
|
stakeBefore :: StakeDatum
|
||||||
stakeBefore = StakeDatum config.startAmount signer []
|
stakeBefore = StakeDatum config.startAmount signer []
|
||||||
|
|
||||||
|
|
@ -166,7 +133,7 @@ stakeDepositWithdraw config =
|
||||||
[ TxInInfo
|
[ TxInInfo
|
||||||
(TxOutRef "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" 1)
|
(TxOutRef "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" 1)
|
||||||
TxOut
|
TxOut
|
||||||
{ txOutAddress = Address (ScriptCredential $ validatorHash validator) Nothing
|
{ txOutAddress = Address (ScriptCredential stakeValidatorHash) Nothing
|
||||||
, txOutValue =
|
, txOutValue =
|
||||||
st
|
st
|
||||||
<> Value.assetClassValue (untag stake.gtClassRef) (untag stakeBefore.stakedAmount)
|
<> Value.assetClassValue (untag stake.gtClassRef) (untag stakeBefore.stakedAmount)
|
||||||
|
|
@ -175,10 +142,9 @@ stakeDepositWithdraw config =
|
||||||
]
|
]
|
||||||
, txInfoOutputs =
|
, txInfoOutputs =
|
||||||
[ TxOut
|
[ TxOut
|
||||||
{ txOutAddress = Address (ScriptCredential $ validatorHash validator) Nothing
|
{ txOutAddress = Address (ScriptCredential stakeValidatorHash) Nothing
|
||||||
, txOutValue =
|
, txOutValue =
|
||||||
st
|
st <> Value.assetClassValue (untag stake.gtClassRef) (untag stakeAfter.stakedAmount)
|
||||||
<> Value.assetClassValue (untag stake.gtClassRef) (untag stakeAfter.stakedAmount)
|
|
||||||
, txOutDatumHash = Just (toDatumHash stakeAfter)
|
, txOutDatumHash = Just (toDatumHash stakeAfter)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ import Test.Tasty (TestTree, testGroup)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
import Agora.Stake (StakeDatum (StakeDatum), StakeRedeemer (DepositWithdraw), stakePolicy, stakeValidator)
|
import Agora.Stake (Stake (..), StakeDatum (StakeDatum), StakeRedeemer (DepositWithdraw))
|
||||||
|
import Agora.Stake.Scripts (stakePolicy, stakeValidator)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -36,20 +37,23 @@ tests =
|
||||||
"policy"
|
"policy"
|
||||||
[ policySucceedsWith
|
[ policySucceedsWith
|
||||||
"stakeCreation"
|
"stakeCreation"
|
||||||
(stakePolicy Stake.stake)
|
(stakePolicy Stake.stake.gtClassRef)
|
||||||
()
|
()
|
||||||
Stake.stakeCreation
|
Stake.stakeCreation
|
||||||
, policyFailsWith
|
, policyFailsWith
|
||||||
"stakeCreationWrongDatum"
|
"stakeCreationWrongDatum"
|
||||||
(stakePolicy Stake.stake)
|
(stakePolicy Stake.stake.gtClassRef)
|
||||||
()
|
()
|
||||||
Stake.stakeCreationWrongDatum
|
Stake.stakeCreationWrongDatum
|
||||||
, policyFailsWith
|
, policyFailsWith
|
||||||
"stakeCreationUnsigned"
|
"stakeCreationUnsigned"
|
||||||
(stakePolicy Stake.stake)
|
(stakePolicy Stake.stake.gtClassRef)
|
||||||
()
|
()
|
||||||
Stake.stakeCreationUnsigned
|
Stake.stakeCreationUnsigned
|
||||||
, validatorSucceedsWith
|
]
|
||||||
|
, testGroup
|
||||||
|
"validator"
|
||||||
|
[ validatorSucceedsWith
|
||||||
"stakeDepositWithdraw deposit"
|
"stakeDepositWithdraw deposit"
|
||||||
(stakeValidator Stake.stake)
|
(stakeValidator Stake.stake)
|
||||||
(toDatum $ StakeDatum 100_000 signer [])
|
(toDatum $ StakeDatum 100_000 signer [])
|
||||||
|
|
|
||||||
|
|
@ -100,10 +100,10 @@ validatorSucceedsWith ::
|
||||||
PLifted redeemer ->
|
PLifted redeemer ->
|
||||||
ScriptContext ->
|
ScriptContext ->
|
||||||
TestTree
|
TestTree
|
||||||
validatorSucceedsWith tag policy datum redeemer scriptContext =
|
validatorSucceedsWith tag validator datum redeemer scriptContext =
|
||||||
scriptSucceeds tag $
|
scriptSucceeds tag $
|
||||||
compile
|
compile
|
||||||
( policy
|
( validator
|
||||||
# pforgetData (pconstantData datum)
|
# pforgetData (pconstantData datum)
|
||||||
# pforgetData (pconstantData redeemer)
|
# pforgetData (pconstantData redeemer)
|
||||||
# pconstant scriptContext
|
# pconstant scriptContext
|
||||||
|
|
@ -122,10 +122,10 @@ validatorFailsWith ::
|
||||||
PLifted redeemer ->
|
PLifted redeemer ->
|
||||||
ScriptContext ->
|
ScriptContext ->
|
||||||
TestTree
|
TestTree
|
||||||
validatorFailsWith tag policy datum redeemer scriptContext =
|
validatorFailsWith tag validator datum redeemer scriptContext =
|
||||||
scriptFails tag $
|
scriptFails tag $
|
||||||
compile
|
compile
|
||||||
( policy
|
( validator
|
||||||
# pforgetData (pconstantData datum)
|
# pforgetData (pconstantData datum)
|
||||||
# pforgetData (pconstantData redeemer)
|
# pforgetData (pconstantData redeemer)
|
||||||
# pconstant scriptContext
|
# pconstant scriptContext
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ common lang
|
||||||
NamedFieldPuns
|
NamedFieldPuns
|
||||||
NamedWildCards
|
NamedWildCards
|
||||||
NumericUnderscores
|
NumericUnderscores
|
||||||
|
OverloadedLabels
|
||||||
OverloadedStrings
|
OverloadedStrings
|
||||||
PartialTypeSignatures
|
PartialTypeSignatures
|
||||||
PatternGuards
|
PatternGuards
|
||||||
|
|
@ -128,8 +129,12 @@ library
|
||||||
Agora.Governor
|
Agora.Governor
|
||||||
Agora.MultiSig
|
Agora.MultiSig
|
||||||
Agora.Proposal
|
Agora.Proposal
|
||||||
|
Agora.Proposal.Scripts
|
||||||
|
Agora.Proposal.Time
|
||||||
|
Agora.Record
|
||||||
Agora.SafeMoney
|
Agora.SafeMoney
|
||||||
Agora.Stake
|
Agora.Stake
|
||||||
|
Agora.Stake.Scripts
|
||||||
Agora.Treasury
|
Agora.Treasury
|
||||||
|
|
||||||
other-modules:
|
other-modules:
|
||||||
|
|
@ -156,7 +161,10 @@ test-suite agora-test
|
||||||
Spec.AuthorityToken
|
Spec.AuthorityToken
|
||||||
Spec.Effect.TreasuryWithdrawal
|
Spec.Effect.TreasuryWithdrawal
|
||||||
Spec.Model.MultiSig
|
Spec.Model.MultiSig
|
||||||
|
Spec.Proposal
|
||||||
Spec.Sample.Effect.TreasuryWithdrawal
|
Spec.Sample.Effect.TreasuryWithdrawal
|
||||||
|
Spec.Sample.Proposal
|
||||||
|
Spec.Sample.Shared
|
||||||
Spec.Sample.Stake
|
Spec.Sample.Stake
|
||||||
Spec.Stake
|
Spec.Stake
|
||||||
Spec.Util
|
Spec.Util
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,15 @@ import Plutarch.Api.V1 (
|
||||||
PCurrencySymbol (..),
|
PCurrencySymbol (..),
|
||||||
PScriptContext (..),
|
PScriptContext (..),
|
||||||
PScriptPurpose (..),
|
PScriptPurpose (..),
|
||||||
PTxInInfo (..),
|
PTxInInfo (PTxInInfo),
|
||||||
PTxInfo (..),
|
PTxInfo (..),
|
||||||
PTxOut (..),
|
PTxOut (..),
|
||||||
)
|
)
|
||||||
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))
|
||||||
import Plutarch.Builtin (pforgetData)
|
import Plutarch.Builtin (pforgetData)
|
||||||
import Plutarch.List (pfoldr')
|
|
||||||
import Plutarch.Monadic qualified as P
|
import Plutarch.Monadic qualified as P
|
||||||
import Plutus.V1.Ledger.Value (AssetClass)
|
import Plutus.V1.Ledger.Value (AssetClass (AssetClass))
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
|
|
@ -36,11 +35,11 @@ import Prelude
|
||||||
import Agora.Utils (
|
import Agora.Utils (
|
||||||
allOutputs,
|
allOutputs,
|
||||||
passert,
|
passert,
|
||||||
passetClassValueOf,
|
|
||||||
passetClassValueOf',
|
|
||||||
plookup,
|
plookup,
|
||||||
psymbolValueOf,
|
psymbolValueOf,
|
||||||
|
ptokenSpent,
|
||||||
)
|
)
|
||||||
|
import Plutarch.Api.V1.Extra (passetClass, passetClassValueOf)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -132,28 +131,21 @@ authorityTokenPolicy params =
|
||||||
PTxInfo txInfo' <- pmatch $ pfromData ctx.txInfo
|
PTxInfo txInfo' <- pmatch $ pfromData ctx.txInfo
|
||||||
txInfo <- pletFields @'["inputs", "mint"] txInfo'
|
txInfo <- pletFields @'["inputs", "mint"] txInfo'
|
||||||
let inputs = txInfo.inputs
|
let inputs = txInfo.inputs
|
||||||
let authorityTokenInputs =
|
mintedValue = pfromData txInfo.mint
|
||||||
pfoldr' @PBuiltinList
|
AssetClass (govCs, govTn) = params.authority
|
||||||
( \txInInfo' acc -> P.do
|
govAc = passetClass # pconstant govCs # pconstant govTn
|
||||||
PTxInInfo txInInfo <- pmatch (pfromData txInInfo')
|
govTokenSpent = ptokenSpent # govAc # inputs
|
||||||
PTxOut txOut' <- pmatch $ pfromData $ pfield @"resolved" # txInInfo
|
|
||||||
txOut <- pletFields @'["value"] txOut'
|
|
||||||
let txOutValue = pfromData txOut.value
|
|
||||||
passetClassValueOf' params.authority # txOutValue + acc
|
|
||||||
)
|
|
||||||
# 0
|
|
||||||
# inputs
|
|
||||||
let mintedValue = pfromData txInfo.mint
|
|
||||||
let tokenMoved = 0 #< authorityTokenInputs
|
|
||||||
PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose
|
PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose
|
||||||
|
|
||||||
let ownSymbol = pfromData $ pfield @"_0" # ownSymbol'
|
let ownSymbol = pfromData $ pfield @"_0" # ownSymbol'
|
||||||
let mintedATs = passetClassValueOf # ownSymbol # pconstant "" # mintedValue
|
mintedATs = passetClassValueOf # mintedValue # (passetClass # ownSymbol # pconstant "")
|
||||||
pif
|
pif
|
||||||
(0 #< mintedATs)
|
(0 #< mintedATs)
|
||||||
( P.do
|
( P.do
|
||||||
passert "Parent token did not move in minting GATs" tokenMoved
|
passert "Parent token did not move in minting GATs" govTokenSpent
|
||||||
passert "All outputs only emit valid GATs" $
|
passert "All outputs only emit valid GATs" $
|
||||||
allOutputs @PUnit # pfromData ctx.txInfo #$ plam $ \txOut _value _address _datum ->
|
allOutputs @PData # pfromData ctx.txInfo #$ plam $ \txOut _value _address _datum ->
|
||||||
authorityTokensValidIn
|
authorityTokensValidIn
|
||||||
# ownSymbol
|
# ownSymbol
|
||||||
# txOut
|
# txOut
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,11 @@ 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)
|
||||||
|
|
||||||
|
-- | Dummy datum for NoOp effect.
|
||||||
newtype PNoOp (s :: S) = PNoOp (Term s PUnit)
|
newtype PNoOp (s :: S) = PNoOp (Term s PUnit)
|
||||||
deriving (PlutusType, PIsData) via (DerivePNewtype PNoOp PUnit)
|
deriving (PlutusType, PIsData) via (DerivePNewtype PNoOp PUnit)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,23 +33,32 @@ 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)
|
||||||
import Plutus.V1.Ledger.Value (CurrencySymbol, Value)
|
import Plutus.V1.Ledger.Value (CurrencySymbol, Value)
|
||||||
import PlutusTx qualified
|
import PlutusTx qualified
|
||||||
|
|
||||||
|
{- | Datum that encodes behavior of Treasury Withdrawal effect.
|
||||||
|
|
||||||
|
Note: This Datum acts like a "predefined redeemer". Which is to say that
|
||||||
|
it encodes the properties a redeemer would, but is locked in-place until
|
||||||
|
spend.
|
||||||
|
-}
|
||||||
data TreasuryWithdrawalDatum = TreasuryWithdrawalDatum
|
data TreasuryWithdrawalDatum = TreasuryWithdrawalDatum
|
||||||
{ receivers :: [(Credential, Value)]
|
{ receivers :: [(Credential, Value)]
|
||||||
|
-- ^ AssocMap for Value sent to each receiver from the treasury.
|
||||||
, treasuries :: [Credential]
|
, treasuries :: [Credential]
|
||||||
|
-- ^ What Credentials is spending from legal.
|
||||||
}
|
}
|
||||||
deriving stock (Show, GHC.Generic)
|
deriving stock (Show, GHC.Generic)
|
||||||
deriving anyclass (Generic)
|
deriving anyclass (Generic)
|
||||||
|
|
||||||
PlutusTx.makeLift ''TreasuryWithdrawalDatum
|
PlutusTx.makeLift ''TreasuryWithdrawalDatum
|
||||||
PlutusTx.unstableMakeIsData ''TreasuryWithdrawalDatum
|
PlutusTx.makeIsDataIndexed ''TreasuryWithdrawalDatum [('TreasuryWithdrawalDatum, 0)]
|
||||||
|
|
||||||
|
-- | Haskell-level version of 'TreasuryWithdrawalDatum'.
|
||||||
newtype PTreasuryWithdrawalDatum (s :: S)
|
newtype PTreasuryWithdrawalDatum (s :: S)
|
||||||
= PTreasuryWithdrawalDatum
|
= PTreasuryWithdrawalDatum
|
||||||
( Term
|
( Term
|
||||||
|
|
@ -69,15 +77,17 @@ 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 ()
|
||||||
ptryFrom' opq cont =
|
ptryFrom' opq cont =
|
||||||
-- this will need to not use punsafeCoerce...
|
-- TODO: This should not use 'punsafeCoerce'.
|
||||||
|
-- Blocked by 'PCredential', and 'PTuple'.
|
||||||
cont (punsafeCoerce opq, ())
|
cont (punsafeCoerce opq, ())
|
||||||
|
|
||||||
{- | Withdraws given list of values to specific target addresses.
|
{- | Withdraws given list of values to specific target addresses.
|
||||||
|
|
@ -90,7 +100,7 @@ instance PTryFrom PData PTreasuryWithdrawalDatum where
|
||||||
Note:
|
Note:
|
||||||
It should check...
|
It should check...
|
||||||
1. Transaction outputs should contain all of what Datum specified
|
1. Transaction outputs should contain all of what Datum specified
|
||||||
2. Left over assests should be redirected back to Treasury
|
2. Left over assets should be redirected back to Treasury
|
||||||
It can be more flexiable over...
|
It can be more flexiable over...
|
||||||
- The number of outputs themselves
|
- The number of outputs themselves
|
||||||
-}
|
-}
|
||||||
|
|
@ -99,7 +109,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 $
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
{- |
|
{- |
|
||||||
Module : Agora.Governor
|
Module : Agora.Governor
|
||||||
Maintainer : emi@haskell.fyi
|
Maintainer : emi@haskell.fyi
|
||||||
|
|
@ -19,8 +21,8 @@ module Agora.Governor (
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Agora.Proposal (ProposalId, ProposalThresholds)
|
import Agora.Proposal (ProposalId, ProposalThresholds)
|
||||||
import Plutarch (popaque)
|
|
||||||
import Plutarch.Api.V1 (PMintingPolicy, PValidator)
|
import Plutarch.Api.V1 (PMintingPolicy, PValidator)
|
||||||
|
import PlutusTx qualified
|
||||||
|
|
||||||
-- | Datum for the Governor script.
|
-- | Datum for the Governor script.
|
||||||
data GovernorDatum = GovernorDatum
|
data GovernorDatum = GovernorDatum
|
||||||
|
|
@ -30,6 +32,8 @@ data GovernorDatum = GovernorDatum
|
||||||
-- ^ What tag the next proposal will get upon creating.
|
-- ^ What tag the next proposal will get upon creating.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlutusTx.makeIsDataIndexed ''GovernorDatum [('GovernorDatum, 0)]
|
||||||
|
|
||||||
{- | Redeemer for Governor script. The governor has two primary
|
{- | Redeemer for Governor script. The governor has two primary
|
||||||
responsibilities:
|
responsibilities:
|
||||||
|
|
||||||
|
|
@ -43,6 +47,8 @@ data GovernorRedeemer
|
||||||
-- and allows minting GATs for each effect script.
|
-- and allows minting GATs for each effect script.
|
||||||
MintGATs
|
MintGATs
|
||||||
|
|
||||||
|
PlutusTx.makeIsDataIndexed ''GovernorRedeemer [('CreateProposal, 0), ('MintGATs, 1)]
|
||||||
|
|
||||||
-- | Parameters for creating Governor scripts.
|
-- | Parameters for creating Governor scripts.
|
||||||
data Governor
|
data Governor
|
||||||
= Governor
|
= Governor
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import Plutarch.DataRepr (
|
||||||
PIsDataReprInstances (PIsDataReprInstances),
|
PIsDataReprInstances (PIsDataReprInstances),
|
||||||
)
|
)
|
||||||
import Plutarch.Lift (
|
import Plutarch.Lift (
|
||||||
|
PConstantDecl,
|
||||||
PLifted,
|
PLifted,
|
||||||
PUnsafeLiftDecl,
|
PUnsafeLiftDecl,
|
||||||
)
|
)
|
||||||
|
|
@ -73,7 +74,7 @@ newtype PMultiSig (s :: S) = PMultiSig
|
||||||
via (PIsDataReprInstances PMultiSig)
|
via (PIsDataReprInstances PMultiSig)
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PMultiSig where type PLifted PMultiSig = MultiSig
|
instance PUnsafeLiftDecl PMultiSig where type PLifted PMultiSig = MultiSig
|
||||||
deriving via (DerivePConstantViaData MultiSig PMultiSig) instance (PConstant MultiSig)
|
deriving via (DerivePConstantViaData MultiSig PMultiSig) instance (PConstantDecl MultiSig)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,23 +11,25 @@ module Agora.Proposal (
|
||||||
-- * Haskell-land
|
-- * Haskell-land
|
||||||
Proposal (..),
|
Proposal (..),
|
||||||
ProposalDatum (..),
|
ProposalDatum (..),
|
||||||
|
ProposalRedeemer (..),
|
||||||
ProposalStatus (..),
|
ProposalStatus (..),
|
||||||
ProposalThresholds (..),
|
ProposalThresholds (..),
|
||||||
ProposalVotes (..),
|
ProposalVotes (..),
|
||||||
ProposalId (..),
|
ProposalId (..),
|
||||||
ResultTag (..),
|
ResultTag (..),
|
||||||
|
emptyVotesFor,
|
||||||
|
|
||||||
-- * Plutarch-land
|
-- * Plutarch-land
|
||||||
PProposalDatum (..),
|
PProposalDatum (..),
|
||||||
|
PProposalRedeemer (..),
|
||||||
PProposalStatus (..),
|
PProposalStatus (..),
|
||||||
PProposalThresholds (..),
|
PProposalThresholds (..),
|
||||||
PProposalVotes (..),
|
PProposalVotes (..),
|
||||||
PProposalId (..),
|
PProposalId (..),
|
||||||
PResultTag (..),
|
PResultTag (..),
|
||||||
|
|
||||||
-- * Scripts
|
-- * Plutarch helpers
|
||||||
proposalValidator,
|
proposalDatumValid,
|
||||||
proposalPolicy,
|
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import GHC.Generics qualified as GHC
|
import GHC.Generics qualified as GHC
|
||||||
|
|
@ -35,30 +37,44 @@ import Generics.SOP (Generic, I (I))
|
||||||
import Plutarch.Api.V1 (
|
import Plutarch.Api.V1 (
|
||||||
PDatumHash,
|
PDatumHash,
|
||||||
PMap,
|
PMap,
|
||||||
PMintingPolicy,
|
|
||||||
PPubKeyHash,
|
PPubKeyHash,
|
||||||
PValidator,
|
|
||||||
PValidatorHash,
|
PValidatorHash,
|
||||||
)
|
)
|
||||||
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.SafeMoney (GTTag)
|
import Agora.SafeMoney (GTTag)
|
||||||
import Plutarch (popaque)
|
import Agora.Utils (pkeysEqual, pnotNull)
|
||||||
import Plutarch.Lift (DerivePConstantViaNewtype (..), PUnsafeLiftDecl (..))
|
import Control.Applicative (Const)
|
||||||
|
import Control.Arrow (first)
|
||||||
|
import Plutarch.Builtin (PBuiltinMap)
|
||||||
|
import Plutarch.DataRepr (DerivePConstantViaData (..), PDataFields, PIsDataReprInstances (..))
|
||||||
|
import Plutarch.Lift (
|
||||||
|
DerivePConstantViaNewtype (..),
|
||||||
|
PConstantDecl,
|
||||||
|
PUnsafeLiftDecl (..),
|
||||||
|
)
|
||||||
|
import Plutarch.Monadic qualified as P
|
||||||
import Plutarch.SafeMoney (PDiscrete, Tagged)
|
import Plutarch.SafeMoney (PDiscrete, Tagged)
|
||||||
|
import Plutarch.TryFrom (PTryFrom (PTryFromExcess, ptryFrom'))
|
||||||
|
import Plutarch.Unsafe (punsafeCoerce)
|
||||||
|
import Plutus.V1.Ledger.Api (DatumHash, PubKeyHash, ValidatorHash)
|
||||||
|
import Plutus.V1.Ledger.Value (AssetClass)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Haskell-land
|
-- Haskell-land
|
||||||
|
|
||||||
|
{- | Identifies a Proposal, issued upon creation of a proposal. In practice,
|
||||||
|
this number starts at zero, and increments by one for each proposal.
|
||||||
|
The 100th proposal will be @'ProposalId' 99@. This counter lives
|
||||||
|
in the 'Agora.Governor.Governor'. See 'Agora.Governor.nextProposalId', and
|
||||||
|
'Agora.Governor.pgetNextProposalId'.
|
||||||
|
-}
|
||||||
|
newtype ProposalId = ProposalId {proposalTag :: Integer}
|
||||||
|
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
||||||
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
{- | Encodes a result. Typically, for a Yes/No proposal, we encode it like this:
|
{- | Encodes a result. Typically, for a Yes/No proposal, we encode it like this:
|
||||||
|
|
||||||
@
|
@
|
||||||
|
|
@ -70,8 +86,10 @@ newtype ResultTag = ResultTag {getResultTag :: Integer}
|
||||||
deriving stock (Eq, Show, Ord)
|
deriving stock (Eq, Show, Ord)
|
||||||
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
||||||
|
|
||||||
{- | The "status" of the proposal. This is only useful for state transitions,
|
{- | The "status" of the proposal. This is only useful for state transitions that
|
||||||
as opposed to time-based "phases".
|
need to happen as a result of a transaction as opposed to time-based "periods".
|
||||||
|
|
||||||
|
See the note on wording & the state machine in the tech-design.
|
||||||
|
|
||||||
If the proposal is 'VotingReady', for instance, that doesn't necessarily
|
If the proposal is 'VotingReady', for instance, that doesn't necessarily
|
||||||
mean that voting is possible, as this also requires the timing to be right.
|
mean that voting is possible, as this also requires the timing to be right.
|
||||||
|
|
@ -92,28 +110,39 @@ data ProposalStatus
|
||||||
-- This means that once the timing requirements align,
|
-- This means that once the timing requirements align,
|
||||||
-- proposal will be able to be voted on.
|
-- proposal will be able to be voted on.
|
||||||
VotingReady
|
VotingReady
|
||||||
|
| -- | The proposal has been voted on, and the votes have been locked
|
||||||
|
-- permanently. The proposal now goes into a locking time after the
|
||||||
|
-- normal voting time. After this, it's possible to execute the proposal.
|
||||||
|
Locked
|
||||||
| -- | The proposal has finished.
|
| -- | The proposal has finished.
|
||||||
--
|
--
|
||||||
-- This can mean it's been voted on and completed, but it can also mean
|
-- This can mean it's been voted on and completed, but it can also mean
|
||||||
-- the proposal failed due to time constraints or didn't
|
-- the proposal failed due to time constraints or didn't
|
||||||
-- get to 'VotingReady' first.
|
-- get to 'VotingReady' first.
|
||||||
--
|
--
|
||||||
|
-- At this stage, the 'votes' field of 'ProposalDatum' is frozen.
|
||||||
|
--
|
||||||
|
-- See 'AdvanceProposal' for documentation on state transitions.
|
||||||
|
--
|
||||||
-- TODO: The owner of the proposal may choose to reclaim their proposal.
|
-- TODO: The owner of the proposal may choose to reclaim their proposal.
|
||||||
Finished
|
Finished
|
||||||
deriving stock (Eq, Show, GHC.Generic)
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
PlutusTx.makeIsDataIndexed ''ProposalStatus [('Draft, 0), ('VotingReady, 1), ('Finished, 2)]
|
PlutusTx.makeIsDataIndexed ''ProposalStatus [('Draft, 0), ('VotingReady, 1), ('Locked, 2), ('Finished, 3)]
|
||||||
|
|
||||||
{- | The threshold values for various state transitions to happen.
|
{- | The threshold values for various state transitions to happen.
|
||||||
This data is stored centrally (in the 'Agora.Governor.Governor') and copied over
|
This data is stored centrally (in the 'Agora.Governor.Governor') and copied over
|
||||||
to 'Proposal's when they are created.
|
to 'Proposal's when they are created.
|
||||||
-}
|
-}
|
||||||
data ProposalThresholds = ProposalThresholds
|
data ProposalThresholds = ProposalThresholds
|
||||||
{ execute :: Tagged GTTag Integer
|
{ countVoting :: Tagged GTTag Integer
|
||||||
-- ^ How much GT minimum must a particular 'ResultTag' accumulate for it to pass.
|
-- ^ How much GT minimum must a particular 'ResultTag' accumulate for it to pass.
|
||||||
, draft :: Tagged GTTag Integer
|
, create :: Tagged GTTag Integer
|
||||||
-- ^ How much GT required to "create" a proposal.
|
-- ^ How much GT required to "create" a proposal.
|
||||||
, vote :: Tagged GTTag Integer
|
--
|
||||||
|
-- It is recommended this be a high enough amount, in order to prevent DOS from bad
|
||||||
|
-- actors.
|
||||||
|
, startVoting :: Tagged GTTag Integer
|
||||||
-- ^ How much GT required to allow voting to happen.
|
-- ^ How much GT required to allow voting to happen.
|
||||||
-- (i.e. to move into 'VotingReady')
|
-- (i.e. to move into 'VotingReady')
|
||||||
}
|
}
|
||||||
|
|
@ -138,9 +167,15 @@ newtype ProposalVotes = ProposalVotes
|
||||||
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
||||||
deriving stock (Eq, Show, GHC.Generic)
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
|
-- | Create a 'ProposalVotes' that has the same shape as the 'effects' field.
|
||||||
|
emptyVotesFor :: forall a. AssocMap.Map ResultTag a -> ProposalVotes
|
||||||
|
emptyVotesFor = ProposalVotes . AssocMap.mapWithKey (const . const 0)
|
||||||
|
|
||||||
-- | Haskell-level datum for Proposal scripts.
|
-- | Haskell-level datum for Proposal scripts.
|
||||||
data ProposalDatum = ProposalDatum
|
data ProposalDatum = ProposalDatum
|
||||||
{ -- TODO: could we encode this more efficiently?
|
{ proposalId :: ProposalId
|
||||||
|
-- ^ Identification of the proposal.
|
||||||
|
, -- TODO: could we encode this more efficiently?
|
||||||
-- This is shaped this way for future proofing.
|
-- This is shaped this way for future proofing.
|
||||||
-- See https://github.com/Liqwid-Labs/agora/issues/39
|
-- See https://github.com/Liqwid-Labs/agora/issues/39
|
||||||
effects :: AssocMap.Map ResultTag [(ValidatorHash, DatumHash)]
|
effects :: AssocMap.Map ResultTag [(ValidatorHash, DatumHash)]
|
||||||
|
|
@ -158,17 +193,62 @@ data ProposalDatum = ProposalDatum
|
||||||
|
|
||||||
PlutusTx.makeIsDataIndexed ''ProposalDatum [('ProposalDatum, 0)]
|
PlutusTx.makeIsDataIndexed ''ProposalDatum [('ProposalDatum, 0)]
|
||||||
|
|
||||||
{- | Identifies a Proposal, issued upon creation of a proposal.
|
-- | Haskell-level redeemer for Proposal scripts.
|
||||||
In practice, this number starts at zero, and increments by one
|
data ProposalRedeemer
|
||||||
for each proposal. The 100th proposal will be @'ProposalId' 99@.
|
= -- | Cast one or more votes towards a particular 'ResultTag'.
|
||||||
This counter lives in the 'Governor', see 'nextProposalId'.
|
Vote ResultTag
|
||||||
-}
|
| -- | Add one or more public keys to the cosignature list.
|
||||||
newtype ProposalId = ProposalId {proposalTag :: Integer}
|
-- Must be signed by those cosigning.
|
||||||
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
--
|
||||||
|
-- This is particularly used in the 'Draft' 'ProposalStatus',
|
||||||
|
-- where matching 'Agora.Stake.Stake's can be called to advance the proposal,
|
||||||
|
-- provided enough GT is shared among them.
|
||||||
|
Cosign [PubKeyHash]
|
||||||
|
| -- | Allow unlocking one or more stakes with votes towards particular 'ResultTag'.
|
||||||
|
Unlock ResultTag
|
||||||
|
| -- | Advance the proposal, performing the required checks for whether that is legal.
|
||||||
|
--
|
||||||
|
-- These are roughly the checks for each possible transition:
|
||||||
|
--
|
||||||
|
-- === @'Draft' -> 'VotingReady'@:
|
||||||
|
--
|
||||||
|
-- 1. The sum of all of the cosigner's GT is larger than the 'startVoting' field of 'ProposalThresholds'.
|
||||||
|
-- 2. The proposal's current time ensures 'isDraftPeriod'.
|
||||||
|
--
|
||||||
|
-- === @'VotingReady' -> 'Locked'@:
|
||||||
|
--
|
||||||
|
-- 1. The sum of all votes is larger than 'countVoting'.
|
||||||
|
-- 2. The winning 'ResultTag' has more votes than all other 'ResultTag's.
|
||||||
|
-- 3. The proposal's current time ensures 'isVotingPeriod'.
|
||||||
|
--
|
||||||
|
-- === @'Locked' -> 'Finished'@:
|
||||||
|
--
|
||||||
|
-- 1. The proposal's current time ensures 'isExecutionPeriod'.
|
||||||
|
-- 2. The transaction mints the GATs to the receiving effects.
|
||||||
|
--
|
||||||
|
-- === @* -> 'Finished'@:
|
||||||
|
--
|
||||||
|
-- If the proposal has run out of time for the current 'ProposalStatus', it will always be possible
|
||||||
|
-- to transition into 'Finished' status, because it has expired (and failed).
|
||||||
|
AdvanceProposal
|
||||||
deriving stock (Eq, Show, GHC.Generic)
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
|
PlutusTx.makeIsDataIndexed
|
||||||
|
''ProposalRedeemer
|
||||||
|
[ ('Vote, 0)
|
||||||
|
, ('Cosign, 1)
|
||||||
|
, ('Unlock, 2)
|
||||||
|
, ('AdvanceProposal, 3)
|
||||||
|
]
|
||||||
|
|
||||||
-- | Parameters that identify the Proposal validator script.
|
-- | Parameters that identify the Proposal validator script.
|
||||||
data Proposal = Proposal
|
data Proposal = Proposal
|
||||||
|
{ governorSTAssetClass :: AssetClass
|
||||||
|
, stakeSTAssetClass :: AssetClass
|
||||||
|
, maximumCosigners :: Integer
|
||||||
|
-- ^ Arbitrary limit for maximum amount of cosigners on a proposal.
|
||||||
|
}
|
||||||
|
deriving stock (Show, Eq)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Plutarch-land
|
-- Plutarch-land
|
||||||
|
|
@ -181,17 +261,37 @@ instance PUnsafeLiftDecl PResultTag where type PLifted PResultTag = ResultTag
|
||||||
deriving via
|
deriving via
|
||||||
(DerivePConstantViaNewtype ResultTag PResultTag PInteger)
|
(DerivePConstantViaNewtype ResultTag PResultTag PInteger)
|
||||||
instance
|
instance
|
||||||
(PConstant ResultTag)
|
(PConstantDecl ResultTag)
|
||||||
|
|
||||||
|
-- FIXME: This instance and the one below, for 'PProposalId', should be derived.
|
||||||
|
-- Soon this will be possible through 'DerivePNewtype'.
|
||||||
|
instance PTryFrom PData (PAsData PResultTag) where
|
||||||
|
type PTryFromExcess PData (PAsData PResultTag) = PTryFromExcess PData (PAsData PInteger)
|
||||||
|
ptryFrom' d k =
|
||||||
|
ptryFrom' @_ @(PAsData PInteger) d $
|
||||||
|
-- JUSTIFICATION:
|
||||||
|
-- We are coercing from @PAsData PInteger@ to @PAsData PResultTag@.
|
||||||
|
-- Since 'PResultTag' is a simple newtype, their shape is the same.
|
||||||
|
k . first punsafeCoerce
|
||||||
|
|
||||||
-- | Plutarch-level version of 'PProposalId'.
|
-- | Plutarch-level version of 'PProposalId'.
|
||||||
newtype PProposalId (s :: S) = PProposalId (Term s PInteger)
|
newtype PProposalId (s :: S) = PProposalId (Term s PInteger)
|
||||||
deriving (PlutusType, PIsData, PEq, POrd) via (DerivePNewtype PProposalId PInteger)
|
deriving (PlutusType, PIsData, PEq, POrd) via (DerivePNewtype PProposalId PInteger)
|
||||||
|
|
||||||
|
instance PTryFrom PData (PAsData PProposalId) where
|
||||||
|
type PTryFromExcess PData (PAsData PProposalId) = PTryFromExcess PData (PAsData PInteger)
|
||||||
|
ptryFrom' d k =
|
||||||
|
ptryFrom' @_ @(PAsData PInteger) d $
|
||||||
|
-- JUSTIFICATION:
|
||||||
|
-- We are coercing from @PAsData PInteger@ to @PAsData PProposalId@.
|
||||||
|
-- Since 'PProposalId' is a simple newtype, their shape is the same.
|
||||||
|
k . first punsafeCoerce
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PProposalId where type PLifted PProposalId = ProposalId
|
instance PUnsafeLiftDecl PProposalId where type PLifted PProposalId = ProposalId
|
||||||
deriving via
|
deriving via
|
||||||
(DerivePConstantViaNewtype ProposalId PProposalId PInteger)
|
(DerivePConstantViaNewtype ProposalId PProposalId PInteger)
|
||||||
instance
|
instance
|
||||||
(PConstant ProposalId)
|
(PConstantDecl ProposalId)
|
||||||
|
|
||||||
-- | Plutarch-level version of 'ProposalStatus'.
|
-- | Plutarch-level version of 'ProposalStatus'.
|
||||||
data PProposalStatus (s :: S)
|
data PProposalStatus (s :: S)
|
||||||
|
|
@ -199,6 +299,7 @@ data PProposalStatus (s :: S)
|
||||||
-- e.g. like Tilde used 'pmatchEnum'.
|
-- e.g. like Tilde used 'pmatchEnum'.
|
||||||
PDraft (Term s (PDataRecord '[]))
|
PDraft (Term s (PDataRecord '[]))
|
||||||
| PVotingReady (Term s (PDataRecord '[]))
|
| PVotingReady (Term s (PDataRecord '[]))
|
||||||
|
| PLocked (Term s (PDataRecord '[]))
|
||||||
| PFinished (Term s (PDataRecord '[]))
|
| PFinished (Term s (PDataRecord '[]))
|
||||||
deriving stock (GHC.Generic)
|
deriving stock (GHC.Generic)
|
||||||
deriving anyclass (Generic)
|
deriving anyclass (Generic)
|
||||||
|
|
@ -208,7 +309,7 @@ data PProposalStatus (s :: S)
|
||||||
via PIsDataReprInstances PProposalStatus
|
via PIsDataReprInstances PProposalStatus
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PProposalStatus where type PLifted PProposalStatus = ProposalStatus
|
instance PUnsafeLiftDecl PProposalStatus where type PLifted PProposalStatus = ProposalStatus
|
||||||
deriving via (DerivePConstantViaData ProposalStatus PProposalStatus) instance (PConstant ProposalStatus)
|
deriving via (DerivePConstantViaData ProposalStatus PProposalStatus) instance (PConstantDecl ProposalStatus)
|
||||||
|
|
||||||
-- | Plutarch-level version of 'ProposalThresholds'.
|
-- | Plutarch-level version of 'ProposalThresholds'.
|
||||||
newtype PProposalThresholds (s :: S) = PProposalThresholds
|
newtype PProposalThresholds (s :: S) = PProposalThresholds
|
||||||
|
|
@ -230,7 +331,7 @@ newtype PProposalThresholds (s :: S) = PProposalThresholds
|
||||||
via (PIsDataReprInstances PProposalThresholds)
|
via (PIsDataReprInstances PProposalThresholds)
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PProposalThresholds where type PLifted PProposalThresholds = ProposalThresholds
|
instance PUnsafeLiftDecl PProposalThresholds where type PLifted PProposalThresholds = ProposalThresholds
|
||||||
deriving via (DerivePConstantViaData ProposalThresholds PProposalThresholds) instance (PConstant ProposalThresholds)
|
deriving via (DerivePConstantViaData ProposalThresholds PProposalThresholds) instance (PConstantDecl ProposalThresholds)
|
||||||
|
|
||||||
-- | Plutarch-level version of 'ProposalVotes'.
|
-- | Plutarch-level version of 'ProposalVotes'.
|
||||||
newtype PProposalVotes (s :: S)
|
newtype PProposalVotes (s :: S)
|
||||||
|
|
@ -241,7 +342,7 @@ instance PUnsafeLiftDecl PProposalVotes where type PLifted PProposalVotes = Prop
|
||||||
deriving via
|
deriving via
|
||||||
(DerivePConstantViaNewtype ProposalVotes PProposalVotes (PMap PResultTag PInteger))
|
(DerivePConstantViaNewtype ProposalVotes PProposalVotes (PMap PResultTag PInteger))
|
||||||
instance
|
instance
|
||||||
(PConstant ProposalVotes)
|
(PConstantDecl ProposalVotes)
|
||||||
|
|
||||||
-- | Plutarch-level version of 'ProposalDatum'.
|
-- | Plutarch-level version of 'ProposalDatum'.
|
||||||
newtype PProposalDatum (s :: S) = PProposalDatum
|
newtype PProposalDatum (s :: S) = PProposalDatum
|
||||||
|
|
@ -249,9 +350,10 @@ newtype PProposalDatum (s :: S) = PProposalDatum
|
||||||
Term
|
Term
|
||||||
s
|
s
|
||||||
( PDataRecord
|
( PDataRecord
|
||||||
'[ "effects" ':= PMap PResultTag (PMap PValidatorHash PDatumHash)
|
'[ "proposalId" ':= PProposalId
|
||||||
|
, "effects" ':= PMap PResultTag (PMap PValidatorHash PDatumHash)
|
||||||
, "status" ':= PProposalStatus
|
, "status" ':= PProposalStatus
|
||||||
, "cosigners" ':= PBuiltinList PPubKeyHash
|
, "cosigners" ':= PBuiltinList (PAsData PPubKeyHash)
|
||||||
, "thresholds" ':= PProposalThresholds
|
, "thresholds" ':= PProposalThresholds
|
||||||
, "votes" ':= PProposalVotes
|
, "votes" ':= PProposalVotes
|
||||||
]
|
]
|
||||||
|
|
@ -264,19 +366,71 @@ newtype PProposalDatum (s :: S) = PProposalDatum
|
||||||
(PlutusType, PIsData, PDataFields)
|
(PlutusType, PIsData, PDataFields)
|
||||||
via (PIsDataReprInstances PProposalDatum)
|
via (PIsDataReprInstances PProposalDatum)
|
||||||
|
|
||||||
|
-- TODO: Derive this.
|
||||||
|
instance PTryFrom PData (PAsData PProposalDatum) where
|
||||||
|
type PTryFromExcess PData (PAsData PProposalDatum) = Const ()
|
||||||
|
ptryFrom' d k =
|
||||||
|
k (punsafeCoerce d, ())
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PProposalDatum where type PLifted PProposalDatum = ProposalDatum
|
instance PUnsafeLiftDecl PProposalDatum where type PLifted PProposalDatum = ProposalDatum
|
||||||
deriving via (DerivePConstantViaData ProposalDatum PProposalDatum) instance (PConstant ProposalDatum)
|
deriving via (DerivePConstantViaData ProposalDatum PProposalDatum) instance (PConstantDecl ProposalDatum)
|
||||||
|
|
||||||
|
-- | Plutarch-level version of 'ProposalRedeemer'.
|
||||||
|
data PProposalRedeemer (s :: S)
|
||||||
|
= PVote (Term s (PDataRecord '["resultTag" ':= PResultTag]))
|
||||||
|
| PCosign (Term s (PDataRecord '["newCosigners" ':= PBuiltinList (PAsData PPubKeyHash)]))
|
||||||
|
| PUnlock (Term s (PDataRecord '["resultTag" ':= PResultTag]))
|
||||||
|
| PAdvanceProposal (Term s (PDataRecord '[]))
|
||||||
|
deriving stock (GHC.Generic)
|
||||||
|
deriving anyclass (Generic)
|
||||||
|
deriving anyclass (PIsDataRepr)
|
||||||
|
deriving
|
||||||
|
(PlutusType, PIsData)
|
||||||
|
via PIsDataReprInstances PProposalRedeemer
|
||||||
|
|
||||||
|
-- See below.
|
||||||
|
instance PTryFrom PData (PAsData PProposalRedeemer) where
|
||||||
|
type PTryFromExcess PData (PAsData PProposalRedeemer) = Const ()
|
||||||
|
ptryFrom' d k =
|
||||||
|
k (punsafeCoerce d, ())
|
||||||
|
|
||||||
|
-- TODO: Waiting on PTryFrom for 'PPubKeyHash'
|
||||||
|
-- deriving via
|
||||||
|
-- PAsData (PIsDataReprInstances PProposalRedeemer)
|
||||||
|
-- instance
|
||||||
|
-- PTryFrom PData (PAsData PProposalRedeemer)
|
||||||
|
|
||||||
|
instance PUnsafeLiftDecl PProposalRedeemer where type PLifted PProposalRedeemer = ProposalRedeemer
|
||||||
|
deriving via (DerivePConstantViaData ProposalRedeemer PProposalRedeemer) instance (PConstantDecl ProposalRedeemer)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | Policy for Proposals.
|
{- | Check for various invariants a proposal must uphold.
|
||||||
proposalPolicy :: Proposal -> ClosedTerm PMintingPolicy
|
This can be used to check both upon creation and
|
||||||
proposalPolicy _ =
|
upon any following state transitions in the proposal.
|
||||||
plam $ \_redeemer _ctx' -> P.do
|
-}
|
||||||
popaque (pconstant ())
|
proposalDatumValid :: Proposal -> Term s (Agora.Proposal.PProposalDatum :--> PBool)
|
||||||
|
proposalDatumValid proposal =
|
||||||
|
phoistAcyclic $
|
||||||
|
plam $ \datum' -> P.do
|
||||||
|
datum <- pletFields @'["effects", "cosigners", "votes"] $ datum'
|
||||||
|
|
||||||
-- | Validator for Proposals.
|
let effects :: Term _ (PBuiltinMap Agora.Proposal.PResultTag (PBuiltinMap Plutarch.Api.V1.PValidatorHash Plutarch.Api.V1.PDatumHash))
|
||||||
proposalValidator :: Proposal -> ClosedTerm PValidator
|
effects =
|
||||||
proposalValidator _ =
|
-- JUSTIFICATION:
|
||||||
plam $ \_datum _redeemer _ctx' -> P.do
|
-- @datum.effects : PMap PResultTag (PMap PValidatorHash PDatumHash)@
|
||||||
popaque (pconstant ())
|
-- @PMap PResultTag (PMap PValidatorHash PDatumHash)@ is equivalent to
|
||||||
|
-- @PBuiltinMap PResultTag (PBuiltinMap Plutarch.Api.V1.PValidatorHash Plutarch.Api.V1.PDatumHash)@
|
||||||
|
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
|
||||||
|
, ptraceIfFalse "Proposal has fewer cosigners than the limit" $ plength # (pfromData datum.cosigners) #<= pconstant proposal.maximumCosigners
|
||||||
|
, ptraceIfFalse "Proposal votes and effects are compatible with each other" $ pkeysEqual # datum.effects # pto (pfromData datum.votes)
|
||||||
|
]
|
||||||
|
|
|
||||||
228
agora/Agora/Proposal/Scripts.hs
Normal file
228
agora/Agora/Proposal/Scripts.hs
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
{- |
|
||||||
|
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 (..),
|
||||||
|
Proposal (governorSTAssetClass, stakeSTAssetClass),
|
||||||
|
)
|
||||||
|
import Agora.Record (mkRecordConstr, (.&), (.=))
|
||||||
|
import Agora.Stake (findStakeOwnedBy)
|
||||||
|
import Agora.Utils (
|
||||||
|
anyOutput,
|
||||||
|
findTxOutByTxOutRef,
|
||||||
|
getMintingPolicySymbol,
|
||||||
|
passert,
|
||||||
|
pisUniq,
|
||||||
|
psymbolValueOf,
|
||||||
|
ptokenSpent,
|
||||||
|
ptxSignedBy,
|
||||||
|
pvalueSpent,
|
||||||
|
)
|
||||||
|
import Plutarch.Api.V1 (
|
||||||
|
PMintingPolicy,
|
||||||
|
PScriptContext (PScriptContext),
|
||||||
|
PScriptPurpose (PMinting, PSpending),
|
||||||
|
PTxInfo (PTxInfo),
|
||||||
|
PValidator,
|
||||||
|
)
|
||||||
|
import Plutarch.Api.V1.Extra (passetClass, passetClassValueOf)
|
||||||
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.TryFrom (ptryFrom)
|
||||||
|
import Plutus.V1.Ledger.Value (AssetClass (AssetClass))
|
||||||
|
|
||||||
|
{- | 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.
|
||||||
|
-}
|
||||||
|
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 ())
|
||||||
|
|
||||||
|
{- | 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'.
|
||||||
|
|
||||||
|
=== 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.
|
||||||
|
-}
|
||||||
|
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", "datums", "signatories"] txInfo'
|
||||||
|
PSpending ((pfield @"_0" #) -> txOutRef) <- pmatch $ pfromData ctx.purpose
|
||||||
|
|
||||||
|
PJust txOut <- pmatch $ findTxOutByTxOutRef # txOutRef # txInfoF.inputs
|
||||||
|
txOutF <- pletFields @'["address", "value"] $ txOut
|
||||||
|
|
||||||
|
(pfromData -> proposalDatum, _) <-
|
||||||
|
ptryFrom @(PAsData PProposalDatum) datum
|
||||||
|
(pfromData -> proposalRedeemer, _) <-
|
||||||
|
ptryFrom @(PAsData PProposalRedeemer) redeemer
|
||||||
|
|
||||||
|
proposalF <-
|
||||||
|
pletFields
|
||||||
|
@'[ "proposalId"
|
||||||
|
, "effects"
|
||||||
|
, "status"
|
||||||
|
, "cosigners"
|
||||||
|
, "thresholds"
|
||||||
|
, "votes"
|
||||||
|
]
|
||||||
|
proposalDatum
|
||||||
|
|
||||||
|
ownAddress <- plet $ txOutF.address
|
||||||
|
|
||||||
|
let stCurrencySymbol =
|
||||||
|
pconstant $ getMintingPolicySymbol (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
|
||||||
|
|
||||||
|
signedBy <- plet $ ptxSignedBy # txInfoF.signatories
|
||||||
|
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
pmatch proposalRedeemer $ \case
|
||||||
|
PVote _r -> P.do
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PCosign r -> P.do
|
||||||
|
newSigs <- plet $ pfield @"newCosigners" # r
|
||||||
|
|
||||||
|
passert "Cosigners are unique" $
|
||||||
|
pisUniq # newSigs
|
||||||
|
|
||||||
|
passert "Signed by all new cosigners" $
|
||||||
|
pall # signedBy # newSigs
|
||||||
|
|
||||||
|
passert "As many new cosigners as Stake datums" $
|
||||||
|
spentStakeST #== plength # newSigs
|
||||||
|
|
||||||
|
passert "All new cosigners are witnessed by their Stake datums" $
|
||||||
|
pall
|
||||||
|
# plam
|
||||||
|
( \sig ->
|
||||||
|
pmatch
|
||||||
|
( findStakeOwnedBy # stakeSTAssetClass
|
||||||
|
# pfromData sig
|
||||||
|
# txInfoF.datums
|
||||||
|
# txInfoF.inputs
|
||||||
|
)
|
||||||
|
$ \case
|
||||||
|
PNothing -> pcon PFalse
|
||||||
|
PJust _ -> pcon PTrue
|
||||||
|
)
|
||||||
|
# newSigs
|
||||||
|
|
||||||
|
passert "Signatures are correctly added to cosignature list" $
|
||||||
|
anyOutput @PProposalDatum # ctx.txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \newValue address newProposalDatum -> P.do
|
||||||
|
let updatedSigs = pconcat # newSigs # proposalF.cosigners
|
||||||
|
correctDatum =
|
||||||
|
pdata newProposalDatum
|
||||||
|
#== pdata
|
||||||
|
( mkRecordConstr
|
||||||
|
PProposalDatum
|
||||||
|
( #proposalId .= proposalF.proposalId
|
||||||
|
.& #effects .= proposalF.effects
|
||||||
|
.& #status .= proposalF.status
|
||||||
|
.& #cosigners .= pdata updatedSigs
|
||||||
|
.& #thresholds .= proposalF.thresholds
|
||||||
|
.& #votes .= proposalF.votes
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ 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
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PAdvanceProposal _r -> P.do
|
||||||
|
popaque (pconstant ())
|
||||||
262
agora/Agora/Proposal/Time.hs
Normal file
262
agora/Agora/Proposal/Time.hs
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||||
|
|
||||||
|
{- |
|
||||||
|
Module : Agora.Proposal.Time
|
||||||
|
Maintainer : emi@haskell.fyi
|
||||||
|
Description: Time functions for proposal phases.
|
||||||
|
|
||||||
|
Time functions for proposal phases.
|
||||||
|
-}
|
||||||
|
module Agora.Proposal.Time (
|
||||||
|
-- * Haskell-land
|
||||||
|
ProposalTime (..),
|
||||||
|
ProposalTimingConfig (..),
|
||||||
|
ProposalStartingTime (..),
|
||||||
|
|
||||||
|
-- * Plutarch-land
|
||||||
|
PProposalTime (..),
|
||||||
|
PProposalTimingConfig (..),
|
||||||
|
PProposalStartingTime (..),
|
||||||
|
|
||||||
|
-- * Compute periods given config and starting time.
|
||||||
|
currentProposalTime,
|
||||||
|
isDraftPeriod,
|
||||||
|
isVotingPeriod,
|
||||||
|
isLockingPeriod,
|
||||||
|
isExecutionPeriod,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Agora.Record (mkRecordConstr, (.&), (.=))
|
||||||
|
import GHC.Generics qualified as GHC
|
||||||
|
import Generics.SOP (Generic, I (I))
|
||||||
|
import Plutarch.Api.V1 (
|
||||||
|
PExtended (PFinite),
|
||||||
|
PInterval (PInterval),
|
||||||
|
PLowerBound (PLowerBound),
|
||||||
|
PPOSIXTime,
|
||||||
|
PPOSIXTimeRange,
|
||||||
|
PUpperBound (PUpperBound),
|
||||||
|
)
|
||||||
|
import Plutarch.DataRepr (PDataFields, PIsDataReprInstances (..))
|
||||||
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.Numeric (AdditiveSemigroup ((+)))
|
||||||
|
import Plutarch.Unsafe (punsafeCoerce)
|
||||||
|
import Plutus.V1.Ledger.Time (POSIXTime)
|
||||||
|
import PlutusTx qualified
|
||||||
|
import Prelude hiding ((+))
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
{- | == Establishing timing in Proposal interactions.
|
||||||
|
|
||||||
|
In Plutus, it's impossible to determine time exactly. It's also impossible
|
||||||
|
to get a single point in time, yet often we need to check
|
||||||
|
various constraints on time.
|
||||||
|
|
||||||
|
For the purposes of proposals, there's a single most important feature:
|
||||||
|
The ability to determine if we can perform an action. In order to correctly
|
||||||
|
determine if we are able to perform certain actions, we need to know what
|
||||||
|
time it roughly is, compared to when the proposal was created.
|
||||||
|
|
||||||
|
'ProposalTime' represents "the time according to the proposal".
|
||||||
|
Its representation is opaque, and doesn't matter.
|
||||||
|
|
||||||
|
Various functions work simply on 'ProposalTime' and 'ProposalTimingConfig'.
|
||||||
|
In particular, 'currentProposalTime' is useful for extracting the time
|
||||||
|
from the 'Plutus.V1.Ledger.Api.txInfoValidPeriod' field
|
||||||
|
of 'Plutus.V1.Ledger.Api.TxInfo'.
|
||||||
|
|
||||||
|
We avoid 'PPOSIXTimeRange' where we can in order to save on operations.
|
||||||
|
-}
|
||||||
|
data ProposalTime = ProposalTime
|
||||||
|
{ lowerBound :: POSIXTime
|
||||||
|
, upperBound :: POSIXTime
|
||||||
|
}
|
||||||
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
|
PlutusTx.makeIsDataIndexed ''ProposalTime [('ProposalTime, 0)]
|
||||||
|
|
||||||
|
-- | Represents the starting time of the proposal.
|
||||||
|
newtype ProposalStartingTime = ProposalStartingTime
|
||||||
|
{ getProposalStartingTime :: POSIXTime
|
||||||
|
}
|
||||||
|
deriving newtype (PlutusTx.ToData, PlutusTx.FromData, PlutusTx.UnsafeFromData)
|
||||||
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
|
{- | Configuration of proposal timings.
|
||||||
|
|
||||||
|
See: https://github.com/Liqwid-Labs/agora/blob/master/docs/tech-design/proposals.md#when-may-interactions-occur
|
||||||
|
-}
|
||||||
|
data ProposalTimingConfig = ProposalTimingConfig
|
||||||
|
{ draftTime :: POSIXTime
|
||||||
|
-- ^ "D": the length of the draft period.
|
||||||
|
, votingTime :: POSIXTime
|
||||||
|
-- ^ "V": the length of the voting period.
|
||||||
|
, lockingTime :: POSIXTime
|
||||||
|
-- ^ "L": the length of the locking period.
|
||||||
|
, executingTime :: POSIXTime
|
||||||
|
-- ^ "E": the length of the execution period.
|
||||||
|
}
|
||||||
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
|
PlutusTx.makeIsDataIndexed ''ProposalTimingConfig [('ProposalTimingConfig, 0)]
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Plutarch-level version of 'ProposalTime'.
|
||||||
|
newtype PProposalTime (s :: S)
|
||||||
|
= PProposalTime
|
||||||
|
( Term
|
||||||
|
s
|
||||||
|
( PDataRecord
|
||||||
|
'[ "lowerBound" ':= PPOSIXTime
|
||||||
|
, "upperBound" ':= PPOSIXTime
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
deriving stock (GHC.Generic)
|
||||||
|
deriving anyclass (Generic)
|
||||||
|
deriving anyclass (PIsDataRepr)
|
||||||
|
deriving
|
||||||
|
(PlutusType, PIsData, PDataFields)
|
||||||
|
via (PIsDataReprInstances PProposalTime)
|
||||||
|
|
||||||
|
-- | Plutarch-level version of 'ProposalStartingTime'.
|
||||||
|
newtype PProposalStartingTime (s :: S) = PProposalStartingTime (Term s PPOSIXTime)
|
||||||
|
deriving (PlutusType, PIsData, PEq, POrd) via (DerivePNewtype PProposalStartingTime PPOSIXTime)
|
||||||
|
|
||||||
|
-- | Plutarch-level version of 'ProposalTimingConfig'.
|
||||||
|
newtype PProposalTimingConfig (s :: S) = PProposalTimingConfig
|
||||||
|
{ getProposalTimingConfig ::
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PDataRecord
|
||||||
|
'[ "draftTime" ':= PPOSIXTime
|
||||||
|
, "votingTime" ':= PPOSIXTime
|
||||||
|
, "lockingTime" ':= PPOSIXTime
|
||||||
|
, "executingTime" ':= PPOSIXTime
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
deriving stock (GHC.Generic)
|
||||||
|
deriving anyclass (Generic)
|
||||||
|
deriving anyclass (PIsDataRepr)
|
||||||
|
deriving
|
||||||
|
(PlutusType, PIsData, PDataFields)
|
||||||
|
via (PIsDataReprInstances PProposalTimingConfig)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- FIXME: Orphan instance, move this to plutarch-extra.
|
||||||
|
instance AdditiveSemigroup (Term s PPOSIXTime) where
|
||||||
|
(punsafeCoerce @_ @_ @PInteger -> x) + (punsafeCoerce @_ @_ @PInteger -> y) = punsafeCoerce $ x + y
|
||||||
|
|
||||||
|
{- | Get the current proposal time, from the 'Plutus.V1.Ledger.Api.txInfoValidPeriod' field.
|
||||||
|
|
||||||
|
If it's impossible to get a fully-bounded time, (e.g. either end of the 'PPOSIXTimeRange' is
|
||||||
|
an infinity) then we error out.
|
||||||
|
-}
|
||||||
|
currentProposalTime :: forall (s :: S). Term s (PPOSIXTimeRange :--> PProposalTime)
|
||||||
|
currentProposalTime = phoistAcyclic $
|
||||||
|
plam $ \iv -> P.do
|
||||||
|
PInterval iv' <- pmatch iv
|
||||||
|
ivf <- pletFields @'["from", "to"] iv'
|
||||||
|
PLowerBound lb <- pmatch ivf.from
|
||||||
|
PUpperBound ub <- pmatch ivf.to
|
||||||
|
lbf <- pletFields @'["_0", "_1"] lb
|
||||||
|
ubf <- pletFields @'["_0", "_1"] ub
|
||||||
|
mkRecordConstr PProposalTime $
|
||||||
|
#lowerBound
|
||||||
|
.= pmatch
|
||||||
|
lbf._0
|
||||||
|
( \case
|
||||||
|
PFinite ((pfield @"_0" #) -> d) -> d
|
||||||
|
_ -> ptraceError "currentProposalTime: Can't get fully-bounded proposal time."
|
||||||
|
)
|
||||||
|
.& #upperBound
|
||||||
|
.= pmatch
|
||||||
|
ubf._0
|
||||||
|
( \case
|
||||||
|
PFinite ((pfield @"_0" #) -> d) -> d
|
||||||
|
_ -> ptraceError "currentProposalTime: Can't get fully-bounded proposal time."
|
||||||
|
)
|
||||||
|
|
||||||
|
-- | Check if 'PProposalTime' is within two 'PPOSIXTime'. Inclusive.
|
||||||
|
proposalTimeWithin ::
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PPOSIXTime
|
||||||
|
:--> PPOSIXTime
|
||||||
|
:--> PProposalTime
|
||||||
|
:--> PBool
|
||||||
|
)
|
||||||
|
proposalTimeWithin = phoistAcyclic $
|
||||||
|
plam $ \l h proposalTime' -> P.do
|
||||||
|
PProposalTime proposalTime <- pmatch proposalTime'
|
||||||
|
ptf <- pletFields @'["lowerBound", "upperBound"] proposalTime
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ l #<= pfromData ptf.lowerBound
|
||||||
|
, pfromData ptf.upperBound #<= h
|
||||||
|
]
|
||||||
|
|
||||||
|
-- | True if the 'PProposalTime' is in the draft period.
|
||||||
|
isDraftPeriod ::
|
||||||
|
forall (s :: S).
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PProposalTimingConfig
|
||||||
|
:--> PProposalStartingTime
|
||||||
|
:--> PProposalTime
|
||||||
|
:--> PBool
|
||||||
|
)
|
||||||
|
isDraftPeriod = phoistAcyclic $
|
||||||
|
plam $ \config s' -> pmatch s' $ \(PProposalStartingTime s) ->
|
||||||
|
proposalTimeWithin # s # (s + pfield @"draftTime" # config)
|
||||||
|
|
||||||
|
-- | True if the 'PProposalTime' is in the voting period.
|
||||||
|
isVotingPeriod ::
|
||||||
|
forall (s :: S).
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PProposalTimingConfig
|
||||||
|
:--> PProposalStartingTime
|
||||||
|
:--> PProposalTime
|
||||||
|
:--> PBool
|
||||||
|
)
|
||||||
|
isVotingPeriod = phoistAcyclic $
|
||||||
|
plam $ \config s' -> pmatch s' $ \(PProposalStartingTime s) ->
|
||||||
|
pletFields @'["draftTime", "votingTime"] config $ \f ->
|
||||||
|
proposalTimeWithin # s # (s + f.draftTime + f.votingTime)
|
||||||
|
|
||||||
|
-- | True if the 'PProposalTime' is in the locking period.
|
||||||
|
isLockingPeriod ::
|
||||||
|
forall (s :: S).
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PProposalTimingConfig
|
||||||
|
:--> PProposalStartingTime
|
||||||
|
:--> PProposalTime
|
||||||
|
:--> PBool
|
||||||
|
)
|
||||||
|
isLockingPeriod = phoistAcyclic $
|
||||||
|
plam $ \config s' -> pmatch s' $ \(PProposalStartingTime s) ->
|
||||||
|
pletFields @'["draftTime", "votingTime", "lockingTime"] config $ \f ->
|
||||||
|
proposalTimeWithin # s # (s + f.draftTime + f.votingTime + f.lockingTime)
|
||||||
|
|
||||||
|
-- | True if the 'PProposalTime' is in the execution period.
|
||||||
|
isExecutionPeriod ::
|
||||||
|
forall (s :: S).
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PProposalTimingConfig
|
||||||
|
:--> PProposalStartingTime
|
||||||
|
:--> PProposalTime
|
||||||
|
:--> PBool
|
||||||
|
)
|
||||||
|
isExecutionPeriod = phoistAcyclic $
|
||||||
|
plam $ \config s' -> pmatch s' $ \(PProposalStartingTime s) ->
|
||||||
|
pletFields @'["draftTime", "votingTime", "lockingTime", "executingTime"] config $ \f ->
|
||||||
|
proposalTimeWithin # s
|
||||||
|
# (s + f.draftTime + f.votingTime + f.lockingTime + f.executingTime)
|
||||||
108
agora/Agora/Record.hs
Normal file
108
agora/Agora/Record.hs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
{- |
|
||||||
|
Module : Agora.Record
|
||||||
|
Maintainer : emi@haskell.fyi
|
||||||
|
Description: PDataRecord helper functions.
|
||||||
|
|
||||||
|
'PDataRecord' helper functions.
|
||||||
|
-}
|
||||||
|
module Agora.Record (
|
||||||
|
mkRecord,
|
||||||
|
mkRecordConstr,
|
||||||
|
(.=),
|
||||||
|
(.&),
|
||||||
|
RecordMorphism,
|
||||||
|
FieldName,
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Category (Category (..))
|
||||||
|
import Data.Coerce (coerce)
|
||||||
|
import GHC.OverloadedLabels (IsLabel (fromLabel))
|
||||||
|
import GHC.TypeLits (Symbol)
|
||||||
|
import Plutarch.DataRepr (PDataRecord (PDCons))
|
||||||
|
import Prelude hiding (id, (.))
|
||||||
|
|
||||||
|
-- | Like 'Data.Proxy.Proxy' but local to this module.
|
||||||
|
data FieldName (sym :: Symbol) = FieldName
|
||||||
|
|
||||||
|
{- | The use of two different 'Symbol's here allows unification to happen,
|
||||||
|
ensuring 'FieldName' has a fully inferred 'Symbol'.
|
||||||
|
|
||||||
|
For example, @'mkRecord' (#foo .= 'pconstantData' (42 :: 'Integer'))@ gets
|
||||||
|
the correct type. Namely, @'Term' s ('PDataRecord' '["foo" ':= 'PInteger'])@.
|
||||||
|
-}
|
||||||
|
instance forall (sym :: Symbol) (sym' :: Symbol). sym ~ sym' => IsLabel sym (FieldName sym) where
|
||||||
|
fromLabel = FieldName
|
||||||
|
|
||||||
|
-- | Turn a constant 'RecordMorphism' into a fully built 'PDataRecord'.
|
||||||
|
mkRecord :: forall (r :: [PLabeledType]) (s :: S). RecordMorphism s '[] r -> Term s (PDataRecord r)
|
||||||
|
mkRecord f = f.runRecordMorphism pdnil
|
||||||
|
|
||||||
|
{- | 'mkRecord' but for known data-types.
|
||||||
|
|
||||||
|
This allows you to dynamically construct a record type constructor.
|
||||||
|
|
||||||
|
=== Example:
|
||||||
|
@
|
||||||
|
'mkRecordConstr'
|
||||||
|
'Agora.Stake.PStakeDatum'
|
||||||
|
( #stakedAmount '.=' 'pconstantData' ('Plutarch.SafeMoney.Tagged' @GTTag 42)
|
||||||
|
'.&' #owner '.=' 'pconstantData' "aabbcc"
|
||||||
|
'.&' #lockedBy '.=' 'pdata' pnil
|
||||||
|
)
|
||||||
|
@
|
||||||
|
Is the same as
|
||||||
|
|
||||||
|
@
|
||||||
|
'pconstant' ('Agora.Stake.StakeDatum' ('Plutarch.SafeMoney.Tagged' 42) "aabbcc" [])
|
||||||
|
@
|
||||||
|
-}
|
||||||
|
mkRecordConstr ::
|
||||||
|
forall (r :: [PLabeledType]) (s :: S) (pt :: PType).
|
||||||
|
PlutusType pt =>
|
||||||
|
-- | The constructor. This is just the Haskell-level constructor for the type.
|
||||||
|
-- For 'Plutarch.Api.V1.Maybe.PMaybeData', this would
|
||||||
|
-- be 'Plutarch.Api.V1.Maybe.PDJust', or 'Plutarch.Api.V1.Maybe.PNothing'.
|
||||||
|
(forall s'. Term s' (PDataRecord r) -> pt s') ->
|
||||||
|
-- | The morphism that builds the record.
|
||||||
|
RecordMorphism s '[] r ->
|
||||||
|
Term s pt
|
||||||
|
mkRecordConstr ctr = pcon . ctr . mkRecord
|
||||||
|
|
||||||
|
-- | A morphism from one 'PDataRecord' to another, representing some sort of consing of data.
|
||||||
|
newtype RecordMorphism (s :: S) (as :: [PLabeledType]) (bs :: [PLabeledType]) = RecordMorphism
|
||||||
|
{ runRecordMorphism ::
|
||||||
|
Term s (PDataRecord as) ->
|
||||||
|
Term s (PDataRecord bs)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance Category (RecordMorphism s) where
|
||||||
|
id = RecordMorphism id
|
||||||
|
f . g = coerce $ f.runRecordMorphism . g.runRecordMorphism
|
||||||
|
|
||||||
|
infix 7 .=
|
||||||
|
|
||||||
|
-- | Cons a labeled type as a 'RecordMorphism'.
|
||||||
|
(.=) ::
|
||||||
|
forall (sym :: Symbol) (a :: PType) (as :: [PLabeledType]) (s :: S).
|
||||||
|
-- | The field name. You can use @-XOverloadedLabels@ to enable the syntax:
|
||||||
|
-- @#hello ~ 'FieldName' "hello"@
|
||||||
|
FieldName sym ->
|
||||||
|
-- | The value at that field. This must be 'PAsData', because the underlying
|
||||||
|
-- type is @'PlutusCore.Data.Constr' 'Integer' ['PlutusCore.Data.Data']@.
|
||||||
|
Term s (PAsData a) ->
|
||||||
|
RecordMorphism s as ((sym ':= a) ': as)
|
||||||
|
_ .= x = RecordMorphism $ pcon . PDCons x
|
||||||
|
|
||||||
|
infixr 6 .&
|
||||||
|
|
||||||
|
-- | Compose two 'RecordMorphism's.
|
||||||
|
(.&) ::
|
||||||
|
forall
|
||||||
|
(s :: S)
|
||||||
|
(a :: [PLabeledType])
|
||||||
|
(b :: [PLabeledType])
|
||||||
|
(c :: [PLabeledType]).
|
||||||
|
RecordMorphism s b c ->
|
||||||
|
RecordMorphism s a b ->
|
||||||
|
RecordMorphism s a c
|
||||||
|
(.&) = (.)
|
||||||
|
|
@ -8,16 +8,20 @@ Description: Vote-lockable stake UTXOs holding GT.
|
||||||
Vote-lockable stake UTXOs holding GT.
|
Vote-lockable stake UTXOs holding GT.
|
||||||
-}
|
-}
|
||||||
module Agora.Stake (
|
module Agora.Stake (
|
||||||
PStakeDatum (..),
|
-- * Haskell-land
|
||||||
PStakeRedeemer (..),
|
|
||||||
StakeDatum (..),
|
StakeDatum (..),
|
||||||
StakeRedeemer (..),
|
StakeRedeemer (..),
|
||||||
ProposalLock (..),
|
|
||||||
PProposalLock (..),
|
|
||||||
Stake (..),
|
Stake (..),
|
||||||
stakePolicy,
|
ProposalLock (..),
|
||||||
stakeValidator,
|
|
||||||
|
-- * Plutarch-land
|
||||||
|
PStakeDatum (..),
|
||||||
|
PStakeRedeemer (..),
|
||||||
|
PProposalLock (..),
|
||||||
|
|
||||||
|
-- * Utility functions
|
||||||
stakeLocked,
|
stakeLocked,
|
||||||
|
findStakeOwnedBy,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -33,16 +37,14 @@ import PlutusTx qualified
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
import Plutarch (popaque)
|
|
||||||
import Plutarch.Api.V1 (
|
import Plutarch.Api.V1 (
|
||||||
PCredential (PPubKeyCredential, PScriptCredential),
|
PDatum,
|
||||||
PMintingPolicy,
|
PDatumHash,
|
||||||
|
PMaybeData (PDJust, PDNothing),
|
||||||
PPubKeyHash,
|
PPubKeyHash,
|
||||||
PScriptPurpose (PMinting, PSpending),
|
PTuple,
|
||||||
PTokenName,
|
PTxInInfo (PTxInInfo),
|
||||||
PValidator,
|
PTxOut (PTxOut),
|
||||||
mintingPolicySymbol,
|
|
||||||
mkMintingPolicy,
|
|
||||||
)
|
)
|
||||||
import Plutarch.DataRepr (
|
import Plutarch.DataRepr (
|
||||||
DerivePConstantViaData (..),
|
DerivePConstantViaData (..),
|
||||||
|
|
@ -50,43 +52,34 @@ import Plutarch.DataRepr (
|
||||||
PIsDataReprInstances (PIsDataReprInstances),
|
PIsDataReprInstances (PIsDataReprInstances),
|
||||||
)
|
)
|
||||||
import Plutarch.Internal (punsafeCoerce)
|
import Plutarch.Internal (punsafeCoerce)
|
||||||
import Plutarch.Lift (PUnsafeLiftDecl (..))
|
import Plutarch.Lift (PConstantDecl, PUnsafeLiftDecl (..))
|
||||||
import Plutarch.Monadic qualified as P
|
import Plutarch.Monadic qualified as P
|
||||||
import Plutus.V1.Ledger.Value (AssetClass (AssetClass))
|
import Plutus.V1.Ledger.Value (AssetClass)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
import Agora.Proposal (PProposalId, PResultTag, ProposalId (..), ResultTag (..))
|
import Agora.Proposal (PProposalId, PResultTag, ProposalId (..), ResultTag (..))
|
||||||
import Agora.SafeMoney (GTTag)
|
import Agora.SafeMoney (GTTag)
|
||||||
import Agora.Utils (
|
import Agora.Utils (
|
||||||
anyInput,
|
|
||||||
anyOutput,
|
|
||||||
paddValue,
|
|
||||||
passert,
|
|
||||||
pfindTxInByTxOutRef,
|
|
||||||
pgeqByClass,
|
|
||||||
pgeqByClass',
|
|
||||||
pgeqBySymbol,
|
|
||||||
pnotNull,
|
pnotNull,
|
||||||
psingletonValue,
|
ptryFindDatum,
|
||||||
psymbolValueOf,
|
|
||||||
ptxSignedBy,
|
|
||||||
pvalueSpent,
|
|
||||||
)
|
)
|
||||||
import Plutarch.Numeric
|
import Control.Applicative (Const)
|
||||||
|
import Plutarch.Api.V1.Extra (PAssetClass, passetClassValueOf)
|
||||||
|
import Plutarch.Numeric ()
|
||||||
import Plutarch.SafeMoney (
|
import Plutarch.SafeMoney (
|
||||||
PDiscrete,
|
PDiscrete,
|
||||||
Tagged (..),
|
Tagged (..),
|
||||||
pdiscreteValue,
|
|
||||||
untag,
|
|
||||||
)
|
)
|
||||||
|
import Plutarch.TryFrom (PTryFrom (PTryFromExcess, ptryFrom'))
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | Parameters for creating Stake scripts.
|
-- | Parameters for creating Stake scripts.
|
||||||
newtype Stake = Stake
|
data Stake = Stake
|
||||||
{ gtClassRef :: Tagged GTTag AssetClass
|
{ gtClassRef :: Tagged GTTag AssetClass
|
||||||
-- ^ Used when inlining the AssetClass of a 'PDiscrete' in the script code.
|
-- ^ Used when inlining the AssetClass of a 'PDiscrete' in the script code.
|
||||||
|
, proposalSTClass :: AssetClass
|
||||||
}
|
}
|
||||||
|
|
||||||
{- | A lock placed on a Stake datum in order to prevent
|
{- | A lock placed on a Stake datum in order to prevent
|
||||||
|
|
@ -135,17 +128,20 @@ data StakeRedeemer
|
||||||
| -- | Destroy a stake, retrieving its LQ, the minimum ADA and any other assets.
|
| -- | Destroy a stake, retrieving its LQ, the minimum ADA and any other assets.
|
||||||
-- Stake must be unlocked.
|
-- Stake must be unlocked.
|
||||||
Destroy
|
Destroy
|
||||||
| -- | Permit a Vote to be added onto a 'Proposal'.
|
| -- | Permit a Vote to be added onto a 'Agora.Proposal.Proposal'.
|
||||||
-- This also adds a lock to the 'lockedBy' field. See 'ProposalLock'.
|
-- This also adds a lock to the 'lockedBy' field. See 'ProposalLock'.
|
||||||
-- This needs to be done in sync with casting a vote, otherwise
|
-- This needs to be done in sync with casting a vote, otherwise
|
||||||
-- it's possible for a lock to be permanently placed on the stake,
|
-- it's possible for a lock to be permanently placed on the stake,
|
||||||
-- and then the funds are lost.
|
-- and then the funds are lost.
|
||||||
PermitVote ProposalLock
|
PermitVote ProposalLock
|
||||||
| -- | Retract a vote, removing it from the 'lockedBy' field. See 'ProposalLock'.
|
| -- | Retract a vote, removing it from the 'lockedBy' field. See 'ProposalLock'.
|
||||||
-- This action checks for permission of the 'Proposal'. Finished proposals are
|
-- This action checks for permission of the 'Agora.Proposal.Proposal'. Finished proposals are
|
||||||
-- always allowed to have votes retracted and won't affect the Proposal datum,
|
-- always allowed to have votes retracted and won't affect the Proposal datum,
|
||||||
-- allowing 'Stake's to be unlocked.
|
-- allowing 'Stake's to be unlocked.
|
||||||
RetractVotes [ProposalLock]
|
RetractVotes [ProposalLock]
|
||||||
|
| -- | The owner can consume stake if nothing is changed about it.
|
||||||
|
-- If the proposal token moves, this is equivalent to the owner consuming it.
|
||||||
|
WitnessStake
|
||||||
deriving stock (Show, GHC.Generic)
|
deriving stock (Show, GHC.Generic)
|
||||||
|
|
||||||
PlutusTx.makeIsDataIndexed
|
PlutusTx.makeIsDataIndexed
|
||||||
|
|
@ -154,13 +150,14 @@ PlutusTx.makeIsDataIndexed
|
||||||
, ('Destroy, 1)
|
, ('Destroy, 1)
|
||||||
, ('PermitVote, 2)
|
, ('PermitVote, 2)
|
||||||
, ('RetractVotes, 3)
|
, ('RetractVotes, 3)
|
||||||
|
, ('WitnessStake, 4)
|
||||||
]
|
]
|
||||||
|
|
||||||
-- | Haskell-level datum for Stake scripts.
|
-- | Haskell-level datum for Stake scripts.
|
||||||
data StakeDatum = StakeDatum
|
data StakeDatum = StakeDatum
|
||||||
{ stakedAmount :: Tagged GTTag Integer
|
{ stakedAmount :: Tagged GTTag Integer
|
||||||
-- ^ Tracks the amount of governance token staked in the datum.
|
-- ^ Tracks the amount of governance token staked in the datum.
|
||||||
-- This also acts as the voting weight for 'Proposal's.
|
-- This also acts as the voting weight for 'Agora.Proposal.Proposal's.
|
||||||
, owner :: PubKeyHash
|
, owner :: PubKeyHash
|
||||||
-- ^ The hash of the public key this stake belongs to.
|
-- ^ The hash of the public key this stake belongs to.
|
||||||
--
|
--
|
||||||
|
|
@ -195,8 +192,13 @@ newtype PStakeDatum (s :: S) = PStakeDatum
|
||||||
(PlutusType, PIsData, PDataFields)
|
(PlutusType, PIsData, PDataFields)
|
||||||
via (PIsDataReprInstances PStakeDatum)
|
via (PIsDataReprInstances PStakeDatum)
|
||||||
|
|
||||||
|
instance PTryFrom PData (PAsData PStakeDatum) where
|
||||||
|
type PTryFromExcess PData (PAsData PStakeDatum) = Const ()
|
||||||
|
ptryFrom' d k =
|
||||||
|
k (punsafeCoerce d, ())
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PStakeDatum where type PLifted PStakeDatum = StakeDatum
|
instance PUnsafeLiftDecl PStakeDatum where type PLifted PStakeDatum = StakeDatum
|
||||||
deriving via (DerivePConstantViaData StakeDatum PStakeDatum) instance (PConstant StakeDatum)
|
deriving via (DerivePConstantViaData StakeDatum PStakeDatum) instance (PConstantDecl StakeDatum)
|
||||||
|
|
||||||
-- | Plutarch-level redeemer for Stake scripts.
|
-- | Plutarch-level redeemer for Stake scripts.
|
||||||
data PStakeRedeemer (s :: S)
|
data PStakeRedeemer (s :: S)
|
||||||
|
|
@ -205,7 +207,8 @@ data PStakeRedeemer (s :: S)
|
||||||
| -- | Destroy a stake, retrieving its LQ, the minimum ADA and any other assets.
|
| -- | Destroy a stake, retrieving its LQ, the minimum ADA and any other assets.
|
||||||
PDestroy (Term s (PDataRecord '[]))
|
PDestroy (Term s (PDataRecord '[]))
|
||||||
| PPermitVote (Term s (PDataRecord '["lock" ':= PProposalLock]))
|
| PPermitVote (Term s (PDataRecord '["lock" ':= PProposalLock]))
|
||||||
| PRetractVotes (Term s (PDataRecord '["locks" ':= PBuiltinList PProposalLock]))
|
| PRetractVotes (Term s (PDataRecord '["locks" ':= PBuiltinList (PAsData PProposalLock)]))
|
||||||
|
| PWitnessStake (Term s (PDataRecord '[]))
|
||||||
deriving stock (GHC.Generic)
|
deriving stock (GHC.Generic)
|
||||||
deriving anyclass (Generic)
|
deriving anyclass (Generic)
|
||||||
deriving anyclass (PIsDataRepr)
|
deriving anyclass (PIsDataRepr)
|
||||||
|
|
@ -213,9 +216,15 @@ data PStakeRedeemer (s :: S)
|
||||||
(PlutusType, PIsData)
|
(PlutusType, PIsData)
|
||||||
via PIsDataReprInstances PStakeRedeemer
|
via PIsDataReprInstances PStakeRedeemer
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PStakeRedeemer where type PLifted PStakeRedeemer = StakeRedeemer
|
deriving via
|
||||||
deriving via (DerivePConstantViaData StakeRedeemer PStakeRedeemer) instance (PConstant StakeRedeemer)
|
PAsData (PIsDataReprInstances PStakeRedeemer)
|
||||||
|
instance
|
||||||
|
PTryFrom PData (PAsData PStakeRedeemer)
|
||||||
|
|
||||||
|
instance PUnsafeLiftDecl PStakeRedeemer where type PLifted PStakeRedeemer = StakeRedeemer
|
||||||
|
deriving via (DerivePConstantViaData StakeRedeemer PStakeRedeemer) instance (PConstantDecl StakeRedeemer)
|
||||||
|
|
||||||
|
-- | Plutarch-level version of 'ProposalLock'.
|
||||||
newtype PProposalLock (s :: S) = PProposalLock
|
newtype PProposalLock (s :: S) = PProposalLock
|
||||||
{ getProposalLock ::
|
{ getProposalLock ::
|
||||||
Term
|
Term
|
||||||
|
|
@ -233,224 +242,13 @@ newtype PProposalLock (s :: S) = PProposalLock
|
||||||
(PlutusType, PIsData, PDataFields)
|
(PlutusType, PIsData, PDataFields)
|
||||||
via (PIsDataReprInstances PProposalLock)
|
via (PIsDataReprInstances PProposalLock)
|
||||||
|
|
||||||
|
deriving via
|
||||||
|
PAsData (PIsDataReprInstances PProposalLock)
|
||||||
|
instance
|
||||||
|
PTryFrom PData (PAsData PProposalLock)
|
||||||
|
|
||||||
instance PUnsafeLiftDecl PProposalLock where type PLifted PProposalLock = ProposalLock
|
instance PUnsafeLiftDecl PProposalLock where type PLifted PProposalLock = ProposalLock
|
||||||
deriving via (DerivePConstantViaData ProposalLock PProposalLock) instance (PConstant ProposalLock)
|
deriving via (DerivePConstantViaData ProposalLock PProposalLock) instance (PConstantDecl ProposalLock)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
{- What this Policy does
|
|
||||||
|
|
||||||
For minting:
|
|
||||||
Check that exactly one state thread is minted
|
|
||||||
Check that an output exists with a state thread and a valid datum
|
|
||||||
Check that no state thread is an input
|
|
||||||
assert TokenName == ValidatorHash of the script that we pay to
|
|
||||||
|
|
||||||
For burning:
|
|
||||||
Check that exactly one state thread is burned
|
|
||||||
Check that datum at state thread is valid and not locked
|
|
||||||
-}
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-- | Policy for Stake state threads.
|
|
||||||
stakePolicy :: Stake -> ClosedTerm PMintingPolicy
|
|
||||||
stakePolicy stake =
|
|
||||||
plam $ \_redeemer ctx' -> P.do
|
|
||||||
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
|
||||||
txInfo' <- plet ctx.txInfo
|
|
||||||
txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo'
|
|
||||||
|
|
||||||
PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose
|
|
||||||
ownSymbol <- plet $ pfield @"_0" # ownSymbol'
|
|
||||||
spentST <- plet $ psymbolValueOf # ownSymbol #$ pvalueSpent # pfromData txInfo'
|
|
||||||
mintedST <- plet $ psymbolValueOf # ownSymbol # txInfo.mint
|
|
||||||
|
|
||||||
let burning = P.do
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
|
|
||||||
passert "ST burned" $
|
|
||||||
mintedST #== -1
|
|
||||||
|
|
||||||
passert "An unlocked input existed containing an ST" $
|
|
||||||
anyInput @PStakeDatum # pfromData txInfo'
|
|
||||||
#$ plam
|
|
||||||
$ \value _ stakeDatum' -> P.do
|
|
||||||
let hasST = psymbolValueOf # ownSymbol # value #== 1
|
|
||||||
let unlocked = pnot # (stakeLocked # stakeDatum')
|
|
||||||
hasST #&& unlocked
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
|
|
||||||
let minting = P.do
|
|
||||||
passert "ST at inputs must be 0" $
|
|
||||||
spentST #== 0
|
|
||||||
|
|
||||||
passert "Minted ST must be exactly 1" $
|
|
||||||
mintedST #== 1
|
|
||||||
|
|
||||||
passert "A UTXO must exist with the correct output" $
|
|
||||||
anyOutput @PStakeDatum # pfromData txInfo'
|
|
||||||
#$ plam
|
|
||||||
$ \value address stakeDatum' -> P.do
|
|
||||||
let cred = pfield @"credential" # address
|
|
||||||
pmatch cred $ \case
|
|
||||||
-- Should pay to a script address
|
|
||||||
PPubKeyCredential _ -> pcon PFalse
|
|
||||||
PScriptCredential validatorHash' -> P.do
|
|
||||||
validatorHash <- pletFields @'["_0"] validatorHash'
|
|
||||||
stakeDatum <- pletFields @'["owner", "stakedAmount"] stakeDatum'
|
|
||||||
|
|
||||||
-- TODO: figure out why this is required :/ (specifically, why `validatorHash._0` is `PData`)
|
|
||||||
tn <- plet (pfromData (punsafeCoerce validatorHash._0 :: Term _ (PAsData PTokenName)))
|
|
||||||
|
|
||||||
let stValue =
|
|
||||||
psingletonValue
|
|
||||||
# ownSymbol
|
|
||||||
-- This coerce is safe because the structure
|
|
||||||
-- of PValidatorHash is the same as PTokenName.
|
|
||||||
# tn
|
|
||||||
# 1
|
|
||||||
let expectedValue =
|
|
||||||
paddValue
|
|
||||||
# (pdiscreteValue stake.gtClassRef # stakeDatum.stakedAmount)
|
|
||||||
# stValue
|
|
||||||
let ownerSignsTransaction =
|
|
||||||
ptxSignedBy
|
|
||||||
# ctx.txInfo
|
|
||||||
# stakeDatum.owner
|
|
||||||
|
|
||||||
-- TODO: This is quite inefficient now, as it does two lookups
|
|
||||||
-- instead of a more efficient single pass,
|
|
||||||
-- but it doesn't really matter for this. At least it's correct.
|
|
||||||
let valueCorrect =
|
|
||||||
foldr1
|
|
||||||
(#&&)
|
|
||||||
[ pgeqByClass' (AssetClass ("", "")) # value # expectedValue
|
|
||||||
, pgeqByClass' (untag stake.gtClassRef)
|
|
||||||
# value
|
|
||||||
# expectedValue
|
|
||||||
, pgeqByClass
|
|
||||||
# ownSymbol
|
|
||||||
# tn
|
|
||||||
# value
|
|
||||||
# expectedValue
|
|
||||||
]
|
|
||||||
|
|
||||||
ownerSignsTransaction
|
|
||||||
#&& valueCorrect
|
|
||||||
popaque (pconstant ())
|
|
||||||
|
|
||||||
pif (0 #< mintedST) minting burning
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-- | Validator intended for Stake UTXOs to live in.
|
|
||||||
stakeValidator :: Stake -> ClosedTerm PValidator
|
|
||||||
stakeValidator stake =
|
|
||||||
plam $ \datum redeemer ctx' -> P.do
|
|
||||||
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
|
||||||
txInfo' <- plet ctx.txInfo
|
|
||||||
txInfo <- pletFields @'["mint", "inputs", "outputs"] txInfo'
|
|
||||||
|
|
||||||
-- TODO: Use PTryFrom
|
|
||||||
let stakeRedeemer :: Term _ PStakeRedeemer
|
|
||||||
stakeRedeemer = pfromData $ punsafeCoerce redeemer
|
|
||||||
stakeDatum' :: Term _ PStakeDatum
|
|
||||||
stakeDatum' = pfromData $ punsafeCoerce datum
|
|
||||||
stakeDatum <- pletFields @'["owner", "stakedAmount"] stakeDatum'
|
|
||||||
|
|
||||||
PSpending txOutRef <- pmatch $ pfromData ctx.purpose
|
|
||||||
|
|
||||||
PJust txInInfo <- pmatch $ pfindTxInByTxOutRef # (pfield @"_0" # txOutRef) # txInfo'
|
|
||||||
ownAddress <- plet $ pfield @"address" #$ pfield @"resolved" # txInInfo
|
|
||||||
let continuingValue = pfield @"value" #$ pfield @"resolved" # txInInfo
|
|
||||||
|
|
||||||
-- Whether the owner signs this transaction or not.
|
|
||||||
ownerSignsTransaction <- plet $ ptxSignedBy # ctx.txInfo # stakeDatum.owner
|
|
||||||
|
|
||||||
stCurrencySymbol <- plet $ pconstant $ mintingPolicySymbol $ mkMintingPolicy (stakePolicy stake)
|
|
||||||
mintedST <- plet $ psymbolValueOf # stCurrencySymbol # txInfo.mint
|
|
||||||
spentST <- plet $ psymbolValueOf # stCurrencySymbol #$ pvalueSpent # txInfo'
|
|
||||||
|
|
||||||
-- Is the stake currently locked?
|
|
||||||
stakeIsLocked <- plet $ stakeLocked # stakeDatum'
|
|
||||||
|
|
||||||
pmatch stakeRedeemer $ \case
|
|
||||||
PDestroy _ -> P.do
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
passert "Should burn ST" $
|
|
||||||
mintedST #== -1
|
|
||||||
passert "Stake unlocked" $ pnot # stakeIsLocked
|
|
||||||
passert
|
|
||||||
"Owner signs this transaction"
|
|
||||||
ownerSignsTransaction
|
|
||||||
popaque (pconstant ())
|
|
||||||
--------------------------------------------------------------------------
|
|
||||||
PRetractVotes _ -> P.do
|
|
||||||
passert
|
|
||||||
"Owner signs this transaction"
|
|
||||||
ownerSignsTransaction
|
|
||||||
-- TODO: check proposal constraints
|
|
||||||
popaque (pconstant ())
|
|
||||||
--------------------------------------------------------------------------
|
|
||||||
PPermitVote _ -> P.do
|
|
||||||
passert
|
|
||||||
"Owner signs this transaction"
|
|
||||||
ownerSignsTransaction
|
|
||||||
-- TODO: check proposal constraints
|
|
||||||
popaque (pconstant ())
|
|
||||||
--------------------------------------------------------------------------
|
|
||||||
PDepositWithdraw r -> P.do
|
|
||||||
passert "ST at inputs must be 1" $
|
|
||||||
spentST #== 1
|
|
||||||
passert "Stake unlocked" $
|
|
||||||
pnot #$ stakeIsLocked
|
|
||||||
passert
|
|
||||||
"Owner signs this transaction"
|
|
||||||
ownerSignsTransaction
|
|
||||||
passert "A UTXO must exist with the correct output" $
|
|
||||||
anyOutput @PStakeDatum # txInfo'
|
|
||||||
#$ plam
|
|
||||||
$ \value address newStakeDatum' -> P.do
|
|
||||||
newStakeDatum <- pletFields @'["owner", "stakedAmount"] newStakeDatum'
|
|
||||||
delta <- plet $ pfield @"delta" # r
|
|
||||||
let isScriptAddress = pdata address #== ownAddress
|
|
||||||
let correctOutputDatum =
|
|
||||||
foldr1
|
|
||||||
(#&&)
|
|
||||||
[ stakeDatum.owner #== newStakeDatum.owner
|
|
||||||
, (stakeDatum.stakedAmount + delta) #== newStakeDatum.stakedAmount
|
|
||||||
, -- We can't magically conjure GT anyway (no input to spend!)
|
|
||||||
-- do we need to check this, really?
|
|
||||||
zero #<= pfromData newStakeDatum.stakedAmount
|
|
||||||
]
|
|
||||||
let expectedValue = paddValue # continuingValue # (pdiscreteValue stake.gtClassRef # delta)
|
|
||||||
|
|
||||||
-- TODO: Same as above. This is quite inefficient now, as it does two lookups
|
|
||||||
-- instead of a more efficient single pass,
|
|
||||||
-- but it doesn't really matter for this. At least it's correct.
|
|
||||||
let valueCorrect =
|
|
||||||
foldr1
|
|
||||||
(#&&)
|
|
||||||
[ pgeqByClass' (AssetClass ("", "")) # value # expectedValue
|
|
||||||
, pgeqByClass' (untag stake.gtClassRef)
|
|
||||||
# value
|
|
||||||
# expectedValue
|
|
||||||
, pgeqBySymbol
|
|
||||||
# stCurrencySymbol
|
|
||||||
# value
|
|
||||||
# expectedValue
|
|
||||||
]
|
|
||||||
|
|
||||||
foldr1
|
|
||||||
(#&&)
|
|
||||||
[ ptraceIfFalse "isScriptAddress" isScriptAddress
|
|
||||||
, ptraceIfFalse "correctOutputDatum" correctOutputDatum
|
|
||||||
, ptraceIfFalse "valueCorrect" valueCorrect
|
|
||||||
]
|
|
||||||
|
|
||||||
popaque (pconstant ())
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
@ -461,3 +259,58 @@ stakeLocked = phoistAcyclic $
|
||||||
let locks :: Term _ (PBuiltinList (PAsData PProposalLock))
|
let locks :: Term _ (PBuiltinList (PAsData PProposalLock))
|
||||||
locks = pfield @"lockedBy" # stakeDatum
|
locks = pfield @"lockedBy" # stakeDatum
|
||||||
in pnotNull # locks
|
in pnotNull # locks
|
||||||
|
|
||||||
|
-- | Find a stake owned by a particular PK.
|
||||||
|
findStakeOwnedBy ::
|
||||||
|
Term
|
||||||
|
s
|
||||||
|
( PAssetClass
|
||||||
|
:--> PPubKeyHash
|
||||||
|
:--> PBuiltinList (PAsData (PTuple PDatumHash PDatum))
|
||||||
|
:--> PBuiltinList (PAsData PTxInInfo)
|
||||||
|
:--> PMaybe (PAsData PStakeDatum)
|
||||||
|
)
|
||||||
|
findStakeOwnedBy = phoistAcyclic $
|
||||||
|
plam $ \ac pk datums inputs ->
|
||||||
|
pmatch (pfind # (isInputStakeOwnedBy # ac # pk # datums) # inputs) $ \case
|
||||||
|
PNothing -> pcon PNothing
|
||||||
|
PJust (pfromData -> v) -> P.do
|
||||||
|
let txOut = pfield @"resolved" # pto v
|
||||||
|
txOutF <- pletFields @'["datumHash"] $ txOut
|
||||||
|
pmatch txOutF.datumHash $ \case
|
||||||
|
PDNothing _ -> pcon PNothing
|
||||||
|
PDJust ((pfield @"_0" #) -> dh) -> P.do
|
||||||
|
ptryFindDatum @(PAsData PStakeDatum) # dh # datums
|
||||||
|
|
||||||
|
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?
|
||||||
|
isInputStakeOwnedBy ::
|
||||||
|
Term
|
||||||
|
_
|
||||||
|
( PAssetClass :--> PPubKeyHash
|
||||||
|
:--> PBuiltinList (PAsData (PTuple PDatumHash PDatum))
|
||||||
|
:--> PAsData PTxInInfo
|
||||||
|
:--> PBool
|
||||||
|
)
|
||||||
|
isInputStakeOwnedBy =
|
||||||
|
plam $ \ac ss datums txInInfo' -> P.do
|
||||||
|
PTxInInfo ((pfield @"resolved" #) -> txOut) <- pmatch $ pfromData txInInfo'
|
||||||
|
PTxOut txOut' <- pmatch txOut
|
||||||
|
txOutF <- pletFields @'["value", "datumHash"] txOut'
|
||||||
|
outStakeST <- plet $ passetClassValueOf # txOutF.value # ac
|
||||||
|
pmatch txOutF.datumHash $ \case
|
||||||
|
PDNothing _ -> pcon PFalse
|
||||||
|
PDJust ((pfield @"_0" #) -> datumHash) ->
|
||||||
|
pif
|
||||||
|
(outStakeST #== 1)
|
||||||
|
( pmatch (ptryFindDatum @(PAsData PStakeDatum) # datumHash # datums) $ \case
|
||||||
|
PNothing -> pcon PFalse
|
||||||
|
PJust v -> stakeDatumOwnedBy # ss # pfromData (punsafeCoerce v)
|
||||||
|
)
|
||||||
|
(pcon PFalse)
|
||||||
|
|
|
||||||
405
agora/Agora/Stake/Scripts.hs
Normal file
405
agora/Agora/Stake/Scripts.hs
Normal file
|
|
@ -0,0 +1,405 @@
|
||||||
|
{- |
|
||||||
|
Module : Agora.Stake.Scripts
|
||||||
|
Maintainer : emi@haskell.fyi
|
||||||
|
Description: Plutus Scripts for Stakes.
|
||||||
|
|
||||||
|
Plutus Scripts for Stakes.
|
||||||
|
-}
|
||||||
|
module Agora.Stake.Scripts (stakePolicy, stakeValidator) where
|
||||||
|
|
||||||
|
import Agora.SafeMoney (GTTag)
|
||||||
|
import Agora.Stake
|
||||||
|
import Agora.Utils (
|
||||||
|
anyInput,
|
||||||
|
anyOutput,
|
||||||
|
paddValue,
|
||||||
|
passert,
|
||||||
|
pfindTxInByTxOutRef,
|
||||||
|
pgeqByClass,
|
||||||
|
pgeqByClass',
|
||||||
|
pgeqBySymbol,
|
||||||
|
psingletonValue,
|
||||||
|
psymbolValueOf,
|
||||||
|
ptokenSpent,
|
||||||
|
ptxSignedBy,
|
||||||
|
pvalueSpent,
|
||||||
|
validatorHashToTokenName,
|
||||||
|
)
|
||||||
|
import Plutarch.Api.V1 (
|
||||||
|
PCredential (PPubKeyCredential, PScriptCredential),
|
||||||
|
PMintingPolicy,
|
||||||
|
PScriptPurpose (PMinting, PSpending),
|
||||||
|
PTokenName,
|
||||||
|
PTxInfo,
|
||||||
|
PValidator,
|
||||||
|
mintingPolicySymbol,
|
||||||
|
mkMintingPolicy,
|
||||||
|
)
|
||||||
|
import Plutarch.Api.V1.Extra (passetClass, passetClassValueOf)
|
||||||
|
import Plutarch.Internal (punsafeCoerce)
|
||||||
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.Numeric
|
||||||
|
import Plutarch.SafeMoney (
|
||||||
|
Tagged (..),
|
||||||
|
pdiscreteValue',
|
||||||
|
untag,
|
||||||
|
)
|
||||||
|
import Plutarch.TryFrom (ptryFrom)
|
||||||
|
import Plutus.V1.Ledger.Value (AssetClass (AssetClass))
|
||||||
|
import Prelude hiding (Num (..))
|
||||||
|
|
||||||
|
{- | Policy for Stake state threads.
|
||||||
|
|
||||||
|
== What this Policy does
|
||||||
|
|
||||||
|
=== For minting:
|
||||||
|
|
||||||
|
- Check that exactly one state thread is minted.
|
||||||
|
- Check that an output exists with a state thread and a valid datum.
|
||||||
|
- Check that no state thread is an input.
|
||||||
|
- assert @'Plutus.V1.Ledger.Api.TokenName' == 'Plutus.V1.Ledger.Api.ValidatorHash'@
|
||||||
|
of the script that we pay to.
|
||||||
|
|
||||||
|
=== For burning:
|
||||||
|
|
||||||
|
- Check that exactly one state thread is burned.
|
||||||
|
- Check that datum at state thread is valid and not locked.
|
||||||
|
-}
|
||||||
|
stakePolicy ::
|
||||||
|
-- | The (governance) token that a Stake can store.
|
||||||
|
Tagged GTTag AssetClass ->
|
||||||
|
ClosedTerm PMintingPolicy
|
||||||
|
stakePolicy gtClassRef =
|
||||||
|
plam $ \_redeemer ctx' -> P.do
|
||||||
|
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
||||||
|
txInfo <- plet $ ctx.txInfo
|
||||||
|
let _a :: Term _ PTxInfo
|
||||||
|
_a = txInfo
|
||||||
|
txInfoF <- pletFields @'["mint", "inputs", "outputs", "signatories"] txInfo
|
||||||
|
|
||||||
|
PMinting ownSymbol' <- pmatch $ pfromData ctx.purpose
|
||||||
|
ownSymbol <- plet $ pfield @"_0" # ownSymbol'
|
||||||
|
spentST <- plet $ psymbolValueOf # ownSymbol #$ pvalueSpent # txInfoF.inputs
|
||||||
|
mintedST <- plet $ psymbolValueOf # ownSymbol # txInfoF.mint
|
||||||
|
|
||||||
|
let burning = P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
passert "ST burned" $
|
||||||
|
mintedST #== -1
|
||||||
|
|
||||||
|
passert "An unlocked input existed containing an ST" $
|
||||||
|
anyInput @PStakeDatum # txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \value _ stakeDatum' -> P.do
|
||||||
|
let hasST = psymbolValueOf # ownSymbol # value #== 1
|
||||||
|
let unlocked = pnot # (stakeLocked # stakeDatum')
|
||||||
|
hasST #&& unlocked
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
|
||||||
|
let minting = P.do
|
||||||
|
passert "ST at inputs must be 0" $
|
||||||
|
spentST #== 0
|
||||||
|
|
||||||
|
passert "Minted ST must be exactly 1" $
|
||||||
|
mintedST #== 1
|
||||||
|
|
||||||
|
passert "A UTXO must exist with the correct output" $
|
||||||
|
anyOutput @PStakeDatum # txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \value address stakeDatum' -> P.do
|
||||||
|
let cred = pfield @"credential" # address
|
||||||
|
pmatch cred $ \case
|
||||||
|
-- Should pay to a script address
|
||||||
|
PPubKeyCredential _ -> pcon PFalse
|
||||||
|
PScriptCredential validatorHash -> P.do
|
||||||
|
stakeDatum <- pletFields @'["owner", "stakedAmount"] stakeDatum'
|
||||||
|
|
||||||
|
tn :: Term _ PTokenName <- plet (validatorHashToTokenName $ pfromData $ pfield @"_0" # validatorHash)
|
||||||
|
|
||||||
|
let stValue =
|
||||||
|
psingletonValue
|
||||||
|
# ownSymbol
|
||||||
|
-- This coerce is safe because the structure
|
||||||
|
-- of PValidatorHash is the same as PTokenName.
|
||||||
|
# tn
|
||||||
|
# 1
|
||||||
|
let expectedValue =
|
||||||
|
paddValue
|
||||||
|
# (pdiscreteValue' gtClassRef # stakeDatum.stakedAmount)
|
||||||
|
# stValue
|
||||||
|
let ownerSignsTransaction =
|
||||||
|
ptxSignedBy
|
||||||
|
# txInfoF.signatories
|
||||||
|
# stakeDatum.owner
|
||||||
|
|
||||||
|
-- TODO: This is quite inefficient now, as it does two lookups
|
||||||
|
-- instead of a more efficient single pass,
|
||||||
|
-- but it doesn't really matter for this. At least it's correct.
|
||||||
|
let valueCorrect =
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ pgeqByClass' (AssetClass ("", "")) # value # expectedValue
|
||||||
|
, pgeqByClass' (untag gtClassRef)
|
||||||
|
# value
|
||||||
|
# expectedValue
|
||||||
|
, pgeqByClass
|
||||||
|
# ownSymbol
|
||||||
|
# tn
|
||||||
|
# value
|
||||||
|
# expectedValue
|
||||||
|
]
|
||||||
|
|
||||||
|
ownerSignsTransaction
|
||||||
|
#&& valueCorrect
|
||||||
|
popaque (pconstant ())
|
||||||
|
|
||||||
|
pif (0 #< mintedST) minting burning
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
{- | Validator intended for Stake UTXOs to be locked by.
|
||||||
|
|
||||||
|
== What this Validator does:
|
||||||
|
|
||||||
|
=== 'DepositWithdraw'
|
||||||
|
|
||||||
|
Deposit or withdraw some GT to the stake.
|
||||||
|
|
||||||
|
- Tx must be signed by the owner.
|
||||||
|
- The 'stakedAmount' field must be updated.
|
||||||
|
- The stake must not be locked.
|
||||||
|
- The new UTXO must have the previous value plus the difference
|
||||||
|
as stated by the redeemer.
|
||||||
|
|
||||||
|
=== 'PermitVote'
|
||||||
|
|
||||||
|
Allow a 'ProposalLock' to be put on the stake in order to vote
|
||||||
|
on a proposal.
|
||||||
|
|
||||||
|
- A proposal token must be spent alongside the stake.
|
||||||
|
|
||||||
|
* Its total votes must be correctly updated to include this stake's
|
||||||
|
contribution.
|
||||||
|
|
||||||
|
- Tx must be signed by the owner.
|
||||||
|
|
||||||
|
=== 'RetractVotes'
|
||||||
|
|
||||||
|
Remove a 'ProposalLock' set when voting on a proposal.
|
||||||
|
|
||||||
|
- A proposal token must be spent alongside the stake.
|
||||||
|
- Tx must be signed by the owner.
|
||||||
|
|
||||||
|
=== 'Destroy'
|
||||||
|
|
||||||
|
Destroy the stake in order to reclaim the min ADA.
|
||||||
|
|
||||||
|
- The stake must not be locked.
|
||||||
|
- Tx must be signed by the owner.
|
||||||
|
|
||||||
|
=== 'WitnessStake'
|
||||||
|
|
||||||
|
Allow this Stake to be included in a transaction without making
|
||||||
|
any changes to it. In the future,
|
||||||
|
this could use [CIP-31](https://cips.cardano.org/cips/cip31/) instead.
|
||||||
|
|
||||||
|
- Tx must be signed by the owner __or__ a proposal ST token must be spent
|
||||||
|
alongside the stake.
|
||||||
|
- The datum and value must remain unchanged.
|
||||||
|
-}
|
||||||
|
stakeValidator :: Stake -> ClosedTerm PValidator
|
||||||
|
stakeValidator stake =
|
||||||
|
plam $ \datum redeemer ctx' -> P.do
|
||||||
|
ctx <- pletFields @'["txInfo", "purpose"] ctx'
|
||||||
|
txInfo <- plet $ pfromData ctx.txInfo
|
||||||
|
txInfoF <- pletFields @'["mint", "inputs", "outputs", "signatories"] txInfo
|
||||||
|
|
||||||
|
(pfromData -> stakeRedeemer, _) <- ptryFrom redeemer
|
||||||
|
|
||||||
|
-- TODO: Use PTryFrom
|
||||||
|
let stakeDatum' :: Term _ PStakeDatum
|
||||||
|
stakeDatum' = pfromData $ punsafeCoerce datum
|
||||||
|
stakeDatum <- pletFields @'["owner", "stakedAmount"] stakeDatum'
|
||||||
|
|
||||||
|
PSpending txOutRef <- pmatch $ pfromData ctx.purpose
|
||||||
|
|
||||||
|
PJust txInInfo <- pmatch $ pfindTxInByTxOutRef # (pfield @"_0" # txOutRef) # txInfoF.inputs
|
||||||
|
ownAddress <- plet $ pfield @"address" #$ pfield @"resolved" # txInInfo
|
||||||
|
let continuingValue = pfield @"value" #$ pfield @"resolved" # txInInfo
|
||||||
|
|
||||||
|
-- Whether the owner signs this transaction or not.
|
||||||
|
ownerSignsTransaction <- plet $ ptxSignedBy # txInfoF.signatories # stakeDatum.owner
|
||||||
|
|
||||||
|
stCurrencySymbol <- plet $ pconstant $ mintingPolicySymbol $ mkMintingPolicy (stakePolicy stake.gtClassRef)
|
||||||
|
mintedST <- plet $ psymbolValueOf # stCurrencySymbol # txInfoF.mint
|
||||||
|
valueSpent <- plet $ pvalueSpent # txInfoF.inputs
|
||||||
|
spentST <- plet $ psymbolValueOf # stCurrencySymbol #$ valueSpent
|
||||||
|
|
||||||
|
let AssetClass (propCs, propTn) = stake.proposalSTClass
|
||||||
|
proposalSTClass = passetClass # pconstant propCs # pconstant propTn
|
||||||
|
spentProposalST <- plet $ passetClassValueOf # valueSpent # proposalSTClass
|
||||||
|
|
||||||
|
-- Is the stake currently locked?
|
||||||
|
stakeIsLocked <- plet $ stakeLocked # stakeDatum'
|
||||||
|
|
||||||
|
pmatch stakeRedeemer $ \case
|
||||||
|
PDestroy _ -> P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
passert "Should burn ST" $
|
||||||
|
mintedST #== -1
|
||||||
|
|
||||||
|
passert "Stake unlocked" $ pnot # stakeIsLocked
|
||||||
|
|
||||||
|
passert "Owner signs this transaction" ownerSignsTransaction
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PRetractVotes _ -> P.do
|
||||||
|
passert
|
||||||
|
"Owner signs this transaction"
|
||||||
|
ownerSignsTransaction
|
||||||
|
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
-- This puts trust into the Proposal. The Proposal must necessarily check
|
||||||
|
-- that this is not abused.
|
||||||
|
passert "Proposal ST spent" $
|
||||||
|
spentProposalST #== 1
|
||||||
|
|
||||||
|
passert "A UTXO must exist with the correct output" $
|
||||||
|
anyOutput @PStakeDatum # txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \value address newStakeDatum' -> P.do
|
||||||
|
let isScriptAddress = pdata address #== ownAddress
|
||||||
|
let _correctOutputDatum = pdata newStakeDatum' #== pdata stakeDatum'
|
||||||
|
let valueCorrect = pdata continuingValue #== pdata value
|
||||||
|
pif
|
||||||
|
isScriptAddress
|
||||||
|
( foldl1
|
||||||
|
(#&&)
|
||||||
|
[ ptraceIfFalse "valueCorrect" valueCorrect
|
||||||
|
]
|
||||||
|
)
|
||||||
|
(pcon PFalse)
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PPermitVote _ -> P.do
|
||||||
|
passert
|
||||||
|
"Owner signs this transaction"
|
||||||
|
ownerSignsTransaction
|
||||||
|
|
||||||
|
-- This puts trust into the Proposal. The Proposal must necessarily check
|
||||||
|
-- that this is not abused.
|
||||||
|
passert "Proposal ST spent" $
|
||||||
|
spentProposalST #== 1
|
||||||
|
|
||||||
|
passert "A UTXO must exist with the correct output" $
|
||||||
|
anyOutput @PStakeDatum # txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \value address newStakeDatum' -> P.do
|
||||||
|
let isScriptAddress = pdata address #== ownAddress
|
||||||
|
let _correctOutputDatum = pdata newStakeDatum' #== pdata stakeDatum'
|
||||||
|
let valueCorrect = pdata continuingValue #== pdata value
|
||||||
|
pif
|
||||||
|
isScriptAddress
|
||||||
|
( foldl1
|
||||||
|
(#&&)
|
||||||
|
[ ptraceIfFalse "valueCorrect" valueCorrect
|
||||||
|
]
|
||||||
|
)
|
||||||
|
(pcon PFalse)
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
PWitnessStake _ -> P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
|
||||||
|
let AssetClass (propCs, propTn) = stake.proposalSTClass
|
||||||
|
propAssetClass = passetClass # pconstant propCs # pconstant propTn
|
||||||
|
proposalTokenMoved =
|
||||||
|
ptokenSpent
|
||||||
|
# propAssetClass
|
||||||
|
# txInfoF.inputs
|
||||||
|
|
||||||
|
-- In order for cosignature to be witnessed, it must be possible for a
|
||||||
|
-- proposal to allow this transaction to happen. This puts trust into the Proposal.
|
||||||
|
-- The Proposal must necessarily check that this is not abused.
|
||||||
|
passert
|
||||||
|
"Owner signs this transaction OR proposal token is spent"
|
||||||
|
(ownerSignsTransaction #|| proposalTokenMoved)
|
||||||
|
|
||||||
|
passert "A UTXO must exist with the correct output" $
|
||||||
|
anyOutput @PStakeDatum # txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \value address newStakeDatum' -> P.do
|
||||||
|
let isScriptAddress = pdata address #== ownAddress
|
||||||
|
let correctOutputDatum = pdata newStakeDatum' #== pdata stakeDatum'
|
||||||
|
let valueCorrect = pdata continuingValue #== pdata value
|
||||||
|
pif
|
||||||
|
isScriptAddress
|
||||||
|
( foldl1
|
||||||
|
(#&&)
|
||||||
|
[ ptraceIfFalse "valueCorrect" valueCorrect
|
||||||
|
, ptraceIfFalse "correctOutputDatum" correctOutputDatum
|
||||||
|
]
|
||||||
|
)
|
||||||
|
(pcon PFalse)
|
||||||
|
popaque (pconstant ())
|
||||||
|
PDepositWithdraw r -> P.do
|
||||||
|
passert "ST at inputs must be 1" $
|
||||||
|
spentST #== 1
|
||||||
|
passert "Stake unlocked" $
|
||||||
|
pnot #$ stakeIsLocked
|
||||||
|
passert
|
||||||
|
"Owner signs this transaction"
|
||||||
|
ownerSignsTransaction
|
||||||
|
passert "A UTXO must exist with the correct output" $
|
||||||
|
anyOutput @PStakeDatum # txInfo
|
||||||
|
#$ plam
|
||||||
|
$ \value address newStakeDatum' -> P.do
|
||||||
|
newStakeDatum <- pletFields @'["owner", "stakedAmount"] newStakeDatum'
|
||||||
|
delta <- plet $ pfield @"delta" # r
|
||||||
|
let isScriptAddress = pdata address #== ownAddress
|
||||||
|
let correctOutputDatum =
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ stakeDatum.owner #== newStakeDatum.owner
|
||||||
|
, (stakeDatum.stakedAmount + delta) #== newStakeDatum.stakedAmount
|
||||||
|
, -- We can't magically conjure GT anyway (no input to spend!)
|
||||||
|
-- do we need to check this, really?
|
||||||
|
zero #<= pfromData newStakeDatum.stakedAmount
|
||||||
|
]
|
||||||
|
let expectedValue = paddValue # continuingValue # (pdiscreteValue' stake.gtClassRef # delta)
|
||||||
|
|
||||||
|
-- TODO: Same as above. This is quite inefficient now, as it does two lookups
|
||||||
|
-- instead of a more efficient single pass,
|
||||||
|
-- but it doesn't really matter for this. At least it's correct.
|
||||||
|
let valueCorrect =
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ pgeqByClass' (AssetClass ("", "")) # value # expectedValue
|
||||||
|
, pgeqByClass' (untag stake.gtClassRef)
|
||||||
|
# value
|
||||||
|
# expectedValue
|
||||||
|
, pgeqBySymbol
|
||||||
|
# stCurrencySymbol
|
||||||
|
# value
|
||||||
|
# expectedValue
|
||||||
|
]
|
||||||
|
|
||||||
|
foldr1
|
||||||
|
(#&&)
|
||||||
|
[ ptraceIfFalse "isScriptAddress" isScriptAddress
|
||||||
|
, ptraceIfFalse "correctOutputDatum" correctOutputDatum
|
||||||
|
, ptraceIfFalse "valueCorrect" valueCorrect
|
||||||
|
]
|
||||||
|
|
||||||
|
popaque (pconstant ())
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
{- |
|
{- |
|
||||||
Module: Agora.Treasury
|
Module: Agora.Treasury
|
||||||
Maintainer: jack@mlabs.city
|
Maintainer: jack@mlabs.city
|
||||||
|
|
@ -8,24 +10,60 @@ treasury.
|
||||||
-}
|
-}
|
||||||
module Agora.Treasury (module Agora.Treasury) where
|
module Agora.Treasury (module Agora.Treasury) where
|
||||||
|
|
||||||
|
import Agora.AuthorityToken (singleAuthorityTokenBurned)
|
||||||
|
import Agora.Utils (passert)
|
||||||
import GHC.Generics qualified as GHC
|
import GHC.Generics qualified as GHC
|
||||||
import Generics.SOP
|
import Generics.SOP
|
||||||
|
import Plutarch.Api.V1 (PValidator)
|
||||||
import Plutarch.Api.V1.Contexts (PScriptPurpose (PMinting))
|
import Plutarch.Api.V1.Contexts (PScriptPurpose (PMinting))
|
||||||
import Plutarch.Api.V1.Value (PCurrencySymbol, PValue)
|
import Plutarch.Api.V1.Value (PValue)
|
||||||
import Plutarch.DataRepr (
|
import Plutarch.DataRepr (
|
||||||
PDataFields,
|
DerivePConstantViaData (..),
|
||||||
PIsDataReprInstances (PIsDataReprInstances),
|
PIsDataReprInstances (PIsDataReprInstances),
|
||||||
)
|
)
|
||||||
|
import Plutarch.Lift (PConstantDecl (..), PLifted (..), PUnsafeLiftDecl)
|
||||||
import Plutarch.Monadic qualified as P
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.TryFrom (PTryFrom, ptryFrom)
|
||||||
import Plutus.V1.Ledger.Value (CurrencySymbol)
|
import Plutus.V1.Ledger.Value (CurrencySymbol)
|
||||||
|
import PlutusTx qualified
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
import Agora.AuthorityToken (singleAuthorityTokenBurned)
|
-- | Redeemer for Treasury actions.
|
||||||
import Agora.Utils (passert)
|
data TreasuryRedeemer
|
||||||
import Plutarch (popaque)
|
= -- | Allow transaction to pass by delegating to GAT burn.
|
||||||
import Plutarch.Api.V1 (PValidator)
|
SpendTreasuryGAT
|
||||||
import Plutarch.Unsafe (punsafeCoerce)
|
deriving stock (Eq, Show, GHC.Generic)
|
||||||
|
|
||||||
|
PlutusTx.makeIsDataIndexed
|
||||||
|
''TreasuryRedeemer
|
||||||
|
[ ('SpendTreasuryGAT, 0)
|
||||||
|
]
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
{- | Plutarch level type representing valid redeemers of the
|
||||||
|
treasury.
|
||||||
|
-}
|
||||||
|
newtype PTreasuryRedeemer (s :: S)
|
||||||
|
= -- | Alters treasury parameters, subject to the burning of a
|
||||||
|
-- governance authority token.
|
||||||
|
PSpendTreasuryGAT (Term s (PDataRecord '[]))
|
||||||
|
deriving stock (GHC.Generic)
|
||||||
|
deriving anyclass (Generic, PIsDataRepr)
|
||||||
|
deriving
|
||||||
|
(PlutusType, PIsData)
|
||||||
|
via PIsDataReprInstances PTreasuryRedeemer
|
||||||
|
|
||||||
|
deriving via
|
||||||
|
PAsData (PIsDataReprInstances PTreasuryRedeemer)
|
||||||
|
instance
|
||||||
|
PTryFrom PData (PAsData PTreasuryRedeemer)
|
||||||
|
|
||||||
|
instance PUnsafeLiftDecl PTreasuryRedeemer where type PLifted PTreasuryRedeemer = TreasuryRedeemer
|
||||||
|
deriving via (DerivePConstantViaData TreasuryRedeemer PTreasuryRedeemer) instance (PConstantDecl TreasuryRedeemer)
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
{- | Validator ensuring that transactions consuming the treasury
|
{- | Validator ensuring that transactions consuming the treasury
|
||||||
do so in a valid manner.
|
do so in a valid manner.
|
||||||
|
|
@ -33,12 +71,8 @@ import Plutarch.Unsafe (punsafeCoerce)
|
||||||
treasuryValidator ::
|
treasuryValidator ::
|
||||||
CurrencySymbol ->
|
CurrencySymbol ->
|
||||||
ClosedTerm PValidator
|
ClosedTerm PValidator
|
||||||
treasuryValidator gatCs' = plam $ \datum redeemer ctx' -> P.do
|
treasuryValidator gatCs' = plam $ \_datum redeemer ctx' -> P.do
|
||||||
-- TODO: Use PTryFrom
|
(treasuryRedeemer, _) <- ptryFrom redeemer
|
||||||
let treasuryRedeemer :: Term _ (PAsData PTreasuryRedeemer)
|
|
||||||
treasuryRedeemer = punsafeCoerce redeemer
|
|
||||||
_treasuryDatum' :: Term _ (PAsData PTreasuryDatum)
|
|
||||||
_treasuryDatum' = punsafeCoerce datum
|
|
||||||
|
|
||||||
-- plet required fields from script context.
|
-- plet required fields from script context.
|
||||||
ctx <- pletFields @["txInfo", "purpose"] ctx'
|
ctx <- pletFields @["txInfo", "purpose"] ctx'
|
||||||
|
|
@ -47,7 +81,7 @@ treasuryValidator gatCs' = plam $ \datum redeemer ctx' -> P.do
|
||||||
PMinting _ <- pmatch ctx.purpose
|
PMinting _ <- pmatch ctx.purpose
|
||||||
|
|
||||||
-- Ensure redeemer type is valid.
|
-- Ensure redeemer type is valid.
|
||||||
PAlterTreasuryParams _ <- pmatch $ pfromData treasuryRedeemer
|
PSpendTreasuryGAT _ <- pmatch $ pfromData treasuryRedeemer
|
||||||
|
|
||||||
-- Get the minted value from txInfo.
|
-- Get the minted value from txInfo.
|
||||||
txInfo' <- plet ctx.txInfo
|
txInfo' <- plet ctx.txInfo
|
||||||
|
|
@ -60,37 +94,3 @@ treasuryValidator gatCs' = plam $ \datum redeemer ctx' -> P.do
|
||||||
passert "A single authority token has been burned" $ singleAuthorityTokenBurned gatCs txInfo' mint
|
passert "A single authority token has been burned" $ singleAuthorityTokenBurned gatCs txInfo' mint
|
||||||
|
|
||||||
popaque $ pconstant ()
|
popaque $ pconstant ()
|
||||||
|
|
||||||
{- | Plutarch level type representing datum of the treasury.
|
|
||||||
Contains:
|
|
||||||
|
|
||||||
- @stateThread@ representing the asset class of the
|
|
||||||
treasury's state thread token.
|
|
||||||
-}
|
|
||||||
newtype PTreasuryDatum (s :: S)
|
|
||||||
= PTreasuryDatum
|
|
||||||
( Term
|
|
||||||
s
|
|
||||||
( PDataRecord
|
|
||||||
'[ "stateThread" ':= PCurrencySymbol
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
deriving stock (GHC.Generic)
|
|
||||||
deriving anyclass (Generic, PIsDataRepr)
|
|
||||||
deriving
|
|
||||||
(PlutusType, PIsData, PDataFields)
|
|
||||||
via PIsDataReprInstances PTreasuryDatum
|
|
||||||
|
|
||||||
{- | Plutarch level type representing valid redeemers of the
|
|
||||||
treasury.
|
|
||||||
-}
|
|
||||||
newtype PTreasuryRedeemer (s :: S)
|
|
||||||
= -- | Alters treasury parameters, subject to the burning of a
|
|
||||||
-- governance authority token.
|
|
||||||
PAlterTreasuryParams (Term s (PDataRecord '[]))
|
|
||||||
deriving stock (GHC.Generic)
|
|
||||||
deriving anyclass (Generic, PIsDataRepr)
|
|
||||||
deriving
|
|
||||||
(PlutusType, PIsData)
|
|
||||||
via PIsDataReprInstances PTreasuryRedeemer
|
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,13 @@ module Agora.Utils (
|
||||||
passert,
|
passert,
|
||||||
pfind',
|
pfind',
|
||||||
pfindDatum,
|
pfindDatum,
|
||||||
pfindDatum',
|
ptryFindDatum,
|
||||||
pvalueSpent,
|
pvalueSpent,
|
||||||
ptxSignedBy,
|
ptxSignedBy,
|
||||||
paddValue,
|
paddValue,
|
||||||
plookup,
|
plookup,
|
||||||
pfromMaybe,
|
pfromMaybe,
|
||||||
psymbolValueOf,
|
psymbolValueOf,
|
||||||
passetClassValueOf,
|
|
||||||
passetClassValueOf',
|
|
||||||
pgeqByClass,
|
pgeqByClass,
|
||||||
pgeqBySymbol,
|
pgeqBySymbol,
|
||||||
pgeqByClass',
|
pgeqByClass',
|
||||||
|
|
@ -27,6 +25,10 @@ module Agora.Utils (
|
||||||
pfindMap,
|
pfindMap,
|
||||||
pnotNull,
|
pnotNull,
|
||||||
pisJust,
|
pisJust,
|
||||||
|
ptokenSpent,
|
||||||
|
pkeysEqual,
|
||||||
|
pnub,
|
||||||
|
pisUniq,
|
||||||
|
|
||||||
-- * Functions which should (probably) not be upstreamed
|
-- * Functions which should (probably) not be upstreamed
|
||||||
anyOutput,
|
anyOutput,
|
||||||
|
|
@ -36,6 +38,8 @@ module Agora.Utils (
|
||||||
scriptHashFromAddress,
|
scriptHashFromAddress,
|
||||||
findOutputsToAddress,
|
findOutputsToAddress,
|
||||||
findTxOutDatum,
|
findTxOutDatum,
|
||||||
|
validatorHashToTokenName,
|
||||||
|
getMintingPolicySymbol,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -52,21 +56,27 @@ import Plutarch.Api.V1 (
|
||||||
PDatumHash,
|
PDatumHash,
|
||||||
PMap,
|
PMap,
|
||||||
PMaybeData (PDJust),
|
PMaybeData (PDJust),
|
||||||
|
PMintingPolicy,
|
||||||
PPubKeyHash,
|
PPubKeyHash,
|
||||||
PTokenName,
|
PTokenName (PTokenName),
|
||||||
PTuple,
|
PTuple,
|
||||||
PTxInInfo (PTxInInfo),
|
PTxInInfo (PTxInInfo),
|
||||||
PTxInfo (PTxInfo),
|
PTxInfo,
|
||||||
PTxOut (PTxOut),
|
PTxOut (PTxOut),
|
||||||
PTxOutRef,
|
PTxOutRef,
|
||||||
PValidatorHash,
|
PValidatorHash,
|
||||||
PValue,
|
PValue,
|
||||||
|
mintingPolicySymbol,
|
||||||
|
mkMintingPolicy,
|
||||||
)
|
)
|
||||||
import Plutarch.Api.V1.AssocMap (PMap (PMap))
|
import Plutarch.Api.V1.AssocMap (PMap (PMap))
|
||||||
|
import Plutarch.Api.V1.Extra (PAssetClass, passetClassValueOf, pvalueOf)
|
||||||
import Plutarch.Api.V1.Value (PValue (PValue))
|
import Plutarch.Api.V1.Value (PValue (PValue))
|
||||||
import Plutarch.Builtin (ppairDataBuiltin)
|
import Plutarch.Builtin (ppairDataBuiltin)
|
||||||
import Plutarch.Internal (punsafeCoerce)
|
import Plutarch.Map.Extra (pkeys)
|
||||||
import Plutarch.Monadic qualified as P
|
import Plutarch.Monadic qualified as P
|
||||||
|
import Plutarch.TryFrom (PTryFrom, ptryFrom)
|
||||||
|
import Plutus.V1.Ledger.Api (CurrencySymbol)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- Validator-level utility functions
|
-- Validator-level utility functions
|
||||||
|
|
@ -76,24 +86,24 @@ passert :: Term s PString -> Term s PBool -> Term s k -> Term s k
|
||||||
passert errorMessage check k = pif check k (ptraceError errorMessage)
|
passert errorMessage check k = pif check k (ptraceError errorMessage)
|
||||||
|
|
||||||
-- | Find a datum with the given hash.
|
-- | Find a datum with the given hash.
|
||||||
pfindDatum :: Term s (PDatumHash :--> PTxInfo :--> PMaybe PDatum)
|
pfindDatum :: Term s (PDatumHash :--> PBuiltinList (PAsData (PTuple PDatumHash PDatum)) :--> PMaybe PDatum)
|
||||||
pfindDatum = phoistAcyclic $
|
pfindDatum = phoistAcyclic $
|
||||||
plam $ \datumHash txInfo'' -> P.do
|
plam $ \datumHash datums -> plookupTuple # datumHash # datums
|
||||||
PTxInfo txInfo' <- pmatch txInfo''
|
|
||||||
plookupTuple # datumHash #$ pfield @"data" # txInfo'
|
|
||||||
|
|
||||||
{- | Find a datum with the given hash.
|
-- | Find a datum with the given hash, and `ptryFrom` it.
|
||||||
NOTE: this is unsafe in the sense that, if the data layout is wrong, this is UB.
|
ptryFindDatum :: forall (a :: PType) (s :: S). PTryFrom PData a => Term s (PDatumHash :--> PBuiltinList (PAsData (PTuple PDatumHash PDatum)) :--> PMaybe a)
|
||||||
-}
|
ptryFindDatum = phoistAcyclic $
|
||||||
pfindDatum' :: PIsData a => Term s (PDatumHash :--> PTxInfo :--> PMaybe (PAsData a))
|
plam $ \datumHash inputs ->
|
||||||
pfindDatum' = phoistAcyclic $ plam $ \dh x -> punsafeCoerce $ pfindDatum # dh # x
|
pmatch (pfindDatum # datumHash # inputs) $ \case
|
||||||
|
PNothing -> pcon PNothing
|
||||||
|
PJust datum -> P.do
|
||||||
|
(datum', _) <- ptryFrom (pto datum)
|
||||||
|
pcon (PJust datum')
|
||||||
|
|
||||||
-- | Check if a PubKeyHash signs this transaction.
|
-- | Check if a PubKeyHash signs this transaction.
|
||||||
ptxSignedBy :: Term s (PTxInfo :--> PAsData PPubKeyHash :--> PBool)
|
ptxSignedBy :: Term s (PBuiltinList (PAsData PPubKeyHash) :--> PAsData PPubKeyHash :--> PBool)
|
||||||
ptxSignedBy = phoistAcyclic $
|
ptxSignedBy = phoistAcyclic $
|
||||||
plam $ \txInfo' pkh -> P.do
|
plam $ \sigs sig -> pelem # sig # sigs
|
||||||
txInfo <- pletFields @'["signatories"] txInfo'
|
|
||||||
pelem @PBuiltinList # pkh # txInfo.signatories
|
|
||||||
|
|
||||||
-- | Get the first element that matches a predicate or return Nothing.
|
-- | Get the first element that matches a predicate or return Nothing.
|
||||||
pfind' ::
|
pfind' ::
|
||||||
|
|
@ -183,30 +193,17 @@ psymbolValueOf =
|
||||||
PMap m <- pmatch (pfromData m')
|
PMap m <- pmatch (pfromData m')
|
||||||
pfoldr # plam (\x v -> pfromData (psndBuiltin # x) + v) # 0 # m
|
pfoldr # plam (\x v -> pfromData (psndBuiltin # x) + v) # 0 # m
|
||||||
|
|
||||||
-- | Extract amount from PValue belonging to a Plutarch-level asset class.
|
|
||||||
passetClassValueOf ::
|
|
||||||
Term s (PCurrencySymbol :--> PTokenName :--> PValue :--> PInteger)
|
|
||||||
passetClassValueOf =
|
|
||||||
phoistAcyclic $
|
|
||||||
plam $ \sym token value'' -> P.do
|
|
||||||
PValue value' <- pmatch value''
|
|
||||||
PMap value <- pmatch value'
|
|
||||||
m' <- pexpectJust 0 (plookup # pdata sym # value)
|
|
||||||
PMap m <- pmatch (pfromData m')
|
|
||||||
v <- pexpectJust 0 (plookup # pdata token # m)
|
|
||||||
pfromData v
|
|
||||||
|
|
||||||
-- | Extract amount from PValue belonging to a Haskell-level AssetClass.
|
-- | Extract amount from PValue belonging to a Haskell-level AssetClass.
|
||||||
passetClassValueOf' :: AssetClass -> Term s (PValue :--> PInteger)
|
passetClassValueOf' :: AssetClass -> Term s (PValue :--> PInteger)
|
||||||
passetClassValueOf' (AssetClass (sym, token)) =
|
passetClassValueOf' (AssetClass (sym, token)) =
|
||||||
passetClassValueOf # pconstant sym # pconstant token
|
phoistAcyclic $ plam $ \value -> pvalueOf # value # pconstant sym # pconstant token
|
||||||
|
|
||||||
-- | Return '>=' on two values comparing by only a particular AssetClass.
|
-- | Return '>=' on two values comparing by only a particular AssetClass.
|
||||||
pgeqByClass :: Term s (PCurrencySymbol :--> PTokenName :--> PValue :--> PValue :--> PBool)
|
pgeqByClass :: Term s (PCurrencySymbol :--> PTokenName :--> PValue :--> PValue :--> PBool)
|
||||||
pgeqByClass =
|
pgeqByClass =
|
||||||
phoistAcyclic $
|
phoistAcyclic $
|
||||||
plam $ \cs tn a b ->
|
plam $ \cs tn a b ->
|
||||||
passetClassValueOf # cs # tn # b #<= passetClassValueOf # cs # tn # a
|
pvalueOf # b # cs # tn #<= pvalueOf # a # cs # tn
|
||||||
|
|
||||||
-- | Return '>=' on two values comparing by only a particular CurrencySymbol.
|
-- | Return '>=' on two values comparing by only a particular CurrencySymbol.
|
||||||
pgeqBySymbol :: Term s (PCurrencySymbol :--> PValue :--> PValue :--> PBool)
|
pgeqBySymbol :: Term s (PCurrencySymbol :--> PValue :--> PValue :--> PBool)
|
||||||
|
|
@ -262,46 +259,100 @@ paddValue = phoistAcyclic $
|
||||||
)
|
)
|
||||||
|
|
||||||
-- | Sum of all value at input.
|
-- | Sum of all value at input.
|
||||||
pvalueSpent :: Term s (PTxInfo :--> PValue)
|
pvalueSpent :: Term s (PBuiltinList (PAsData PTxInInfo) :--> PValue)
|
||||||
pvalueSpent = phoistAcyclic $
|
pvalueSpent = phoistAcyclic $
|
||||||
plam $ \txInfo' ->
|
plam $ \inputs ->
|
||||||
pmatch txInfo' $ \(PTxInfo txInfo) ->
|
pfoldr
|
||||||
pfoldr
|
# plam
|
||||||
# plam
|
( \txInInfo' v ->
|
||||||
( \txInInfo' v ->
|
pmatch
|
||||||
pmatch
|
(pfromData txInInfo')
|
||||||
(pfromData txInInfo')
|
$ \(PTxInInfo txInInfo) ->
|
||||||
$ \(PTxInInfo txInInfo) ->
|
paddValue
|
||||||
paddValue
|
# pmatch
|
||||||
# pmatch
|
(pfield @"resolved" # txInInfo)
|
||||||
(pfield @"resolved" # txInInfo)
|
(\(PTxOut o) -> pfromData $ pfield @"value" # o)
|
||||||
(\(PTxOut o) -> pfromData $ pfield @"value" # o)
|
# v
|
||||||
# v
|
)
|
||||||
)
|
# pconstant mempty
|
||||||
# pconstant mempty
|
# inputs
|
||||||
# (pfield @"inputs" # txInfo)
|
|
||||||
|
|
||||||
-- | Find the TxInInfo by a TxOutRef.
|
-- | Find the TxInInfo by a TxOutRef.
|
||||||
pfindTxInByTxOutRef :: Term s (PTxOutRef :--> PTxInfo :--> PMaybe PTxInInfo)
|
pfindTxInByTxOutRef :: Term s (PTxOutRef :--> PBuiltinList (PAsData PTxInInfo) :--> PMaybe PTxInInfo)
|
||||||
pfindTxInByTxOutRef = phoistAcyclic $
|
pfindTxInByTxOutRef = phoistAcyclic $
|
||||||
plam $ \txOutRef txInfo' ->
|
plam $ \txOutRef inputs ->
|
||||||
pmatch txInfo' $ \(PTxInfo txInfo) ->
|
pfindMap
|
||||||
pfindMap
|
# plam
|
||||||
# plam
|
( \txInInfo' ->
|
||||||
( \txInInfo' ->
|
plet (pfromData txInInfo') $ \r ->
|
||||||
plet (pfromData txInInfo') $ \r ->
|
pmatch r $ \(PTxInInfo txInInfo) ->
|
||||||
pmatch r $ \(PTxInInfo txInInfo) ->
|
pif
|
||||||
pif
|
(pdata txOutRef #== pfield @"outRef" # txInInfo)
|
||||||
(pdata txOutRef #== pfield @"outRef" # txInInfo)
|
(pcon (PJust r))
|
||||||
(pcon (PJust r))
|
(pcon PNothing)
|
||||||
(pcon PNothing)
|
)
|
||||||
)
|
#$ inputs
|
||||||
#$ (pfield @"inputs" # txInfo)
|
|
||||||
|
|
||||||
-- | True if a list is not empty.
|
-- | True if a list is not empty.
|
||||||
pnotNull :: forall list a. PIsListLike list a => Term _ (list a :--> PBool)
|
pnotNull :: forall list a. PIsListLike list a => Term _ (list a :--> PBool)
|
||||||
pnotNull = phoistAcyclic $ plam $ pelimList (\_ _ -> pcon PTrue) (pcon PFalse)
|
pnotNull = phoistAcyclic $ plam $ pelimList (\_ _ -> pcon PTrue) (pcon PFalse)
|
||||||
|
|
||||||
|
{- | Check if a particular asset class has been spent in the input list.
|
||||||
|
|
||||||
|
When using this as an authority check, you __MUST__ ensure the authority
|
||||||
|
knows how to ensure its end of the contract.
|
||||||
|
-}
|
||||||
|
ptokenSpent :: forall {s :: S}. Term s (PAssetClass :--> PBuiltinList (PAsData PTxInInfo) :--> PBool)
|
||||||
|
ptokenSpent =
|
||||||
|
plam $ \tokenClass inputs ->
|
||||||
|
0
|
||||||
|
#< pfoldr @PBuiltinList
|
||||||
|
# plam
|
||||||
|
( \txInInfo' acc -> P.do
|
||||||
|
PTxInInfo txInInfo <- pmatch (pfromData txInInfo')
|
||||||
|
PTxOut txOut' <- pmatch $ pfromData $ pfield @"resolved" # txInInfo
|
||||||
|
txOut <- pletFields @'["value"] txOut'
|
||||||
|
let txOutValue = pfromData txOut.value
|
||||||
|
acc + passetClassValueOf # txOutValue # tokenClass
|
||||||
|
)
|
||||||
|
# 0
|
||||||
|
# inputs
|
||||||
|
|
||||||
|
{- | True if both maps have exactly the same keys.
|
||||||
|
Using @'#=='@ is not sufficient, because keys returned are not ordered.
|
||||||
|
-}
|
||||||
|
pkeysEqual :: forall (s :: S) k a b. Term s (PMap k a :--> PMap k b :--> PBool)
|
||||||
|
pkeysEqual = phoistAcyclic $
|
||||||
|
plam $ \p q -> P.do
|
||||||
|
pks <- plet $ pkeys # p
|
||||||
|
qks <- plet $ pkeys # q
|
||||||
|
pall # plam (\pk -> pelem # pk # qks) # pks
|
||||||
|
#&& pall # plam (\qk -> pelem # qk # pks) # qks
|
||||||
|
|
||||||
|
-- | / O(n^2) /. Clear out duplicates in a list. The order is not preserved.
|
||||||
|
pnub :: forall list a (s :: S). (PEq a, PIsListLike list a) => Term s (list a :--> list a)
|
||||||
|
pnub =
|
||||||
|
phoistAcyclic $
|
||||||
|
precList
|
||||||
|
( \self x xs ->
|
||||||
|
pif
|
||||||
|
(pnot #$ pelem # x # xs)
|
||||||
|
(pcons # x # (self # xs))
|
||||||
|
(self # xs)
|
||||||
|
)
|
||||||
|
(const pnil)
|
||||||
|
|
||||||
|
-- | / O(n^2) /. Check if a list contains no duplicates.
|
||||||
|
pisUniq :: forall list a (s :: S). (PEq a, PIsListLike list a) => Term s (list a :--> PBool)
|
||||||
|
pisUniq =
|
||||||
|
phoistAcyclic $
|
||||||
|
precList
|
||||||
|
( \self x xs ->
|
||||||
|
(pnot #$ pelem # x # xs)
|
||||||
|
#&& (self # xs)
|
||||||
|
)
|
||||||
|
(const $ pcon PTrue)
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
{- Functions which should (probably) not be upstreamed
|
{- Functions which should (probably) not be upstreamed
|
||||||
All of these functions are quite inefficient.
|
All of these functions are quite inefficient.
|
||||||
|
|
@ -311,18 +362,19 @@ pnotNull = phoistAcyclic $ plam $ pelimList (\_ _ -> pcon PTrue) (pcon PFalse)
|
||||||
anyOutput ::
|
anyOutput ::
|
||||||
forall (datum :: PType) s.
|
forall (datum :: PType) s.
|
||||||
( PIsData datum
|
( PIsData datum
|
||||||
|
, PTryFrom PData (PAsData datum)
|
||||||
) =>
|
) =>
|
||||||
Term s (PTxInfo :--> (PValue :--> PAddress :--> datum :--> PBool) :--> PBool)
|
Term s (PTxInfo :--> (PValue :--> PAddress :--> datum :--> PBool) :--> PBool)
|
||||||
anyOutput = phoistAcyclic $
|
anyOutput = phoistAcyclic $
|
||||||
plam $ \txInfo' predicate -> P.do
|
plam $ \txInfo' predicate -> P.do
|
||||||
txInfo <- pletFields @'["outputs"] txInfo'
|
txInfo <- pletFields @'["outputs", "datums"] txInfo'
|
||||||
pany
|
pany
|
||||||
# plam
|
# plam
|
||||||
( \txOut'' -> P.do
|
( \txOut'' -> P.do
|
||||||
PTxOut txOut' <- pmatch (pfromData txOut'')
|
PTxOut txOut' <- pmatch (pfromData txOut'')
|
||||||
txOut <- pletFields @'["value", "datumHash", "address"] txOut'
|
txOut <- pletFields @'["value", "datumHash", "address"] txOut'
|
||||||
PDJust dh <- pmatch txOut.datumHash
|
PDJust dh <- pmatch txOut.datumHash
|
||||||
pmatch (pfindDatum' @datum # (pfield @"_0" # dh) # txInfo') $ \case
|
pmatch (ptryFindDatum @(PAsData datum) # (pfield @"_0" # dh) # txInfo.datums) $ \case
|
||||||
PJust datum -> P.do
|
PJust datum -> P.do
|
||||||
predicate # txOut.value # txOut.address # pfromData datum
|
predicate # txOut.value # txOut.address # pfromData datum
|
||||||
PNothing -> pcon PFalse
|
PNothing -> pcon PFalse
|
||||||
|
|
@ -333,18 +385,19 @@ anyOutput = phoistAcyclic $
|
||||||
allOutputs ::
|
allOutputs ::
|
||||||
forall (datum :: PType) s.
|
forall (datum :: PType) s.
|
||||||
( PIsData datum
|
( PIsData datum
|
||||||
|
, PTryFrom PData (PAsData datum)
|
||||||
) =>
|
) =>
|
||||||
Term s (PTxInfo :--> (PTxOut :--> PValue :--> PAddress :--> datum :--> PBool) :--> PBool)
|
Term s (PTxInfo :--> (PTxOut :--> PValue :--> PAddress :--> datum :--> PBool) :--> PBool)
|
||||||
allOutputs = phoistAcyclic $
|
allOutputs = phoistAcyclic $
|
||||||
plam $ \txInfo' predicate -> P.do
|
plam $ \txInfo' predicate -> P.do
|
||||||
txInfo <- pletFields @'["outputs"] txInfo'
|
txInfo <- pletFields @'["outputs", "datums"] txInfo'
|
||||||
pall
|
pall
|
||||||
# plam
|
# plam
|
||||||
( \txOut'' -> P.do
|
( \txOut'' -> P.do
|
||||||
PTxOut txOut' <- pmatch (pfromData txOut'')
|
PTxOut txOut' <- pmatch (pfromData txOut'')
|
||||||
txOut <- pletFields @'["value", "datumHash", "address"] txOut'
|
txOut <- pletFields @'["value", "datumHash", "address"] txOut'
|
||||||
PDJust dh <- pmatch txOut.datumHash
|
PDJust dh <- pmatch txOut.datumHash
|
||||||
pmatch (pfindDatum' @datum # (pfield @"_0" # dh) # txInfo') $ \case
|
pmatch (ptryFindDatum @(PAsData datum) # (pfield @"_0" # dh) # txInfo.datums) $ \case
|
||||||
PJust datum -> P.do
|
PJust datum -> P.do
|
||||||
predicate # pfromData txOut'' # txOut.value # txOut.address # pfromData datum
|
predicate # pfromData txOut'' # txOut.value # txOut.address # pfromData datum
|
||||||
PNothing -> pcon PFalse
|
PNothing -> pcon PFalse
|
||||||
|
|
@ -355,11 +408,12 @@ allOutputs = phoistAcyclic $
|
||||||
anyInput ::
|
anyInput ::
|
||||||
forall (datum :: PType) s.
|
forall (datum :: PType) s.
|
||||||
( PIsData datum
|
( PIsData datum
|
||||||
|
, PTryFrom PData (PAsData datum)
|
||||||
) =>
|
) =>
|
||||||
Term s (PTxInfo :--> (PValue :--> PAddress :--> datum :--> PBool) :--> PBool)
|
Term s (PTxInfo :--> (PValue :--> PAddress :--> datum :--> PBool) :--> PBool)
|
||||||
anyInput = phoistAcyclic $
|
anyInput = phoistAcyclic $
|
||||||
plam $ \txInfo' predicate -> P.do
|
plam $ \txInfo' predicate -> P.do
|
||||||
txInfo <- pletFields @'["inputs"] txInfo'
|
txInfo <- pletFields @'["inputs", "datums"] txInfo'
|
||||||
pany
|
pany
|
||||||
# plam
|
# plam
|
||||||
( \txInInfo'' -> P.do
|
( \txInInfo'' -> P.do
|
||||||
|
|
@ -368,7 +422,7 @@ anyInput = phoistAcyclic $
|
||||||
PTxOut txOut' <- pmatch (pfromData txOut'')
|
PTxOut txOut' <- pmatch (pfromData txOut'')
|
||||||
txOut <- pletFields @'["value", "datumHash", "address"] txOut'
|
txOut <- pletFields @'["value", "datumHash", "address"] txOut'
|
||||||
PDJust dh <- pmatch txOut.datumHash
|
PDJust dh <- pmatch txOut.datumHash
|
||||||
pmatch (pfindDatum' @datum # (pfield @"_0" # dh) # txInfo') $ \case
|
pmatch (ptryFindDatum @(PAsData datum) # (pfield @"_0" # dh) # txInfo.datums) $ \case
|
||||||
PJust datum -> P.do
|
PJust datum -> P.do
|
||||||
predicate # txOut.value # txOut.address # pfromData datum
|
predicate # txOut.value # txOut.address # pfromData datum
|
||||||
PNothing -> pcon PFalse
|
PNothing -> pcon PFalse
|
||||||
|
|
@ -385,10 +439,10 @@ psingletonValue = phoistAcyclic $
|
||||||
in res
|
in res
|
||||||
|
|
||||||
-- | Finds the TxOut of an effect from TxInfo and TxOutRef
|
-- | Finds the TxOut of an effect from TxInfo and TxOutRef
|
||||||
findTxOutByTxOutRef :: Term s (PTxOutRef :--> PTxInfo :--> PMaybe PTxOut)
|
findTxOutByTxOutRef :: Term s (PTxOutRef :--> PBuiltinList (PAsData PTxInInfo) :--> PMaybe PTxOut)
|
||||||
findTxOutByTxOutRef = phoistAcyclic $
|
findTxOutByTxOutRef = phoistAcyclic $
|
||||||
plam $ \txOutRef txInfo ->
|
plam $ \txOutRef inputs ->
|
||||||
pmatch (pfindTxInByTxOutRef # txOutRef # txInfo) $ \case
|
pmatch (pfindTxInByTxOutRef # txOutRef # inputs) $ \case
|
||||||
PJust ((pfield @"resolved" #) -> txOut) -> pcon $ PJust txOut
|
PJust ((pfield @"resolved" #) -> txOut) -> pcon $ PJust txOut
|
||||||
PNothing -> pcon PNothing
|
PNothing -> pcon PNothing
|
||||||
|
|
||||||
|
|
@ -401,23 +455,28 @@ scriptHashFromAddress = phoistAcyclic $
|
||||||
_ -> pcon PNothing
|
_ -> pcon PNothing
|
||||||
|
|
||||||
-- | Find all TxOuts sent to an Address
|
-- | Find all TxOuts sent to an Address
|
||||||
findOutputsToAddress :: Term s (PTxInfo :--> PAddress :--> PBuiltinList (PAsData PTxOut))
|
findOutputsToAddress :: Term s (PBuiltinList (PAsData PTxOut) :--> PAddress :--> PBuiltinList (PAsData PTxOut))
|
||||||
findOutputsToAddress = phoistAcyclic $
|
findOutputsToAddress = phoistAcyclic $
|
||||||
plam $ \info address' -> P.do
|
plam $ \outputs address' -> P.do
|
||||||
address <- plet $ pdata address'
|
address <- plet $ pdata address'
|
||||||
let outputs = pfromData $ pfield @"outputs" # info
|
pfilter # plam (\(pfromData -> txOut) -> pfield @"address" # txOut #== address)
|
||||||
filteredOutputs =
|
# outputs
|
||||||
pfilter
|
|
||||||
# plam
|
|
||||||
(\(pfromData -> txOut) -> pfield @"address" # txOut #== address)
|
|
||||||
# outputs
|
|
||||||
filteredOutputs
|
|
||||||
|
|
||||||
-- | Find the data corresponding to a TxOut, if there is one
|
-- | Find the data corresponding to a TxOut, if there is one
|
||||||
findTxOutDatum :: Term s (PTxInfo :--> PTxOut :--> PMaybe PDatum)
|
findTxOutDatum :: Term s (PBuiltinList (PAsData (PTuple PDatumHash PDatum)) :--> PTxOut :--> PMaybe PDatum)
|
||||||
findTxOutDatum = phoistAcyclic $
|
findTxOutDatum = phoistAcyclic $
|
||||||
plam $ \info out -> P.do
|
plam $ \datums out -> P.do
|
||||||
datumHash' <- pmatch $ pfromData $ pfield @"datumHash" # out
|
datumHash' <- pmatch $ pfromData $ pfield @"datumHash" # out
|
||||||
case datumHash' of
|
case datumHash' of
|
||||||
PDJust ((pfield @"_0" #) -> datumHash) -> pfindDatum # datumHash # info
|
PDJust ((pfield @"_0" #) -> datumHash) -> pfindDatum # datumHash # datums
|
||||||
_ -> pcon PNothing
|
_ -> pcon PNothing
|
||||||
|
|
||||||
|
{- | Safely convert a 'PValidatorHash' into a 'PTokenName'. This can be useful for tagging
|
||||||
|
tokens for extra safety.
|
||||||
|
-}
|
||||||
|
validatorHashToTokenName :: forall (s :: S). Term s PValidatorHash -> Term s PTokenName
|
||||||
|
validatorHashToTokenName vh = pcon (PTokenName (pto vh))
|
||||||
|
|
||||||
|
-- | Get the CurrencySymbol of a PMintingPolicy.
|
||||||
|
getMintingPolicySymbol :: ClosedTerm PMintingPolicy -> CurrencySymbol
|
||||||
|
getMintingPolicySymbol v = mintingPolicySymbol $ mkMintingPolicy v
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,7 @@ module PPrelude (
|
||||||
module Plutarch,
|
module Plutarch,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
-- NOTE: These are not exported by Plutarch.Prelude, for some reason.
|
-- 'compile' is not exported by Plutarch.Prelude.
|
||||||
-- Maybe we can 'fix' this upstream?
|
import Plutarch (compile)
|
||||||
import Plutarch (ClosedTerm, POpaque, compile)
|
|
||||||
import Plutarch.Prelude
|
import Plutarch.Prelude
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ This document gives an overview of the technical design of the proposals system
|
||||||
|
|
||||||
| Specification | Implementation | Last revision |
|
| Specification | Implementation | Last revision |
|
||||||
|:-----------:|:-----------:|:-------------:|
|
|:-----------:|:-----------:|:-------------:|
|
||||||
| WIP | WIP | v0.1 2022-04-11 |
|
| WIP | WIP | v0.1 2022-04-27 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -35,35 +35,31 @@ Initiating a proposal requires the proposer to have more than a certain amount o
|
||||||
|
|
||||||
### Voting stages
|
### Voting stages
|
||||||
|
|
||||||
The life-cycle of a proposal is neatly represented by a state machine, with the 'draft' phase being the initial state, and 'executed' and 'failed' being the terminating states. Please note that this state-machine representation is purely conceptual and should not be expected to reflect technical implementation.
|
The life-cycle of a proposal is neatly represented by a state machine, with the 'draft' state being the initial state, and 'executed' and 'failed' being the terminating states.
|
||||||
|
|
||||||
|
Note: this state-machine representation is purely conceptual and should not be expected to reflect technical implementation.
|
||||||
|
**Please note that this state-machine representation is purely conceptual and should not be expected to reflect technical implementation.** This is because some transitions in the state machine representation don't need to happen on-chain, as a transaction. A key example of this is a proposal going from the "lock" phase to the "execution" phase. No on-chain transition takes place: it is simply that we have reached the time in the real-world, when the proposal is allowed to be executed.
|
||||||
|
|
||||||
|
To make the following diagram clear, we employ the following terminology:
|
||||||
|
|
||||||
|
|
||||||
|
> state
|
||||||
|
> A 'state' in our conceptual FSM representation above. Useful for thinking about proposals. Does not necessarily reflect a change occurring on-chain.
|
||||||
|
|
||||||
|
|
||||||
|
> period
|
||||||
|
> A segment of real-world, POSIX time. As we transition from one period to another, a proposal's status (see below) will not be updated.
|
||||||
|
|
||||||
|
|
||||||
|
> status
|
||||||
|
> The 'status' of a proposal is stored in the proposal's datum and is thus always represented on-chain. Changing this requires a transaction to take place.
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### When may interactions occur?
|
|
||||||
|
|
||||||
Consider the following 'stages' of a proposal:
|
|
||||||
|
|
||||||
- `S`: when the proposal was created.
|
|
||||||
- `D`: the length of the draft period.
|
|
||||||
- `V`: the length of the voting period.
|
|
||||||
- `L`: the length of the locking period.
|
|
||||||
- `E`: the length of the execution period.
|
|
||||||
|
|
||||||
| Action | Valid POSIXTimeRange | Valid _stored_ state(s) |
|
|
||||||
|-------------------------------------|-------------------------------------|-------------------------|
|
|
||||||
| Witness | \[S, ∞) | \* |
|
|
||||||
| Cosign | \[S, S + D) | Draft |
|
|
||||||
| AdvanceProposal | \[S, S + D) | Draft |
|
|
||||||
| Vote | \[S + D, S + D + V) | Voting |
|
|
||||||
| Unlock | \[S + D, ∞) | \* |
|
|
||||||
| CountVotes | \[S + D + V, S + D + V + L) | Voting |
|
|
||||||
| ExecuteProposal (if quorum reached) | \[S + D + V + L, S + D + V + L + E) | Voting |
|
|
||||||
|
|
||||||
> Jack 2022-02-02: I will consider revising this table further at a later time.
|
|
||||||
|
|
||||||
#### Draft phase
|
#### Draft phase
|
||||||
|
|
||||||
During the draft phase, a new UTXO at the proposal script has been created. At this stage, only votes in favor of co-signing the draft are counted. For the proposal to transition to the voting phase, a threshold of GT will have to be staked backing the proposal. This threshold will be determined on a per-system basis and could itself be a 'governable' parameter. It's important to note that cosignatures are not locking votes. Cosignatures are more like a delegated approval to a proposal. The sum of all cosignatures must tally to the threshold, and all cosigner stake datums must fit into a single transaction to witness their size.
|
During the draft phase, a new UTXO at the proposal script has been created. At this stage, only votes in favor of co-signing the draft are counted. For the proposal to transition to the voting phase, a threshold of GT will have to be staked backing the proposal. This threshold will be determined on a per-system basis and could itself be a 'governable' parameter. It's important to note that cosignatures are not locking votes. Cosignatures are more like a delegated approval to a proposal. The sum of all cosignatures must tally to the threshold, and all cosigner stake datums must fit into a single transaction to witness their size. A limit on the maximum amount of cosigners is placed in order to prevent a situation where the stake datums no longer fit in the transaction. The number doesn't matter and may be expressed in a parameterized way.
|
||||||
|
|
||||||
#### Voting phase
|
#### Voting phase
|
||||||
|
|
||||||
|
|
|
||||||
178
flake.lock
generated
178
flake.lock
generated
|
|
@ -117,23 +117,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autodocodec": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1644358110,
|
|
||||||
"narHash": "sha256-X1TNZlmO2qDFk3OL4Z1v/gzvd3ouoACAiMweutsYek4=",
|
|
||||||
"owner": "srid",
|
|
||||||
"repo": "autodocodec",
|
|
||||||
"rev": "42b42a7407f33c6c74fa4e8c84906aebfed28daf",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "srid",
|
|
||||||
"ref": "ghc921",
|
|
||||||
"repo": "autodocodec",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cabal-32": {
|
"cabal-32": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
@ -463,21 +446,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat-ci_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1641672839,
|
|
||||||
"narHash": "sha256-Bdwv+DKeEMlRNPDpZxSz0sSrqQBvdKO5fZ8LmvrgCOU=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-compat-ci",
|
|
||||||
"rev": "e832114bc18376c0f3fa13c19bf5ff253cc6570a",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "flake-compat-ci",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat_2": {
|
"flake-compat_2": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
@ -495,22 +463,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat_3": {
|
"flake-compat_3": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1641205782,
|
|
||||||
"narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat_4": {
|
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1606424373,
|
"lastModified": 1606424373,
|
||||||
|
|
@ -527,7 +479,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-compat_5": {
|
"flake-compat_4": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1606424373,
|
"lastModified": 1606424373,
|
||||||
|
|
@ -981,7 +933,7 @@
|
||||||
},
|
},
|
||||||
"hercules-ci-agent": {
|
"hercules-ci-agent": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_5",
|
"flake-compat": "flake-compat_4",
|
||||||
"nix-darwin": "nix-darwin",
|
"nix-darwin": "nix-darwin",
|
||||||
"nixos-20_09": "nixos-20_09",
|
"nixos-20_09": "nixos-20_09",
|
||||||
"nixos-unstable": "nixos-unstable",
|
"nixos-unstable": "nixos-unstable",
|
||||||
|
|
@ -1004,7 +956,7 @@
|
||||||
},
|
},
|
||||||
"hercules-ci-effects": {
|
"hercules-ci-effects": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_4",
|
"flake-compat": "flake-compat_3",
|
||||||
"hercules-ci-agent": "hercules-ci-agent",
|
"hercules-ci-agent": "hercules-ci-agent",
|
||||||
"nixpkgs": "nixpkgs_3",
|
"nixpkgs": "nixpkgs_3",
|
||||||
"nixpkgs-nixops": "nixpkgs-nixops"
|
"nixpkgs-nixops": "nixpkgs-nixops"
|
||||||
|
|
@ -1088,6 +1040,55 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hspec": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1649095108,
|
||||||
|
"narHash": "sha256-cPmt4hvmdh727VT6UAL8yFArmm4FAWeg3K5Qi3XtU4g=",
|
||||||
|
"owner": "srid",
|
||||||
|
"repo": "hspec",
|
||||||
|
"rev": "44f2a143e10c93df237af428457d0e4b74ae270a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "srid",
|
||||||
|
"ref": "askAncestors",
|
||||||
|
"repo": "hspec",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hspec-golden": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1648755064,
|
||||||
|
"narHash": "sha256-5a6BksZx00o2iL0Ei/L1Kkou2BsnsIagN+tTmqYyKfs=",
|
||||||
|
"owner": "stackbuilders",
|
||||||
|
"repo": "hspec-golden",
|
||||||
|
"rev": "4b0ad56b2de0254a7b1e0feda917656f78a5bcda",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "stackbuilders",
|
||||||
|
"repo": "hspec-golden",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hspec-hedgehog": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1602603478,
|
||||||
|
"narHash": "sha256-XnS3zjQ7eh3iBOWq+Z/YcwrfWI55hV6k8LsZ8qm/qOc=",
|
||||||
|
"owner": "parsonsmatt",
|
||||||
|
"repo": "hspec-hedgehog",
|
||||||
|
"rev": "eb617d854542510f0129acdea4bf52e50b13042e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "parsonsmatt",
|
||||||
|
"repo": "hspec-hedgehog",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"iohk-nix": {
|
"iohk-nix": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
@ -1592,19 +1593,24 @@
|
||||||
"plutarch": {
|
"plutarch": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"Shrinker": "Shrinker",
|
"Shrinker": "Shrinker",
|
||||||
"autodocodec": "autodocodec",
|
|
||||||
"cardano-base": "cardano-base",
|
"cardano-base": "cardano-base",
|
||||||
"cardano-crypto": "cardano-crypto",
|
"cardano-crypto": "cardano-crypto",
|
||||||
"cardano-prelude": "cardano-prelude",
|
"cardano-prelude": "cardano-prelude",
|
||||||
"cryptonite": "cryptonite",
|
"cryptonite": "cryptonite",
|
||||||
"flake-compat": "flake-compat_3",
|
"emanote": [
|
||||||
"flake-compat-ci": "flake-compat-ci_3",
|
"plutarch",
|
||||||
|
"haskell-nix",
|
||||||
|
"nixpkgs-unstable"
|
||||||
|
],
|
||||||
"flat": "flat",
|
"flat": "flat",
|
||||||
"foundation": "foundation",
|
"foundation": "foundation",
|
||||||
"haskell-language-server": "haskell-language-server_2",
|
"haskell-language-server": "haskell-language-server_2",
|
||||||
"haskell-nix": "haskell-nix_4",
|
"haskell-nix": "haskell-nix_4",
|
||||||
"hercules-ci-effects": "hercules-ci-effects",
|
"hercules-ci-effects": "hercules-ci-effects",
|
||||||
"hs-memory": "hs-memory",
|
"hs-memory": "hs-memory",
|
||||||
|
"hspec": "hspec",
|
||||||
|
"hspec-golden": "hspec-golden",
|
||||||
|
"hspec-hedgehog": "hspec-hedgehog",
|
||||||
"iohk-nix": "iohk-nix_2",
|
"iohk-nix": "iohk-nix_2",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"plutarch",
|
"plutarch",
|
||||||
|
|
@ -1614,24 +1620,21 @@
|
||||||
"nixpkgs-2111": "nixpkgs-2111_5",
|
"nixpkgs-2111": "nixpkgs-2111_5",
|
||||||
"plutus": "plutus_2",
|
"plutus": "plutus_2",
|
||||||
"protolude": "protolude",
|
"protolude": "protolude",
|
||||||
"safe-coloured-text": "safe-coloured-text",
|
|
||||||
"sized-functors": "sized-functors",
|
"sized-functors": "sized-functors",
|
||||||
"sydtest": "sydtest",
|
"th-extras": "th-extras"
|
||||||
"th-extras": "th-extras",
|
|
||||||
"validity": "validity"
|
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1648639396,
|
"lastModified": 1650382454,
|
||||||
"narHash": "sha256-pAkEsIDXJckVYufVPUzD/4sq4/uE7iyV0IR2BuLhZjY=",
|
"narHash": "sha256-b31DK+E/0MtR45+Z+F5U1E8jjcewvZ42UmFLZlXDAYM=",
|
||||||
"owner": "peter-mlabs",
|
"owner": "peter-mlabs",
|
||||||
"repo": "plutarch",
|
"repo": "plutarch",
|
||||||
"rev": "a7a410da209b9c14c834a41e07b1c197c2a4dcd6",
|
"rev": "6ef18aacd02050fc07398e399cff5e8734c1045e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "peter-mlabs",
|
"owner": "peter-mlabs",
|
||||||
"repo": "plutarch",
|
"repo": "plutarch",
|
||||||
"rev": "a7a410da209b9c14c834a41e07b1c197c2a4dcd6",
|
"rev": "6ef18aacd02050fc07398e399cff5e8734c1045e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1771,23 +1774,6 @@
|
||||||
"plutarch": "plutarch"
|
"plutarch": "plutarch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-coloured-text": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1644357337,
|
|
||||||
"narHash": "sha256-sXSKw8m6O9K/H2BBiYqO5e4sJIo+9UP+UvEukRn28d8=",
|
|
||||||
"owner": "srid",
|
|
||||||
"repo": "safe-coloured-text",
|
|
||||||
"rev": "034f3612525568b422e0c62b52417d77b7cf31c2",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "srid",
|
|
||||||
"ref": "ghc921",
|
|
||||||
"repo": "safe-coloured-text",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sized-functors": {
|
"sized-functors": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
@ -1917,23 +1903,6 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sydtest": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1645114028,
|
|
||||||
"narHash": "sha256-P6ZwwfFeN8fpi3fziz9yERTn7BfxdE/j/OofUu+4GdA=",
|
|
||||||
"owner": "srid",
|
|
||||||
"repo": "sydtest",
|
|
||||||
"rev": "9c6c7678f7aabe22e075aab810a6a2e304591d24",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "srid",
|
|
||||||
"ref": "ghc921",
|
|
||||||
"repo": "sydtest",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"th-extras": {
|
"th-extras": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
|
|
@ -1950,23 +1919,6 @@
|
||||||
"rev": "787ed752c1e5d41b5903b74e171ed087de38bffa",
|
"rev": "787ed752c1e5d41b5903b74e171ed087de38bffa",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"validity": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1644358698,
|
|
||||||
"narHash": "sha256-dpMIu08qXMzy8Kilk/2VWpuwIsfqFtpg/3mkwt5pdjA=",
|
|
||||||
"owner": "srid",
|
|
||||||
"repo": "validity",
|
|
||||||
"rev": "f7982549b95d0ab727950dc876ca06b1862135ba",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "srid",
|
|
||||||
"ref": "ghc921",
|
|
||||||
"repo": "validity",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@
|
||||||
# see https://github.com/NixOS/nix/issues/6013
|
# see https://github.com/NixOS/nix/issues/6013
|
||||||
inputs.nixpkgs-2111 = { url = "github:NixOS/nixpkgs/nixpkgs-21.11-darwin"; };
|
inputs.nixpkgs-2111 = { url = "github:NixOS/nixpkgs/nixpkgs-21.11-darwin"; };
|
||||||
|
|
||||||
# Rev is this PR https://github.com/peter-mlabs/plutarch/pull/5.
|
|
||||||
inputs.plutarch.url =
|
inputs.plutarch.url =
|
||||||
"github:peter-mlabs/plutarch?rev=a7a410da209b9c14c834a41e07b1c197c2a4dcd6";
|
"github:peter-mlabs/plutarch?rev=6ef18aacd02050fc07398e399cff5e8734c1045e";
|
||||||
|
inputs.plutarch.inputs.emanote.follows =
|
||||||
|
"plutarch/haskell-nix/nixpkgs-unstable";
|
||||||
inputs.plutarch.inputs.nixpkgs.follows =
|
inputs.plutarch.inputs.nixpkgs.follows =
|
||||||
"plutarch/haskell-nix/nixpkgs-unstable";
|
"plutarch/haskell-nix/nixpkgs-unstable";
|
||||||
|
|
||||||
|
|
|
||||||
6
hie.yaml
6
hie.yaml
|
|
@ -1,8 +1,2 @@
|
||||||
cradle:
|
cradle:
|
||||||
cabal:
|
cabal:
|
||||||
- path: "./agora"
|
|
||||||
component: "lib:agora"
|
|
||||||
- path: "./agora-bench"
|
|
||||||
component: "benchmark:agora-bench"
|
|
||||||
- path: "./agora-test"
|
|
||||||
component: "test:agora-test"
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue