Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions dao/dao-lib/Dao/Configuration/Script.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import Dao.ScriptArgument (
)
import Dao.Shared (
convertDatum,
hasBurnedTokens,
hasOneOfToken,
hasSingleTokenWithSymbolAndTokenName,
hasSymbolInValue,
hasTokenInValue,
hasTokenInValueNoErrors,
untypedPolicy,
untypedValidator,
Expand Down Expand Up @@ -55,7 +55,6 @@ import LambdaBuffers.ApplicationTypes.Tally (
import PlutusLedgerApi.V1.Interval (before)
import PlutusLedgerApi.V1.Time (POSIXTime (POSIXTime))
import PlutusLedgerApi.V1.Value (Value)
import PlutusLedgerApi.V2 (CurrencySymbol)
import PlutusLedgerApi.V2.Contexts (
ScriptContext (ScriptContext, scriptContextPurpose, scriptContextTxInfo),
ScriptPurpose (Minting, Spending),
Expand All @@ -79,7 +78,7 @@ import PlutusTx (
compile,
)
import PlutusTx.Prelude (
Bool,
Bool (True),
BuiltinData,
Integer,
any,
Expand Down Expand Up @@ -195,20 +194,19 @@ validateConfiguration
hasTallyNft :: Value -> Bool
hasTallyNft = hasSymbolInValue dynamicConfigDatum'tallyNft

-- Ensure there is exactly one output that contains the 'TallyStateDatum' datum
-- The `convertDatum` helper will throw an error if the output datum is not found
-- Get TallyStateDatum from spent inputs (not reference)
-- The Tally NFT must be spent and burned during upgrades
TallyStateDatum {tallyStateDatum'proposal = proposal, ..} =
case filter (hasTallyNft . txOutValue . txInInfoResolved) txInfoReferenceInputs of
[] -> traceError "Should be exactly one tally NFT in the reference inputs. None found."
case filter (hasTallyNft . txOutValue . txInInfoResolved) txInfoInputs of
[] -> traceError "Should be exactly one tally NFT in the inputs. None found."
[TxInInfo {txInInfoResolved = TxOut {..}}] ->
convertDatum txInfoData txOutDatum
_ -> traceError "Should be exactly one tally NFT in the reference inputs. More than one found."
_ -> traceError "Should be exactly one tally NFT in the inputs. More than one found."

-- Ensure that the 'ProposalType' set in the 'tsProposal' field
-- of the 'TallyStateDatum' is 'Upgrade', and retrieve the upgrade symbol
upgradeMinter :: CurrencySymbol
upgradeMinter = case proposal of
ProposalType'Upgrade u -> u
-- Ensure that the 'ProposalType' is 'Upgrade'
isUpgradeProposal :: Bool
!isUpgradeProposal = case proposal of
ProposalType'Upgrade _ -> True
_ -> traceError "Not an upgrade proposal"

-- The total votes, for and against, in the 'TallyStateDatum'
Expand All @@ -233,9 +231,9 @@ validateConfiguration
"majority is too small"
(majorityPercent >= dynamicConfigDatum'upgradeMajorityPercent)

-- Make sure the upgrade token was minted
hasUpgradeMinterToken :: Bool
!hasUpgradeMinterToken = hasTokenInValue upgradeMinter "validateConfiguration, upgradeMinter" txInfoMint
-- Verify the Tally NFT is being burned (instead of checking for upgrade token)
tallyNftIsBurned :: Bool
!tallyNftIsBurned = hasBurnedTokens dynamicConfigDatum'tallyNft txInfoMint "Tally NFT must be burned for upgrade"

-- Ensure the proposal has finished
isAfterTallyEndTime :: Bool
Expand All @@ -244,8 +242,9 @@ validateConfiguration
`before` txInfoValidRange
in
traceIfFalse "Should be exactly one configuration NFT in the inputs" hasConfigurationNft
&& traceIfFalse "Not an upgrade proposal" isUpgradeProposal
&& traceIfFalse "The proposal doesn't have enough votes" hasEnoughVotes
&& traceIfFalse "Should be exactly one upgrade token minted" hasUpgradeMinterToken
&& tallyNftIsBurned
&& traceIfFalse "Tallying not over. Try again later" isAfterTallyEndTime
validateConfiguration _ _ _ _ = traceError "Wrong script purpose"

Expand Down
21 changes: 21 additions & 0 deletions dao/dao-lib/Dao/Shared.hs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module Dao.Shared (
integerToByteString,
isScriptCredential,
lovelacesOf,
hasExactAssetCount,
) where

import Dao.ScriptArgument (ValidatorParams)
Expand All @@ -44,14 +45,17 @@ import PlutusTx.Prelude (
Maybe (Just, Nothing),
check,
divide,
foldr,
fromMaybe,
isJust,
length,
modulo,
otherwise,
traceError,
traceIfFalse,
($),
(&&),
(+),
(.),
(<),
(<>),
Expand Down Expand Up @@ -139,6 +143,23 @@ countOfTokenInValue symbol tokenName (Value value) =
lovelacesOf :: Value -> Integer
lovelacesOf = countOfTokenInValue adaSymbol adaToken

{-# INLINEABLE hasExactAssetCount #-}

{- | Check if a Value contains exactly the expected number of distinct asset types
Used to prevent token dust attacks by ensuring no extra tokens are present
-}
hasExactAssetCount :: Value -> Integer -> Bool
hasExactAssetCount (Value val) expectedCount =
let
-- Count total number of distinct token types across all currency symbols
countTokensInMap :: Map TokenName Integer -> Integer
countTokensInMap tokenMap = fromMaybe 0 $ Just (length (Map.toList tokenMap))

totalAssets :: Integer
totalAssets = foldr (\(_, tokenMap) acc -> acc + countTokensInMap tokenMap) 0 (Map.toList val)
in
totalAssets == expectedCount

{-# INLINEABLE hasOneOfToken #-}
hasOneOfToken :: CurrencySymbol -> TokenName -> Value -> Bool
hasOneOfToken symbol tokenName (Value value) = case Map.lookup symbol value of
Expand Down
Loading