Merge branch 'staging' into seungheonoh/updatepse
This commit is contained in:
commit
dac9dc2394
42 changed files with 2344 additions and 1716 deletions
|
|
@ -0,0 +1,66 @@
|
|||
module Sample.AuthorityToken.UnauthorizedMintingExploit (
|
||||
Parameters (..),
|
||||
exploit,
|
||||
mkTestCase,
|
||||
) where
|
||||
|
||||
import Control.Exception (assert)
|
||||
import Plutarch.Context (input, mint, normalizeValue, output, script, withValue)
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value
|
||||
import Sample.Shared (authorityTokenPolicy, authorityTokenSymbol, minAda)
|
||||
import Test.Specification (SpecificationTree, testPolicy)
|
||||
import Test.Util (CombinableBuilder, mkMinting, validatorHashes)
|
||||
|
||||
data Parameters = Parameters
|
||||
{ burntGAT :: Int
|
||||
, mintedGAT :: Int
|
||||
}
|
||||
|
||||
exploit ::
|
||||
forall b.
|
||||
CombinableBuilder b =>
|
||||
Parameters ->
|
||||
b
|
||||
exploit (Parameters burntGAT mintedGAT) =
|
||||
assert (burntGAT > mintedGAT && mintedGAT > 0) $
|
||||
effectInputBuilder <> maliciousGATOutputBuilder
|
||||
where
|
||||
(effectScriptHashes, rest) = splitAt burntGAT validatorHashes
|
||||
maliciousScripts = take mintedGAT rest
|
||||
|
||||
gatValue hash =
|
||||
Value.singleton
|
||||
authorityTokenSymbol
|
||||
(validatorHashToTokenName hash)
|
||||
|
||||
mkGATUTxO hash =
|
||||
mconcat
|
||||
[ script hash
|
||||
, withValue $ normalizeValue $ minAda <> gatValue hash 1
|
||||
]
|
||||
|
||||
effectInputBuilder =
|
||||
foldMap
|
||||
( \effectHash ->
|
||||
mconcat
|
||||
[ mint $ gatValue effectHash $ negate 1
|
||||
, input $ mkGATUTxO effectHash
|
||||
]
|
||||
)
|
||||
effectScriptHashes
|
||||
|
||||
maliciousGATOutputBuilder =
|
||||
foldMap
|
||||
( \scriptHash ->
|
||||
mconcat
|
||||
[ mint $ gatValue scriptHash 1
|
||||
, output $ mkGATUTxO scriptHash
|
||||
]
|
||||
)
|
||||
maliciousScripts
|
||||
|
||||
mkTestCase :: String -> Parameters -> SpecificationTree
|
||||
mkTestCase name ps =
|
||||
testPolicy False name authorityTokenPolicy () $
|
||||
mkMinting exploit ps authorityTokenSymbol
|
||||
|
|
@ -17,12 +17,12 @@ import Agora.Effect.GovernorMutation (
|
|||
import Agora.Governor (GovernorDatum (..), GovernorRedeemer (MutateGovernor))
|
||||
import Agora.Proposal (ProposalId (..), ProposalThresholds (..))
|
||||
import Agora.SafeMoney (AuthorityTokenTag)
|
||||
import Agora.Utils (validatorHashToTokenName)
|
||||
import Data.Default.Class (Default (def))
|
||||
import Data.Map ((!))
|
||||
import Data.Tagged (Tagged (..))
|
||||
import Plutarch.Api.V2 (validatorHash)
|
||||
import Plutarch.Extra.AssetClass (AssetClass (AssetClass), assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import PlutusLedgerApi.V1 qualified as Interval (always)
|
||||
import PlutusLedgerApi.V1.Address (scriptHashAddress)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value (
|
||||
|
|
@ -114,7 +114,7 @@ mkEffectTxInfo newGovDatum =
|
|||
, nextProposalId = ProposalId 0
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = 3
|
||||
, maximumCreatedProposalsPerStake = 3
|
||||
}
|
||||
governorInputDatum :: Datum
|
||||
governorInputDatum = Datum $ toBuiltinData governorInputDatum'
|
||||
|
|
@ -186,7 +186,7 @@ validNewGovernorDatum =
|
|||
, nextProposalId = ProposalId 42
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = 3
|
||||
, maximumCreatedProposalsPerStake = 3
|
||||
}
|
||||
|
||||
invalidNewGovernorDatum :: GovernorDatum
|
||||
|
|
@ -199,5 +199,5 @@ invalidNewGovernorDatum =
|
|||
, nextProposalId = ProposalId 42
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = 3
|
||||
, maximumCreatedProposalsPerStake = 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ import PlutusLedgerApi.V2 (
|
|||
ValidatorHash,
|
||||
)
|
||||
import Sample.Shared (
|
||||
deterministicTracingConfing,
|
||||
deterministicTracingConfig,
|
||||
minAda,
|
||||
)
|
||||
import Sample.Shared qualified as Shared
|
||||
|
|
@ -72,7 +72,7 @@ data Parameters = Parameters
|
|||
-- ^ Whether the 'GovernorDatum.proposalThresholds' field of the output
|
||||
-- governor datum is valid or not.
|
||||
, datumMaxTimeRangeWidthValid :: Bool
|
||||
-- ^ Whether the 'GovernorDatum.maximumProposalsPerStake'field of the
|
||||
-- ^ Whether the 'GovernorDatum.maximumCreatedProposalsPerStake'field of the
|
||||
-- output governor datum is valid or not.
|
||||
, datumTimingConfigValid :: Bool
|
||||
-- ^ Whether the 'GovernorDatum.proposalTimings'field of the output
|
||||
|
|
@ -96,7 +96,7 @@ validGovernorOutputDatum =
|
|||
, nextProposalId = ProposalId 0
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = 3
|
||||
, maximumCreatedProposalsPerStake = 3
|
||||
}
|
||||
|
||||
invalidProposalThresholds :: ProposalThresholds
|
||||
|
|
@ -106,7 +106,7 @@ invalidMaxTimeRangeWidth :: MaxTimeRangeWidth
|
|||
invalidMaxTimeRangeWidth = MaxTimeRangeWidth 0
|
||||
|
||||
invalidProposalTimings :: ProposalTimingConfig
|
||||
invalidProposalTimings = ProposalTimingConfig (-1) (-1) (-1) (-1)
|
||||
invalidProposalTimings = ProposalTimingConfig (-1) (-1) (-1) (-1) (-1) (-1)
|
||||
|
||||
witnessRef :: TxOutRef
|
||||
witnessRef = TxOutRef "b0353c22b0bd6c5296a8eef160ba25d90b5dc82a9bb8bdaa6823ffc19515d6ad" 0
|
||||
|
|
@ -124,7 +124,7 @@ scripts =
|
|||
(fmap (view #script) . view #scripts)
|
||||
( runLinker
|
||||
linker
|
||||
(agoraScripts deterministicTracingConfing)
|
||||
(agoraScripts deterministicTracingConfig)
|
||||
governor
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ module Sample.Governor.Mutate (
|
|||
|
||||
import Agora.Governor (GovernorDatum (..), GovernorRedeemer (MutateGovernor))
|
||||
import Agora.Proposal (ProposalId (ProposalId), ProposalThresholds (..))
|
||||
import Agora.Utils (scriptHashToTokenName)
|
||||
import Data.Default (def)
|
||||
import Data.Map ((!))
|
||||
import Plutarch.Api.V2 (PMintingPolicy, mintingPolicySymbol, mkMintingPolicy, validatorHash)
|
||||
|
|
@ -33,6 +32,7 @@ import Plutarch.Context (
|
|||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (scriptHashToTokenName)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value
|
||||
import PlutusLedgerApi.V2 (
|
||||
CurrencySymbol (CurrencySymbol),
|
||||
|
|
@ -105,7 +105,7 @@ governorInputDatum =
|
|||
, nextProposalId = ProposalId 0
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = 3
|
||||
, maximumCreatedProposalsPerStake = 3
|
||||
}
|
||||
|
||||
mkGovernorOutputDatum ::
|
||||
|
|
@ -115,7 +115,7 @@ mkGovernorOutputDatum DatumValid =
|
|||
Just $
|
||||
toData $
|
||||
governorInputDatum
|
||||
{ maximumProposalsPerStake = 4
|
||||
{ maximumCreatedProposalsPerStake = 4
|
||||
}
|
||||
mkGovernorOutputDatum ValueInvalid =
|
||||
let invalidProposalThresholds =
|
||||
|
|
|
|||
|
|
@ -35,12 +35,14 @@ module Sample.Proposal.Advance (
|
|||
mkMintGATsWithoutTagBundle,
|
||||
mkBadGovernorOutputDatumBundle,
|
||||
mkUnexpectedOutputStakeBundles,
|
||||
mkFastforwardToFinishBundles,
|
||||
mkBadGovernorRedeemerBundle,
|
||||
) where
|
||||
|
||||
import Agora.Governor (
|
||||
Governor (..),
|
||||
GovernorDatum (..),
|
||||
GovernorRedeemer (MintGATs),
|
||||
GovernorRedeemer (CreateProposal, MintGATs),
|
||||
)
|
||||
import Agora.Proposal (
|
||||
ProposalDatum (..),
|
||||
|
|
@ -67,7 +69,6 @@ import Agora.SafeMoney (AuthorityTokenTag, GTTag)
|
|||
import Agora.Stake (
|
||||
StakeDatum (..),
|
||||
)
|
||||
import Agora.Utils (scriptHashToTokenName)
|
||||
import Control.Applicative (liftA2)
|
||||
import Control.Monad.State (execState, modify, when)
|
||||
import Data.Default (def)
|
||||
|
|
@ -85,10 +86,12 @@ import Plutarch.Context (
|
|||
timeRange,
|
||||
withDatum,
|
||||
withInlineDatum,
|
||||
withRedeemer,
|
||||
withRef,
|
||||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (AssetClass (AssetClass), assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (scriptHashToTokenName)
|
||||
import Plutarch.Lift (PLifted, PUnsafeLiftDecl)
|
||||
import PlutusLedgerApi.V2 (
|
||||
Credential (PubKeyCredential),
|
||||
|
|
@ -100,6 +103,7 @@ import PlutusLedgerApi.V2 (
|
|||
TxOutRef (TxOutRef),
|
||||
ValidatorHash,
|
||||
)
|
||||
import PlutusTx qualified
|
||||
import Sample.Proposal.Shared (
|
||||
governorTxRef,
|
||||
proposalTxRef,
|
||||
|
|
@ -164,9 +168,18 @@ data ParameterBundle = ParameterBundle
|
|||
}
|
||||
|
||||
-- | Everything about the generated governor stuff.
|
||||
newtype GovernorParameters = GovernorParameters
|
||||
data GovernorParameters = forall
|
||||
(redeemer :: Type)
|
||||
(predeemer :: PType).
|
||||
( PUnsafeLiftDecl predeemer
|
||||
, PLifted predeemer ~ redeemer
|
||||
, PIsData predeemer
|
||||
, PlutusTx.ToData redeemer
|
||||
) =>
|
||||
GovernorParameters
|
||||
{ invalidGovernorOutputDatum :: Bool
|
||||
-- ^ The output governor datum will be changed.
|
||||
, governorRedeemer :: redeemer
|
||||
}
|
||||
|
||||
-- | Everything about the generated authority token stuff.
|
||||
|
|
@ -278,7 +291,7 @@ mkVotes ps =
|
|||
|
||||
-- | The starting time of every generated proposal.
|
||||
proposalStartingTime :: POSIXTime
|
||||
proposalStartingTime = 0
|
||||
proposalStartingTime = 100
|
||||
|
||||
-- | Create the input proposal datum given the parameters.
|
||||
mkProposalInputDatum :: ProposalParameters -> ProposalDatum
|
||||
|
|
@ -413,14 +426,14 @@ governorInputDatum =
|
|||
, nextProposalId = ProposalId 42
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = 3
|
||||
, maximumCreatedProposalsPerStake = 3
|
||||
}
|
||||
|
||||
-- | Create the output governor datum given the parameters.
|
||||
mkGovernorOutputDatum :: GovernorParameters -> GovernorDatum
|
||||
mkGovernorOutputDatum ps =
|
||||
if ps.invalidGovernorOutputDatum
|
||||
then governorInputDatum {maximumProposalsPerStake = 15}
|
||||
then governorInputDatum {maximumCreatedProposalsPerStake = 15}
|
||||
else governorInputDatum
|
||||
|
||||
-- | Reference to the governor UTXO.
|
||||
|
|
@ -431,7 +444,7 @@ governorRef = TxOutRef governorTxRef 2
|
|||
governor validator.
|
||||
-}
|
||||
mkGovernorBuilder :: forall b. CombinableBuilder b => GovernorParameters -> b
|
||||
mkGovernorBuilder ps =
|
||||
mkGovernorBuilder ps@(GovernorParameters _ redeemer) =
|
||||
let gst = assetClassValue governorAssetClass 1
|
||||
value = sortValue $ gst <> minAda
|
||||
in mconcat
|
||||
|
|
@ -441,6 +454,7 @@ mkGovernorBuilder ps =
|
|||
, withValue value
|
||||
, withRef governorRef
|
||||
, withDatum governorInputDatum
|
||||
, withRedeemer redeemer
|
||||
]
|
||||
, output $
|
||||
mconcat
|
||||
|
|
@ -451,12 +465,6 @@ mkGovernorBuilder ps =
|
|||
]
|
||||
]
|
||||
|
||||
{- | The proposal redeemer used to spend the governor UTXO, which is always
|
||||
'MintGATs' in this case.
|
||||
-}
|
||||
governorRedeemer :: GovernorRedeemer
|
||||
governorRedeemer = MintGATs
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- * Authority Token
|
||||
|
|
@ -537,16 +545,22 @@ mkTestTree name pb val =
|
|||
proposalInputDatum
|
||||
proposalRedeemer
|
||||
(spend proposalRef)
|
||||
|
||||
governor =
|
||||
maybe [] singleton $
|
||||
testValidator
|
||||
(fromJust val.forGovernorValidator)
|
||||
"governor"
|
||||
governorValidator
|
||||
governorInputDatum
|
||||
governorRedeemer
|
||||
(spend governorRef)
|
||||
<$ pb.governorParameters
|
||||
maybe
|
||||
[]
|
||||
( singleton
|
||||
. ( \(GovernorParameters _ governorRedeemer) ->
|
||||
testValidator
|
||||
(fromJust val.forGovernorValidator)
|
||||
"governor"
|
||||
governorValidator
|
||||
governorInputDatum
|
||||
governorRedeemer
|
||||
(spend governorRef)
|
||||
)
|
||||
)
|
||||
(pb.governorParameters)
|
||||
|
||||
authority = case pb.authorityTokenParameters of
|
||||
[] -> []
|
||||
|
|
@ -715,20 +729,20 @@ mkMockEffects useAuthScript n = effects
|
|||
effectsPerGroup
|
||||
(zip effectScripts effectMetadata)
|
||||
|
||||
numberOfVotesThatExceedsTheMinimumRequirement :: Integer
|
||||
numberOfVotesThatExceedsTheMinimumRequirement =
|
||||
untag (def @ProposalThresholds).execute + 1
|
||||
numberOfVotesThatJustMeetsTheMinimumRequirement :: Integer
|
||||
numberOfVotesThatJustMeetsTheMinimumRequirement =
|
||||
untag (def @ProposalThresholds).execute
|
||||
|
||||
mkWinnerVotes :: Index -> (Winner, Integer)
|
||||
mkWinnerVotes idx =
|
||||
( EffectAt idx
|
||||
, numberOfVotesThatExceedsTheMinimumRequirement
|
||||
, numberOfVotesThatJustMeetsTheMinimumRequirement
|
||||
)
|
||||
|
||||
ambiguousWinnerVotes :: (Winner, Integer)
|
||||
ambiguousWinnerVotes =
|
||||
( All
|
||||
, numberOfVotesThatExceedsTheMinimumRequirement
|
||||
, numberOfVotesThatJustMeetsTheMinimumRequirement
|
||||
)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
@ -826,6 +840,7 @@ mkValidToNextStateBundle nCosigners nEffects authScript from =
|
|||
gov =
|
||||
GovernorParameters
|
||||
{ invalidGovernorOutputDatum = False
|
||||
, governorRedeemer = MintGATs
|
||||
}
|
||||
in b
|
||||
{ governorParameters = Just gov
|
||||
|
|
@ -1065,4 +1080,47 @@ mkBadGovernorOutputDatumBundle nCosigners nEffects =
|
|||
}
|
||||
where
|
||||
template = mkValidFromLockedBundle nCosigners nEffects
|
||||
gov = GovernorParameters True
|
||||
gov = GovernorParameters True MintGATs
|
||||
|
||||
mkBadGovernorRedeemerBundle ::
|
||||
Word ->
|
||||
Word ->
|
||||
ParameterBundle
|
||||
mkBadGovernorRedeemerBundle nCosigners nEffects =
|
||||
template
|
||||
{ governorParameters = Just gov
|
||||
}
|
||||
where
|
||||
template = mkValidFromLockedBundle nCosigners nEffects
|
||||
gov = GovernorParameters False CreateProposal
|
||||
|
||||
mkFastforwardToFinishBundles ::
|
||||
Word ->
|
||||
Word ->
|
||||
[ParameterBundle]
|
||||
mkFastforwardToFinishBundles nCosigners nEffects = updateTemplate <$> templates
|
||||
where
|
||||
templates = mkValidToFailedStateBundles nCosigners nEffects
|
||||
mkMaliciousTimRange =
|
||||
let lb = proposalStartingTime - 1
|
||||
dub =
|
||||
1
|
||||
+ proposalStartingTime
|
||||
+ (def :: ProposalTimingConfig).draftTime
|
||||
vub =
|
||||
dub
|
||||
+ (def :: ProposalTimingConfig).votingTime
|
||||
+ (def :: ProposalTimingConfig).lockingTime
|
||||
lub =
|
||||
vub
|
||||
+ (def :: ProposalTimingConfig).executingTime
|
||||
go Draft = (lb, dub)
|
||||
go VotingReady = (lb, vub)
|
||||
go Locked = (lb, lub)
|
||||
go Finished = error "cannot advance from Finished"
|
||||
in uncurry closedBoundedInterval . go
|
||||
updateTemplate template =
|
||||
template
|
||||
{ transactionTimeRange =
|
||||
mkMaliciousTimRange template.proposalParameters.fromStatus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ import Agora.Proposal.Time (
|
|||
)
|
||||
import Agora.SafeMoney (GTTag)
|
||||
import Agora.Stake (
|
||||
ProposalLock (Cosigned, Created),
|
||||
ProposalAction (Cosigned, Created),
|
||||
ProposalLock (ProposalLock),
|
||||
StakeDatum (..),
|
||||
StakeRedeemer (PermitVote),
|
||||
)
|
||||
|
|
@ -196,7 +197,7 @@ mkStakeInputDatum ps =
|
|||
amount = mkStakeAmount sps.gtAmount
|
||||
owner = mkStakeOwner sps.stakeOwner
|
||||
locks = case sps.stakeOwner of
|
||||
Creator -> [Created defProposalId]
|
||||
Creator -> [ProposalLock defProposalId Created]
|
||||
_ -> []
|
||||
in StakeDatum
|
||||
{ stakedAmount = amount
|
||||
|
|
@ -212,7 +213,7 @@ mkStakeOuputDatum ps =
|
|||
locks =
|
||||
if sps.dontUpdateLocks
|
||||
then inpDatum.lockedBy
|
||||
else Cosigned defProposalId : inpDatum.lockedBy
|
||||
else ProposalLock defProposalId Cosigned : inpDatum.lockedBy
|
||||
in inpDatum {lockedBy = locks}
|
||||
|
||||
stakeRedeemer :: StakeRedeemer
|
||||
|
|
|
|||
|
|
@ -17,12 +17,19 @@ module Sample.Proposal.Create (
|
|||
timeRangeNotTightParameters,
|
||||
timeRangeNotClosedParameters,
|
||||
invalidProposalStatusParameters,
|
||||
fakeSSTParameters,
|
||||
wrongGovernorRedeemer,
|
||||
wrongGovernorRedeemer1,
|
||||
) where
|
||||
|
||||
import Agora.Governor (
|
||||
Governor (..),
|
||||
GovernorDatum (..),
|
||||
GovernorRedeemer (CreateProposal),
|
||||
GovernorRedeemer (
|
||||
CreateProposal,
|
||||
MintGATs,
|
||||
MutateGovernor
|
||||
),
|
||||
)
|
||||
import Agora.Proposal (
|
||||
ProposalDatum (..),
|
||||
|
|
@ -40,7 +47,8 @@ import Agora.Proposal.Time (
|
|||
)
|
||||
import Agora.SafeMoney (GTTag)
|
||||
import Agora.Stake (
|
||||
ProposalLock (..),
|
||||
ProposalAction (Created, Voted),
|
||||
ProposalLock (ProposalLock),
|
||||
StakeDatum (..),
|
||||
StakeRedeemer (PermitVote),
|
||||
)
|
||||
|
|
@ -63,10 +71,14 @@ import Plutarch.Context (
|
|||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value
|
||||
import PlutusLedgerApi.V2 (
|
||||
Credential (PubKeyCredential),
|
||||
POSIXTime (POSIXTime),
|
||||
POSIXTimeRange,
|
||||
Redeemer (Redeemer),
|
||||
ToData (toBuiltinData),
|
||||
TxOutRef (TxOutRef),
|
||||
always,
|
||||
)
|
||||
|
|
@ -85,6 +97,7 @@ import Sample.Shared (
|
|||
signer,
|
||||
signer2,
|
||||
stakeAssetClass,
|
||||
stakeSymbol,
|
||||
stakeValidator,
|
||||
stakeValidatorHash,
|
||||
)
|
||||
|
|
@ -95,6 +108,7 @@ import Test.Util (
|
|||
mkMinting,
|
||||
mkSpending,
|
||||
sortValue,
|
||||
validatorHashes,
|
||||
)
|
||||
|
||||
-- | Parameters for creating a proposal.
|
||||
|
|
@ -115,11 +129,15 @@ data Parameters = Parameters
|
|||
-- ^ Is 'TxInfo.validTimeRange' closed?
|
||||
, proposalStatus :: ProposalStatus
|
||||
-- ^ The status of the newly created proposal.
|
||||
, fakeSST :: Bool
|
||||
-- ^ Whether to use SST that doesn't belong to the stake validator.
|
||||
, governorRedeemer :: Redeemer
|
||||
-- ^ The redeemer used to spend the governor.
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | See 'GovernorDatum.maximumProposalsPerStake'.
|
||||
-- | See 'GovernorDatum.maximumCreatedProposalsPerStake'.
|
||||
maxProposalPerStake :: Integer
|
||||
maxProposalPerStake = 3
|
||||
|
||||
|
|
@ -143,7 +161,7 @@ alteredStakeOwner = PubKeyCredential signer2
|
|||
|
||||
-- | Locks the stake that the input stake already has.
|
||||
defLocks :: [ProposalLock]
|
||||
defLocks = [Created (ProposalId 0)]
|
||||
defLocks = [ProposalLock (ProposalId 0) Created]
|
||||
|
||||
-- | The effect of the newly created proposal.
|
||||
defEffects :: StrictMap.Map ResultTag ProposalEffectGroup
|
||||
|
|
@ -164,7 +182,7 @@ governorInputDatum =
|
|||
, nextProposalId = thisProposalId
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = maxProposalPerStake
|
||||
, maximumCreatedProposalsPerStake = maxProposalPerStake
|
||||
}
|
||||
|
||||
-- | Create governor output datum given the parameters.
|
||||
|
|
@ -179,7 +197,7 @@ mkGovernorOutputDatum ps =
|
|||
, nextProposalId = nextPid
|
||||
, proposalTimings = def
|
||||
, createProposalTimeRangeMaxWidth = def
|
||||
, maximumProposalsPerStake = maxProposalPerStake
|
||||
, maximumCreatedProposalsPerStake = maxProposalPerStake
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
@ -190,7 +208,7 @@ mkStakeInputDatum ps =
|
|||
let locks =
|
||||
if ps.createdMoreThanMaximumProposals
|
||||
then
|
||||
Created . ProposalId
|
||||
flip ProposalLock Created . ProposalId
|
||||
<$> take
|
||||
(fromInteger maxProposalPerStake)
|
||||
[1 ..]
|
||||
|
|
@ -209,10 +227,10 @@ mkStakeOutputDatum ps =
|
|||
newLocks =
|
||||
if ps.invalidNewLocks
|
||||
then
|
||||
[ Voted thisProposalId (ResultTag 0)
|
||||
, Voted thisProposalId (ResultTag 1)
|
||||
[ ProposalLock thisProposalId $ Voted (ResultTag 0) 100
|
||||
, ProposalLock thisProposalId $ Voted (ResultTag 1) 100
|
||||
]
|
||||
else [Created thisProposalId]
|
||||
else [ProposalLock thisProposalId Created]
|
||||
locks = newLocks <> inputDatum.lockedBy
|
||||
newOwner = mkOwner ps
|
||||
in inputDatum
|
||||
|
|
@ -289,6 +307,30 @@ createProposal ps = builder
|
|||
|
||||
---
|
||||
|
||||
attacker = head validatorHashes
|
||||
|
||||
fakeStakeBuilder =
|
||||
if ps.fakeSST
|
||||
then
|
||||
mconcat
|
||||
[ input @b $
|
||||
mconcat
|
||||
[ script attacker
|
||||
, withValue $
|
||||
Value.singleton
|
||||
stakeSymbol
|
||||
(validatorHashToTokenName attacker)
|
||||
1
|
||||
, withDatum $
|
||||
(mkStakeInputDatum ps)
|
||||
{ stakedAmount = 10000000000
|
||||
}
|
||||
]
|
||||
]
|
||||
else mempty
|
||||
|
||||
---
|
||||
|
||||
governorValue = sortValue $ gst <> minAda
|
||||
stakeValue =
|
||||
sortValue $
|
||||
|
|
@ -324,7 +366,7 @@ createProposal ps = builder
|
|||
[ script governorValidatorHash
|
||||
, withValue governorValue
|
||||
, withDatum governorInputDatum
|
||||
, withRedeemer governorRedeemer
|
||||
, withRedeemer ps.governorRedeemer
|
||||
, withRef governorRef
|
||||
]
|
||||
, output $
|
||||
|
|
@ -334,19 +376,39 @@ createProposal ps = builder
|
|||
, withDatum (mkGovernorOutputDatum ps)
|
||||
]
|
||||
, ---
|
||||
input $
|
||||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue stakeValue
|
||||
, withDatum (mkStakeInputDatum ps)
|
||||
, withRef stakeRef
|
||||
]
|
||||
, output $
|
||||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue stakeValue
|
||||
, withDatum (mkStakeOutputDatum ps)
|
||||
]
|
||||
if ps.fakeSST
|
||||
then
|
||||
mconcat
|
||||
[ input @b $
|
||||
mconcat
|
||||
[ script attacker
|
||||
, withValue $
|
||||
Value.singleton
|
||||
stakeSymbol
|
||||
(validatorHashToTokenName attacker)
|
||||
1
|
||||
, withDatum $
|
||||
(mkStakeInputDatum ps)
|
||||
{ stakedAmount = 10000000000
|
||||
}
|
||||
]
|
||||
]
|
||||
else
|
||||
mconcat
|
||||
[ input $
|
||||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue stakeValue
|
||||
, withDatum (mkStakeInputDatum ps)
|
||||
, withRef stakeRef
|
||||
]
|
||||
, output $
|
||||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue stakeValue
|
||||
, withDatum (mkStakeOutputDatum ps)
|
||||
]
|
||||
]
|
||||
, ---
|
||||
output $
|
||||
mconcat
|
||||
|
|
@ -354,6 +416,8 @@ createProposal ps = builder
|
|||
, withValue proposalValue
|
||||
, withDatum (mkProposalOutputDatum ps)
|
||||
]
|
||||
, ---
|
||||
fakeStakeBuilder
|
||||
]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
@ -362,10 +426,6 @@ createProposal ps = builder
|
|||
stakeRedeemer :: StakeRedeemer
|
||||
stakeRedeemer = PermitVote
|
||||
|
||||
-- | Spend the governor with the 'CreateProposal' redeemer.
|
||||
governorRedeemer :: GovernorRedeemer
|
||||
governorRedeemer = CreateProposal
|
||||
|
||||
-- | Mint the PST with an arbitrary redeemer. Doesn't really matter.
|
||||
proposalPolicyRedeemer :: ()
|
||||
proposalPolicyRedeemer = ()
|
||||
|
|
@ -383,6 +443,8 @@ totallyValidParameters =
|
|||
, timeRangeTightEnough = True
|
||||
, timeRangeClosed = True
|
||||
, proposalStatus = Draft
|
||||
, fakeSST = False
|
||||
, governorRedeemer = Redeemer $ toBuiltinData CreateProposal
|
||||
}
|
||||
|
||||
invalidOutputGovernorDatumParameters :: Parameters
|
||||
|
|
@ -435,6 +497,24 @@ invalidProposalStatusParameters =
|
|||
)
|
||||
[VotingReady, Locked, Finished]
|
||||
|
||||
fakeSSTParameters :: Parameters
|
||||
fakeSSTParameters =
|
||||
totallyValidParameters
|
||||
{ fakeSST = True
|
||||
}
|
||||
|
||||
wrongGovernorRedeemer :: Parameters
|
||||
wrongGovernorRedeemer =
|
||||
totallyValidParameters
|
||||
{ governorRedeemer = Redeemer $ toBuiltinData MintGATs
|
||||
}
|
||||
|
||||
wrongGovernorRedeemer1 :: Parameters
|
||||
wrongGovernorRedeemer1 =
|
||||
totallyValidParameters
|
||||
{ governorRedeemer = Redeemer $ toBuiltinData MutateGovernor
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
{- | Create a test tree that runs the proposal minting policy, the governor
|
||||
|
|
@ -467,7 +547,7 @@ mkTestTree
|
|||
"governor"
|
||||
governorValidator
|
||||
governorInputDatum
|
||||
governorRedeemer
|
||||
ps.governorRedeemer
|
||||
(spend governorRef)
|
||||
|
||||
stakeTest =
|
||||
|
|
|
|||
|
|
@ -20,9 +20,10 @@ import Agora.Proposal.Time (
|
|||
)
|
||||
import Agora.SafeMoney (GTTag)
|
||||
import Agora.Stake (
|
||||
ProposalLock (
|
||||
ProposalAction (
|
||||
Voted
|
||||
),
|
||||
ProposalLock (ProposalLock),
|
||||
StakeDatum (..),
|
||||
StakeRedeemer (PermitVote, RetractVotes),
|
||||
)
|
||||
|
|
@ -128,8 +129,19 @@ mkStakeInputOutputDatums op =
|
|||
|
||||
allStakes = take 10 $ firstStake : otherStakes
|
||||
|
||||
createdAt = (def :: ProposalTimingConfig).votingTime - 1
|
||||
|
||||
stakeWithLock =
|
||||
(\stake -> stake {lockedBy = [Voted defProposalId defResultTag]})
|
||||
( \stake ->
|
||||
stake
|
||||
{ lockedBy =
|
||||
[ ProposalLock defProposalId $
|
||||
Voted
|
||||
defResultTag
|
||||
createdAt
|
||||
]
|
||||
}
|
||||
)
|
||||
<$> allStakes
|
||||
in wrap op (,) allStakes stakeWithLock
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module Sample.Proposal.Unlock (
|
|||
SignedBy (..),
|
||||
TransactionParameters (..),
|
||||
ProposalParameters (..),
|
||||
SSTOwner (..),
|
||||
StakeParameters (..),
|
||||
Validity (..),
|
||||
unlock,
|
||||
|
|
@ -26,6 +27,8 @@ module Sample.Proposal.Unlock (
|
|||
mkRemoveCreatorLockBeforeFinished,
|
||||
mkCreatorRetractVotes,
|
||||
mkChangeOutputStakeValue,
|
||||
mkUseFakeStakes,
|
||||
mkDisrespectCooldown,
|
||||
) where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
@ -40,13 +43,18 @@ import Agora.Proposal (
|
|||
ProposalVotes (..),
|
||||
ResultTag (..),
|
||||
)
|
||||
import Agora.Proposal.Time (ProposalStartingTime (ProposalStartingTime), ProposalTimingConfig (..))
|
||||
import Agora.Proposal.Time (
|
||||
ProposalStartingTime (ProposalStartingTime),
|
||||
ProposalTimingConfig (..),
|
||||
)
|
||||
import Agora.SafeMoney (GTTag)
|
||||
import Agora.Stake (
|
||||
ProposalAction (Created, Voted),
|
||||
ProposalLock (..),
|
||||
StakeDatum (..),
|
||||
StakeRedeemer (RetractVotes),
|
||||
)
|
||||
import Data.Coerce (coerce)
|
||||
import Data.Default.Class (Default (def))
|
||||
import Data.Map.Strict qualified as StrictMap
|
||||
import Data.Tagged (Tagged, untag)
|
||||
|
|
@ -64,8 +72,11 @@ import Plutarch.Context (
|
|||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value
|
||||
import PlutusLedgerApi.V2 (
|
||||
Credential (PubKeyCredential),
|
||||
POSIXTime,
|
||||
PubKeyHash,
|
||||
TxOutRef (..),
|
||||
)
|
||||
|
|
@ -76,7 +87,7 @@ import Sample.Shared (
|
|||
proposalAssetClass,
|
||||
proposalValidator,
|
||||
proposalValidatorHash,
|
||||
stakeAssetClass,
|
||||
stakeSymbol,
|
||||
stakeValidator,
|
||||
stakeValidatorHash,
|
||||
)
|
||||
|
|
@ -138,7 +149,7 @@ data ParameterBundle = ParameterBundle
|
|||
|
||||
data SignedBy = Owner | Delegatee | Unknown
|
||||
|
||||
data TimeRange = WhileVoting | AfterVoting
|
||||
data TimeRange = WhileVoting {offset :: POSIXTime} | AfterVoting
|
||||
|
||||
data TransactionParameters = TransactionParameters
|
||||
{ signedBy :: SignedBy
|
||||
|
|
@ -162,12 +173,18 @@ data StakeRole
|
|||
Irrelevant
|
||||
deriving stock (Bounded, Enum, Show)
|
||||
|
||||
data SSTOwner
|
||||
= StakeValidator
|
||||
| Attacker
|
||||
|
||||
data StakeParameters = StakeParameters
|
||||
{ numStakes :: Integer
|
||||
, stakeRole :: StakeRole
|
||||
, removeVoterLock :: Bool
|
||||
, removeCreatorLock :: Bool
|
||||
, alterOutputValue :: Bool
|
||||
, sstOwner :: SSTOwner
|
||||
, votingLockCreatedAt :: POSIXTime
|
||||
}
|
||||
|
||||
data Validity = Validity
|
||||
|
|
@ -194,14 +211,20 @@ mkStakeInputDatum ps =
|
|||
where
|
||||
stakeLocks = mkStakeLocks' ps.stakeRole
|
||||
|
||||
mkStakeLocks' Voter = [Voted defProposalId defVoteFor]
|
||||
mkStakeLocks' Creator = [Created defProposalId]
|
||||
mkStakeLocks' Voter =
|
||||
[ ProposalLock defProposalId $
|
||||
Voted defVoteFor ps.votingLockCreatedAt
|
||||
]
|
||||
mkStakeLocks' Creator = [ProposalLock defProposalId Created]
|
||||
mkStakeLocks' Both = mkStakeLocks' Voter <> mkStakeLocks' Creator
|
||||
mkStakeLocks' Irrelevant =
|
||||
let ProposalId pid = defProposalId
|
||||
ResultTag vid = defVoteFor
|
||||
in [ Voted (ProposalId $ pid + 1) (ResultTag $ vid + 1)
|
||||
, Created (ProposalId $ pid + 1)
|
||||
in [ ProposalLock (ProposalId $ pid + 1) $
|
||||
Voted
|
||||
(ResultTag $ vid + 1)
|
||||
ps.votingLockCreatedAt
|
||||
, ProposalLock (ProposalId $ pid + 1) Created
|
||||
]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
|
@ -275,18 +298,21 @@ unlock ps = builder
|
|||
|
||||
---
|
||||
|
||||
sst = assetClassValue stakeAssetClass 1
|
||||
sstName = case ps.stakeParameters.sstOwner of
|
||||
StakeValidator -> validatorHashToTokenName stakeValidatorHash
|
||||
_ -> ""
|
||||
|
||||
sst = Value.singleton stakeSymbol sstName 1
|
||||
|
||||
stakeInputDatum = mkStakeInputDatum ps.stakeParameters
|
||||
|
||||
-- TODO respect timing
|
||||
removeLocks v c =
|
||||
filter $
|
||||
not
|
||||
. ( \case
|
||||
Created pid -> c && pid == defProposalId
|
||||
Cosigned pid -> c && pid == defProposalId
|
||||
Voted pid _ -> v && pid == defProposalId
|
||||
)
|
||||
filter $ \(ProposalLock pid action) ->
|
||||
pid == defProposalId
|
||||
&& case action of
|
||||
Voted _ _ -> v
|
||||
_ -> c
|
||||
|
||||
stakeOutputDatum =
|
||||
stakeInputDatum
|
||||
|
|
@ -342,9 +368,14 @@ unlock ps = builder
|
|||
ProposalStartingTime s = defStartingTime
|
||||
|
||||
time = case ps.transactionParameters.timeRange of
|
||||
WhileVoting ->
|
||||
let lb = s + (def :: ProposalTimingConfig).draftTime
|
||||
ub = lb + (def :: ProposalTimingConfig).votingTime
|
||||
WhileVoting offset ->
|
||||
let lb =
|
||||
ps.stakeParameters.votingLockCreatedAt
|
||||
+ offset
|
||||
ub =
|
||||
s
|
||||
+ (def :: ProposalTimingConfig).draftTime
|
||||
+ (def :: ProposalTimingConfig).votingTime
|
||||
in closedBoundedInterval (lb + 1) (ub - 1)
|
||||
AfterVoting ->
|
||||
let lb =
|
||||
|
|
@ -415,12 +446,22 @@ mkValidVoterRetractVotes i =
|
|||
, removeVoterLock = True
|
||||
, removeCreatorLock = False
|
||||
, alterOutputValue = False
|
||||
, sstOwner = StakeValidator
|
||||
, votingLockCreatedAt =
|
||||
coerce defStartingTime
|
||||
+ (def :: ProposalTimingConfig).draftTime
|
||||
+ 1
|
||||
}
|
||||
, transactionParameters =
|
||||
TransactionParameters
|
||||
{ signedBy = Owner
|
||||
, timeRange =
|
||||
WhileVoting
|
||||
{ offset =
|
||||
coerce
|
||||
(def :: ProposalTimingConfig).minStakeVotingTime
|
||||
+ 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -530,10 +571,6 @@ mkCreatorRetractVotes i =
|
|||
template.stakeParameters
|
||||
{ stakeRole = Creator
|
||||
}
|
||||
, transactionParameters =
|
||||
template.transactionParameters
|
||||
{ timeRange = WhileVoting
|
||||
}
|
||||
}
|
||||
|
||||
mkChangeOutputStakeValue :: Integer -> ParameterBundle
|
||||
|
|
@ -545,3 +582,29 @@ mkChangeOutputStakeValue i =
|
|||
{ alterOutputValue = True
|
||||
}
|
||||
}
|
||||
|
||||
mkUseFakeStakes :: Integer -> ParameterBundle
|
||||
mkUseFakeStakes i =
|
||||
let template = mkValidVoterCreatorRetractVotes i
|
||||
in template
|
||||
{ stakeParameters =
|
||||
template.stakeParameters
|
||||
{ sstOwner = Attacker
|
||||
}
|
||||
}
|
||||
|
||||
mkDisrespectCooldown :: Integer -> ParameterBundle
|
||||
mkDisrespectCooldown i =
|
||||
let template = mkValidVoterCreatorRetractVotes i
|
||||
in template
|
||||
{ transactionParameters =
|
||||
template.transactionParameters
|
||||
{ timeRange =
|
||||
WhileVoting
|
||||
{ offset =
|
||||
coerce
|
||||
(def :: ProposalTimingConfig).minStakeVotingTime
|
||||
- 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ module Sample.Proposal.Vote (
|
|||
mkTestTree,
|
||||
mkValidOwnerVoteBundle,
|
||||
mkValidDelegateeVoteBundle,
|
||||
delegateeVoteWithOwnAndDelegatedStakeBundle,
|
||||
transparentAssets,
|
||||
transactionNotAuthorized,
|
||||
voteForNonexistentOutcome,
|
||||
|
|
@ -35,6 +36,7 @@ import Agora.Proposal (
|
|||
ProposalId (ProposalId),
|
||||
ProposalRedeemer (Vote),
|
||||
ProposalStatus (VotingReady),
|
||||
ProposalThresholds (vote),
|
||||
ProposalVotes (ProposalVotes),
|
||||
ResultTag (ResultTag),
|
||||
)
|
||||
|
|
@ -44,7 +46,8 @@ import Agora.Proposal.Time (
|
|||
)
|
||||
import Agora.SafeMoney (GTTag)
|
||||
import Agora.Stake (
|
||||
ProposalLock (Voted),
|
||||
ProposalAction (Voted),
|
||||
ProposalLock (ProposalLock),
|
||||
StakeDatum (..),
|
||||
StakeRedeemer (Destroy, PermitVote),
|
||||
)
|
||||
|
|
@ -66,7 +69,7 @@ import Plutarch.Context (
|
|||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (adaClass, assetClassValue)
|
||||
import PlutusLedgerApi.V2 (Credential (PubKeyCredential), PubKeyHash)
|
||||
import PlutusLedgerApi.V2 (Credential (PubKeyCredential), Interval, POSIXTime, PubKeyHash)
|
||||
import PlutusLedgerApi.V2.Contexts (TxOutRef (TxOutRef))
|
||||
import Sample.Proposal.Shared (proposalTxRef)
|
||||
import Sample.Shared (
|
||||
|
|
@ -98,6 +101,7 @@ newtype VoteParameters = VoteParameters {voteFor :: ResultTag}
|
|||
|
||||
data StakeParameters = StakeParameters
|
||||
{ numStakes :: Integer
|
||||
, mixInDelegateeAsOwner :: Bool
|
||||
, stakeInputParameters :: StakeInputParameters
|
||||
, stakeOutputParameters :: StakeOutputParameters
|
||||
}
|
||||
|
|
@ -142,6 +146,24 @@ delegatee = pubKeyHashes !! 1
|
|||
unknownSig :: PubKeyHash
|
||||
unknownSig = pubKeyHashes !! 2
|
||||
|
||||
validTimeRangeLowerBound :: POSIXTime
|
||||
validTimeRangeLowerBound =
|
||||
0
|
||||
+ (def :: ProposalTimingConfig).draftTime
|
||||
+ 1
|
||||
|
||||
validTimeRangeUpperBound :: POSIXTime
|
||||
validTimeRangeUpperBound =
|
||||
validTimeRangeLowerBound
|
||||
+ (def :: ProposalTimingConfig).votingTime
|
||||
- 2
|
||||
|
||||
validTimeRange :: Interval POSIXTime
|
||||
validTimeRange =
|
||||
closedBoundedInterval
|
||||
validTimeRangeLowerBound
|
||||
validTimeRangeUpperBound
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
initialVotes :: StrictMap.Map ResultTag Integer
|
||||
|
|
@ -194,8 +216,8 @@ mkStakeInputDatum params =
|
|||
, owner = PubKeyCredential stakeOwner
|
||||
, delegatedTo = Just (PubKeyCredential delegatee)
|
||||
, lockedBy =
|
||||
[ Voted (ProposalId 0) (ResultTag 0)
|
||||
, Voted (ProposalId 1) (ResultTag 2)
|
||||
[ ProposalLock (ProposalId 0) $ Voted (ResultTag 0) 100
|
||||
, ProposalLock (ProposalId 1) $ Voted (ResultTag 2) 200
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -224,9 +246,11 @@ vote params =
|
|||
<> minAda
|
||||
|
||||
newLock =
|
||||
Voted
|
||||
ProposalLock
|
||||
proposalInputDatum.proposalId
|
||||
params.voteParameters.voteFor
|
||||
$ Voted
|
||||
params.voteParameters.voteFor
|
||||
validTimeRangeUpperBound
|
||||
|
||||
updatedLocks =
|
||||
if params.stakeParameters.stakeOutputParameters.dontAddNewLock
|
||||
|
|
@ -256,6 +280,16 @@ vote params =
|
|||
stakeRedeemer =
|
||||
mkStakeRedeemer params.stakeParameters.stakeOutputParameters
|
||||
|
||||
mixOwner i datum =
|
||||
if params.stakeParameters.mixInDelegateeAsOwner
|
||||
&& i == 2
|
||||
then
|
||||
datum
|
||||
{ owner = PubKeyCredential delegatee
|
||||
, delegatedTo = Nothing
|
||||
}
|
||||
else datum
|
||||
|
||||
stakeBuilder :: b
|
||||
stakeBuilder =
|
||||
foldMap
|
||||
|
|
@ -265,7 +299,7 @@ vote params =
|
|||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue stakeInputValue
|
||||
, withInlineDatum stakeInputDatum
|
||||
, withInlineDatum $ mixOwner i stakeInputDatum
|
||||
, withRedeemer stakeRedeemer
|
||||
, withRef $ mkStakeRef numProposals' i
|
||||
]
|
||||
|
|
@ -276,7 +310,7 @@ vote params =
|
|||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue stakeOutputValue
|
||||
, withInlineDatum stakeOutputDatum
|
||||
, withInlineDatum $ mixOwner i stakeOutputDatum
|
||||
]
|
||||
]
|
||||
)
|
||||
|
|
@ -344,13 +378,6 @@ vote params =
|
|||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
validTimeRange =
|
||||
closedBoundedInterval
|
||||
((def :: ProposalTimingConfig).draftTime + 1)
|
||||
((def :: ProposalTimingConfig).votingTime - 1)
|
||||
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
miscBuilder :: b
|
||||
miscBuilder =
|
||||
mconcat
|
||||
|
|
@ -419,9 +446,10 @@ mkValidOwnerVoteBundle stakes =
|
|||
, stakeParameters =
|
||||
StakeParameters
|
||||
{ numStakes = stakes
|
||||
, mixInDelegateeAsOwner = False
|
||||
, stakeInputParameters =
|
||||
StakeInputParameters
|
||||
{ perStakeGTs = 114514
|
||||
{ perStakeGTs = (def :: ProposalThresholds).vote
|
||||
}
|
||||
, stakeOutputParameters =
|
||||
StakeOutputParameters
|
||||
|
|
@ -452,6 +480,16 @@ mkValidDelegateeVoteBundle stakes =
|
|||
}
|
||||
}
|
||||
|
||||
delegateeVoteWithOwnAndDelegatedStakeBundle :: ParameterBundle
|
||||
delegateeVoteWithOwnAndDelegatedStakeBundle =
|
||||
let template = mkValidDelegateeVoteBundle 5
|
||||
in template
|
||||
{ stakeParameters =
|
||||
template.stakeParameters
|
||||
{ mixInDelegateeAsOwner = True
|
||||
}
|
||||
}
|
||||
|
||||
ownerVoteWithSignleStake :: ParameterBundle
|
||||
ownerVoteWithSignleStake = mkValidOwnerVoteBundle 1
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module Sample.Shared (
|
|||
signer,
|
||||
signer2,
|
||||
minAda,
|
||||
deterministicTracingConfing,
|
||||
deterministicTracingConfig,
|
||||
mkRedeemer,
|
||||
|
||||
-- * Agora Scripts
|
||||
|
|
@ -72,9 +72,6 @@ import Agora.Proposal.Time (
|
|||
ProposalTimingConfig (..),
|
||||
)
|
||||
import Agora.SafeMoney (GovernorSTTag, ProposalSTTag, StakeSTTag)
|
||||
import Agora.Utils (
|
||||
validatorHashToTokenName,
|
||||
)
|
||||
import Data.Default.Class (Default (..))
|
||||
import Data.Map (Map, (!))
|
||||
import Data.Tagged (Tagged (..))
|
||||
|
|
@ -86,6 +83,7 @@ import Plutarch.Api.V2 (
|
|||
validatorHash,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (AssetClass (AssetClass))
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import PlutusLedgerApi.V1.Address (scriptHashAddress)
|
||||
import PlutusLedgerApi.V1.Value (TokenName, Value)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value (
|
||||
|
|
@ -123,8 +121,8 @@ import ScriptExport.ScriptInfo (runLinker)
|
|||
-- Plutarch compiler configauration.
|
||||
-- TODO: add the ability to change this value. Maybe wrap everything in a
|
||||
-- Reader monad?
|
||||
deterministicTracingConfing :: Config
|
||||
deterministicTracingConfing = Config DetTracing
|
||||
deterministicTracingConfig :: Config
|
||||
deterministicTracingConfig = Config DetTracing
|
||||
|
||||
governor :: Governor
|
||||
governor = Governor oref gt mc
|
||||
|
|
@ -144,7 +142,7 @@ agoraScripts =
|
|||
(fmap (view #script) . view #scripts)
|
||||
( runLinker
|
||||
linker
|
||||
(Bootstrap.agoraScripts deterministicTracingConfing)
|
||||
(Bootstrap.agoraScripts deterministicTracingConfig)
|
||||
governor
|
||||
)
|
||||
|
||||
|
|
@ -242,6 +240,8 @@ instance Default ProposalTimingConfig where
|
|||
, votingTime = 1000
|
||||
, lockingTime = 2000
|
||||
, executingTime = 3000
|
||||
, minStakeVotingTime = 100
|
||||
, votingTimeRangeMaxWidth = 1000000
|
||||
}
|
||||
|
||||
{- | Default value of 'Agora.Governor.GovernorDatum.createProposalTimeRangeMaxWidth'.
|
||||
|
|
|
|||
|
|
@ -20,8 +20,7 @@ module Sample.Stake.Create (
|
|||
import Agora.Governor (Governor (gtClassRef))
|
||||
import Agora.Proposal (ProposalId (ProposalId))
|
||||
import Agora.SafeMoney (GTTag)
|
||||
import Agora.Stake (ProposalLock (Created), StakeDatum (..))
|
||||
import Agora.Utils (validatorHashToTokenName)
|
||||
import Agora.Stake (ProposalAction (Created), ProposalLock (ProposalLock), StakeDatum (..))
|
||||
import Data.Semigroup (stimesMonoid)
|
||||
import Data.Tagged (Tagged)
|
||||
import Plutarch.Context (
|
||||
|
|
@ -36,6 +35,7 @@ import Plutarch.Context (
|
|||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import Plutarch.Lift (PUnsafeLiftDecl (PLifted))
|
||||
import PlutusLedgerApi.V1.Value qualified as Value
|
||||
import PlutusLedgerApi.V2 (
|
||||
|
|
@ -255,6 +255,6 @@ alreadyHasLocks =
|
|||
{ stakedAmount = 114514
|
||||
, owner = PubKeyCredential signer
|
||||
, delegatedTo = Nothing
|
||||
, lockedBy = [Created $ ProposalId 0]
|
||||
, lockedBy = [ProposalLock (ProposalId 0) Created]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ module Sample.Stake.Destroy (
|
|||
|
||||
import Agora.Proposal (ProposalId (..))
|
||||
import Agora.Stake (
|
||||
ProposalLock (Created),
|
||||
ProposalAction (Created),
|
||||
ProposalLock (ProposalLock),
|
||||
StakeDatum (..),
|
||||
StakeRedeemer (Destroy),
|
||||
)
|
||||
|
|
@ -105,7 +106,7 @@ mkStakeInputDatum ps =
|
|||
{ stakedAmount = 114514
|
||||
, owner = PubKeyCredential owner
|
||||
, delegatedTo = Just $ PubKeyCredential delegatee
|
||||
, lockedBy = [Created $ ProposalId 0 | ps.notUnlocked]
|
||||
, lockedBy = [ProposalLock (ProposalId 0) Created | ps.notUnlocked]
|
||||
}
|
||||
|
||||
mkStakeRef :: Int -> TxOutRef
|
||||
|
|
|
|||
74
agora-specs/Sample/Stake/UnauthorizedMintingExploit.hs
Normal file
74
agora-specs/Sample/Stake/UnauthorizedMintingExploit.hs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
module Sample.Stake.UnauthorizedMintingExploit (
|
||||
Parameters (..),
|
||||
exploit,
|
||||
mkTestCase,
|
||||
) where
|
||||
|
||||
import Plutarch.Context (
|
||||
input,
|
||||
mint,
|
||||
normalizeValue,
|
||||
output,
|
||||
script,
|
||||
withValue,
|
||||
)
|
||||
import Plutarch.Extra.AssetClass (assetClassValue)
|
||||
import Plutarch.Extra.ScriptContext (validatorHashToTokenName)
|
||||
import PlutusLedgerApi.V1.Value qualified as Value
|
||||
import Sample.Shared (
|
||||
minAda,
|
||||
stakeAssetClass,
|
||||
stakePolicy,
|
||||
stakeSymbol,
|
||||
stakeValidatorHash,
|
||||
)
|
||||
import Test.Specification (SpecificationTree, testPolicy)
|
||||
import Test.Util (
|
||||
CombinableBuilder,
|
||||
mkMinting,
|
||||
validatorHashes,
|
||||
)
|
||||
|
||||
newtype Parameters = Parameters
|
||||
{ inputSST :: Int
|
||||
}
|
||||
|
||||
exploit ::
|
||||
forall b.
|
||||
CombinableBuilder b =>
|
||||
Parameters ->
|
||||
b
|
||||
exploit (Parameters inputSST) =
|
||||
mconcat
|
||||
[ input $
|
||||
mconcat
|
||||
[ script attacker
|
||||
, withValue $
|
||||
normalizeValue $
|
||||
minAda <> fakeSSTValue inputSST
|
||||
]
|
||||
, mint $ fakeSSTValue $ negate inputSST
|
||||
, mint sst
|
||||
, output $
|
||||
mconcat
|
||||
[ script stakeValidatorHash
|
||||
, withValue $
|
||||
normalizeValue $
|
||||
minAda <> sst
|
||||
]
|
||||
]
|
||||
where
|
||||
attacker = head validatorHashes
|
||||
|
||||
fakeSSTValue =
|
||||
Value.singleton
|
||||
stakeSymbol
|
||||
(validatorHashToTokenName attacker)
|
||||
. fromIntegral
|
||||
|
||||
sst = assetClassValue stakeAssetClass 1
|
||||
|
||||
mkTestCase :: String -> Parameters -> SpecificationTree
|
||||
mkTestCase name ps =
|
||||
testPolicy False name stakePolicy () $
|
||||
mkMinting exploit ps stakeSymbol
|
||||
Loading…
Add table
Add a link
Reference in a new issue