Skip to content

Commit 2d8c13c

Browse files
Update taco shop tutorial for JsLIGO v2 (#49)
* feat(doc): updated taco shop tutorial * Update tutorial for JsLIGO v2 * Fix dry-run command
1 parent 2c67445 commit 2d8c13c

File tree

30 files changed

+1670
-1747
lines changed

30 files changed

+1670
-1747
lines changed

gitlab-pages/docs/intro/ligo-intro.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ For a quick overview, [get-started](../tutorials/getting-started) is a good choi
9898

9999
Your choice to learn LIGO is already available:
100100
- Read [basics](../language-basics/types) to have a basic comprehension
101-
- Write your first [smart contract](../tutorials/taco-shop/tezos-taco-shop-smart-contract).
101+
- Write your first [smart contract](../tutorials/taco-shop/selling-tacos).
102102
- Others resources are available on [marigold.dev](https://www.marigold.dev/learn)
103103

104104
### Do you want to build a production-ready project?

gitlab-pages/docs/tutorials/getting-started/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,4 +579,4 @@ octez-client get contract storage for counter
579579
Now you have a simple LIGO smart contract and can test it, deploy it, and call it.
580580
You can use it as a starting point to write your own contracts and experiment with LIGO.
581581

582-
You can also continue with the [Taco shop tutorial](../taco-shop/tezos-taco-shop-smart-contract) to learn more about programming with LIGO.
582+
You can also continue with the [Taco shop tutorial](../taco-shop/selling-tacos) to learn more about programming with LIGO.
Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
---
2+
title: "Part 3: Getting the payouts"
3+
pagination_next: null
4+
---
5+
6+
Now that the customer-facing entrypoint of the contract is ready, you can set up the administrator-related entrypoint.
7+
In this case, Pedro needs a way to reset the stock of tacos and send the tez from the contract to his account.
8+
You could do this in two entrypoints, but for simplicity this tutorial shows how to do both of these things in one entrypoint named `payout`.
9+
10+
## Adding administrator information
11+
12+
Also for the sake of simplicity, the contract provides no way to change Pedro's account address after the contract is deployed.
13+
In production applications, the address of the administrator should be in the contract storage and an entrypoint should allow the current administrator to change the administrator address.
14+
As it is, this contract cannot change the administrator address after it is deployed, so use caution.
15+
16+
<Syntax syntax="jsligo">
17+
18+
1. In the `payout` entrypoint, add this code to verify that the administrator is calling the entrypoint:
19+
20+
```jsligo skip
21+
// Ensure that only the admin can call this entrypoint
22+
if (Tezos.get_sender() != storage.admin_address) {
23+
failwith("Only the admin can call this entrypoint");
24+
}
25+
```
26+
27+
The function `Tezos.get_sender` returns the address of the account that called the smart contract.
28+
29+
1. Add this code to generate the operation that sends tez to the administrator account:
30+
31+
```jsligo skip
32+
// Create contract object that represents the target account
33+
const receiver_contract = $match(Tezos.get_contract_opt(storage.admin_address), {
34+
"Some": (contract) => contract,
35+
"None": () => failwith("Couldn't find account"),
36+
});
37+
38+
// Create operation to send tez
39+
const payout_operation = Tezos.Operation.transaction(unit, Tezos.get_balance(), receiver_contract);
40+
```
41+
42+
Sending tez to a user account means treating the user account as though it is a smart contract account.
43+
This way, sending tez to a user account works in the same way as sending tez to a smart contract.
44+
45+
The `Tezos.Operation.transaction` function creates a Tezos transaction.
46+
There are many kinds of internal transactions in Tezos, but most smart contracts deal with these transactions:
47+
48+
- Transferring tez to another account
49+
- Calling an entrypoint on a smart contract
50+
51+
Calling an entrypoint on a smart contract (either the current contract or another contract) is beyond the scope of this tutorial.
52+
For information, see [Calling a contract](../../syntax/contracts/operation#calling-a-contract).
53+
54+
The `Tezos.Operation.transaction` function takes these parameters:
55+
56+
1. The parameter to pass, in this case `unit`, which means no value
57+
1. The amount of tez to include with the transaction, in this case all of the tez the contract has, denoted by the `Tezos.get_balance` function
58+
1. The address of the target contract
59+
60+
1. Add this code to calculate the new value of the storage, using the existing admin address and the default taco data:
61+
62+
```jsligo skip
63+
// Restore stock of tacos
64+
const new_storage: storage = {
65+
admin_address: storage.admin_address,
66+
taco_data: default_taco_data,
67+
};
68+
```
69+
70+
1. Replace the `payout` entrypoint's `return` statement with this code:
71+
72+
```jsligo skip
73+
return [[payout_operation], new_storage];
74+
```
75+
76+
Creating the transaction is not enough to run it; you must return it in the list of operations at the end of the entrypoint.
77+
78+
The complete entrypoint looks like this:
79+
80+
```jsligo skip
81+
// @entry
82+
const payout = (_u: unit, storage: storage): [
83+
list<operation>,
84+
storage
85+
] => {
86+
87+
// Ensure that only the admin can call this entrypoint
88+
if (Tezos.get_sender() != storage.admin_address) {
89+
failwith("Only the admin can call this entrypoint");
90+
}
91+
92+
// Create contract object that represents the target account
93+
const receiver_contract = $match(Tezos.get_contract_opt(storage.admin_address), {
94+
"Some": (contract) => contract,
95+
"None": () => failwith("Couldn't find account"),
96+
});
97+
98+
// Create operation to send tez
99+
const payout_operation = Tezos.Operation.transaction(unit, Tezos.get_balance(), receiver_contract);
100+
101+
// Restore stock of tacos
102+
const new_storage: storage = {
103+
admin_address: storage.admin_address,
104+
taco_data: default_taco_data,
105+
};
106+
107+
return [[payout_operation], new_storage];
108+
}
109+
```
110+
111+
</Syntax>
112+
113+
<Syntax syntax="cameligo">
114+
115+
1. In the `payout` entrypoint, add this code to verify that the administrator is calling the entrypoint:
116+
117+
```cameligo skip
118+
(* Ensure that only the admin can call this entrypoint *)
119+
let _ = if (Tezos.get_sender () <> storage.admin_address) then
120+
failwith "Only the admin can call this entrypoint" in
121+
```
122+
123+
The function `Tezos.get_sender` returns the address of the account that called the smart contract.
124+
125+
1. Add this code to generate the operation that sends tez to the administrator account:
126+
127+
```cameligo skip
128+
(* Create contract object that represents the target account *)
129+
let receiver_contract = match Tezos.get_contract_opt storage.admin_address with
130+
| Some contract -> contract
131+
| None -> failwith "Couldn't find account" in
132+
133+
(* Create operation to send tez *)
134+
let payout_operation = Tezos.Operation.transaction unit (Tezos.get_balance ()) receiver_contract in
135+
```
136+
137+
Sending tez to a user account means treating the user account as though it is a smart contract account.
138+
This way, sending tez to a user account works in the same way as sending tez to a smart contract.
139+
140+
The `Tezos.Operation.transaction` function creates a Tezos transaction.
141+
There are many kinds of internal transactions in Tezos, but most smart contracts deal with these transactions:
142+
143+
- Transferring tez to another account
144+
- Calling an entrypoint on a smart contract
145+
146+
Calling an entrypoint on a smart contract (either the current contract or another contract) is beyond the scope of this tutorial.
147+
For information, see [Calling a contract](../../syntax/contracts/operation#calling-a-contract).
148+
149+
The `Tezos.Operation.transaction` function takes these parameters:
150+
151+
1. The parameter to pass, in this case `unit`, which means no value
152+
1. The amount of tez to include with the transaction, in this case all of the tez the contract has, denoted by the `Tezos.get_balance` function
153+
1. The address of the target contract
154+
155+
1. Add this code to calculate the new value of the storage, using the existing admin address and the default taco data:
156+
157+
```cameligo skip
158+
(* Restore stock of tacos *)
159+
let new_storage : storage = {
160+
admin_address = storage.admin_address;
161+
taco_data = default_taco_data
162+
} in
163+
```
164+
165+
1. Replace the last line of the `payout` entrypoint with this code:
166+
167+
```cameligo skip
168+
[payout_operation], new_storage
169+
```
170+
171+
Creating the transaction is not enough to run it; you must return it in the list of operations at the end of the entrypoint.
172+
173+
The complete entrypoint looks like this:
174+
175+
```cameligo skip
176+
[@entry]
177+
let payout (_u : unit) (storage : storage) : operation list * storage =
178+
179+
(* Ensure that only the admin can call this entrypoint *)
180+
let _ = if (Tezos.get_sender () <> storage.admin_address) then
181+
failwith "Only the admin can call this entrypoint" in
182+
183+
(* Create contract object that represents the target account *)
184+
let receiver_contract = match Tezos.get_contract_opt storage.admin_address with
185+
| Some contract -> contract
186+
| None -> failwith "Couldn't find account" in
187+
188+
(* Create operation to send tez *)
189+
let payout_operation = Tezos.Operation.transaction unit (Tezos.get_balance ()) receiver_contract in
190+
191+
(* Restore stock of tacos *)
192+
let new_storage : storage = {
193+
admin_address = storage.admin_address;
194+
taco_data = default_taco_data
195+
} in
196+
197+
[payout_operation], new_storage
198+
```
199+
200+
</Syntax>
201+
202+
That's all you need to do to reset the storage and send the contract's tez to the administrator.
203+
If you want to extend this logic, try separating the `payout` entrypoint into separate entrypoints for paying out the tez and resetting the stock of tacos.
204+
205+
## Testing the new entrypoint
206+
207+
Of course, after you implement the `payout` entrypoint, you should add tests for it.
208+
209+
<Syntax syntax="jsligo">
210+
211+
1. At the end of the test function, add this code to get the current balance of Pedro's account before calling the entrypoint:
212+
213+
```jsligo skip
214+
// Test the payout entrypoint as the administrator
215+
const admin_balance_before = Test.Address.get_balance(admin_address);
216+
```
217+
218+
1. Add this code to set the account that smart contract calls come from in the test scenario:
219+
220+
```jsligo skip
221+
Test.State.set_source(admin_address);
222+
```
223+
224+
Now when you call the `Test.Contract.transfer` function, the transaction comes from Pedro's account.
225+
226+
1. Add this code to call the `payout` entrypoint and verify that the storage was updated, as in previous tests:
227+
228+
```jsligo skip
229+
const payout_result =
230+
Test.Contract.transfer(
231+
Test.Typed_address.get_entrypoint("payout", contract.taddr),
232+
unit,
233+
0 as tez
234+
);
235+
$match(payout_result, {
236+
"Success": (_s) => (() => {
237+
const storage = Test.Typed_address.get_storage(contract.taddr);
238+
// Check that the stock has been reset
239+
Assert.assert(
240+
eq_in_map(
241+
Map.find(1 as nat, TacoShop.default_taco_data),
242+
storage.taco_data,
243+
1 as nat
244+
));
245+
Assert.assert(
246+
eq_in_map(
247+
Map.find(2 as nat, TacoShop.default_taco_data),
248+
storage.taco_data,
249+
2 as nat
250+
));
251+
Test.IO.log("Successfully reset taco storage");
252+
})(),
253+
"Fail": (_err) => failwith("Failed to reset taco storage"),
254+
});
255+
```
256+
257+
1. Add this code to verify that Pedro's account received the tez from the contract:
258+
259+
```jsligo skip
260+
// Check that the admin account got a payout
261+
const admin_balance_after = Test.Address.get_balance(admin_address);
262+
Assert.assert(Test.Compare.lt(admin_balance_before, admin_balance_after));
263+
```
264+
265+
The exact amounts differ because calling the `payout` entrypoint costs a small fee, but this code verifies that Pedro's account has more tez in it after calling the `payout` entrypoint.
266+
267+
1. Add this code to generate a test account and verify that it can't call the `payout` entrypoint because it is not the administrator:
268+
269+
```jsligo skip
270+
// Verify that the entrypoint fails if called by someone else
271+
const other_user_account = Test.Account.address(1 as nat);
272+
Test.State.set_source(other_user_account);
273+
const failed_payout_result =
274+
Test.Contract.transfer(
275+
Test.Typed_address.get_entrypoint("payout", contract.taddr),
276+
unit,
277+
0 as tez
278+
);
279+
$match(failed_payout_result, {
280+
"Success": (_s) => failwith("A non-admin user was able to call the payout entrypoint"),
281+
"Fail": (_err) => Test.IO.log("Successfully prevented a non-admin user from calling the payout entrypoint"),
282+
});
283+
```
284+
285+
1. Run the test with `ligo run test taco_shop.jsligo` and verify that the test runs successfully.
286+
287+
</Syntax>
288+
289+
<Syntax syntax="cameligo">
290+
291+
1. At the end of the test function, replace the last block with this code so the function can continue:
292+
293+
```cameligo skip
294+
let () = match fail_result with
295+
| Success _s -> failwith "Test was able to buy a taco for the wrong price"
296+
| Fail _err -> Test.IO.log "Contract successfully blocked purchase with incorrect price" in
297+
```
298+
299+
1. Add this code to get the current balance of Pedro's account before calling the entrypoint:
300+
301+
```cameligo skip
302+
(* Test the payout entrypoint as the administrator *)
303+
let admin_balance_before = Test.Address.get_balance admin_address in
304+
```
305+
306+
1. Add this code to set the account that smart contract calls come from in the test scenario:
307+
308+
```cameligo skip
309+
let () = Test.State.set_source admin_address in
310+
```
311+
312+
Now when you call the `Test.Contract.transfer` function, the transaction comes from Pedro's account.
313+
314+
1. Add this code to call the `payout` entrypoint and verify that the storage was updated, as in previous tests:
315+
316+
```cameligo skip
317+
let payout_result = Test.Contract.transfer
318+
(Test.Typed_address.get_entrypoint "payout" contract.taddr)
319+
unit
320+
0tez
321+
in
322+
let () = match payout_result with
323+
| Success _s -> let storage = Test.Typed_address.get_storage contract.taddr in
324+
let () = Assert.assert
325+
(eq_in_map (Map.find 1n TacoShop.default_taco_data)
326+
storage.taco_data
327+
1n) in
328+
let () = Assert.assert
329+
(eq_in_map (Map.find 2n TacoShop.default_taco_data)
330+
storage.taco_data
331+
2n) in
332+
Test.IO.log "Successfully reset taco storage"
333+
| Fail _err -> failwith "Failed to reset taco storage" in
334+
```
335+
336+
1. Add this code to verify that Pedro's account received the tez from the contract:
337+
338+
```cameligo skip
339+
(* Check that the admin account got a payout *)
340+
let admin_balance_after = Test.Address.get_balance admin_address in
341+
let () = Assert.assert (Test.Compare.lt admin_balance_before admin_balance_after) in
342+
```
343+
344+
The exact amounts differ because calling the `payout` entrypoint costs a small fee, but this code verifies that Pedro's account has more tez in it after calling the `payout` entrypoint.
345+
346+
1. Add this code to generate a test account and verify that it can't call the `payout` entrypoint because it is not the administrator:
347+
348+
```cameligo skip
349+
(* Verify that the entrypoint fails if called by someone else *)
350+
let other_user_account = Test.Account.address 1n in
351+
let _ = Test.State.set_source other_user_account in
352+
let failed_payout_result = Test.Contract.transfer
353+
(Test.Typed_address.get_entrypoint "payout" contract.taddr)
354+
unit
355+
0tez
356+
in
357+
match failed_payout_result with
358+
| Success _s -> failwith "A non-admin user was able to call the payout entrypoint"
359+
| Fail _err -> Test.IO.log "Successfully prevented a non-admin user from calling the payout entrypoint"
360+
```
361+
362+
1. Run the test with `ligo run test taco_shop.mligo` and verify that the test runs successfully.
363+
364+
</Syntax>
365+
366+
Now you can allow different users to do different things in the contract.
367+
368+
## Conclusion
369+
370+
Now you have a contract that Pedro can use to sell tacos and manage the profits and the taco stock.
371+
From here you can expand the contract in many ways, such as:
372+
373+
- Adding more types of tacos
374+
- Changing how the price of tacos is calculated
375+
- Expanding the administrator functionality
376+
- Accepting more than the price of the taco as a tip
377+
- Adding more tests
378+
379+
You can also try deploying the contract to a test network and trying it in a real Tezos environment.
380+
For a tutorial that covers deploying a contract, see [Deploy a smart contract](https://docs.tezos.com/tutorials/smart-contract) on docs.tezos.com.

0 commit comments

Comments
 (0)