add tests for advancement from draft phrase; refactoring

This commit is contained in:
Hongrui Fang 2022-07-05 07:55:12 +08:00
parent d433ab17d6
commit 9c8d04dbc6
No known key found for this signature in database
GPG key ID: 1C4711FFF64C0254
12 changed files with 1402 additions and 1078 deletions

View file

@ -23,17 +23,13 @@ import Agora.Effect.TreasuryWithdrawal (
TreasuryWithdrawalDatum (TreasuryWithdrawalDatum),
treasuryWithdrawalValidator,
)
import Crypto.Hash qualified as Crypto
import Data.ByteArray qualified as BA
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as C (pack)
import Plutarch.Api.V1 (mkValidator, validatorHash)
import PlutusLedgerApi.V1 (
Address (Address),
Credential (..),
CurrencySymbol (CurrencySymbol),
DatumHash (DatumHash),
PubKeyHash (PubKeyHash),
PubKeyHash,
ScriptContext (..),
ScriptPurpose (Spending),
TokenName (TokenName),
@ -56,10 +52,10 @@ import PlutusLedgerApi.V1 (
Validator,
ValidatorHash (ValidatorHash),
Value,
toBuiltin,
)
import PlutusLedgerApi.V1.Interval qualified as Interval (always)
import PlutusLedgerApi.V1.Value qualified as Value (singleton)
import Test.Util (scriptCredentials, userCredentials)
-- | A sample Currency Symbol.
currSymbol :: CurrencySymbol
@ -69,16 +65,13 @@ currSymbol = CurrencySymbol "12312099"
signer :: PubKeyHash
signer = "8a30896c4fd5e79843e4ca1bd2cdbaa36f8c0bc3be7401214142019c"
blake2b_224 :: BS.ByteString -> BS.ByteString
blake2b_224 = BS.pack . BA.unpack . Crypto.hashWith Crypto.Blake2b_224
-- | List of users who the effect will pay to.
users :: [Credential]
users = PubKeyCredential . PubKeyHash . toBuiltin . blake2b_224 . C.pack . show <$> ([1 ..] :: [Integer])
users = userCredentials
-- | List of users who the effect will pay to.
treasuries :: [Credential]
treasuries = ScriptCredential . ValidatorHash . toBuiltin . blake2b_224 . C.pack . show <$> ([1 ..] :: [Integer])
treasuries = scriptCredentials
inputGAT :: TxInInfo
inputGAT =

View file

@ -8,17 +8,6 @@ This module tests primarily the happy path for Proposal interactions
module Sample.Proposal (
-- * Script contexts
proposalCreation,
cosignProposal,
proposalRef,
stakeRef,
voteOnProposal,
VotingParameters (..),
advanceProposalSuccess,
advanceProposalFailureTimeout,
TransitionParameters (..),
advanceFinishedProposal,
advanceProposalInsufficientVotes,
advanceProposalWithInvalidOutputStake,
) where
import Agora.Governor (GovernorDatum (..))
@ -27,78 +16,41 @@ import Agora.Proposal (
ProposalDatum (..),
ProposalId (..),
ProposalStatus (..),
ProposalThresholds (..),
ProposalVotes (..),
ResultTag (..),
emptyVotesFor,
)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (..),
)
import Agora.Stake (
ProposalLock (ProposalLock),
Stake (..),
StakeDatum (..),
)
import Data.Default.Class (Default (def))
import Data.Tagged (Tagged (..), untag)
import Plutarch.Context (
BaseBuilder,
MintingBuilder,
buildMintingUnsafe,
buildTxInfoUnsafe,
input,
mint,
output,
script,
signedWith,
timeRange,
txId,
withDatum,
withRefIndex,
withTxId,
withValue,
)
import PlutusLedgerApi.V1 (
Datum (Datum),
DatumHash,
POSIXTime,
POSIXTimeRange,
PubKeyHash,
ScriptContext (..),
ToData (toBuiltinData),
TxInInfo (TxInInfo),
TxInfo (..),
TxOut (TxOut, txOutAddress, txOutDatumHash, txOutValue),
TxOutRef (..),
ValidatorHash,
)
import PlutusLedgerApi.V1.Value qualified as Value (
assetClassValue,
singleton,
)
import PlutusTx.AssocMap qualified as AssocMap
import Sample.Proposal.Shared (proposalRef, stakeRef)
import Sample.Shared (
govValidatorHash,
minAda,
proposal,
proposalPolicySymbol,
proposalStartingTimeFromTimeRange,
proposalValidatorHash,
signer,
signer2,
stake,
stakeAddress,
stakeAssetClass,
stakeValidatorHash,
)
import Test.Util (
closedBoundedInterval,
datumPair,
toDatumHash,
updateMap,
)
proposalCreation :: ScriptContext
@ -160,656 +112,3 @@ proposalCreation =
. withDatum govAfter
]
in buildMintingUnsafe builder
-- | 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, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
proposalBefore :: ProposalDatum
proposalBefore =
ProposalDatum
{ proposalId = ProposalId 0
, effects = effects
, status = Draft
, cosigners = [signer]
, thresholds = def
, votes = emptyVotesFor effects
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
stakeDatum :: StakeDatum
stakeDatum = StakeDatum (Tagged 50_000_000) signer2 []
proposalAfter :: ProposalDatum
proposalAfter = proposalBefore {cosigners = newSigners <> proposalBefore.cosigners}
validTimeRange :: POSIXTimeRange
validTimeRange =
closedBoundedInterval
10
((def :: ProposalTimingConfig).draftTime - 10)
builder :: BaseBuilder
builder =
mconcat
[ txId "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be"
, mint st
, mconcat $ signedWith <$> newSigners
, timeRange validTimeRange
, input $
script proposalValidatorHash
. withValue (st <> Value.singleton "" "" 10_000_000)
. withDatum proposalBefore
. withTxId (txOutRefId proposalRef)
. withRefIndex (txOutRefIdx proposalRef)
, input $
script stakeValidatorHash
. withValue
( Value.singleton "" "" 10_000_000
<> Value.assetClassValue (untag stake.gtClassRef) 50_000_000
<> Value.assetClassValue stakeAssetClass 1
)
. withDatum stakeDatum
. withTxId (txOutRefId stakeRef)
. withRefIndex (txOutRefIdx stakeRef)
, output $
script proposalValidatorHash
. withValue (st <> Value.singleton "" "" 10_000_000)
. withDatum proposalAfter
, output $
script stakeValidatorHash
. withValue
( Value.singleton "" "" 10_000_000
<> Value.assetClassValue (untag stake.gtClassRef) 50_000_000
<> Value.assetClassValue stakeAssetClass 1
)
. withDatum stakeDatum
]
in buildTxInfoUnsafe builder
--------------------------------------------------------------------------------
-- | Parameters for creating a voting transaction.
data VotingParameters = VotingParameters
{ voteFor :: ResultTag
-- ^ The outcome the transaction is voting for.
, voteCount :: Integer
-- ^ The count of votes.
}
-- | Create a valid transaction that votes on a propsal, given the parameters.
voteOnProposal :: VotingParameters -> TxInfo
voteOnProposal params =
let pst = Value.singleton proposalPolicySymbol "" 1
sst = Value.assetClassValue stakeAssetClass 1
---
stakeOwner = signer
---
effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
---
initialVotes :: AssocMap.Map ResultTag Integer
initialVotes =
AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 4242)
]
---
proposalInputDatum :: ProposalDatum
proposalInputDatum =
ProposalDatum
{ proposalId = ProposalId 42
, effects = effects
, status = VotingReady
, cosigners = [stakeOwner]
, thresholds = def
, votes = ProposalVotes initialVotes
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
---
existingLocks :: [ProposalLock]
existingLocks =
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
]
---
stakeInputDatum :: StakeDatum
stakeInputDatum =
StakeDatum
{ stakedAmount = Tagged params.voteCount
, owner = stakeOwner
, lockedBy = existingLocks
}
---
updatedVotes :: AssocMap.Map ResultTag Integer
updatedVotes = updateMap (Just . (+ params.voteCount)) params.voteFor initialVotes
---
proposalOutputDatum :: ProposalDatum
proposalOutputDatum =
proposalInputDatum
{ votes = ProposalVotes updatedVotes
}
---
-- Off-chain code should do exactly like this: prepend new lock toStatus the list.
updatedLocks :: [ProposalLock]
updatedLocks = ProposalLock params.voteFor proposalInputDatum.proposalId : existingLocks
---
stakeOutputDatum :: StakeDatum
stakeOutputDatum =
stakeInputDatum
{ lockedBy = updatedLocks
}
---
validTimeRange =
closedBoundedInterval
((def :: ProposalTimingConfig).draftTime + 1)
((def :: ProposalTimingConfig).votingTime - 1)
builder :: BaseBuilder
builder =
mconcat
[ txId "827598fb2d69a896bbd9e645bb14c307df907f422b39eecbe4d6329bc30b428c"
, signedWith stakeOwner
, timeRange validTimeRange
, input $
script proposalValidatorHash
. withValue pst
. withDatum proposalInputDatum
. withTxId (txOutRefId proposalRef)
. withRefIndex (txOutRefIdx proposalRef)
, input $
script stakeValidatorHash
. withValue
( sst
<> Value.assetClassValue (untag stake.gtClassRef) params.voteCount
<> minAda
)
. withDatum stakeInputDatum
. withTxId (txOutRefId stakeRef)
. withRefIndex (txOutRefIdx stakeRef)
, output $
script proposalValidatorHash
. withValue pst
. withDatum proposalOutputDatum
, output $
script stakeValidatorHash
. withValue
( sst
<> Value.assetClassValue (untag stake.gtClassRef) params.voteCount
<> minAda
)
. withDatum stakeOutputDatum
]
in buildTxInfoUnsafe builder
--------------------------------------------------------------------------------
-- | Parameters for state transition of proposals.
data TransitionParameters = TransitionParameters
{ -- The initial status of the proposal.
initialProposalStatus :: ProposalStatus
, -- The starting time of the proposal.
proposalStartingTime :: ProposalStartingTime
}
-- | Create a 'TxInfo' that update the status of a proposal.
mkTransitionTxInfo ::
-- | Initial state of the proposal.
ProposalStatus ->
-- | Next state of the proposal.
ProposalStatus ->
-- | Effects.
AssocMap.Map ResultTag (AssocMap.Map ValidatorHash DatumHash) ->
-- | Votes.
ProposalVotes ->
-- | Starting time of the proposal.
ProposalStartingTime ->
-- | Valid time range of the transaction.
POSIXTimeRange ->
-- | Whether to add an unchanged stake or not.
Bool ->
TxInfo
mkTransitionTxInfo from to effects votes startingTime validTime shouldAddUnchangedStake =
let pst = Value.singleton proposalPolicySymbol "" 1
sst = Value.assetClassValue stakeAssetClass 1
proposalInputDatum :: ProposalDatum
proposalInputDatum =
ProposalDatum
{ proposalId = ProposalId 0
, effects = effects
, status = from
, cosigners = [signer]
, thresholds = def
, votes = votes
, timingConfig = def
, startingTime = startingTime
}
proposalOutputDatum :: ProposalDatum
proposalOutputDatum =
proposalInputDatum
{ status = to
}
stakeOwner = signer
stakedAmount = 200
existingLocks :: [ProposalLock]
existingLocks =
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
]
stakeInputDatum :: StakeDatum
stakeInputDatum =
StakeDatum
{ stakedAmount = Tagged stakedAmount
, owner = stakeOwner
, lockedBy = existingLocks
}
stakeOutputDatum :: StakeDatum
stakeOutputDatum = stakeInputDatum
stakeBuilder :: BaseBuilder
stakeBuilder =
if shouldAddUnchangedStake
then
mconcat
[ input $
script stakeValidatorHash
. withValue sst
. withDatum stakeInputDatum
. withTxId (txOutRefId stakeRef)
, output $
script stakeValidatorHash
. withValue (sst <> minAda)
. withDatum stakeOutputDatum
]
else mempty
builder :: BaseBuilder
builder =
mconcat
[ txId "95ba4015e30aef16a3461ea97a779f814aeea6b8009d99a94add4b8293be737a"
, signedWith stakeOwner
, timeRange validTime
, input $
script proposalValidatorHash
. withValue pst
. withDatum proposalInputDatum
. withTxId (txOutRefId proposalRef)
, output $
script proposalValidatorHash
. withValue (pst <> minAda)
. withDatum proposalOutputDatum
]
in buildTxInfoUnsafe $ builder <> stakeBuilder
-- | Wrapper around 'advanceProposalSuccess'', with valid stake.
advanceProposalSuccess :: TransitionParameters -> TxInfo
advanceProposalSuccess ps = advanceProposalSuccess' ps True
{- | Create a valid 'TxInfo' that advances a proposal, given the parameters.
The second parameter determines wherther valid stake should be included.
Note that 'TransitionParameters.initialProposalStatus' should not be 'Finished'.
-}
advanceProposalSuccess' :: TransitionParameters -> Bool -> TxInfo
advanceProposalSuccess' params =
let -- Status of the output proposal.
toStatus :: ProposalStatus
toStatus = case params.initialProposalStatus of
Draft -> VotingReady
VotingReady -> Locked
Locked -> Finished
Finished -> error "Cannot advance 'Finished' proposal"
effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
emptyVotes@(ProposalVotes emptyVotes') = emptyVotesFor effects
-- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
updateMap
(\_ -> Just $ untag (def :: ProposalThresholds).execute + 1)
(ResultTag 0)
emptyVotes'
votes :: ProposalVotes
votes = case params.initialProposalStatus of
Draft -> emptyVotes
-- With sufficient votes
_ -> outcome0WinningVotes
proposalStartingTime :: POSIXTime
proposalStartingTime =
let (ProposalStartingTime startingTime) = params.proposalStartingTime
in startingTime
timeRange :: POSIXTimeRange
timeRange = case params.initialProposalStatus of
-- [S + 1, S + D - 1]
Draft ->
closedBoundedInterval
(proposalStartingTime + 1)
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime - 1)
-- [S + D + V + 1, S + D + V + L - 1]
VotingReady ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
- 1
)
-- [S + D + V + L + 1, S + + D + V + L + E - 1]
Locked ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime - 1
)
Finished -> error "Cannot advance 'Finished' proposal"
in mkTransitionTxInfo
params.initialProposalStatus
toStatus
effects
votes
params.proposalStartingTime
timeRange
{- | Create a valid 'TxInfo' that advances a proposal to failed state, given the parameters.
The reason why the proposal fails is the proposal has ran out of time.
Note that 'TransitionParameters.initialProposalStatus' should not be 'Finished'.
-}
advanceProposalFailureTimeout :: TransitionParameters -> TxInfo
advanceProposalFailureTimeout params =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
emptyVotes@(ProposalVotes emptyVotes') = emptyVotesFor effects
-- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
updateMap
(\_ -> Just $ untag (def :: ProposalThresholds).vote + 1)
(ResultTag 0)
emptyVotes'
votes :: ProposalVotes
votes = case params.initialProposalStatus of
Draft -> emptyVotes
-- With sufficient votes
_ -> outcome0WinningVotes
proposalStartingTime :: POSIXTime
proposalStartingTime =
let (ProposalStartingTime startingTime) = params.proposalStartingTime
in startingTime
timeRange :: POSIXTimeRange
timeRange = case params.initialProposalStatus of
-- [S + D + 1, S + D + V - 1]
Draft ->
closedBoundedInterval
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime + 1)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime - 1
)
-- [S + D + V + L + 1, S + D + V + L + E -1]
VotingReady ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
- 1
)
-- [S + D + V + L + E + 1, S + D + V + L + E + 100]
Locked ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
+ 100
)
Finished -> error "Cannot advance 'Finished' proposal"
in mkTransitionTxInfo
params.initialProposalStatus
Finished
effects
votes
params.proposalStartingTime
timeRange
True
-- | An invalid 'TxInfo' that tries to advance a 'VotingReady' proposal without sufficient votes.
advanceProposalInsufficientVotes :: TxInfo
advanceProposalInsufficientVotes =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
-- Insufficient votes.
votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 1)
, (ResultTag 1, 0)
]
)
proposalStartingTime = 0
-- Valid time range.
-- [S + D + 1, S + V + 10]
timeRange =
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ 10
)
in mkTransitionTxInfo
VotingReady
Locked
effects
votes
(ProposalStartingTime proposalStartingTime)
timeRange
True
-- | An invalid 'TxInfo' that tries to advance a 'Finished' proposal.
advanceFinishedProposal :: TxInfo
advanceFinishedProposal =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
-- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
AssocMap.fromList
[ (ResultTag 0, untag (def :: ProposalThresholds).vote + 1)
, (ResultTag 1, 0)
]
---
timeRange =
closedBoundedInterval
((def :: ProposalTimingConfig).lockingTime + 1)
((def :: ProposalTimingConfig).executingTime - 1)
in mkTransitionTxInfo
Finished
Finished
effects
outcome0WinningVotes
(ProposalStartingTime 0)
timeRange
True
{- | An illegal 'TxInfo' that tries to output a changed stake with 'AdvanceProposal'.
From the perspective of stake validator, the transition is totally valid,
so the proposal validator should reject this.
-}
advanceProposalWithInvalidOutputStake :: TxInfo
advanceProposalWithInvalidOutputStake =
let templateTxInfo =
advanceProposalSuccess'
TransitionParameters
{ initialProposalStatus = VotingReady
, proposalStartingTime = ProposalStartingTime 0
}
False
---
-- Now we create a new lock on an arbitrary stake
sst = Value.assetClassValue stakeAssetClass 1
---
stakeOwner = signer
stakedAmount = 200
---
existingLocks :: [ProposalLock]
existingLocks =
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
]
---
stakeInputDatum' :: StakeDatum
stakeInputDatum' =
StakeDatum
{ stakedAmount = Tagged stakedAmount
, owner = stakeOwner
, lockedBy = existingLocks
}
stakeInputDatum :: Datum
stakeInputDatum = Datum $ toBuiltinData stakeInputDatum'
stakeInput :: TxOut
stakeInput =
TxOut
{ txOutAddress = stakeAddress
, txOutValue =
mconcat
[ sst
, Value.assetClassValue (untag stake.gtClassRef) stakedAmount
, minAda
]
, txOutDatumHash = Just $ toDatumHash stakeInputDatum
}
---
updatedLocks :: [ProposalLock]
updatedLocks = ProposalLock (ResultTag 42) (ProposalId 27) : existingLocks
---
stakeOutputDatum' :: StakeDatum
stakeOutputDatum' =
stakeInputDatum'
{ lockedBy = updatedLocks
}
stakeOutputDatum :: Datum
stakeOutputDatum = Datum $ toBuiltinData stakeOutputDatum'
stakeOutput :: TxOut
stakeOutput =
stakeInput
{ txOutDatumHash = Just $ toDatumHash stakeOutputDatum
}
in templateTxInfo
{ txInfoInputs = TxInInfo stakeRef stakeInput : templateTxInfo.txInfoInputs
, txInfoOutputs = stakeOutput : templateTxInfo.txInfoOutputs
, txInfoData =
(datumPair <$> [stakeInputDatum, stakeOutputDatum])
<> templateTxInfo.txInfoData
, txInfoSignatories = [stakeOwner]
}

View file

@ -0,0 +1,509 @@
module Sample.Proposal.Advance (
advanceToNextStateInTimeParameters,
advanceToFailedStateDueToTimeoutParameters,
insufficientVotesParameters,
insufficientCosignsParameters,
advanceFromFinishedParameters,
invalidOutputStakeParameters,
mkTestTree,
Parameters (..),
) where
import Agora.Proposal (
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (AdvanceProposal),
ProposalStatus (..),
ProposalThresholds (..),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
emptyVotesFor,
)
import Agora.Proposal.Scripts (proposalValidator)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (
draftTime,
executingTime,
lockingTime,
votingTime
),
)
import Agora.SafeMoney (GTTag)
import Agora.Stake (
ProposalLock (ProposalLock),
Stake (gtClassRef),
StakeDatum (..),
StakeRedeemer (WitnessStake),
)
import Agora.Stake.Scripts (stakeValidator)
import Data.Coerce (coerce)
import Data.Default (def)
import Data.List (sort)
import Data.Tagged (Tagged (..), untag)
import Plutarch.Context (
BaseBuilder,
buildTxInfoUnsafe,
input,
output,
script,
signedWith,
timeRange,
txId,
withDatum,
withOutRef,
withTxId,
withValue,
)
import PlutusLedgerApi.V1 (
DatumHash,
POSIXTime,
POSIXTimeRange,
PubKeyHash,
ScriptContext (ScriptContext),
ScriptPurpose (Spending),
TxInfo,
TxOutRef (TxOutRef),
ValidatorHash,
always,
)
import PlutusLedgerApi.V1.Value qualified as Value
import PlutusTx.AssocMap qualified as AssocMap
import Sample.Proposal.Shared (proposalTxRef, stakeTxRef, testFunc)
import Sample.Shared (
minAda,
proposalPolicySymbol,
proposalValidatorHash,
stake,
stakeAssetClass,
stakeValidatorHash,
)
import Sample.Shared qualified as Shared
import Test.Specification (SpecificationTree, group)
import Test.Util (closedBoundedInterval, pubKeyHashes, sortValue, updateMap)
-- | Parameters for state transition of proposals.
data Parameters = Parameters
{ fromStatus :: ProposalStatus
-- ^ Initial state of the proposal.
, toStatus :: ProposalStatus
-- ^ Next state of the proposal.
, votes :: ProposalVotes
-- ^ Votes.
, includeAllStakes :: Bool
-- ^ Whether to add an extra cosigner without stake or not.
, validTimeRange :: POSIXTimeRange
-- ^ Valid time range of the transaction.
, alterOutputStakes :: Bool
-- ^ Whether to alter th output stakes or not.
, stakeCount :: Integer
-- ^ The number of stakes.
, signByAllCosigners :: Bool
, perStakeGTs :: Tagged GTTag Integer
}
---
proposalRef :: TxOutRef
proposalRef = TxOutRef proposalTxRef 1
mkStakeRef :: Int -> TxOutRef
mkStakeRef = TxOutRef stakeTxRef . (+ 2) . fromIntegral
---
defEffects :: AssocMap.Map ResultTag (AssocMap.Map ValidatorHash DatumHash)
defEffects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
emptyVotes :: ProposalVotes
emptyVotes = emptyVotesFor defEffects
proposalStartingTime :: POSIXTime
proposalStartingTime = 0
---
mkProposalInputDatum :: Parameters -> ProposalDatum
mkProposalInputDatum ps =
ProposalDatum
{ proposalId = ProposalId 0
, effects = defEffects
, status = ps.fromStatus
, cosigners = mkStakeOwners ps
, thresholds = def
, votes = ps.votes
, timingConfig = def
, startingTime = ProposalStartingTime proposalStartingTime
}
mkStakeInputDatums :: Parameters -> [StakeDatum]
mkStakeInputDatums ps =
map
( \pk ->
StakeDatum
{ stakedAmount = ps.perStakeGTs
, owner = pk
, lockedBy = existingLocks
}
)
$ mkStakeOwners ps
where
existingLocks :: [ProposalLock]
existingLocks =
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
]
---
proposalScriptPurpose :: ScriptPurpose
proposalScriptPurpose = Spending proposalRef
mkStakeScriptPurpose :: Int -> ScriptPurpose
mkStakeScriptPurpose = Spending . mkStakeRef
---
proposalRedeemer :: ProposalRedeemer
proposalRedeemer = AdvanceProposal
stakeRedeemer :: StakeRedeemer
stakeRedeemer = WitnessStake
---
mkStakeOwners :: Parameters -> [PubKeyHash]
mkStakeOwners ps =
sort $
take
(fromIntegral ps.stakeCount)
pubKeyHashes
---
-- | Create a 'TxInfo' that update the status of a proposal.
advance ::
Parameters ->
TxInfo
advance ps =
let pst = Value.singleton proposalPolicySymbol "" 1
sst = Value.assetClassValue stakeAssetClass 1
proposalInputDatum :: ProposalDatum
proposalInputDatum =
mkProposalInputDatum ps
proposalOutputDatum :: ProposalDatum
proposalOutputDatum =
proposalInputDatum
{ status = ps.toStatus
}
stakeInputDatums :: [StakeDatum]
stakeInputDatums = mkStakeInputDatums ps
mkStakeOutputDatum :: StakeDatum -> StakeDatum
mkStakeOutputDatum si =
if ps.alterOutputStakes
then
si
{ stakedAmount = ps.perStakeGTs + 1
}
else si
stakeValue =
let gts =
if ps.perStakeGTs == 0
then mempty
else
Value.assetClassValue
(untag stake.gtClassRef)
(untag ps.perStakeGTs)
in sortValue $
sst <> minAda
<> gts
stakeBuilder :: BaseBuilder
stakeBuilder =
foldMap
( \(si, idx) ->
let so = mkStakeOutputDatum si
in mconcat @BaseBuilder
[ input $
script stakeValidatorHash
. withValue stakeValue
. withDatum si
. withOutRef (mkStakeRef idx)
, output $
script stakeValidatorHash
. withValue stakeValue
. withDatum so
]
)
$ let withIds = zip stakeInputDatums [0 ..]
in if ps.includeAllStakes
then withIds
else [head withIds]
signBuilder :: BaseBuilder
signBuilder =
let sos = mkStakeOwners ps
in if ps.signByAllCosigners
then foldMap signedWith sos
else signedWith $ head sos
builder :: BaseBuilder
builder =
mconcat
[ txId "95ba4015e30aef16a3461ea97a779f814aeea6b8009d99a94add4b8293be737a"
, signBuilder
, timeRange ps.validTimeRange
, input $
script proposalValidatorHash
. withValue pst
. withDatum proposalInputDatum
. withTxId proposalTxRef
, output $
script proposalValidatorHash
. withValue (pst <> minAda)
. withDatum proposalOutputDatum
]
in buildTxInfoUnsafe $ builder <> stakeBuilder
---
mkInTimeTimeRange :: ProposalStatus -> POSIXTimeRange
mkInTimeTimeRange advanceFrom =
case advanceFrom of
-- [S + 1, S + D - 1]
Draft ->
closedBoundedInterval
(proposalStartingTime + 1)
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime - 1)
-- [S + D + V + 1, S + D + V + L - 1]
VotingReady ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
- 1
)
-- [S + D + V + L + 1, S + + D + V + L + E - 1]
Locked ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime - 1
)
Finished -> error "Cannot advance 'Finished' proposal"
mkTooLateTimeRange :: ProposalStatus -> POSIXTimeRange
mkTooLateTimeRange advanceFrom =
case advanceFrom of
-- [S + D + 1, S + D + V - 1]
Draft ->
closedBoundedInterval
(proposalStartingTime + (def :: ProposalTimingConfig).draftTime + 1)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime - 1
)
-- [S + D + V + L + 1, S + D + V + L + E -1]
VotingReady ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
- 1
)
-- [S + D + V + L + E + 1, S + D + V + L + E + 100]
Locked ->
closedBoundedInterval
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
+ 1
)
( proposalStartingTime
+ (def :: ProposalTimingConfig).draftTime
+ (def :: ProposalTimingConfig).votingTime
+ (def :: ProposalTimingConfig).lockingTime
+ (def :: ProposalTimingConfig).executingTime
+ 100
)
Finished -> error "Cannot advance 'Finished' proposal"
---
getNextState :: ProposalStatus -> ProposalStatus
getNextState = \case
Draft -> VotingReady
VotingReady -> Locked
Locked -> Finished
Finished -> error "Cannot advance 'Finished' proposal"
---
advanceToNextStateInTimeParameters :: Int -> [Parameters]
advanceToNextStateInTimeParameters nCosigners =
map
( \from ->
let -- Set the vote count of outcome 0 to @def.countingVoting + 1@,
-- meaning that outcome 0 will be the winner.
outcome0WinningVotes =
ProposalVotes $
updateMap
(\_ -> Just $ untag (def :: ProposalThresholds).execute + 1)
(ResultTag 0)
(coerce emptyVotes)
votes = case from of
Draft -> emptyVotes
-- With sufficient votes
_ -> outcome0WinningVotes
includeAllStakes = case from of
Draft -> True
_ -> False
signByAllCosigners = case from of
Draft -> True
_ -> False
in Parameters
{ fromStatus = from
, toStatus = getNextState from
, votes = votes
, includeAllStakes = includeAllStakes
, validTimeRange = mkInTimeTimeRange from
, alterOutputStakes = False
, stakeCount = fromIntegral nCosigners
, signByAllCosigners = signByAllCosigners
, perStakeGTs =
(def :: ProposalThresholds).vote
`div` fromIntegral nCosigners + 1
}
)
[Draft, VotingReady, Locked]
advanceToFailedStateDueToTimeoutParameters :: Int -> [Parameters]
advanceToFailedStateDueToTimeoutParameters nCosigners =
map
( \from ->
Parameters
{ fromStatus = from
, toStatus = Finished
, votes = emptyVotes
, includeAllStakes = False
, validTimeRange = mkTooLateTimeRange from
, alterOutputStakes = False
, stakeCount = fromIntegral nCosigners
, signByAllCosigners = False
, perStakeGTs = 1
}
)
[Draft, VotingReady, Locked]
insufficientVotesParameters :: Parameters
insufficientVotesParameters =
let votes = emptyVotes
from = VotingReady
to = getNextState from
in Parameters
{ fromStatus = from
, toStatus = to
, votes = votes
, includeAllStakes = False
, validTimeRange = mkInTimeTimeRange from
, alterOutputStakes = False
, stakeCount = 1
, signByAllCosigners = True
, perStakeGTs = 20
}
insufficientCosignsParameters :: Int -> Parameters
insufficientCosignsParameters nCosigners =
(\ps -> ps {perStakeGTs = 0}) $
head $
advanceToNextStateInTimeParameters nCosigners
advanceFromFinishedParameters :: Parameters
advanceFromFinishedParameters =
Parameters
{ fromStatus = Finished
, toStatus = Finished
, votes = emptyVotes
, includeAllStakes = False
, validTimeRange = always
, alterOutputStakes = False
, stakeCount = 1
, signByAllCosigners = True
, perStakeGTs = 20
}
invalidOutputStakeParameters :: Int -> [Parameters]
invalidOutputStakeParameters nCosigners =
(\ps -> ps {alterOutputStakes = True})
<$> advanceToNextStateInTimeParameters nCosigners
---
mkTestTree :: String -> Parameters -> Bool -> SpecificationTree
mkTestTree name ps isValidForProposalValidator = group name [proposal, stake]
where
txInfo = advance ps
proposal =
let proposalInputDatum = mkProposalInputDatum ps
in testFunc
isValidForProposalValidator
"propsoal"
(proposalValidator Shared.proposal)
proposalInputDatum
proposalRedeemer
( ScriptContext
txInfo
proposalScriptPurpose
)
stake =
let idx = 0
stakeInputDatum = mkStakeInputDatums ps !! idx
isValid = not $ ps.alterOutputStakes
in testFunc
isValid
"stake"
(stakeValidator Shared.stake)
stakeInputDatum
stakeRedeemer
( ScriptContext
txInfo
(mkStakeScriptPurpose idx)
)

View file

@ -0,0 +1,344 @@
module Sample.Proposal.Cosign (
Parameters (..),
validCosignNParameters,
duplicateCosignersParameters,
statusNotDraftCosignNParameters,
invalidStakeOutputParameters,
mkTestTree,
) where
import Agora.Proposal (
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (Cosign),
ProposalStatus (..),
ResultTag (ResultTag),
emptyVotesFor,
)
import Agora.Proposal.Scripts (proposalValidator)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (draftTime),
)
import Agora.SafeMoney (GTTag)
import Agora.Stake (
Stake (gtClassRef),
StakeDatum (StakeDatum, owner),
StakeRedeemer (WitnessStake),
stakedAmount,
)
import Agora.Stake.Scripts (stakeValidator)
import Data.Coerce (coerce)
import Data.Default (def)
import Data.List (sort)
import Data.Tagged (Tagged, untag)
import Plutarch.Context (
BaseBuilder,
buildTxInfoUnsafe,
input,
output,
script,
signedWith,
timeRange,
txId,
withDatum,
withRefIndex,
withTxId,
withValue,
)
import PlutusLedgerApi.V1 (
POSIXTimeRange,
PubKeyHash,
ScriptContext (ScriptContext),
ScriptPurpose (Spending),
TxInfo,
TxOutRef (..),
Value,
)
import PlutusLedgerApi.V1.Value qualified as Value
import PlutusTx.AssocMap qualified as AssocMap
import Sample.Proposal.Shared (proposalTxRef, stakeTxRef, testFunc)
import Sample.Shared (
minAda,
proposalPolicySymbol,
proposalValidatorHash,
signer,
stake,
stakeAssetClass,
stakeValidatorHash,
)
import Sample.Shared qualified as Shared
import Test.Specification (
SpecificationTree,
group,
)
import Test.Util (closedBoundedInterval, pubKeyHashes, sortValue)
-- | Parameters for cosigning a proposal.
data Parameters = Parameters
{ newCosigners :: [PubKeyHash]
-- ^ New cosigners to be added, and the owners of the generated stakes.
, proposalStatus :: ProposalStatus
-- ^ Current state of the proposal.
, alterOutputStakes :: Bool
-- ^ Whether to generate invalid stake outputs.
-- In particular, the 'stakedAmount' of all the stake datums will be set to zero.
}
-- | Owner of the creator stake, doesn't really matter in this case.
proposalCreator :: PubKeyHash
proposalCreator = signer
-- | The amount of GTs every generated stake has, doesn't really matter in this case.
perStakedGTs :: Tagged GTTag Integer
perStakedGTs = 5
{- | Create input proposal datum given the parameters.
In particular, 'status' is set to 'proposalStstus'.
-}
mkProposalInputDatum :: Parameters -> ProposalDatum
mkProposalInputDatum ps =
let effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
in ProposalDatum
{ proposalId = ProposalId 0
, effects = effects
, status = ps.proposalStatus
, cosigners = [proposalCreator]
, thresholds = def
, votes = emptyVotesFor effects
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
{- | Create the output proposal datum given the parameters.
The 'newCosigners' is added to the exisiting list of cosigners, note the said list should be sorted in
ascending order.
-}
mkProposalOutputDatum :: Parameters -> ProposalDatum
mkProposalOutputDatum ps =
let inputDatum = mkProposalInputDatum ps
in inputDatum
{ cosigners = sort $ inputDatum.cosigners <> ps.newCosigners
}
-- | Create all the input stakes given the parameters.
mkStakeInputDatums :: Parameters -> [StakeDatum]
mkStakeInputDatums = fmap (\pk -> StakeDatum perStakedGTs pk []) . newCosigners
-- | Create a 'TxInfo' that tries to cosign a proposal with new cosigners.
cosign :: Parameters -> TxInfo
cosign ps = buildTxInfoUnsafe builder
where
pst = Value.singleton proposalPolicySymbol "" 1
sst = Value.assetClassValue stakeAssetClass 1
---
stakeInputDatums :: [StakeDatum]
stakeInputDatums = mkStakeInputDatums ps
stakeValue :: Value
stakeValue =
sortValue $
minAda
<> Value.assetClassValue
(untag stake.gtClassRef)
(untag perStakedGTs)
<> sst
stakeBuilder :: BaseBuilder
stakeBuilder =
foldMap
( \(stakeDatum, refIdx) ->
let stakeOutputDatum =
if ps.alterOutputStakes
then stakeDatum {stakedAmount = 0}
else stakeDatum
in mconcat @BaseBuilder
[ input $
script stakeValidatorHash
. withValue stakeValue
. withDatum stakeDatum
. withTxId stakeTxRef
. withRefIndex refIdx
, output $
script stakeValidatorHash
. withValue stakeValue
. withDatum stakeOutputDatum
, signedWith stakeDatum.owner
]
)
$ zip
stakeInputDatums
[2 ..]
---
proposalInputDatum :: ProposalDatum
proposalInputDatum = mkProposalInputDatum ps
proposalOutputDatum :: ProposalDatum
proposalOutputDatum = mkProposalOutputDatum ps
proposalBuilder :: BaseBuilder
proposalBuilder =
mconcat
[ input $
script proposalValidatorHash
. withValue pst
. withDatum proposalInputDatum
. withTxId proposalTxRef
. withRefIndex proposalRefIdx
, output $
script proposalValidatorHash
. withValue (sortValue (pst <> minAda))
. withDatum proposalOutputDatum
]
validTimeRange :: POSIXTimeRange
validTimeRange =
closedBoundedInterval
(coerce proposalInputDatum.startingTime + 1)
( coerce proposalInputDatum.startingTime
+ proposalInputDatum.timingConfig.draftTime - 1
)
---
builder :: BaseBuilder
builder =
mconcat
[ txId "05c67819fc3381a2052b929ab439244b7b5fe3b3bd07f2134055bbbb21bd9e52"
, timeRange validTimeRange
, proposalBuilder
, stakeBuilder
]
-- | Reference index of the proposal UTXO.
proposalRefIdx :: Integer
proposalRefIdx = 1
-- | Spend the proposal ST.
proposalScriptPurpose :: ScriptPurpose
proposalScriptPurpose =
Spending
( TxOutRef
proposalTxRef
proposalRefIdx
)
-- | Consume the given stake.
mkStakeScriptPurpose :: Int -> ScriptPurpose
mkStakeScriptPurpose idx =
Spending $
TxOutRef
stakeTxRef
$ proposalRefIdx + 1 + fromIntegral idx
-- | Create a proposal redeemer which cosigns with the new cosginers.
mkProposalRedeemer :: Parameters -> ProposalRedeemer
mkProposalRedeemer (sort . newCosigners -> cs) = Cosign cs
-- | Stake redeemer for cosuming all the stakes generated in the module.
stakeRedeemer :: StakeRedeemer
stakeRedeemer = WitnessStake
---
-- | Create a valid parameters that cosign the proposal with a given number of cosigners.
validCosignNParameters :: Int -> Parameters
validCosignNParameters n
| n > 0 =
Parameters
{ newCosigners = take n pubKeyHashes
, proposalStatus = Draft
, alterOutputStakes = False
}
| otherwise = error "Number of cosigners should be positive"
---
{- | Parameters that make 'cosign' yield duplicate cosigners.
Invalid for the ptoposal validator, perfectly valid for stake validator.
-}
duplicateCosignersParameters :: Parameters
duplicateCosignersParameters =
Parameters
{ newCosigners = [proposalCreator]
, proposalStatus = Draft
, alterOutputStakes = False
}
---
{- | Generate a list of parameters that sets proposal status to something other than 'Draft'.
Invalid for the ptoposal validator, perfectly valid for stake validator.
-}
statusNotDraftCosignNParameters :: Int -> [Parameters]
statusNotDraftCosignNParameters n =
map
( \st ->
Parameters
{ newCosigners = take n pubKeyHashes
, proposalStatus = st
, alterOutputStakes = False
}
)
[VotingReady, Locked, Finished]
---
{- | Parameters thet change the output stake datums.
Invalid for both proposal validator and stake validator.
-}
invalidStakeOutputParameters :: Parameters
invalidStakeOutputParameters =
(validCosignNParameters 2)
{ alterOutputStakes = True
}
---
-- | Create a test tree given the parameters. Both the proposal validator and stake validator will be run.
mkTestTree ::
-- | The name of the test group.
String ->
Parameters ->
-- | Are the parameters valid for the proposal validator?
Bool ->
SpecificationTree
mkTestTree name ps isValid = group name [proposal, stake]
where
txInfo = cosign ps
proposal =
let proposalInputDatum = mkProposalInputDatum ps
in testFunc
isValid
"propsoal"
(proposalValidator Shared.proposal)
proposalInputDatum
(mkProposalRedeemer ps)
( ScriptContext
txInfo
proposalScriptPurpose
)
stake =
let idx = 0
stakeInputDatum = mkStakeInputDatums ps !! idx
isValid = not ps.alterOutputStakes
in testFunc
isValid
"stake"
(stakeValidator Shared.stake)
stakeInputDatum
stakeRedeemer
( ScriptContext
txInfo
(mkStakeScriptPurpose idx)
)

View file

@ -1,9 +1,39 @@
module Sample.Proposal.Shared (proposalRef, stakeRef) where
module Sample.Proposal.Shared (proposalTxRef, stakeTxRef, testFunc) where
import PlutusLedgerApi.V1 (TxOutRef (..))
import Plutarch.Api.V1 (PValidator)
import Plutarch.Lift (PUnsafeLiftDecl (..))
import PlutusLedgerApi.V1 (ScriptContext, ToData, TxId)
import Test.Specification (
SpecificationTree,
validatorFailsWith,
validatorSucceedsWith,
)
proposalRef :: TxOutRef
proposalRef = TxOutRef "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be" 1
-- | 'TxId' of all the propsoal inputs in the samples.
proposalTxRef :: TxId
proposalTxRef = "0b2086cbf8b6900f8cb65e012de4516cb66b5cb08a9aaba12a8b88be"
stakeRef :: TxOutRef
stakeRef = TxOutRef "0ca36f3a357bc69579ab2531aecd1e7d3714d993c7820f40b864be15" 0
-- | 'TxId' of all the stake inputs in the samples.
stakeTxRef :: TxId
stakeTxRef = "0ca36f3a357bc69579ab2531aecd1e7d3714d993c7820f40b864be15"
-- | Get the test function given whether a test case is valid.
testFunc ::
forall {datum :: PType} {redeemer :: PType}.
( PUnsafeLiftDecl datum
, PUnsafeLiftDecl redeemer
, ToData (PLifted datum)
, ToData (PLifted redeemer)
) =>
-- | Should the validator pass?
Bool ->
String ->
ClosedTerm PValidator ->
PLifted datum ->
PLifted redeemer ->
ScriptContext ->
SpecificationTree
testFunc isValid =
if isValid
then validatorSucceedsWith
else validatorFailsWith

View file

@ -11,6 +11,33 @@ module Sample.Proposal.UnlockStake (
--------------------------------------------------------------------------------
import Agora.Proposal (
ProposalDatum (..),
ProposalId (..),
ProposalRedeemer (Unlock),
ProposalStatus (..),
ProposalVotes (..),
ResultTag (..),
)
import Agora.Proposal.Scripts (proposalValidator)
import Agora.Proposal.Time (ProposalStartingTime (ProposalStartingTime))
import Agora.Stake (ProposalLock (ProposalLock), Stake (..), StakeDatum (..))
import Control.Monad (join)
import Data.Coerce (coerce)
import Data.Default.Class (Default (def))
import Data.Tagged (Tagged (..), untag)
import Plutarch.Context (
BaseBuilder,
buildTxInfoUnsafe,
input,
output,
script,
txId,
withDatum,
withRefIndex,
withTxId,
withValue,
)
import PlutusLedgerApi.V1 (
DatumHash,
ScriptContext (..),
@ -21,19 +48,7 @@ import PlutusLedgerApi.V1 (
)
import PlutusLedgerApi.V1.Value qualified as Value
import PlutusTx.AssocMap qualified as AssocMap
--------------------------------------------------------------------------------
import Agora.Proposal (
ProposalDatum (..),
ProposalId (..),
ProposalRedeemer (Unlock),
ProposalStatus (..),
ProposalVotes (..),
ResultTag (..),
)
import Agora.Proposal.Time (ProposalStartingTime (ProposalStartingTime))
import Agora.Stake (ProposalLock (ProposalLock), Stake (..), StakeDatum (..))
import Sample.Proposal.Shared (proposalTxRef, stakeTxRef, testFunc)
import Sample.Shared (
minAda,
proposalPolicySymbol,
@ -43,19 +58,9 @@ import Sample.Shared (
stakeAssetClass,
stakeValidatorHash,
)
import Test.Util (sortValue, updateMap)
--------------------------------------------------------------------------------
import Agora.Proposal.Scripts (proposalValidator)
import Control.Monad (join)
import Data.Coerce (coerce)
import Data.Default.Class (Default (def))
import Data.Tagged (Tagged (..), untag)
import Plutarch.Context (BaseBuilder, buildTxInfoUnsafe, input, output, script, txId, withDatum, withRefIndex, withTxId, withValue)
import Sample.Proposal.Shared (proposalRef, stakeRef)
import Sample.Shared qualified as Shared
import Test.Specification (SpecificationTree, validatorFailsWith, validatorSucceedsWith)
import Test.Specification (SpecificationTree)
import Test.Util (sortValue, updateMap)
--------------------------------------------------------------------------------
@ -223,8 +228,8 @@ unlockStake p =
script proposalValidatorHash
. withValue pst
. withDatum i
. withTxId (txOutRefId proposalRef)
. withRefIndex (txOutRefIdx proposalRef + coerce i.proposalId)
. withTxId proposalTxRef
. withRefIndex (coerce i.proposalId + 2)
, output $
script proposalValidatorHash
. withValue (sortValue $ pst <> minAda)
@ -249,8 +254,8 @@ unlockStake p =
script stakeValidatorHash
. withValue stakeValue
. withDatum sInDatum
. withTxId (txOutRefId stakeRef)
. withRefIndex (txOutRefIdx stakeRef)
. withTxId stakeTxRef
. withRefIndex 1
, output $
script stakeValidatorHash
. withValue stakeValue
@ -271,6 +276,14 @@ mkProposalValidatorTestCase p shouldSucceed =
let datum = mkProposalInputDatum p $ ProposalId 0
redeemer = Unlock (ResultTag 0)
name = show p
scriptContext = ScriptContext (unlockStake p) (Spending proposalRef)
f = if shouldSucceed then validatorSucceedsWith else validatorFailsWith
in f name (proposalValidator Shared.proposal) datum redeemer scriptContext
scriptContext =
ScriptContext
(unlockStake p)
(Spending (TxOutRef proposalTxRef 2))
in testFunc
shouldSucceed
name
(proposalValidator Shared.proposal)
datum
redeemer
scriptContext

View file

@ -0,0 +1,249 @@
module Sample.Proposal.Vote (
validVoteParameters,
mkTestTree,
) where
import Agora.Proposal (
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (Vote),
ProposalStatus (VotingReady),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
)
import Agora.Proposal.Scripts (proposalValidator)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
ProposalTimingConfig (draftTime, votingTime),
)
import Agora.Stake (
ProposalLock (ProposalLock),
Stake (gtClassRef),
StakeDatum (..),
StakeRedeemer (PermitVote),
)
import Agora.Stake.Scripts (stakeValidator)
import Data.Default (Default (def))
import Data.Tagged (Tagged (Tagged), untag)
import Plutarch.Context (
BaseBuilder,
buildTxInfoUnsafe,
input,
output,
script,
signedWith,
timeRange,
txId,
withDatum,
withOutRef,
withValue,
)
import PlutusLedgerApi.V1 (
PubKeyHash,
ScriptContext (..),
ScriptPurpose (Spending),
TxInfo,
TxOutRef (TxOutRef),
)
import PlutusLedgerApi.V1.Value qualified as Value
import PlutusTx.AssocMap qualified as AssocMap
import Sample.Proposal.Shared (proposalTxRef, stakeTxRef, testFunc)
import Sample.Shared (
minAda,
proposalPolicySymbol,
proposalValidatorHash,
signer,
stake,
stakeAssetClass,
stakeValidatorHash,
)
import Sample.Shared qualified as Shared
import Test.Specification (
SpecificationTree,
group,
validatorSucceedsWith,
)
import Test.Util (closedBoundedInterval, sortValue, updateMap)
proposalRef :: TxOutRef
proposalRef = TxOutRef proposalTxRef 0
stakeRef :: TxOutRef
stakeRef = TxOutRef stakeTxRef 1
-- | Parameters for creating a voting transaction.
data Parameters = Parameters
{ voteFor :: ResultTag
-- ^ The outcome the transaction is voting for.
, voteCount :: Integer
-- ^ The count of votes.
}
stakeOwner :: PubKeyHash
stakeOwner = signer
initialVotes :: AssocMap.Map ResultTag Integer
initialVotes =
AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 4242)
]
proposalInputDatum :: ProposalDatum
proposalInputDatum =
ProposalDatum
{ proposalId = ProposalId 42
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = VotingReady
, cosigners = [stakeOwner]
, thresholds = def
, votes = ProposalVotes initialVotes
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
existingLocks :: [ProposalLock]
existingLocks =
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
]
mkStakeInputDatum :: Parameters -> StakeDatum
mkStakeInputDatum params =
StakeDatum
{ stakedAmount = Tagged params.voteCount
, owner = stakeOwner
, lockedBy = existingLocks
}
mkProposalRedeemer :: Parameters -> ProposalRedeemer
mkProposalRedeemer = Vote . voteFor
mkNewLock :: Parameters -> ProposalLock
mkNewLock ps = ProposalLock ps.voteFor proposalInputDatum.proposalId
mkStakeRedeemer :: Parameters -> StakeRedeemer
mkStakeRedeemer = PermitVote . mkNewLock
-- | Create a valid transaction that votes on a propsal, given the parameters.
vote :: Parameters -> TxInfo
vote params =
let pst = Value.singleton proposalPolicySymbol "" 1
sst = Value.assetClassValue stakeAssetClass 1
---
stakeInputDatum = mkStakeInputDatum params
---
updatedVotes :: AssocMap.Map ResultTag Integer
updatedVotes = updateMap (Just . (+ params.voteCount)) params.voteFor initialVotes
---
proposalOutputDatum :: ProposalDatum
proposalOutputDatum =
proposalInputDatum
{ votes = ProposalVotes updatedVotes
}
---
-- Off-chain code should do exactly like this: prepend new lock toStatus the list.
updatedLocks :: [ProposalLock]
updatedLocks = mkNewLock params : existingLocks
---
stakeOutputDatum :: StakeDatum
stakeOutputDatum =
stakeInputDatum
{ lockedBy = updatedLocks
}
---
validTimeRange =
closedBoundedInterval
((def :: ProposalTimingConfig).draftTime + 1)
((def :: ProposalTimingConfig).votingTime - 1)
---
stakeValue =
sortValue $
sst
<> Value.assetClassValue (untag stake.gtClassRef) params.voteCount
<> minAda
builder :: BaseBuilder
builder =
mconcat
[ txId "827598fb2d69a896bbd9e645bb14c307df907f422b39eecbe4d6329bc30b428c"
, signedWith stakeOwner
, timeRange validTimeRange
, input $
script proposalValidatorHash
. withValue pst
. withDatum proposalInputDatum
. withOutRef proposalRef
, input $
script stakeValidatorHash
. withValue stakeValue
. withDatum stakeInputDatum
. withOutRef stakeRef
, output $
script proposalValidatorHash
. withValue pst
. withDatum proposalOutputDatum
, output $
script stakeValidatorHash
. withValue stakeValue
. withDatum stakeOutputDatum
]
in buildTxInfoUnsafe builder
---
validVoteParameters :: Parameters
validVoteParameters =
Parameters
{ voteFor = ResultTag 0
, voteCount = 27
}
---
mkTestTree :: String -> Parameters -> Bool -> SpecificationTree
mkTestTree name ps isValid = group name [proposal, stake]
where
txInfo = vote ps
proposal =
testFunc
isValid
"propsoal"
(proposalValidator Shared.proposal)
proposalInputDatum
(mkProposalRedeemer ps)
( ScriptContext
txInfo
(Spending proposalRef)
)
stake =
let stakeInputDatum = mkStakeInputDatum ps
in validatorSucceedsWith
"stake"
(stakeValidator Shared.stake)
stakeInputDatum
(mkStakeRedeemer ps)
( ScriptContext
txInfo
(Spending stakeRef)
)

View file

@ -1,5 +1,3 @@
{-# LANGUAGE QuasiQuotes #-}
{- |
Module : Spec.Proposal
Maintainer : emi@haskell.fyi
@ -11,45 +9,19 @@ module Spec.Proposal (specs) where
import Agora.Proposal (
Proposal (..),
ProposalDatum (..),
ProposalId (ProposalId),
ProposalRedeemer (..),
ProposalStatus (..),
ProposalThresholds (..),
ProposalVotes (ProposalVotes),
ResultTag (ResultTag),
cosigners,
effects,
emptyVotesFor,
proposalId,
status,
thresholds,
votes,
)
import Agora.Proposal.Scripts (proposalPolicy, proposalValidator)
import Agora.Proposal.Time (
ProposalStartingTime (ProposalStartingTime),
)
import Agora.Stake (
ProposalLock (ProposalLock),
StakeDatum (StakeDatum),
StakeRedeemer (PermitVote, WitnessStake),
)
import Agora.Stake.Scripts (stakeValidator)
import Data.Default.Class (Default (def))
import Data.Tagged (Tagged (Tagged), untag)
import PlutusLedgerApi.V1 (ScriptContext (..), ScriptPurpose (..))
import PlutusTx.AssocMap qualified as AssocMap
import Agora.Proposal.Scripts (proposalPolicy)
import Sample.Proposal qualified as Proposal
import Sample.Proposal.Advance qualified as Advance
import Sample.Proposal.Cosign qualified as Cosign
import Sample.Proposal.UnlockStake qualified as UnlockStake
import Sample.Shared (signer, signer2)
import Sample.Shared qualified as Shared (proposal, stake)
import Sample.Proposal.Vote qualified as Vote
import Sample.Shared qualified as Shared (proposal)
import Test.Specification (
SpecificationTree,
group,
policySucceedsWith,
validatorFailsWith,
validatorSucceedsWith,
)
-- | Stake specs.
@ -67,279 +39,133 @@ specs =
"validator"
[ group
"cosignature"
[ validatorSucceedsWith
"proposal"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = Draft
, cosigners = [signer]
, thresholds = def
, votes =
emptyVotesFor $
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(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))
]
$ let cosignerCases = [1, 5, 10]
mkLegalGroup nCosigners =
Cosign.mkTestTree
("with " <> show nCosigners <> " cosigners")
(Cosign.validCosignNParameters nCosigners)
True
legalGroup =
group "legal" $
map mkLegalGroup cosignerCases
mkIllegalStatusNotDraftGroup nCosigners =
group ("with " <> show nCosigners <> " cosigners") $
map
( \ps ->
Cosign.mkTestTree
("status: " <> show ps.proposalStatus)
ps
False
)
(Cosign.statusNotDraftCosignNParameters nCosigners)
illegalStatusNotDraftGroup =
group "proposal status not Draft" $
map mkIllegalStatusNotDraftGroup cosignerCases
illegalGroup =
group
"illegal"
[ Cosign.mkTestTree
"duplicate cosigners"
Cosign.duplicateCosignersParameters
False
, Cosign.mkTestTree
"altered output stake"
Cosign.invalidStakeOutputParameters
False
, illegalStatusNotDraftGroup
]
in [legalGroup, illegalGroup]
, group
"voting"
[ validatorSucceedsWith
"proposal"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 42
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
[ Vote.mkTestTree "legal" Vote.validVoteParameters True
-- TODO: add negative test cases
]
, group "advancing" $
let mkFromDraft nCosigners =
let name = "with " <> show nCosigners <> " cosigner(s)"
legalGroup =
group
"legal"
[ Advance.mkTestTree
"to next state"
( head $
Advance.advanceToNextStateInTimeParameters
nCosigners
)
True
, Advance.mkTestTree
"to failed state"
( head $
Advance.advanceToFailedStateDueToTimeoutParameters
nCosigners
)
True
]
, status = VotingReady
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 42)
, (ResultTag 1, 4242)
]
illegalGroup =
group
"illegal"
[ Advance.mkTestTree
"insufficient cosigns"
(Advance.insufficientCosignsParameters nCosigners)
False
, Advance.mkTestTree
"invalid stake output"
(head $ Advance.invalidOutputStakeParameters nCosigners)
False
]
in group name [legalGroup, illegalGroup]
draftGroup = group "from draft" $ map mkFromDraft [1, 5, 10]
legalGroup =
group
"legal"
[ group "advance to next state" $
map
( \ps ->
let name = "from: " <> show ps.fromStatus
in Advance.mkTestTree name ps True
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
(Vote (ResultTag 0))
( ScriptContext
( Proposal.voteOnProposal
Proposal.VotingParameters
{ Proposal.voteFor = ResultTag 0
, Proposal.voteCount = 27
}
)
(Spending Proposal.proposalRef)
)
, validatorSucceedsWith
"stake"
(stakeValidator Shared.stake)
( StakeDatum
(Tagged 27)
signer
[ ProposalLock (ResultTag 0) (ProposalId 0)
, ProposalLock (ResultTag 2) (ProposalId 1)
(tail $ Advance.advanceToNextStateInTimeParameters 1)
, group "advance to failed state" $
map
( \ps ->
let name = "from: " <> show ps.fromStatus
in Advance.mkTestTree name ps True
)
(tail $ Advance.advanceToFailedStateDueToTimeoutParameters 1)
]
)
(PermitVote $ ProposalLock (ResultTag 0) (ProposalId 42))
( ScriptContext
( Proposal.voteOnProposal
Proposal.VotingParameters
{ Proposal.voteFor = ResultTag 0
, Proposal.voteCount = 27
}
)
(Spending Proposal.stakeRef)
)
]
, group
"advancing"
[ group "successfully advance to next state" $
map
( \(name, initialState) ->
validatorSucceedsWith
name
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = initialState
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[
( ResultTag 0
, case initialState of
Draft -> 0
_ -> untag (def :: ProposalThresholds).execute + 1
)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
( Proposal.advanceProposalSuccess
Proposal.TransitionParameters
{ Proposal.initialProposalStatus = initialState
, Proposal.proposalStartingTime = ProposalStartingTime 0
}
)
(Spending Proposal.proposalRef)
)
)
[ ("Draft -> VotringReady", Draft)
, ("VotingReady -> Locked", VotingReady)
, ("Locked -> Finished", Locked)
]
, group "successfully advance to failed state: timeout" $
map
( \(name, initialState) ->
validatorSucceedsWith
name
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = initialState
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[
( ResultTag 0
, case initialState of
Draft -> 0
_ -> untag (def :: ProposalThresholds).vote + 1
)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
( Proposal.advanceProposalFailureTimeout
Proposal.TransitionParameters
{ Proposal.initialProposalStatus = initialState
, Proposal.proposalStartingTime = ProposalStartingTime 0
}
)
(Spending Proposal.proposalRef)
)
)
[ ("Draft -> Finished", Draft)
, ("VotingReady -> Finished", VotingReady)
, ("Locked -> Finished", Locked)
]
, validatorFailsWith
"illegal: insufficient votes"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = VotingReady
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 1)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
Proposal.advanceProposalInsufficientVotes
(Spending Proposal.proposalRef)
)
, validatorFailsWith
"illegal: initial state is Finished"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = Finished
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, untag (def :: ProposalThresholds).vote + 1)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
Proposal.advanceFinishedProposal
(Spending Proposal.proposalRef)
)
, validatorFailsWith
"illegal: with stake input"
(proposalValidator Shared.proposal)
( ProposalDatum
{ proposalId = ProposalId 0
, effects =
AssocMap.fromList
[ (ResultTag 0, AssocMap.empty)
, (ResultTag 1, AssocMap.empty)
]
, status = VotingReady
, cosigners = [signer]
, thresholds = def
, votes =
ProposalVotes
( AssocMap.fromList
[ (ResultTag 0, 0)
, (ResultTag 1, 0)
]
)
, timingConfig = def
, startingTime = ProposalStartingTime 0
}
)
AdvanceProposal
( ScriptContext
Proposal.advanceProposalWithInvalidOutputStake
(Spending Proposal.proposalRef)
)
]
illegalGroup =
group
"illegal"
[ Advance.mkTestTree
"insufficient votes"
Advance.insufficientVotesParameters
False
, Advance.mkTestTree
"initial state is Finished"
Advance.advanceFromFinishedParameters
False
, group
"invalid stake output"
$ do
nStake <- [1, 5]
ps <- tail $ Advance.invalidOutputStakeParameters nStake
let name =
"from " <> show ps.fromStatus <> "with "
<> show nStake
<> " stakes"
pure $ Advance.mkTestTree name ps False
]
in [draftGroup, legalGroup, illegalGroup]
, group "unlocking" $ do
proposalCount <- [1, 42]

View file

@ -13,22 +13,24 @@ module Test.Util (
updateMap,
sortMap,
sortValue,
blake2b_224,
pubKeyHashes,
userCredentials,
scriptCredentials,
) where
--------------------------------------------------------------------------------
import Prelude
--------------------------------------------------------------------------------
import Codec.Serialise (serialise)
import Data.ByteString.Lazy qualified as ByteString.Lazy
--------------------------------------------------------------------------------
import Crypto.Hash qualified as Crypto
import Data.Bifunctor (second)
import Data.ByteArray qualified as BA
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as C
import Data.ByteString.Lazy qualified as ByteString.Lazy
import Data.List (sortOn)
import Plutarch.Crypto (pblake2b_256)
import PlutusLedgerApi.V1 (Credential (PubKeyCredential, ScriptCredential), PubKeyHash (..), ValidatorHash (ValidatorHash))
import PlutusLedgerApi.V1.Interval qualified as PlutusTx
import PlutusLedgerApi.V1.Scripts (Datum (Datum), DatumHash (DatumHash))
import PlutusLedgerApi.V1.Value (Value (..))
@ -36,6 +38,7 @@ import PlutusTx.AssocMap qualified as AssocMap
import PlutusTx.Builtins qualified as PlutusTx
import PlutusTx.IsData qualified as PlutusTx
import PlutusTx.Ord qualified as PlutusTx
import Prelude
--------------------------------------------------------------------------------
@ -106,3 +109,25 @@ sortValue =
. fmap (second sortMap)
. AssocMap.toList
. getValue
--------------------------------------------------------------------------------
-- | Compute the hash of a given byte string using blake2b_224 algorithm.
blake2b_224 :: BS.ByteString -> BS.ByteString
blake2b_224 = BS.pack . BA.unpack . Crypto.hashWith Crypto.Blake2b_224
-- | An infinite list of blake2b_224 hashes.
blake2b_224Hashes :: [BS.ByteString]
blake2b_224Hashes = blake2b_224 . C.pack . show @Integer <$> [0 ..]
-- | An infinite list of *valid* 'PubKeyHash'.
pubKeyHashes :: [PubKeyHash]
pubKeyHashes = PubKeyHash . PlutusTx.toBuiltin <$> blake2b_224Hashes
-- | An infinite list of *valid* user credentials.
userCredentials :: [Credential]
userCredentials = PubKeyCredential <$> pubKeyHashes
-- | An infinite list of *valid* script credentials.
scriptCredentials :: [Credential]
scriptCredentials = ScriptCredential . ValidatorHash . PlutusTx.toBuiltin <$> blake2b_224Hashes

View file

@ -189,8 +189,11 @@ library agora-specs
Sample.Effect.TreasuryWithdrawal
Sample.Governor
Sample.Proposal
Sample.Proposal.Advance
Sample.Proposal.Cosign
Sample.Proposal.Shared
Sample.Proposal.UnlockStake
Sample.Proposal.Vote
Sample.Shared
Sample.Stake
Sample.Treasury

View file

@ -313,13 +313,13 @@ stakeValidator stake =
--
-- Validation strategy I have tried/considered so far:
-- 1. Check that the number of input stakes equals to the number of output stakes, and verify
-- that every input stake has an output stake with the exact same value and datum hash.
-- However this approach has a fatal vulnerability: let's say we have two totally identical stakes,
-- a malicious user can comsume these two stakes and remove GTs from one of them.
-- that there's an output stake with the exact same value and datum hash as the stake being
-- validated , However this approach has a fatal vulnerability: let's say we have two totally
-- identical stakes, a malicious user can comsume these two stakes and remove GTs from one of them.
-- 2. Perform the same checks as the last approch does, while also checking that every output stake is
-- valid(stakedAmount == actual value). However this requires that all the output stake datum are
-- included in the transaction, and we have to find and go through them one by one to access the
-- 'stakedAmount' fields, meaning that computationally this approach is *very* expensive.
-- 'stakedAmount' fields, meaning that computationally this approach is *very* expensive.
-- 3. The one implemented below. Find all the continuous input/output, sort them by 'datumHash', and
-- ensure that the two sorted lists are equal.
let ownInputs =

View file

@ -8,24 +8,57 @@ Agora/Stake/policy/stakeCreation,50939580,148729,2387
Agora/Stake/validator/stakeDepositWithdraw deposit,180222751,492217,5003
Agora/Stake/validator/stakeDepositWithdraw withdraw,180222751,492217,4991
Agora/Proposal/policy/proposalCreation,23140177,69194,1515
Agora/Proposal/validator/cosignature/proposal,240482868,674626,8525
Agora/Proposal/validator/cosignature/stake,136781411,336612,5528
Agora/Proposal/validator/voting/proposal,243946100,678901,8443
Agora/Proposal/validator/voting/stake,128972262,348186,5489
Agora/Proposal/validator/advancing/successfully advance to next state/Draft -> VotringReady,219342631,620576,8350
Agora/Proposal/validator/advancing/successfully advance to next state/VotingReady -> Locked,247748475,699343,8359
Agora/Proposal/validator/advancing/successfully advance to next state/Locked -> Finished,236509366,666512,8359
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Draft -> Finished,214287939,609855,8352
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/VotingReady -> Finished,233681921,660502,8353
Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Locked -> Finished,234812899,662906,8353
Agora/Proposal/validator/cosignature/legal/with 1 cosigners/propsoal,237484909,663370,8471
Agora/Proposal/validator/cosignature/legal/with 1 cosigners/stake,123296365,319226,5470
Agora/Proposal/validator/cosignature/legal/with 5 cosigners/propsoal,675336848,1882805,11101
Agora/Proposal/validator/cosignature/legal/with 5 cosigners/stake,554091553,1461634,7980
Agora/Proposal/validator/cosignature/legal/with 10 cosigners/propsoal,1336993992,3667352,14389
Agora/Proposal/validator/cosignature/legal/with 10 cosigners/stake,1102063894,2914419,11117
Agora/Proposal/validator/cosignature/illegal/duplicate cosigners/stake,123296365,319226,5470
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 1 cosigners/status: VotingReady/stake,123296365,319226,5470
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 1 cosigners/status: Locked/stake,123296365,319226,5470
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 1 cosigners/status: Finished/stake,123296365,319226,5470
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 5 cosigners/status: VotingReady/stake,554091553,1461634,7980
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 5 cosigners/status: Locked/stake,554091553,1461634,7980
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 5 cosigners/status: Finished/stake,554091553,1461634,7980
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 10 cosigners/status: VotingReady/stake,1102063894,2914419,11117
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 10 cosigners/status: Locked/stake,1102063894,2914419,11117
Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 10 cosigners/status: Finished/stake,1102063894,2914419,11117
Agora/Proposal/validator/voting/legal/propsoal,247594094,689025,8443
Agora/Proposal/validator/voting/legal/stake,141390725,374830,5489
Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to next state/propsoal,222990625,630700,8426
Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to next state/stake,123296365,319226,5467
Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to failed state/propsoal,217935933,619979,8428
Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to failed state/stake,123296365,319226,5469
Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/illegal/insufficient cosigns/stake,117222929,305504,5397
Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to next state/propsoal,600833052,1725797,11249
Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to next state/stake,526604275,1381680,8170
Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to failed state/propsoal,240305281,682043,8789
Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to failed state/stake,123296365,319226,5710
Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/illegal/insufficient cosigns/stake,445454241,1167344,7819
Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to next state/propsoal,1164574757,3363392,14778
Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to next state/stake,1154814568,3068129,11548
Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to failed state/propsoal,268266966,759623,9242
Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to failed state/stake,123296365,319226,6012
Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/illegal/insufficient cosigns/stake,1024215053,2732615,10845
Agora/Proposal/validator/advancing/legal/advance to next state/from: VotingReady/propsoal,251396469,709467,8435
Agora/Proposal/validator/advancing/legal/advance to next state/from: VotingReady/stake,123296365,319226,5474
Agora/Proposal/validator/advancing/legal/advance to next state/from: Locked/propsoal,240157360,676636,8435
Agora/Proposal/validator/advancing/legal/advance to next state/from: Locked/stake,123296365,319226,5474
Agora/Proposal/validator/advancing/legal/advance to failed state/from: VotingReady/propsoal,237329915,670626,8429
Agora/Proposal/validator/advancing/legal/advance to failed state/from: VotingReady/stake,123296365,319226,5470
Agora/Proposal/validator/advancing/legal/advance to failed state/from: Locked/propsoal,238460893,673030,8429
Agora/Proposal/validator/advancing/legal/advance to failed state/from: Locked/stake,123296365,319226,5470
Agora/Proposal/validator/advancing/illegal/insufficient votes/stake,123296365,319226,5470
Agora/Proposal/validator/advancing/illegal/initial state is Finished/stake,123296365,319226,5462
"Agora/Proposal/validator/unlocking/legal/1 proposals, voter, unlock stake + retract votes, VotingReady",245987595,688711,8403
"Agora/Proposal/validator/unlocking/legal/1 proposals, creator, unlock stake, Finished",215263333,612711,8405
"Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/1 proposals, voter, unlock stake, Finished",212560614,604622,8407
"Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/1 proposals, voter, unlock stake, Locked",212560614,604622,8407
"Agora/Proposal/validator/unlocking/legal/42 proposals, voter, unlock stake + retract votes, VotingReady",1775652167,5199490,29510
"Agora/Proposal/validator/unlocking/legal/42 proposals, creator, unlock stake, Finished",1448293766,4317963,29694
"Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/42 proposals, voter, unlock stake, Finished",1340653392,3978430,29678
"Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/42 proposals, voter, unlock stake, Locked",1340653392,3978430,29678
"Agora/Proposal/validator/unlocking/legal/42 proposals, voter, unlock stake + retract votes, VotingReady",1775652167,5199490,29511
"Agora/Proposal/validator/unlocking/legal/42 proposals, creator, unlock stake, Finished",1448293766,4317963,29695
"Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/42 proposals, voter, unlock stake, Finished",1340653392,3978430,29679
"Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/42 proposals, voter, unlock stake, Locked",1340653392,3978430,29679
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,21017788,55883,806
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,33204186,88241,900
Agora/Treasury/Validator/Positive/Allows for effect changes,31556709,81546,1452
@ -33,5 +66,5 @@ Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,21017788,55883,80
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,33204186,88241,900
Agora/Governor/policy/GST minting,51007235,144191,2034
Agora/Governor/validator/proposal creation,309689999,834675,9064
Agora/Governor/validator/GATs minting,418560845,1137908,9187
Agora/Governor/validator/GATs minting,421016677,1141838,9187
Agora/Governor/validator/mutate governor state,88986020,248491,8662

1 name cpu mem size
8 Agora/Stake/validator/stakeDepositWithdraw deposit 180222751 492217 5003
9 Agora/Stake/validator/stakeDepositWithdraw withdraw 180222751 492217 4991
10 Agora/Proposal/policy/proposalCreation 23140177 69194 1515
11 Agora/Proposal/validator/cosignature/proposal Agora/Proposal/validator/cosignature/legal/with 1 cosigners/propsoal 240482868 237484909 674626 663370 8525 8471
12 Agora/Proposal/validator/cosignature/stake Agora/Proposal/validator/cosignature/legal/with 1 cosigners/stake 136781411 123296365 336612 319226 5528 5470
13 Agora/Proposal/validator/voting/proposal Agora/Proposal/validator/cosignature/legal/with 5 cosigners/propsoal 243946100 675336848 678901 1882805 8443 11101
14 Agora/Proposal/validator/voting/stake Agora/Proposal/validator/cosignature/legal/with 5 cosigners/stake 128972262 554091553 348186 1461634 5489 7980
15 Agora/Proposal/validator/advancing/successfully advance to next state/Draft -> VotringReady Agora/Proposal/validator/cosignature/legal/with 10 cosigners/propsoal 219342631 1336993992 620576 3667352 8350 14389
16 Agora/Proposal/validator/advancing/successfully advance to next state/VotingReady -> Locked Agora/Proposal/validator/cosignature/legal/with 10 cosigners/stake 247748475 1102063894 699343 2914419 8359 11117
17 Agora/Proposal/validator/advancing/successfully advance to next state/Locked -> Finished Agora/Proposal/validator/cosignature/illegal/duplicate cosigners/stake 236509366 123296365 666512 319226 8359 5470
18 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Draft -> Finished Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 1 cosigners/status: VotingReady/stake 214287939 123296365 609855 319226 8352 5470
19 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/VotingReady -> Finished Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 1 cosigners/status: Locked/stake 233681921 123296365 660502 319226 8353 5470
20 Agora/Proposal/validator/advancing/successfully advance to failed state: timeout/Locked -> Finished Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 1 cosigners/status: Finished/stake 234812899 123296365 662906 319226 8353 5470
21 Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 5 cosigners/status: VotingReady/stake 554091553 1461634 7980
22 Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 5 cosigners/status: Locked/stake 554091553 1461634 7980
23 Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 5 cosigners/status: Finished/stake 554091553 1461634 7980
24 Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 10 cosigners/status: VotingReady/stake 1102063894 2914419 11117
25 Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 10 cosigners/status: Locked/stake 1102063894 2914419 11117
26 Agora/Proposal/validator/cosignature/illegal/proposal status not Draft/with 10 cosigners/status: Finished/stake 1102063894 2914419 11117
27 Agora/Proposal/validator/voting/legal/propsoal 247594094 689025 8443
28 Agora/Proposal/validator/voting/legal/stake 141390725 374830 5489
29 Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to next state/propsoal 222990625 630700 8426
30 Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to next state/stake 123296365 319226 5467
31 Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to failed state/propsoal 217935933 619979 8428
32 Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/legal/to failed state/stake 123296365 319226 5469
33 Agora/Proposal/validator/advancing/from draft/with 1 cosigner(s)/illegal/insufficient cosigns/stake 117222929 305504 5397
34 Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to next state/propsoal 600833052 1725797 11249
35 Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to next state/stake 526604275 1381680 8170
36 Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to failed state/propsoal 240305281 682043 8789
37 Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/legal/to failed state/stake 123296365 319226 5710
38 Agora/Proposal/validator/advancing/from draft/with 5 cosigner(s)/illegal/insufficient cosigns/stake 445454241 1167344 7819
39 Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to next state/propsoal 1164574757 3363392 14778
40 Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to next state/stake 1154814568 3068129 11548
41 Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to failed state/propsoal 268266966 759623 9242
42 Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/legal/to failed state/stake 123296365 319226 6012
43 Agora/Proposal/validator/advancing/from draft/with 10 cosigner(s)/illegal/insufficient cosigns/stake 1024215053 2732615 10845
44 Agora/Proposal/validator/advancing/legal/advance to next state/from: VotingReady/propsoal 251396469 709467 8435
45 Agora/Proposal/validator/advancing/legal/advance to next state/from: VotingReady/stake 123296365 319226 5474
46 Agora/Proposal/validator/advancing/legal/advance to next state/from: Locked/propsoal 240157360 676636 8435
47 Agora/Proposal/validator/advancing/legal/advance to next state/from: Locked/stake 123296365 319226 5474
48 Agora/Proposal/validator/advancing/legal/advance to failed state/from: VotingReady/propsoal 237329915 670626 8429
49 Agora/Proposal/validator/advancing/legal/advance to failed state/from: VotingReady/stake 123296365 319226 5470
50 Agora/Proposal/validator/advancing/legal/advance to failed state/from: Locked/propsoal 238460893 673030 8429
51 Agora/Proposal/validator/advancing/legal/advance to failed state/from: Locked/stake 123296365 319226 5470
52 Agora/Proposal/validator/advancing/illegal/insufficient votes/stake 123296365 319226 5470
53 Agora/Proposal/validator/advancing/illegal/initial state is Finished/stake 123296365 319226 5462
54 Agora/Proposal/validator/unlocking/legal/1 proposals, voter, unlock stake + retract votes, VotingReady 245987595 688711 8403
55 Agora/Proposal/validator/unlocking/legal/1 proposals, creator, unlock stake, Finished 215263333 612711 8405
56 Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/1 proposals, voter, unlock stake, Finished 212560614 604622 8407
57 Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/1 proposals, voter, unlock stake, Locked 212560614 604622 8407
58 Agora/Proposal/validator/unlocking/legal/42 proposals, voter, unlock stake + retract votes, VotingReady 1775652167 5199490 29510 29511
59 Agora/Proposal/validator/unlocking/legal/42 proposals, creator, unlock stake, Finished 1448293766 4317963 29694 29695
60 Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/42 proposals, voter, unlock stake, Finished 1340653392 3978430 29678 29679
61 Agora/Proposal/validator/unlocking/legal/voter unlocks stake after voting/42 proposals, voter, unlock stake, Locked 1340653392 3978430 29678 29679
62 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple 21017788 55883 806
63 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 33204186 88241 900
64 Agora/Treasury/Validator/Positive/Allows for effect changes 31556709 81546 1452
66 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 33204186 88241 900
67 Agora/Governor/policy/GST minting 51007235 144191 2034
68 Agora/Governor/validator/proposal creation 309689999 834675 9064
69 Agora/Governor/validator/GATs minting 418560845 421016677 1137908 1141838 9187
70 Agora/Governor/validator/mutate governor state 88986020 248491 8662