forked from besiwims/plutus-tx-template
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathTutorialAuctionSourceCode.ms
More file actions
255 lines (190 loc) · 7.53 KB
/
TutorialAuctionSourceCode.ms
File metadata and controls
255 lines (190 loc) · 7.53 KB
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
**Table of Contents**
1. [🔰 Introduction](#1-introduction)
2. [📋 Prerequisites](#2-prerequisites)
3. [🚀 Starting in `cabal repl`](#3-starting-in-cabal-repl)
4. [📦 Core Data Types](#4-core-data-types)
* 4.1 [AuctionParams](#41-auctionparams)
* 4.2 [AuctionDatum](#42-auctiondatum)
* 4.3 [AuctionRedeemer](#43-auctionredeemer)
5. [🛠️ Writing `auctionTypedValidator`](#5-writing-auctiontypedvalidator)
* 5.1 [Matching on `NewBid`](#51-matching-on-newbid)
* 5.2 [Matching on `Payout`](#52-matching-on-payout)
6. [📜 Compiling the Validator Script](#6-compiling-the-validator-script)
7. [✍️ Writing Unit Tests](#7-writing-unit-tests)
* 7.1 [Mocking `ScriptContext`](#71-mocking-scriptcontext)
* 7.2 [Example Test Case](#72-example-test-case)
* 7.3 [Running Tests](#73-running-tests)
8. [🧪 Property-Based Testing](#8-property-based-testing)
9. [🔭 Next Steps](#9-next-steps)
10. [📖 Glossary](#10-glossary)
---
## 1. 🔰 Introduction
This tutorial walks Haskell beginners through building and testing a Plutus Auction Validator smart contract. You’ll learn:
* Defining on-chain data types
* Writing validation logic in `auctionTypedValidator`
* Compiling to Plutus Core
* Unit and property-based testing
By the end, you’ll be comfortable loading modules in `cabal repl`, stepping through code, and verifying behavior.
## 2. 📋 Prerequisites
* Basic Haskell knowledge (records, pattern matching)
* [GHC](https://www.haskell.org/ghc/) and [Cabal](https://www.haskell.org/cabal/) installed
* Plutus libraries available (via Nix or direct Cabal setup)
## 3. 🚀 Starting in `cabal repl`
1. **Enter your project directory**:
```bash
cd ~/projects/auction
```
2. **Launch the REPL**:
```bash
cabal repl
```
3. **Load the validator module**:
```haskell
λ> :l src/AuctionValidator.hs
[1 of 1] Compiling AuctionValidator ( src/AuctionValidator.hs, interpreted )
Ok, one module loaded.
```
4. **Inspect a type**:
```haskell
λ> :t auctionTypedValidator
auctionTypedValidator :: AuctionParams -> AuctionDatum -> AuctionRedeemer -> ScriptContext -> Bool
```
## 4. 📦 Core Data Types
### 4.1 AuctionParams
```haskell
data AuctionParams = AuctionParams
{ apSeller :: PubKeyHash -- ^ Seller's public key hash
, apCurrencySymbol :: CurrencySymbol -- ^ Minting policy hash
, apTokenName :: TokenName -- ^ Name of the token
, apMinBid :: Lovelace -- ^ Minimum bid amount
, apEndTime :: POSIXTime -- ^ Auction closing time
}
```
* **Step**: In REPL, view fields:
```haskell
λ> :i AuctionParams
```
### 4.2 AuctionDatum
```haskell
newtype AuctionDatum = AuctionDatum { adHighestBid :: Maybe Bid }
```
* Holds `Nothing` (no bids) or `Just Bid` (highest bid so far).
### 4.3 AuctionRedeemer
```haskell
data AuctionRedeemer = NewBid Bid | Payout
```
* **`NewBid`**: Places a new bid.
* **`Payout`**: Closes the auction and distributes funds.
## 5. 🛠️ Writing `auctionTypedValidator`
This function enforces auction rules. It has type:
```haskell
auctionTypedValidator
:: AuctionParams -> AuctionDatum -> AuctionRedeemer -> ScriptContext -> Bool
```
### 5.1 Matching on `NewBid`
```haskell
case redeemer of
NewBid bid ->
and [ sufficientBid bid
, validBidTime
, refundsPreviousHighestBid
, correctOutput bid
]
```
* **`sufficientBid`**: New bid > previous (or ≥ minimum if first)
* **`validBidTime`**: Within `apEndTime`.
* **`refundsPreviousHighestBid`**: Returns lovelace to prior bidder.
* **`correctOutput`**: New output datum + correct token+lovelace bundle.
### 5.2 Matching on `Payout`
```haskell
Payout ->
and [ validPayoutTime
, sellerGetsHighestBid
, highestBidderGetsAsset
]
```
* **`validPayoutTime`**: After `apEndTime`.
* **`sellerGetsHighestBid`**: Seller receives the funds.
* **`highestBidderGetsAsset`**: Winner gets the token (or seller if none).
## 6. 📜 Compiling the Validator Script
In `src/AuctionValidator.hs` you’ll find:
```haskell
auctionValidatorScript :: AuctionParams -> CompiledCode ...
auctionValidatorScript params = $$(PlutusTx.compile [|| auctionUntypedValidator ||])
`PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion100 params
```
* **In REPL**:
```haskell
λ> :l src/AuctionValidator.hs
λ> :browse auctionValidatorScript
```
## 7. ✍️ Writing Unit Tests
Tests live in `test/AuctionValidatorSpec.hs`.
### 7.1 Mocking `ScriptContext`
```haskell
import qualified PlutusTx.AssocMap as AssocMap
mockScriptContext :: ScriptContext
mockScriptContext = ScriptContext
{ scriptContextTxInfo = TxInfo
{ txInfoInputs = []
, txInfoReferenceInputs = []
, txInfoOutputs = []
, txInfoFee = mempty
, txInfoMint = mempty
, txInfoDCert = []
, txInfoWdrl = AssocMap.empty
, txInfoValidRange = always
, txInfoSignatories = []
, txInfoData = AssocMap.empty
, txInfoId = TxId ""
, txInfoRedeemers = AssocMap.empty
}
, scriptContextPurpose = Spending (TxOutRef (TxId "") 0)
}
```
### 7.2 Example Test Case
```haskell
it "rejects NewBid with empty context" $ do
let params = AuctionParams (PubKeyHash "seller") (CurrencySymbol "") (TokenName "TOK") (Lovelace 100) 1620000000000
datum = AuctionDatum Nothing
redeemer = NewBid (Bid "a" (PubKeyHash "b") (Lovelace 150))
auctionTypedValidator params datum redeemer mockScriptContext `shouldBe` False
```
### 7.3 Running Tests
```bash
cabal test auction-tests
```
## 8. 🧪 Property-Based Testing
Use QuickCheck in `test/AuctionValidatorProperties.hs`:
```haskell
import Test.QuickCheck
instance Arbitrary Bid where
arbitrary = Bid <$> arbitrary <*> arbitrary <*> (Lovelace <$> arbitrary `suchThat` (>0))
prop_higherBid :: Bid -> Bid -> Property
prop_higherBid old new = bAmount new > bAmount old ==>
let params = AuctionParams ...
ctx = mockScriptContext
in auctionTypedValidator params (AuctionDatum (Just old)) (NewBid new) ctx === False
main = quickCheck prop_higherBid
```
Run with:
```bash
cabal test auction-properties
```
## 9. 🔭 Next Steps
* Extend tests: first‐bid acceptance, refund checks, payout flows.
* Instantiate on a local Cardano testnet.
* Integrate off‐chain endpoints and CLI.
## 10. 📖 Glossary
| Term | Definition |
| -------------------- | ------------------------------------------------------------------------------ |
| **REPL** | Read–eval–print loop, interactive shell (`cabal repl`). |
| **Record** | Haskell data structure with named fields (e.g., `AuctionParams`). |
| **Pattern Matching** | Checking a value against a pattern (e.g., `case redeemer of NewBid bid -> …`). |
| **CompiledCode** | PlutusTx wrapper for on-chain code after compilation. |
| **`mempty`** | The identity element for a Monoid, e.g., empty fees, empty maps. |
| **`AssocMap`** | Plutus’s Map type used for scripts (e.g., `txInfoData`, `txInfoRedeemers`). |
| **`QuickCheck`** | Library for property‐based testing in Haskell. |
| **`Hspec`** | Behavior‐driven unit testing framework for Haskell. |
| **`Spending`** | A `ScriptPurpose` constructor indicating a spend of a UTXO (with `TxOutRef`). |
---