This repository has been archived by the owner on Dec 2, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 214
/
Currency.hs
189 lines (160 loc) · 6.55 KB
/
Currency.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-ignore-interface-pragmas #-}
-- | Implements a custom currency with a minting policy that allows
-- the minting of a fixed amount of units.
module Plutus.Contracts.Currency(
OneShotCurrency(..)
, CurrencySchema
, CurrencyError(..)
, AsCurrencyError(..)
, curPolicy
-- * Actions etc
, mintContract
, mintedValue
, currencySymbol
-- * Simple minting policy currency
, SimpleMPS(..)
, mintCurrency
) where
import Control.Lens
import PlutusTx.Prelude hiding (Monoid (..), Semigroup (..))
import Plutus.Contract as Contract
import Plutus.Contract.Wallet (getUnspentOutput)
import Ledger (CurrencySymbol, PaymentPubKeyHash, TxId, TxOutRef (..), getCardanoTxId, pubKeyHashAddress,
scriptCurrencySymbol)
import Ledger.Constraints qualified as Constraints
import Ledger.Contexts qualified as V
import Ledger.Scripts
import PlutusTx qualified
import Ledger.Typed.Scripts qualified as Scripts
import Ledger.Value (TokenName, Value)
import Ledger.Value qualified as Value
import Data.Aeson (FromJSON, ToJSON)
import Data.Semigroup (Last (..))
import GHC.Generics (Generic)
import PlutusTx.AssocMap qualified as AssocMap
import Prelude (Semigroup (..))
import Prelude qualified as Haskell
import Schema (ToSchema)
{- HLINT ignore "Use uncurry" -}
-- | A currency that can be created exactly once
data OneShotCurrency = OneShotCurrency
{ curRefTransactionOutput :: (TxId, Integer)
-- ^ Transaction input that must be spent when
-- the currency is minted.
, curAmounts :: AssocMap.Map TokenName Integer
-- ^ How many units of each 'TokenName' are to
-- be minted.
}
deriving stock (Generic, Haskell.Show, Haskell.Eq)
deriving anyclass (ToJSON, FromJSON)
PlutusTx.makeLift ''OneShotCurrency
currencyValue :: CurrencySymbol -> OneShotCurrency -> Value
currencyValue s OneShotCurrency{curAmounts = amts} =
let
values = map (\(tn, i) -> Value.singleton s tn i) (AssocMap.toList amts)
in fold values
mkCurrency :: TxOutRef -> [(TokenName, Integer)] -> OneShotCurrency
mkCurrency (TxOutRef h i) amts =
OneShotCurrency
{ curRefTransactionOutput = (h, i)
, curAmounts = AssocMap.fromList amts
}
checkPolicy :: OneShotCurrency -> () -> V.ScriptContext -> Bool
checkPolicy c@(OneShotCurrency (refHash, refIdx) _) _ [email protected]{V.scriptContextTxInfo=txinfo} =
let
-- see note [Obtaining the currency symbol]
ownSymbol = V.ownCurrencySymbol ctx
minted = V.txInfoMint txinfo
expected = currencyValue ownSymbol c
-- True if the pending transaction mints the amount of
-- currency that we expect
mintOK =
let v = expected == minted
in traceIfFalse "C0" {-"Value minted different from expected"-} v
-- True if the pending transaction spends the output
-- identified by @(refHash, refIdx)@
txOutputSpent =
let v = V.spendsOutput txinfo refHash refIdx
in traceIfFalse "C1" {-"Pending transaction does not spend the designated transaction output"-} v
in mintOK && txOutputSpent
curPolicy :: OneShotCurrency -> MintingPolicy
curPolicy cur = mkMintingPolicyScript $
$$(PlutusTx.compile [|| \c -> Scripts.wrapMintingPolicy (checkPolicy c) ||])
`PlutusTx.applyCode`
PlutusTx.liftCode cur
{- note [Obtaining the currency symbol]
The currency symbol is the address (hash) of the validator. That is why
we can use 'Ledger.scriptAddress' here to get the symbol in off-chain code,
for example in 'mintedValue'.
Inside the validator script (on-chain) we can't use 'Ledger.scriptAddress',
because at that point we don't know the hash of the script yet. That
is why we use 'V.ownCurrencySymbol', which obtains the hash from the
'PolicyCtx' value.
-}
-- | The 'Value' minted by the 'OneShotCurrency' contract
mintedValue :: OneShotCurrency -> Value
mintedValue cur = currencyValue (currencySymbol cur) cur
currencySymbol :: OneShotCurrency -> CurrencySymbol
currencySymbol = scriptCurrencySymbol . curPolicy
newtype CurrencyError =
CurContractError ContractError
deriving stock (Haskell.Eq, Haskell.Show, Generic)
deriving anyclass (ToJSON, FromJSON)
makeClassyPrisms ''CurrencyError
instance AsContractError CurrencyError where
_ContractError = _CurContractError
-- | @mint [(n1, c1), ..., (n_k, c_k)]@ creates a new currency with
-- @k@ token names, minting @c_i@ units of each token @n_i@.
-- If @k == 0@ then no value is minted. A one-shot minting policy
-- script is used to ensure that no more units of the currency can
-- be minted afterwards.
mintContract
:: forall w s e.
( AsCurrencyError e
)
=> PaymentPubKeyHash
-> [(TokenName, Integer)]
-> Contract w s e OneShotCurrency
mintContract pk amounts = mapError (review _CurrencyError) $ do
txOutRef <- getUnspentOutput
utxos <- utxosAt (pubKeyHashAddress pk Nothing)
let theCurrency = mkCurrency txOutRef amounts
curVali = curPolicy theCurrency
lookups = Constraints.mintingPolicy curVali
<> Constraints.unspentOutputs utxos
mintTx = Constraints.mustSpendPubKeyOutput txOutRef
<> Constraints.mustMintValue (mintedValue theCurrency)
tx <- submitTxConstraintsWith @Scripts.Any lookups mintTx
_ <- awaitTxConfirmed (getCardanoTxId tx)
pure theCurrency
-- | Minting policy for a currency that has a fixed amount of tokens issued
-- in one transaction
data SimpleMPS =
SimpleMPS
{ tokenName :: TokenName
, amount :: Integer
}
deriving stock (Haskell.Eq, Haskell.Show, Generic)
deriving anyclass (FromJSON, ToJSON, ToSchema)
type CurrencySchema =
Endpoint "Create native token" SimpleMPS
-- | Use 'mintContract' to create the currency specified by a 'SimpleMPS'
mintCurrency
:: Promise (Maybe (Last OneShotCurrency)) CurrencySchema CurrencyError OneShotCurrency
mintCurrency = endpoint @"Create native token" $ \SimpleMPS{tokenName, amount} -> do
ownPK <- ownPaymentPubKeyHash
cur <- mintContract ownPK [(tokenName, amount)]
tell (Just (Last cur))
pure cur