You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Cabal编译Plutus脚本时遇Illegal type错误求助

Plutus金库脚本编译报错:Illegal type(需启用DataKinds)

尝试用Cabal编译代币奖励的Plutus金库脚本时,执行cabal build或cabal repl始终报Illegal type错误,提示需使用DataKinds。相关代码和错误信息如下,求排查。

Treasury.hs 文件

{-# LANGUAGE DeriveAnyClass        #-}
{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE NoImplicitPrelude #-}

module Stake.StackingTreasury where

import              Ledger              hiding (singleton)
import qualified Ledger.Typed.Scripts as Scripts
import              Ledger.Value        as Value
import              Ledger.Ada
import qualified    PlutusTx
import              PlutusTx.Prelude    hiding (Semigroup (..), unless)

import              Stake.StackingTypes

{-# INLINEABLE mkValidator #-}
mkValidator :: TreasuryParam -> WithdrawalDatum -> StackingDetails -> ScriptContext -> Bool
mkValidator tp dat b ctx =    traceIfFalse "Only Issuer can change Treasury"              signedByIssuer ||
                              traceIfFalse "Access token missing from input"              inputHasAuthToken &&
                              traceIfFalse "Access token missing from contract output"    contractOutputHasAuthToken &&
                              traceIfFalse "Output Value must match StackingDetails"      checkValueToStackingContract &&
                              traceIfFalse "Treasury must keep remaining tokens"          treasuryGetsTokensBack &&
                              traceIfFalse "redeemer is not datum"                        checkReIsOutDat

  where
    info :: TxInfo
    info = scriptContextTxInfo ctx

    signedByIssuer :: Bool
    signedByIssuer = txSignedBy info $ tTreasuryIssuerPkh tp

    -- Create a list of all CurrencySymbol in tx input
    inVals :: [CurrencySymbol]
    inVals = symbols $ valueSpent info

    -- Check that list of CurrencySymbols includes Auth CurrencySymbol
    inputHasAuthToken :: Bool
    inputHasAuthToken = tAccessTokenPolicyId tp `elem` inVals

    -- The Value to be included in Stacking Contract UTXO
    toStackingContract :: Value
    toStackingContract = valueLockedBy info (bountyContractHash tp)

    -- Check that the Auth Token is sent to Bounty Contract UTXO
    contractOutputHasAuthToken :: Bool
    contractOutputHasAuthToken = tAccessTokenPolicyId tp `elem` symbols toStackingContract

    -- Check that the Value sent to Contract UTXO matches what is specified in the Redeemer
    -- Note: For now, we can just remember to match Treasury Redeemer to Bounty Datum
    -- when we build transactions
    checkValueToStackingContract :: Bool
    checkValueToStackingContract =  valueOf toStackingContract (tBountyTokenPolicyId tp) (tBountyTokenName tp) >= tokenAmount b

    -- The UTXO input from Treasury
    ownInput :: TxOut
    ownInput = case findOwnInput ctx of
        Nothing -> traceError "treasury input missing"
        Just i  -> txInInfoResolved i

    -- The UTXO output back to Treasury
    ownOutput :: TxOut
    ownOutput = case getContinuingOutputs ctx of
        [o] -> o -- There must be exactly ONE output UTXO
        _   -> traceError "expected exactly one treasury output"

    -- Values of each
    treasuryInputValue :: Value
    treasuryInputValue = txOutValue ownInput

    treasuryOutputValue :: Value
    treasuryOutputValue = txOutValue ownOutput

    -- Compare Values from and to Treasury to make sure that Treasury gets the right value back.
    treasuryGetsLovelaceBack :: Bool
    treasuryGetsLovelaceBack = getLovelace ( fromValue treasuryInputValue) - getLovelace ( fromValue treasuryOutputValue) <= getLovelace ( fromValue toStackingContract)

    treasuryGetsTokensBack :: Bool
    treasuryGetsTokensBack = valueOf treasuryInputValue (tBountyTokenPolicyId tp) (tBountyTokenName tp) - valueOf treasuryOutputValue (tBountyTokenPolicyId tp) (tBountyTokenName tp) <= valueOf toStackingContract (tBountyTokenPolicyId tp) (tBountyTokenName tp)

    getStackingDatum :: Maybe StackingDatum
    getStackingDatum = let os = [ o | o <- txInfoOutputs info, txOutValue o == toStackingContract ] in
                  case os of
                    [o] -> stackingDatum o (`findDatum` info)
                    _   -> Nothing

    checkReIsOutDat :: Bool
    checkReIsOutDat = case getStackingDatum of
      Nothing -> False
      Just ns -> bedIssuerPkh   ns == issuerPkh b &&
                 bedContributorPkh ns == contributorPkh b &&
                 bedTokenAmount ns == tokenAmount b &&
                 bedExpirationTime ns == expirationTime b

-- typedValidator :: TreasuryParam -> TypedValidator TreasuryTypes
-- typedValidator tp =
--   mkTypedValidator @TreasuryTypes
--     $$(PlutusTx.compile [||mkValidator||]) `PlutusTx.applyCode` PlutusTx.liftCode tp
--     $$(PlutusTx.compile [||wrap||])
--   where
--     wrap = wrapValidator @WithdrawalDatum @StackingDetails

-- validator :: TreasuryParam -> Validator
-- validator = validatorScript . typedValidator

typedValidator :: TreasuryParam -> Scripts.TypedValidator TreasuryTypes
typedValidator tp = Scripts.mkTypedValidator @TreasuryTypes
    ($$(PlutusTx.compile [|| mkValidator ||])
        `PlutusTx.applyCode` PlutusTx.liftCode tp)
    $$(PlutusTx.compile [|| wrap ||])
  where
    wrap = Scripts.wrapValidator @WithdrawalDatum @StackingDetails

validator :: TreasuryParam -> Validator
validator = Scripts.validatorScript . typedValidator

StackingTypes.hs 文件

{-# LANGUAGE DeriveAnyClass        #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE TypeFamilies #-}

module Stake.StackingTypes
    ( TreasuryParam (..)
    , WithdrawalDatum (..)
    , StackingDetails (..)
    , StackingAction (..)
    , StackingParam (..)
    , StackingDatum (..)
    , TreasuryTypes
    , StackingTypes
    , stackingDatum
    ) where

import              Data.Aeson                  (ToJSON, FromJSON)
import              GHC.Generics                (Generic)
import              Schema                      (ToSchema)
import              Ledger              hiding  (singleton)
import              Ledger.Typed.Scripts
import  qualified   PlutusTx
import              PlutusTx.Prelude    hiding  (Semigroup (..), unless)
import              Prelude                     (Show (..))
import  qualified   Prelude                 as  Pr

data TreasuryParam = TreasuryParam
    { tAccessTokenPolicyId   :: !CurrencySymbol
    , bountyContractHash     :: !ValidatorHash
    , tBountyTokenPolicyId   :: !CurrencySymbol
    , tBountyTokenName       :: !TokenName
    , tTreasuryIssuerPkh     :: !PubKeyHash
    } deriving (Pr.Eq, Pr.Ord, Show, Generic, ToJSON, FromJSON, ToSchema)

PlutusTx.makeLift ''TreasuryParam

data WithdrawalDatum = WithdrawalDatum
  { bountyCount     :: !Integer
  , treasuryKey     :: !PubKeyHash
  } deriving (Pr.Eq, Pr.Ord, Show, Generic, ToJSON, FromJSON, ToSchema)

PlutusTx.unstableMakeIsData ''WithdrawalDatum



-- Completion Status - not 0 or 1, but partial
-- Completion status as, for example a % of what is completed?

data StackingDetails = StackingDetails
  { issuerPkh           :: !PubKeyHash
  , contributorPkh      :: !PubKeyHash
  , tokenAmount         :: !Integer
  , expirationTime      :: !POSIXTime
  } deriving (Pr.Eq, Pr.Ord, Show, Generic, ToJSON, FromJSON, ToSchema)

instance Eq StackingDetails where
  {-# INLINABLE (==) #-}
  StackingDetails iP cP tA eT == StackingDetails iP' cP' tA' eT' =
    (iP == iP') && (cP == cP') && (tA == tA') && (eT == eT')

    -- Alternative way of comparisons
    -- a == b = (issuerPkh       a == issuerPkh      b) &&
    --          (contributorPkh  a == contributorPkh b) &&
    --          (lovelaceAmount  a == lovelaceAmount b) &&
    --          (expirationTime  a == expirationTime b)

PlutusTx.unstableMakeIsData ''StackingDetails
PlutusTx.makeLift ''StackingDetails

data TreasuryTypes
instance ValidatorTypes TreasuryTypes where
    type DatumType TreasuryTypes = WithdrawalDatum
    type RedeemerType TreasuryTypes = StackingDetails

-- BountyEscrow
-- INLINABLE to use On Chain
{-# INLINABLE stackingDatum #-}
stackingDatum :: TxOut -> (DatumHash -> Maybe Datum) -> Maybe StackingDatum
stackingDatum o f = do
    dh <- txOutDatum o
    Datum d <- f dh
    PlutusTx.fromBuiltinData d

data StackingDatum = StackingDatum
  { bedIssuerPkh           :: !PubKeyHash
  , bedContributorPkh      :: !PubKeyHash
  , bedTokenAmount         :: !Integer
  , bedExpirationTime      :: !POSIXTime
  } deriving (Pr.Eq, Pr.Ord, Show, Generic, ToJSON, FromJSON, ToSchema)

instance Eq StackingDatum where
  {-# INLINABLE (==) #-}
  StackingDatum bIP bCP  bTA bET == StackingDatum bIP' bCP'  bTA' bET' =
    (bIP == bIP') && (bCP == bCP')  && (bTA == bTA') && (bET == bET')

    -- Alternative way of comparisons
    -- a == b = (bedIssuerPkh       a == bedIssuerPkh      b) &&
    --          (bedContributorPkh  a == bedContributorPkh b) &&
    --          (bedLovelaceAmount  a == bedLovelaceAmount b) &&
    --          (bedTokenAmount     a == bedTokenAmount    b) &&
    --          (bedExpirationTime  a == bedExpirationTime b)

PlutusTx.unstableMakeIsData ''StackingDatum
PlutusTx.makeLift ''StackingDatum

data StackingParam = StackingParam
    { bountyTokenPolicyId     :: !CurrencySymbol
    , bountyTokenName         :: !TokenName
    , accessTokenPolicyId     :: !CurrencySymbol
    , treasuryIssuerPkh       :: !PubKeyHash
    } deriving (Pr.Eq, Pr.Ord, Show, Generic, ToJSON, FromJSON, ToSchema)

PlutusTx.makeLift ''StackingParam

data StackingAction = Cancel BuiltinByteString | Claim
  deriving Show

PlutusTx.makeIsDataIndexed ''StackingAction [('Cancel, 0), ('Claim, 1) ]
PlutusTx.makeLift ''StackingAction

data StackingTypes
instance ValidatorTypes StackingTypes where
    type DatumType StackingTypes = StackingDatum
    type RedeemerType StackingTypes = StackingAction

错误信息

src/Stake/StackingTreasury.hs:115:9: error:
    • Illegal type: ‘"stacking-contract-1.0.0.0-inplace:Stake.StackingTreasury:(115,9)-(115,45)"’
        Perhaps you intended to use DataKinds
    • In the result of the splice:
        $PlutusTx.compile
           template-haskell-2.16.0.0:Language.Haskell.TH.Syntax.unsafeTExpCoerce
             [|| mkValidator ||]
      To see what the splice expanded to, use -ddump-splices
      In the Template Haskell splice
        $$(PlutusTx.compile [|| mkValidator ||])
      In the first argument of ‘PlutusTx.applyCode’, namely
        ‘$$(PlutusTx.compile [|| mkValidator ||])’
    |
115 |     ($$(PlutusTx.compile [|| mkValidator ||])
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

解决方案

1. 为Treasury.hs启用DataKinds扩展

虽然StackingTypes.hs已经开启了DataKinds,但Treasury.hs作为调用模块,同样需要启用该扩展——因为它使用了TreasuryTypes这个类型族实例,而类型族的解析依赖DataKinds。在Treasury.hs的语言扩展列表顶部添加:

{-# LANGUAGE DataKinds #-}

2. 调整模板Haskell编译写法(避免类型拼接歧义)

原代码中用applyCode传递参数的方式可能引发模板Haskell的类型推导问题,建议改为在编译的lambda中直接绑定参数:

typedValidator :: TreasuryParam -> Scripts.TypedValidator TreasuryTypes
typedValidator tp = Scripts.mkTypedValidator @TreasuryTypes
    $$(PlutusTx.compile [|| \tp' -> mkValidator tp' ||])
    $$(PlutusTx.compile [|| Scripts.wrapValidator @WithdrawalDatum @StackingDetails ||])

这种写法更直观,也能避免模板Haskell在拼接代码时的类型错误。

3. 验证类型一致性

确认mkValidator的类型与TreasuryTypes定义的类型完全匹配:

  • TreasuryTypes的DatumType是WithdrawalDatum,RedeemerType是StackingDetails
  • mkValidator的签名是TreasuryParam -> WithdrawalDatum -> StackingDetails -> ScriptContext -> Bool,两者完全匹配,这部分无需修改,但编译前需确认无隐性类型冲突。

4. 检查Plutus依赖版本

确保项目中plutus-core、plutus-tx、plutus-ledger-api等依赖的版本与官方Plutus模板一致,版本不匹配常导致模板Haskell编译时的奇怪类型错误。


内容的提问来源于stack exchange,提问作者Hari Krishna

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 16:41:09