Writing Your First Plutus Smart Contract Q/A

Along with the launch of the Clio.1 Education Hub yesterday, you may have noticed our first lesson we released, “Writing Your First Plutus Smart Contract”. It takes you step by step from an empty file to writing your first smart contract.

As this is a beginner focused lesson and writing Plutus smart contracts is a, as of yet, untouched field, I’ve already received a few messages/questions. As such I thought it would be best to make a public thread here on the forum so that we can have both questions & answers indexed for all to reference going into the future.

Whether you simply don’t understand what a line of code is doing, an explanation isn’t quite clear enough, or you are running into compiler errors, feel free to ask (or if you have any feedback for what to improve, constructive criticism is also appreciated). Anything you don’t understand will probably be the case for someone else also, so take your first steps into the Plutus Developer Community and ask away :+1:

27 Likes

First of all, thank you for the website and the first course. I enjoyed reading it and followed through with the examples in the playground. Just for the record: I’m currently an Android developer with more than a decade of programming experience and I’m very interested in what smart contracts can do. I’ve been following cardano for more than a year and I’m glad to see the progress they are making on the smart contract platform side of things.

I have two questions regarding the course on the site:

1,
Can you please explain what watchSmartContract do exactly? Why is this needed and what happens if a wallet interacts with a smart contract before calling watchSmartContract on it?

2,
After the entire contract was implemented, I was playing around with the actions in the playground and I did the following actions one after the other with a single wallet (with 10 ada initially):

  • watchSmartContract
  • depositAda pin = 123 value = 5
  • depositAda pin = 456 value = 5
  • withdrawAda pin = 123
  • withdrawAda pin = 456

After evaluating the actions above the wallet ended up with 0 ADA, because both withdrawAda calls failed with the incorrect pin error.
This is something I did not expect and would like you to explain what’s going on.
I expected either 123 or 456 to be a valid pin after the first two deposits, but nothing seems to be valid and I’m puzzled.

Thanks a lot!

5 Likes

Ah, it’s great to see an Android dev. get into Plutus :+1: Those are two solid questions.

#1. watchSmartContract is needed so the offchain wallet keeps track of the smart contract. This, from my understanding, is primarily needed for the MockChain emulator to know which address to keep track state of as these updates would be broadcast on the blockchain and available for all to see on a real live chain. However in more complex dApps (check out my Jellybean example as one) you also program the off-chain wallet to specifically watch the smart contract address and then perform automated calls when certain parameters are met, which in that case may require something like a watchSmartContract call prior in order to ensure it triggers in an appropriate time-frame.

#2. This is indeed something that was interesting for me too at first. Given that we are writing contracts in a UTXO based blockchain this is something that many people will end up hitting as a point of confusion.

The collectFromScript call goes through all coins sent to the SC and evaluates the ValidatorScript with the DataScript from each UTXO and the RedeemerScript you provided. The call expects to withdraw all coins form the contract, so if any coins fail to validate, as in your example, then the withdraw fails.

Currently the Plutus team has also implemented a collectFromScriptTxn function which attempts to collect a specific transaction, which could be used to make your example work. I assume it is in the plans (if not I’ll end up writing the function myself and submitting it as a PR) to create a function called something like collectFromScriptMatching which ends up collecting only whichever locked UTXOs which have DataScripts that end up validating, and ignore the rest that cause errors.

Furthermore I expect a collect function which will be available to withdraw specific ada amounts, however this will have to also use some sort of UTXO selection algorithm (or offer several to choose from) which makes matters more complicated. As Plutus is still in a very early stage I think this will simply take time for them to put it all together but as you can see on the Github page they are committing daily so it shall all come in due time.

Hope that cleared up your questions.

7 Likes

Really great to have such clear step by step examples and explanations.
Thanks for that !

2 Likes

Yes!!! Thank you so much for putting this together @bobert!

1 Like

I’m having trouble figuring out how to express this question. It’s also probably something simple and obvious that I’m missing.

But, how does the smart contract know to associate submittedPin with the RedeemerScript pin and myPin with the DataScript pin and not vice-versa?

It doesn’t seem to matter in this SC since we just need to make sure the two are equivalent. But, it’s easy to imagine a lot of on-chain logic where differentiating between the two would be necessary (e.g. if the logic was checking that one was larger than the other). My initial instinct was to guess they are associated by sequence. But, submittedPin shows up first in the onchain code while RedeemerScript shows up second in the off-chain code, if I’m not mistaken.

Glad to see you are continuing to learn more Plutus @JBlock.

Indeed, you are correct that since we’re just doing an equality check it theoretically wouldn’t matter if the inputs got switched.

Generally, the ValidatorScript takes 3 inputs in this specific order: RedeemerScript, DataScript, PendingTx. (Note: In the future I’ll create a lesson that shows how to have a ValidatorScript with more inputs, but that’s not important for now) Inputs to functions must always be in the exact order, as this is how they are differentiated, and especially when your inputs have different types, this is the only way it can work. Thus as you said, it is the sequence that matters.

In the off-chain code when you are writing functions, the order of the functions does not matter. You are simply defining them so they can be in whatever order you wish as long as they work. Obviously when using the SC, the Action Sequence needs to be in an order that makes sense (deposit before you withdraw), but in the function definitions it matters not.

2 Likes

Awesome. Thanks! I’ll look forward to the lesson with the additional input handling.

1 Like

Hey @bobert, just a quick general noobish conceptual question.

Let’s say I have a third party who needs to provide an input into the SC (e.g. Party A deposits and Party B can withdraw, but only if Party C also approves the withdrawal). Is there any way to do that without having an additional SC input for Party C in addition to the RedeemerScript, DataScript, (and PendingTx)?

I was thinking of ways to incorporate Party C’s input into the offchain code (e.g. in the withdrawADA function) and getting it onchain that way. But, I’m guessing there’s no way to communicate Party C’s input to the withdrawADA function as it runs for Party B in Party B’s wallet (i.e. the only way for those two inputs to interact is on-chain)?

How about this question:

Is there any intrinsic problem with separating payToScript and DataScript such that one party funds the SC and another provides the DataScript input to the SC (with yet another party providing the RedeemerScript input to the SC)?

I have been very busy as of late so my apologies for leaving your questions unanswered.

Is there any intrinsic problem with separating payToScript and DataScript such that one party funds the SC and another provides the DataScript input to the SC (with yet another party providing the RedeemerScript input to the SC)?

Yes, the DataScript is tied to the coins you send with payToScript. However it should be possible to encode a data structure into the DataScript which the first party leaves blank when paying to the script, and then the 2nd party fills out and references the 1st party’s coins within one of the fields in the data structure. Then the 3rd party could provide the RedeemerScript with their collect call. From what I can tell, as of yet, these are untread waters, so many of the design patterns/best practices to perform such things are not established yet, but undoubtedly will come in the near future.

3 Likes

Additionally I’d like to announce that the Lesson has been updated and now you can test your wits with a Quiz! This is something we’ve been working on incorporating recently, and now that it’s finally finished, it’s time to test your understanding of the lesson you just recently went through. We intend to leverage quizzes often in future Lessons as well, so we hope you enjoy the new added content.

Check out the Quiz below:

4 Likes

Awesome! Thanks for the info.

Great job!

So, I try to subscribe to SC for changes:

withdrawADA :: Int -> MockWallet ()
withdrawADA pin = do
      register collectFundsTrigger (EventHandler (\_ -> logMsg "Cool, I'm getting my money!"))
      collectFromScript myValidator (RedeemerScript (lifted pin))

-- | An event trigger that fires when the funds for a campaign can be collected
collectFundsTrigger :: EventTrigger
collectFundsTrigger = fundsAtAddressT smartContractAddress (GEQ 10)

But, I don’t see my happy logs…
Could you show, pls, how to do this correctly?

Thx.

2 Likes

Ah @Andrew_Voron , it seems you are registering the collectFundsTrigger when you call the withdrawADA function, however this means that right after registering you collect your funds. Since you are emptying the smart contract of all of its funds before giving the registered trigger it’s chance to check if the contract has funds >= 10, well there is no opportunity for the trigger to activate.

In general it is better to register triggers/event handlers in your deposit function rather than your withdraw function, and it’ll avoid your current issue as well.

For anyone wondering who is confused, this technically isn’t from the Lesson itself but more from my previous Jellybean contract example.

4 Likes

Ups. Definetely! Got it work with scenario, you’ve described…
Moral: If you make Smart Contract, be smart!

Thx a lot!

2 Likes

Hey @bobert and everyone else,

I’m getting this error:

" Line 44, Column 13:

error: parse error on input ‘=’ Perhaps you need a 'let' in a 'do' block? e.g. 'let x = 5' instead of 'x = 5'"

at

“watchSCAddress :: MockWallet ()
watchSCAddress = startWatching scAddress”

Any tips on what I might be doing wrong?

Thanks in advance!

Hey there @JBlock ,

Usually errors like this specify the code right after where your error is, so it’d be easier to know exactly what is wrong if you showed more of your code.

At the very least though what can be surmised is that right before the watchSCAddress type signature, it seems you have a do block where you try to assigned a variable like this:
x = 5

However as it says in the error, you need to use a let to instantiate a variable in a do block, so you need to do:
let x = 5

1 Like

Thanks for the reply @bobert .

There weren’t any do blocks around that error. But, I did find an indentation problem that cleared the error as soon as it was fixed.

Unfortunately that just led to the next error which was related to my inability to create a ValidatorScript with more than three inputs.

good tutorial.

2 Likes