SpaceBudz-Market
After months of work, we were finally able to launch the first contract based market place on Cardano!
This blog post will go deep into how the actual contract aka on-chain validator works and may require some technical knowledge.
Introduction
Before we dive into the on-chain validator, we wanted to give a quick overview over the UTxO and the advanced eUTxO model.
The UTxO model got popular through its introduction in the Bitcoin blockchain. UTxO stands for Unspent Transaction Output and simply said it behaves like cash. Imagine having to pay 20$, but you only have a 50$ bill. You give the cashier the 50$ bill and he returns you a change of 30$. Exactly like this UTxOs work. You always have to spend the whole UTxO amount and create change UTxOs if needed. The sum of all UTxOs at your address represent your balance.
So what exactly is stored inside a UTxO? It’s the value and the address. In order to spend the UTxO, the belonging private key to the address needs to sign the transaction.
This means on-chain it is checked, if the transaction is signed accordingly. The validator is static and can only check for this condition. This model would not allow us to create more complex transactions with different conditions.
The eUTxO model solves exactly this problem and now we can program our own custom validators with Plutus and check our defined conditions, when and how a UTxO can be spent. This is what we refer to as “Smart Contracts”.
Next to programmable validators, so called datums and redeemers are introduced in the eUTxO model. The datum is data, that are stored inside the UTxO next to the value and the address. The redeemer is appended in the transaction, when you try to spend the script UTxO.
The validation of a Plutus script looks like this in a formal form:
If the Plutus script with all supplied arguments evaluates to true, the UTxO can be spent.
Looking at the example above, we have an UTxO A at the contract address Contract A. The UTxO has the datum “State 0” appended. We are now spending the script UTxO A and Alice is able to withdraw 8 ₳ with the redeemer “Withdraw”. A new script UTxO C is created at the contract address with a new datum “State 1”. We transitioned UTxO A from “State 0” to UTxO C with “State 1”. This is nothing else than a state machine, where our datum represents the state and the redeemer the state transition.
SpaceBudz Validator
Getting more specific now, the state machine for SpaceBudz looks like this:
At the contract address we can find two different UTxO types. It’s either a Bid or an Offer (Listing) UTxO. Each SpaceBud has its own Bid and Offer UTxO, which means we could find up to 20k valid UTxOs at the contract address. What does valid mean here? Anyone can create UTxOs at the contract address, so we need to find a way to differentiate between legit and “fake” ones. We can achieve that by using NFTs in our UTxOs. For the offering side it’s easy, we just use a SpaceBud in the Offer UTxO and this one already legitimizes the UTxO. However on the bidding side we do not have any NFT, so we need to create one. For SpaceBudz we created 10k bid NFTs and named them SpaceBudBid{id} (e.g. SpaceBudBid0). These prevent the user from opening up their own Bid UTxOs and they rather have to follow the current state of a specific Bid UTxO. Initially we created 1k Bid UTxOs at the contract, where each UTxO contains 10 bid NFTs. These get automatically distributed over time, so there will be eventually 10k Bid UTxOs.
Looking closer at the actual on-chain code we see the states (datums) marked in blue and the state transitions (redeemers) marked in green. There are 3 valid states StartBid, Bid and Offer. From each state we only allow certain state transitions. Each transition must follow the rules (the boolean expressions) in order to get to the new state successfully.
In the following all state transitions will be explained. For simplicity we don’t show the validator in the middle of the graphics. The whole inputs/outputs and transaction context is checked by the SpaceBudz validator.
Start bidding
We assume all bid tokens have been equally distributed across 10k Bid UTxOs and we don’t cover the case here if multiple bid NFTs are in a single UTxO.
Alice wants to bid 100 ₳ on SpaceBud1. She’s the first one opening up a bid and that’s why we find UTxO A in the state StartBid with the SpaceBudBid1 NFT. In order to open open up the bid successfully, she has to use the BidHigher redeemer and needs to creates a new script UTxO in the state Bid, which has to contain the SpaceBudBid1 NFT and her bid amount (must be greater than 70 ₳).
Overbidding
Bob is now bidding on SpaceBud1 as well. He is willing to bid an higher amount of 200 ₳. In order to do that he has to take the UTxO A (which Alice created before) and needs to use the BidHigher redeemer to transition from Bid to Bid again. Also the SpaceBudBid1 NFT needs to be in the script UTxO as well again. The bid amount needs to be higher than the bid from Alice. Alice of course needs to be refunded.
Listing (Offering)
Alice is the owner of SpaceBud1 now and wants to list it on the market place. Listing is not a direct contract interaction. You simply send a UTxO with your SpaceBud and the Offer datum to the contract address.
Canceling Bid
Alice has an open bid with 500 ₳ on SpaceBud1 and wants to cancel the bid now. She needs to be the signer of the transaction and has to use the Cancel redeemer in order to spend the UTxO A. She can reclaim her 500 ₳ now, but also needs to create a new script UTxO, which contains the SpaceBudBid1 NFT and needs to have the datum StartBid.
Canceling Listing
Alice wants to cancel her listing of the SpaceBud1. She needs to be the signer of the transaction and has to use the Cancel redeemer in order to spend the UTxO A. In this case no new script UTxO needs to be created and our state machine transitioned in the end state.
Buying
Bob decides now to buy SpaceBud1, which was listed by Alice for 500 ₳. He can spend the script UTxO A by using the Buy redeemer and has to pay Alice 490 ₳. The remaining 10 ₳ go to the contract owner as service fee. The fee is illustrated in a simplified way. No new script UTxO had to be created and the state machine transitioned into an end state.
Selling
Alice agrees on the 500 ₳ bid on SpaceBud1 of Bob and is ready to sell it now. Alice can spend the script UTxO A with the Sell redeemer and has to create a new script UTxO, which contains the SpaceBudBid1 NFT and needs to be in the StartBid state. 10 ₳ have to go to the owner address and Bob needs to get the SpaceBud1. Like above the fee is illustrated in a simplified way.
These are the valid transitions and states the on-chain validator can go into. To get a better understanding of how all of this works together, here is an illustration demonstrating that:
Why the migration to v2?
Everything described above is the same in v1 and v2, however the issue lies in not carefully checking, how many script inputs are allowed for the validation.
The validator contains a variable called scriptInputValue. Initially it looked like this:
Here it is checked, if just a single script UTxO is part of the transaction. If that’s not the case the script already fails and further validations are not necessary. With a single script input it’s of course not possible to cancel a bid and directly buy or cancel an offer and directly sell. We definitely wanted to have that feature and we changed the scriptInputValue to:
This one returns now the value of the script input that is currently validated. The validator ignores now the amount of script inputs. This is of course fatal, however we realized that it would have been as fatal with the first version as with the second one. Haskell evaluates values lazy and not all branches in the validator directly needed the scriptInputValue.
So we forced Haskell to evaluate this expression with bang patterns (“!”) regardless of it is directly needed or not. And we also made sure just a single script input is part of the transaction, or if it’s a combination of buying/selling and canceling, we also allow two script inputs:
This is our final version, we are using now in v2.
Summary
Overall the SpaceBudz market place is huge success and everything is working as expected. At the time of writing over 700 SpaceBudz are listed on the market place. This is our first contract implementation and we are happy to share it with the community!
The source code is available here: https://github.com/Berry-Pool/spacebudz