Fixed the tutorial. Enabled underscores in tez amounts. Fixed docs on
Tez and tuples (zero-indexation).
This commit is contained in:
parent
040ed8b6ff
commit
f4d688df7b
@ -3,7 +3,28 @@ id: math-numbers-tez
|
|||||||
title: Math, Numbers & Tez
|
title: Math, Numbers & Tez
|
||||||
---
|
---
|
||||||
|
|
||||||
LIGO offers three built-in numerical types: `int`, `nat` and `tez`.
|
LIGO offers three built-in numerical types: `int`, `nat` and
|
||||||
|
`tez`. Values of type `int` are integers; values of type `nat` are
|
||||||
|
natural numbers (integral numbers greater than or equal to zero);
|
||||||
|
values of type `tez` are units of measure of Tezos tokens.
|
||||||
|
|
||||||
|
* Integer literals are the same found in mainstream programming
|
||||||
|
languages, for example, `10`, `-6` and `0`, but there is only one
|
||||||
|
canonical zero: `0` (so, for instance, `-0` and `00` are invalid).
|
||||||
|
|
||||||
|
* Natural numbers are written as digits follwed by the suffix `n`,
|
||||||
|
like so: `12n`, `0n`, and the same restriction on zero as integers
|
||||||
|
applies: `0n` is the only way to specify the natural zero.
|
||||||
|
|
||||||
|
* Tezos tokens can be specified using literals of three kinds:
|
||||||
|
* units of millionth of `tez`, using the suffix `mutez` after a
|
||||||
|
natural literal, like `10000mutez` or `0mutez`;
|
||||||
|
* units of `tez`, using the suffix `tz` or `tez`, like `3tz` or
|
||||||
|
`3tez`;
|
||||||
|
* decimal amounts of `tz` or `tez`, like `12.3tz` or `12.4tez`.
|
||||||
|
|
||||||
|
Note that large integral values can be expressed using underscores to
|
||||||
|
separate groups of digits, like `1_000mutez` or `0.000_004tez`.
|
||||||
|
|
||||||
## Addition
|
## Addition
|
||||||
|
|
||||||
@ -27,7 +48,7 @@ const a : int = 5 + 10
|
|||||||
const b : int = 5n + 10
|
const b : int = 5n + 10
|
||||||
|
|
||||||
// tez + tez yields tez
|
// tez + tez yields tez
|
||||||
const c : tez = 5mutez + 10mutez
|
const c : tez = 5mutez + 0.000_010tez
|
||||||
|
|
||||||
//tez + int or tez + nat is invalid
|
//tez + int or tez + nat is invalid
|
||||||
// const d : tez = 5mutez + 10n
|
// const d : tez = 5mutez + 10n
|
||||||
@ -57,7 +78,7 @@ let a : int = 5 + 10
|
|||||||
let b : int = 5n + 10
|
let b : int = 5n + 10
|
||||||
|
|
||||||
// tez + tez yields tez
|
// tez + tez yields tez
|
||||||
let c : tez = 5mutez + 10mutez
|
let c : tez = 5mutez + 0.000_010tez
|
||||||
|
|
||||||
// tez + int or tez + nat is invalid
|
// tez + int or tez + nat is invalid
|
||||||
// let d : tez = 5mutez + 10n
|
// let d : tez = 5mutez + 10n
|
||||||
@ -87,7 +108,7 @@ let a : int = 5 + 10;
|
|||||||
let b : int = 5n + 10;
|
let b : int = 5n + 10;
|
||||||
|
|
||||||
// tez + tez yields tez
|
// tez + tez yields tez
|
||||||
let c : tez = 5mutez + 10mutez;
|
let c : tez = 5mutez + 0.000_010tez;
|
||||||
|
|
||||||
// tez + int or tez + nat is invalid:
|
// tez + int or tez + nat is invalid:
|
||||||
// let d : tez = 5mutez + 10n;
|
// let d : tez = 5mutez + 10n;
|
||||||
|
@ -62,32 +62,28 @@ Accessing the components of a tuple in OCaml is achieved by
|
|||||||
[pattern matching](language-basics/unit-option-pattern-matching.md). LIGO
|
[pattern matching](language-basics/unit-option-pattern-matching.md). LIGO
|
||||||
currently supports tuple patterns only in the parameters of functions,
|
currently supports tuple patterns only in the parameters of functions,
|
||||||
not in pattern matching. However, we can access components by their
|
not in pattern matching. However, we can access components by their
|
||||||
position in their tuple, which cannot be done in OCaml.
|
position in their tuple, which cannot be done in OCaml. *Tuple
|
||||||
|
components are zero-indexed*, that is, the first component has index
|
||||||
|
`0`.
|
||||||
|
|
||||||
<!--DOCUSAURUS_CODE_TABS-->
|
<!--DOCUSAURUS_CODE_TABS-->
|
||||||
|
|
||||||
<!--PascaLIGO-->
|
<!--PascaLIGO-->
|
||||||
|
|
||||||
Tuple components are one-indexed and accessed like so:
|
|
||||||
|
|
||||||
```pascaligo group=tuple
|
```pascaligo group=tuple
|
||||||
const first_name : string = full_name.1
|
const first_name : string = full_name.0
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--CameLIGO-->
|
<!--CameLIGO-->
|
||||||
|
|
||||||
Tuple elements are zero-indexed and accessed like so:
|
|
||||||
|
|
||||||
```cameligo group=tuple
|
```cameligo group=tuple
|
||||||
let first_name : string = full_name.0
|
let first_name : string = full_name.0
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--ReasonLIGO-->
|
<!--ReasonLIGO-->
|
||||||
|
|
||||||
Tuple components are one-indexed and accessed like so:
|
|
||||||
|
|
||||||
```reasonligo group=tuple
|
```reasonligo group=tuple
|
||||||
let first_name : string = full_name[1];
|
let first_name : string = full_name[0];
|
||||||
```
|
```
|
||||||
|
|
||||||
<!--END_DOCUSAURUS_CODE_TABS-->
|
<!--END_DOCUSAURUS_CODE_TABS-->
|
||||||
|
@ -1,37 +1,46 @@
|
|||||||
type taco_supply is record
|
type taco_supply is
|
||||||
|
record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
const ownerAddress: address = "tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV";
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
const donationAddress: address = "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx";
|
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
type return is list (operation) * taco_shop_storage
|
||||||
begin
|
|
||||||
// Retrieve the taco_kind from the contract's storage
|
const ownerAddress : address = "tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV"
|
||||||
const taco_kind : taco_supply = get_force(taco_kind_index, taco_shop_storage);
|
const donationAddress : address = "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx"
|
||||||
|
|
||||||
const current_purchase_price : tez = taco_kind.max_price / taco_kind.current_stock;
|
function buy_taco (const taco_kind_index : nat; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
|
block {
|
||||||
|
// Retrieve the taco_kind from the contract's storage or fail
|
||||||
|
const taco_kind : taco_supply =
|
||||||
|
case taco_shop_storage[taco_kind_index] of
|
||||||
|
Some (kind) -> kind
|
||||||
|
| None -> (failwith ("Unknown kind of taco.") : taco_supply)
|
||||||
|
end;
|
||||||
|
|
||||||
|
const current_purchase_price : tez =
|
||||||
|
taco_kind.max_price / taco_kind.current_stock;
|
||||||
|
|
||||||
if amount =/= current_purchase_price then
|
if amount =/= current_purchase_price then
|
||||||
// we won't sell tacos if the amount isn't correct
|
// We won't sell tacos if the amount is not correct
|
||||||
fail("Sorry, the taco you're trying to purchase has a different price");
|
failwith ("Sorry, the taco you are trying to purchase has a different price");
|
||||||
else
|
else skip;
|
||||||
// Decrease the stock by 1n, because we've just sold one
|
|
||||||
taco_kind.current_stock := abs(taco_kind.current_stock - 1n);
|
// Decrease the stock by 1n, because we have just sold one
|
||||||
|
taco_kind.current_stock := abs (taco_kind.current_stock - 1n);
|
||||||
|
|
||||||
// Update the storage with the refreshed taco_kind
|
// Update the storage with the refreshed taco_kind
|
||||||
taco_shop_storage[taco_kind_index] := taco_kind;
|
taco_shop_storage[taco_kind_index] := taco_kind;
|
||||||
|
|
||||||
const receiver: contract(unit) = get_contract(ownerAddress);
|
const receiver : contract (unit) = get_contract (ownerAddress);
|
||||||
const donationReceiver: contract(unit) = get_contract(donationAddress);
|
const donationReceiver : contract (unit) = get_contract (donationAddress);
|
||||||
|
|
||||||
const donationAmount: tez = amount / 10n;
|
const donationAmount : tez = amount / 10n;
|
||||||
|
|
||||||
const operations : list(operation) = list
|
const operations : list (operation) = list [
|
||||||
transaction(unit, amount - donationAmount, receiver);
|
transaction (unit, amount - donationAmount, receiver);
|
||||||
transaction(unit, donationAmount, donationReceiver);
|
transaction (unit, donationAmount, donationReceiver);
|
||||||
end;
|
]
|
||||||
|
} with (operations, taco_shop_storage)
|
||||||
end with (operations, taco_shop_storage)
|
|
||||||
|
@ -3,8 +3,14 @@ id: tezos-taco-shop-payout
|
|||||||
title: Paying out profits from the Taco Shop
|
title: Paying out profits from the Taco Shop
|
||||||
---
|
---
|
||||||
|
|
||||||
In the [previous tutorial](tutorials/get-started/tezos-taco-shop-smart-contract.md) we've learned how to setup & interact with the LIGO CLI. Followed by implementation of a simple Taco Shop smart contract for our entepreneur Pedro. In this tutorial we'll make sure Pedro has access to tokens that people have spent at his shop when buying tacos.
|
In the
|
||||||
|
[previous tutorial](tutorials/get-started/tezos-taco-shop-smart-contract.md)
|
||||||
|
we have learnt how to setup & interact with the LIGO CLI. Followed an
|
||||||
|
implementation of a simple Taco Shop smart contract for our
|
||||||
|
entrepreneur Pedro.
|
||||||
|
|
||||||
|
In this tutorial we will make sure Pedro has access to tokens that
|
||||||
|
people have spent at his shop when buying tacos.
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-payout/get-money.svg" width="50%" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-payout/get-money.svg" width="50%" />
|
||||||
@ -14,133 +20,173 @@ In the [previous tutorial](tutorials/get-started/tezos-taco-shop-smart-contract.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## Analyzing the current contract
|
## Analyzing the Current Contract
|
||||||
|
|
||||||
### **`taco-shop.ligo`**
|
### **`taco-shop.ligo`**
|
||||||
```pascaligo group=a
|
```pascaligo group=a
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) :
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
(list(operation) * taco_shop_storage) is
|
|
||||||
begin
|
type return is list (operation) * taco_shop_storage
|
||||||
// Retrieve the taco_kind from the contract's storage
|
|
||||||
const taco_kind : taco_supply = get_force(taco_kind_index, taco_shop_storage);
|
function buy_taco (const taco_kind_index : nat ; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
|
block {
|
||||||
const current_purchase_price : tez = taco_kind.max_price / taco_kind.current_stock;
|
// Retrieve the taco_kind from the contract's storage or fail
|
||||||
|
const taco_kind : taco_supply =
|
||||||
|
case taco_shop_storage[taco_kind_index] of
|
||||||
|
Some (kind) -> kind
|
||||||
|
| None -> (failwith ("Unknown kind of taco.") : taco_supply)
|
||||||
|
end;
|
||||||
|
|
||||||
|
const current_purchase_price : tez =
|
||||||
|
taco_kind.max_price / taco_kind.current_stock;
|
||||||
|
|
||||||
if amount =/= current_purchase_price then
|
if amount =/= current_purchase_price then
|
||||||
// we won't sell tacos if the amount isn't correct
|
// We won't sell tacos if the amount is not correct
|
||||||
failwith("Sorry, the taco you're trying to purchase has a different price");
|
failwith ("Sorry, the taco you are trying to purchase has a different price");
|
||||||
else
|
else skip;
|
||||||
// Decrease the stock by 1n, because we've just sold one
|
|
||||||
taco_kind.current_stock := abs(taco_kind.current_stock - 1n);
|
// Decrease the stock by 1n, because we have just sold one
|
||||||
|
taco_kind.current_stock := abs (taco_kind.current_stock - 1n);
|
||||||
|
|
||||||
// Update the storage with the refreshed taco_kind
|
// Update the storage with the refreshed taco_kind
|
||||||
taco_shop_storage[taco_kind_index] := taco_kind;
|
taco_shop_storage[taco_kind_index] := taco_kind
|
||||||
end with ((nil : list(operation)), taco_shop_storage)
|
} with ((nil : list (operation)), taco_shop_storage)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Purchase price formula
|
### Purchase Price Formula
|
||||||
Pedro's Taco Shop contract currently enables customers to buy tacos, at a computed price based on a simple formula.
|
|
||||||
|
Pedro's Taco Shop contract currently enables customers to buy tacos,
|
||||||
|
at a price based on a simple formula.
|
||||||
|
|
||||||
```pascaligo skip
|
```pascaligo skip
|
||||||
const current_purchase_price : tez = taco_kind.max_price / taco_kind.current_stock;
|
const current_purchase_price : tez =
|
||||||
|
taco_kind.max_price / taco_kind.current_stock
|
||||||
```
|
```
|
||||||
|
|
||||||
### Replacing *spendable* smart contracts
|
### Replacing *spendable* Smart Contracts
|
||||||
However, due to the [recent protocol upgrade](http://tezos.gitlab.io/mainnet/protocols/004_Pt24m4xi.html) of the Tezos mainnet, Pedro can't access the tokens stored in his Shop's contract directly. This was previously possible via `spendable` smart contracts, which are no longer available in the new protocol. We will have to implement a solution to access tokens from the contract programatically.
|
|
||||||
|
However, due to the
|
||||||
|
[recent protocol upgrade](http://tezos.gitlab.io/mainnet/protocols/004_Pt24m4xi.html)
|
||||||
|
of the Tezos `mainnet`, Pedro cannot access the tokens stored in his
|
||||||
|
shop's contract directly. This was previously possible via *spendable
|
||||||
|
smart contracts*, which are no longer available in the new
|
||||||
|
protocol. We will have to implement a solution to access tokens from
|
||||||
|
the contract programatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Designing a payout scheme
|
## Designing a Payout Scheme
|
||||||
|
|
||||||
Pedro is a standalone bussines owner, and in our case, he doesn't have to split profits / earnings of the taco shop with anyone. So for the sake of simplicity, we'll payout all the earned XTZ directly to Pedro right after a succesful taco purchase.
|
Pedro is a standalone bussines owner, and in our case, he does not
|
||||||
|
have to split profits and earnings of the taco shop with anyone. So
|
||||||
|
for the sake of simplicity, we will payout all the earned XTZ directly
|
||||||
|
to Pedro right after a succesful purchase.
|
||||||
|
|
||||||
This means that after all the *purchase conditions* of our contract are met - e.g. correct amount is sent to the contract - we'll not only decrease the supply of the individual purchased *taco kind*, but we'll also transfer this amount in a *subsequent transaction* to Pedro's personal address.
|
This means that after all the *purchase conditions* of our contract
|
||||||
|
are met, e.g., the correct amount is sent to the contract, we will not
|
||||||
|
only decrease the supply of the individual purchased *taco kind*, but
|
||||||
|
we will also transfer this amount in a *subsequent transaction* to
|
||||||
|
Pedro's personal address.
|
||||||
|
|
||||||
## Forging a payout transaction
|
## Forging a Payout Transaction
|
||||||
|
|
||||||
### Defining the recipient
|
### Defining the Recipient
|
||||||
In order to send tokens, we will need a receiver address - which in our case will be Pedro's personal account. Additionally we'll wrap the given address as a *`contract(unit)`* - which represents either a contract with no parameters, or an implicit account.
|
|
||||||
|
In order to send tokens, we will need a receiver address, which, in
|
||||||
|
our case, will be Pedro's personal account. Additionally we will wrap
|
||||||
|
the given address as a *`contract (unit)`*, which represents either a
|
||||||
|
contract with no parameters, or an implicit account.
|
||||||
|
|
||||||
```pascaligo group=ex1
|
```pascaligo group=ex1
|
||||||
const ownerAddress : address = ("tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV" : address);
|
const ownerAddress : address = ("tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV" : address);
|
||||||
const receiver : contract(unit) = get_contract(ownerAddress);
|
const receiver : contract (unit) = get_contract (ownerAddress);
|
||||||
```
|
```
|
||||||
|
|
||||||
> Would you like to learn more about addresses, contracts and operations in LIGO? Check out the [LIGO cheat sheet](api/cheat-sheet.md)
|
> Would you like to learn more about addresses, contracts and
|
||||||
|
> operations in LIGO? Check out the
|
||||||
|
> [LIGO cheat sheet](api/cheat-sheet.md)
|
||||||
|
|
||||||
### Adding the transaction to the list of output operations
|
### Adding the Transaction to the List of Output Operations
|
||||||
Now we can transfer the `amount` received by `buy_taco` to Pedro's `ownerAddress`. We will do so by forging a `transaction(unit, amount, receiver)` within a list of operations returned at the end of our contract.
|
|
||||||
|
|
||||||
|
Now we can transfer the amount received by `buy_taco` to Pedro's
|
||||||
|
`ownerAddress`. We will do so by forging a `transaction (unit, amount,
|
||||||
|
receiver)` within a list of operations returned at the end of our
|
||||||
|
contract.
|
||||||
|
|
||||||
```pascaligo group=ex1
|
```pascaligo group=ex1
|
||||||
const payoutOperation : operation = transaction(unit, amount, receiver) ;
|
const payoutOperation : operation = transaction (unit, amount, receiver) ;
|
||||||
const operations : list(operation) = list
|
const operations : list (operation) = list [payoutOperation];
|
||||||
payoutOperation
|
|
||||||
end;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Finalizing the contract
|
## Finalizing the Contract
|
||||||
|
|
||||||
### **`taco-shop.ligo`**
|
### **`taco-shop.ligo`**
|
||||||
```pascaligo group=b
|
```pascaligo group=b
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
const ownerAddress: address = ("tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV" : address);
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
type return is list (operation) * taco_shop_storage
|
||||||
begin
|
|
||||||
// Retrieve the taco_kind from the contract's storage
|
const ownerAddress : address =
|
||||||
const taco_kind : taco_supply = get_force(taco_kind_index, taco_shop_storage);
|
("tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV" : address)
|
||||||
|
|
||||||
const current_purchase_price : tez = taco_kind.max_price / taco_kind.current_stock;
|
function buy_taco (const taco_kind_index : nat ; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
|
block {
|
||||||
|
// Retrieve the taco_kind from the contract's storage or fail
|
||||||
|
const taco_kind : taco_supply =
|
||||||
|
case taco_shop_storage[taco_kind_index] of
|
||||||
|
Some (kind) -> kind
|
||||||
|
| None -> (failwith ("Unknown kind of taco.") : taco_supply)
|
||||||
|
end;
|
||||||
|
|
||||||
|
const current_purchase_price : tez =
|
||||||
|
taco_kind.max_price / taco_kind.current_stock;
|
||||||
|
|
||||||
if amount =/= current_purchase_price then
|
if amount =/= current_purchase_price then
|
||||||
// we won't sell tacos if the amount isn't correct
|
// We won't sell tacos if the amount is not correct
|
||||||
failwith("Sorry, the taco you're trying to purchase has a different price");
|
failwith ("Sorry, the taco you are trying to purchase has a different price");
|
||||||
else
|
else skip;
|
||||||
// Decrease the stock by 1n, because we've just sold one
|
|
||||||
taco_kind.current_stock := abs(taco_kind.current_stock - 1n);
|
// Decrease the stock by 1n, because we have just sold one
|
||||||
|
taco_kind.current_stock := abs (taco_kind.current_stock - 1n);
|
||||||
|
|
||||||
// Update the storage with the refreshed taco_kind
|
// Update the storage with the refreshed taco_kind
|
||||||
taco_shop_storage[taco_kind_index] := taco_kind;
|
taco_shop_storage[taco_kind_index] := taco_kind;
|
||||||
|
|
||||||
const receiver : contract(unit) = get_contract(ownerAddress);
|
const receiver : contract(unit) = get_contract (ownerAddress);
|
||||||
const payoutOperation : operation = transaction(unit, amount, receiver);
|
const payoutOperation : operation = transaction (unit, amount, receiver);
|
||||||
const operations : list(operation) = list
|
const operations : list(operation) = list [payoutOperation]
|
||||||
payoutOperation
|
} with ((nil : list (operation)), taco_shop_storage)
|
||||||
end;
|
|
||||||
|
|
||||||
end with (operations, taco_shop_storage)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Dry-run the Contract
|
||||||
|
|
||||||
### Dry-run the contract
|
To confirm that our contract is valid, we can dry-run it. As a result,
|
||||||
|
we see a *new operation* in the list of returned operations to be
|
||||||
To confirm that our contract is valid, we can dry run it. As a result we see a *new operation* in the list of returned operations to be executed subsequently.
|
executed subsequently.
|
||||||
|
|
||||||
```pascaligo skip
|
```pascaligo skip
|
||||||
ligo dry-run taco-shop.ligo --syntax pascaligo --amount 1 buy_taco 1n "map
|
ligo dry-run taco-shop.ligo --syntax pascaligo --amount 1 buy_taco 1n "map [
|
||||||
1n -> record
|
1n -> record [
|
||||||
current_stock = 50n;
|
current_stock = 50n;
|
||||||
max_price = 50000000mutez;
|
max_price = 50tez
|
||||||
end;
|
];
|
||||||
2n -> record
|
2n -> record [
|
||||||
current_stock = 20n;
|
current_stock = 20n;
|
||||||
max_price = 75000000mutez;
|
max_price = 75tez
|
||||||
end;
|
];
|
||||||
end"
|
]"
|
||||||
```
|
```
|
||||||
|
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-payout/dry-run-1.png" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-payout/dry-run-1.png" />
|
||||||
@ -150,32 +196,34 @@ end"
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
**Done! Our tokens are no longer locked in the contract, and instead they are sent to Pedro's personal account/wallet.**
|
**Done! Our tokens are no longer locked in the contract, and instead
|
||||||
|
they are sent to Pedro's personal account/wallet.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 👼 Bonus: donating part of the profits
|
## 👼 Bonus: Donating Part of the Profits
|
||||||
|
|
||||||
Because Pedro is a member of the (STA) Specialty Taco Association, he has decided to donate **10%** of the earnings to the STA. We'll just add a `donationAddress` to the contract, and compute a 10% donation sum from each taco purchase.
|
Because Pedro is a member of the Specialty Taco Association (STA), he
|
||||||
|
has decided to donate **10%** of the earnings to the STA. We will just
|
||||||
|
add a `donationAddress` to the contract, and compute a 10% donation
|
||||||
|
sum from each taco purchase.
|
||||||
|
|
||||||
```pascaligo group=bonus
|
```pascaligo group=bonus
|
||||||
const ownerAddress: address = ("tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV" : address);
|
const ownerAddress : address = ("tz1TGu6TN5GSez2ndXXeDX6LgUDvLzPLqgYV" : address);
|
||||||
const donationAddress: address = ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address);
|
const donationAddress : address = ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address);
|
||||||
```
|
|
||||||
|
|
||||||
```pascaligo group=bonus
|
const receiver : contract (unit) = get_contract (ownerAddress);
|
||||||
const receiver : contract(unit) = get_contract(ownerAddress);
|
const donationReceiver : contract(unit) = get_contract (donationAddress);
|
||||||
const donationReceiver : contract(unit) = get_contract(donationAddress);
|
|
||||||
|
|
||||||
const donationAmount: tez = amount / 10n;
|
const donationAmount : tez = amount / 10n;
|
||||||
|
|
||||||
const operations : list(operation) = list
|
const operations : list (operation) = list [
|
||||||
// Pedro will get 90% of the amount
|
// Pedro will get 90% of the amount
|
||||||
transaction(unit, amount - donationAmount, receiver);
|
transaction (unit, amount - donationAmount, receiver);
|
||||||
transaction(unit, donationAmount, donationReceiver);
|
transaction (unit, donationAmount, donationReceiver)
|
||||||
end;
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
This will result into two operations being subsequently executed on the blockchain:
|
This will result into two operations being subsequently executed on the blockchain:
|
||||||
- Donation transfer (10%)
|
- Donation transfer (10%)
|
||||||
- Pedro's profits (90%)
|
- Pedro's profits (90%)
|
||||||
|
@ -1,23 +1,33 @@
|
|||||||
type taco_supply is record
|
type taco_supply is
|
||||||
|
record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
begin
|
|
||||||
// Retrieve the taco_kind from the contract's storage
|
type return is list (operation) * taco_shop_storage
|
||||||
const taco_kind : taco_supply = get_force(taco_kind_index, taco_shop_storage);
|
|
||||||
|
function buy_taco (const taco_kind_index : nat ; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
const current_purchase_price : tez = taco_kind.max_price / taco_kind.current_stock;
|
block {
|
||||||
|
// Retrieve the taco_kind from the contract's storage or fail
|
||||||
|
const taco_kind : taco_supply =
|
||||||
|
case taco_shop_storage[taco_kind_index] of
|
||||||
|
Some (kind) -> kind
|
||||||
|
| None -> (failwith ("Unknown kind of taco.") : taco_supply)
|
||||||
|
end;
|
||||||
|
|
||||||
|
const current_purchase_price : tez =
|
||||||
|
taco_kind.max_price / taco_kind.current_stock;
|
||||||
|
|
||||||
if amount =/= current_purchase_price then
|
if amount =/= current_purchase_price then
|
||||||
// we won't sell tacos if the amount isn't correct
|
// We won't sell tacos if the amount is not correct
|
||||||
fail("Sorry, the taco you're trying to purchase has a different price");
|
failwith ("Sorry, the taco you are trying to purchase has a different price");
|
||||||
else
|
else skip;
|
||||||
// Decrease the stock by 1n, because we've just sold one
|
|
||||||
taco_kind.current_stock := abs(taco_kind.current_stock - 1n);
|
// Decrease the stock by 1n, because we have just sold one
|
||||||
|
taco_kind.current_stock := abs (taco_kind.current_stock - 1n);
|
||||||
|
|
||||||
// Update the storage with the refreshed taco_kind
|
// Update the storage with the refreshed taco_kind
|
||||||
taco_shop_storage[taco_kind_index] := taco_kind;
|
taco_shop_storage[taco_kind_index] := taco_kind
|
||||||
end with ((nil : list(operation)), taco_shop_storage)
|
} with ((nil : list (operation)), taco_shop_storage)
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
---
|
---
|
||||||
id: tezos-taco-shop-smart-contract
|
id: tezos-taco-shop-smart-contract
|
||||||
title: Taco shop smart contract
|
title: The Taco Shop Smart Contract
|
||||||
---
|
---
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
Meet **Pedro**, our *artisan taco chef* who has decided to open a Taco shop on the Tezos blockchain, using a smart contract. He sells two different kinds of tacos, the **el clásico** and the **especial del chef**.
|
Meet **Pedro**, our *artisan taco chef*, who has decided to open a
|
||||||
|
Taco shop on the Tezos blockchain, using a smart contract. He sells
|
||||||
|
two different kinds of tacos: **el Clásico** and the **Especial
|
||||||
|
del Chef**.
|
||||||
|
|
||||||
To help Pedro open his dream taco shop, we'll implement a smart contract, that will manage supply, pricing & sales of his tacos to the consumers.
|
To help Pedro open his dream taco shop, we will implement a smart
|
||||||
|
contract that will manage supply, pricing & sales of his tacos to the
|
||||||
|
consumers.
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/taco-stand.svg" width="50%" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/taco-stand.svg" width="50%" />
|
||||||
@ -18,91 +23,120 @@ To help Pedro open his dream taco shop, we'll implement a smart contract, that w
|
|||||||
|
|
||||||
## Pricing
|
## Pricing
|
||||||
|
|
||||||
Pedro's tacos are a rare delicacy, so their **price goes up**, as the **stock for the day begins to deplete**.
|
Pedro's tacos are a rare delicacy, so their **price goes up** as the
|
||||||
|
**stock for the day begins to deplete**.
|
||||||
|
|
||||||
Each taco kind, has its own `max_price` that it sells for, and a finite supply for the current sales lifecycle.
|
Each taco kind, has its own `max_price` that it sells for, and a
|
||||||
|
finite supply for the current sales lifecycle.
|
||||||
|
|
||||||
> For the sake of simplicity, we won't implement replenishing of the supply after it runs out.
|
> For the sake of simplicity, we will not implement the replenishing
|
||||||
|
> of the supply after it has run out.
|
||||||
|
|
||||||
### Daily offer
|
### Daily Offer
|
||||||
|
|
||||||
|**kind** |id |**available_stock**| **max_price**|
|
|**kind** |id |**available_stock**| **max_price**|
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
|el clásico | `1n` | `50n` | `50000000mutez` |
|
|Clásico | `1n` | `50n` | `50tez` |
|
||||||
|especial del chef | `2n` | `20n` | `75000000mutez` |
|
|Especial del Chef | `2n` | `20n` | `75tez` |
|
||||||
|
|
||||||
### Calculating the current purchase price
|
### Calculating the Current Purchase Price
|
||||||
|
|
||||||
Current purchase price is calculated with the following equation:
|
The current purchase price is calculated with the following formula:
|
||||||
|
|
||||||
```pascaligo skip
|
```pascaligo skip
|
||||||
current_purchase_price = max_price / available_stock
|
current_purchase_price = max_price / available_stock
|
||||||
```
|
```
|
||||||
|
|
||||||
#### El clásico
|
#### El Clásico
|
||||||
|**available_stock**|**max_price**|**current_purchase_price**|
|
|**available_stock**|**max_price**|**current_purchase_price**|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `50n` | `50000000mutez` | `1tz`|
|
| `50n` | `50tez` | `1tez`|
|
||||||
| `20n` | `50000000mutez` | `2.5tz` |
|
| `20n` | `50tez` | `2.5tez` |
|
||||||
| `5n` | `50000000mutez` | `10tz` |
|
| `5n` | `50tez` | `10tez` |
|
||||||
|
|
||||||
#### Especial del chef
|
#### Especial del chef
|
||||||
|**available_stock**|**max_price**|**current_purchase_price**|
|
|**available_stock**|**max_price**|**current_purchase_price**|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `20n` | `75000000mutez` | `3.75tz` |
|
| `20n` | `75tez` | `3.75tez` |
|
||||||
| `10n` | `75000000mutez` | `7.5tz`|
|
| `10n` | `75tez` | `7.5tez`|
|
||||||
| `5n` | `75000000mutez` | `15tz` |
|
| `5n` | `75tez` | `15tez` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installing LIGO
|
## Installing LIGO
|
||||||
|
|
||||||
In this tutorial, we'll use LIGO's dockerized version for the sake of simplicity. You can find the installation instructions [here](intro/installation.md#dockerized-installation-recommended).
|
In this tutorial, we will use LIGO's dockerized version, for the sake
|
||||||
|
of simplicity. You can find the installation instructions
|
||||||
|
[here](intro/installation.md#dockerized-installation-recommended).
|
||||||
|
|
||||||
The best way to install the dockerized LIGO is as a **global executable** through the installation script, as shown in the screenshot below:
|
The best way to install the dockerized LIGO is as a **global
|
||||||
|
executable** through the installation script, as shown in the
|
||||||
|
screenshot below:
|
||||||
|
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/install-ligo.png" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/install-ligo.png" />
|
||||||
<div style="opacity: 0.7; text-align: center; font-size: 12px; margin-top:-24px;">Installing the <b>next</b> version of LIGO's CLI</div>
|
<div style="opacity: 0.7; text-align: center; font-size: 12px; margin-top:-24px;">Installing the <b>next</b> version of LIGO's CLI</div>
|
||||||
|
|
||||||
## Implementing our first entry point
|
## Implementing our first access function
|
||||||
|
|
||||||
> From now on we'll get a bit more technical. If you run into something we have not covered yet - please try checking out the [LIGO cheat sheet](api/cheat-sheet.md) for some extra tips & tricks.
|
> From now on we will get a bit more technical. If you run into
|
||||||
|
> something we have not covered yet - please try checking out the
|
||||||
|
> [LIGO cheat sheet](api/cheat-sheet.md) for some extra tips & tricks.
|
||||||
|
|
||||||
To begin implementing our smart contract, we need an entry point. We'll call it `main` and it'll specify our contract's storage (`int`) and input parameter (`int`). Of course this is not the final storage/parameter of our contract, but it is something to get us started and test our LIGO installation as well.
|
To begin implementing our smart contract, we need an *access
|
||||||
|
function*, that is the first function being executed. We will call it
|
||||||
|
`main` and it will specify our contract's storage (`int`) and input
|
||||||
|
parameter (`int`). Of course this is not the final storage/parameter
|
||||||
|
of our contract, but it is something to get us started and test our
|
||||||
|
LIGO installation as well.
|
||||||
|
|
||||||
### `taco-shop.ligo`
|
### `taco-shop.ligo`
|
||||||
```pascaligo group=a
|
```pascaligo group=a
|
||||||
function main (const parameter: int; const contractStorage: int) : (list(operation) * int) is
|
function main (const parameter : int; const contractStorage : int) :
|
||||||
block {skip} with ((nil : list(operation)), contractStorage + parameter)
|
list (operation) * int is
|
||||||
|
((nil : list (operation)), contractStorage + parameter)
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's break down the contract above to make sure we understand each bit of the LIGO syntax:
|
Let us break down the contract above to make sure we understand each
|
||||||
|
bit of the LIGO syntax:
|
||||||
|
|
||||||
- **`function main`** - definition of a function that serves as an entry point
|
- **`function main`** - definition of the access function, which takes
|
||||||
- **`(const parameter : int; const contractStorage : int)`** - parameters passed to the function
|
a the parameter of the contract and the storage
|
||||||
- **`const parameter : int`** - parameter provided by a transaction that invokes our contract
|
- **`(const parameter : int; const contractStorage : int)`** -
|
||||||
- **`const contractStorage : int`** - definition of our storage (`int`)
|
parameters passed to the function: the first is called `parameter`
|
||||||
- **`(list(operation) * int)`** - return type of our function, in our case a touple with a list of operations, and an int
|
because it denotes the parameter of a specific invocation of the
|
||||||
- **`block {skip}`** - our function has no body, so we instruct LIGO to `skip` it
|
contract, the second is the storage
|
||||||
- **`with ((nil : list(operation)), contractStorage + parameter)`** - essentially a return statement
|
- **`(list (operation) * int)`** - return type of our function, in our
|
||||||
- **`(nil : list(operation))`** - a `nil` value annotated as a list of operations, because that's required by our return type specified above
|
case a tuple with a list of operations, and an `int` (new value for
|
||||||
- **`contractStorage + parameter`** - a new storage value for our contract, sum of previous storage and a transaction parameter
|
the storage after a succesful run of the contract)
|
||||||
### Running LIGO for the first time
|
- **`((nil : list (operation)), contractStorage + parameter)`** -
|
||||||
|
essentially a return statement
|
||||||
|
- **`(nil : list (operation))`** - a `nil` value annotated as a list
|
||||||
|
of operations, because that is required by our return type specified
|
||||||
|
above
|
||||||
|
- **`contractStorage + parameter`** - a new storage value for our
|
||||||
|
contract, sum of previous storage and a transaction parameter
|
||||||
|
|
||||||
To test that we've installed LIGO correctly, and that `taco-shop.ligo` is a valid contract, we'll dry-run it.
|
### Running LIGO for the First Time
|
||||||
|
|
||||||
> Dry-running is a simulated execution of the smart contract, based on a mock storage value and a parameter.
|
To test that we have installed LIGO correctly, and that
|
||||||
|
`taco-shop.ligo` is a valid contract, we will dry-run it.
|
||||||
|
|
||||||
Our contract has a storage of `int` and accepts a parameter that is also an `int`.
|
> Dry-running is a simulated execution of the smart contract, based on
|
||||||
|
> a mock storage value and a parameter.
|
||||||
|
|
||||||
|
Our contract has a storage of `int` and accepts a parameter that is
|
||||||
|
also an `int`.
|
||||||
|
|
||||||
The `dry-run` command requires a few parameters:
|
The `dry-run` command requires a few parameters:
|
||||||
- **contract** *(file path)*
|
- **contract** *(file path)*
|
||||||
- **entrypoint** *(name of the entrypoint function in the contract)*
|
- **entrypoint** *(name of the access function in the contract)*
|
||||||
- **parameter** *(parameter to execute our contract with)*
|
- **parameter** *(parameter to execute our contract with)*
|
||||||
- **storage** *(starting storage before our contract's code is executed)*
|
- **storage** *(starting storage before our contract's code is executed)*
|
||||||
|
|
||||||
|
It outputs what is returned from our access function: in our case a
|
||||||
And outputs what's returned from our entrypoint - in our case a touple containing an empty list (of operations to apply) and the new storage value - which in our case is the sum of the previous storage and the parameter we've used.
|
tuple containing an empty list (of operations to apply) and the new
|
||||||
|
storage value, which, in our case, is the sum of the previous storage
|
||||||
|
and the parameter we have used for the invocation.
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
# Contract: taco-shop.ligo
|
# Contract: taco-shop.ligo
|
||||||
@ -124,66 +158,80 @@ ligo dry-run taco-shop.ligo --syntax pascaligo main 4 3
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Designing Taco shop's contract storage
|
## Designing the Taco Shop's Contract Storage
|
||||||
|
|
||||||
We know that Pedro's Taco Shop serves two kinds of tacos, so we'll need to manage stock individually, per kind. Let's define a type, that will keep the `stock` & `max_price` per kind - in a record with two fields. Additionally, we'll want to combine our `taco_supply` type into a map, consisting of the entire offer of Pedro's shop.
|
We know that Pedro's Taco Shop serves two kinds of tacos, so we will
|
||||||
|
need to manage stock individually, per kind. Let us define a type,
|
||||||
|
that will keep the `stock` & `max_price` per kind in a record with two
|
||||||
|
fields. Additionally, we will want to combine our `taco_supply` type
|
||||||
|
into a map, consisting of the entire offer of Pedro's shop.
|
||||||
|
|
||||||
**Taco shop's storage**
|
**Taco shop's storage**
|
||||||
```pascaligo group=b
|
```pascaligo group=b
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
|
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
```
|
```
|
||||||
|
|
||||||
Next step is to update the `main` entry point to include `taco_shop_storage` in its storage - while doing that let's set the `parameter` to `unit` as well to clear things up.
|
Next step is to update the `main` access function to include
|
||||||
|
`taco_shop_storage` in its storage. In the meanwhile, let us set the
|
||||||
|
`parameter` to `unit` as well to clear things up.
|
||||||
|
|
||||||
**`taco-shop.ligo`**
|
**`taco-shop.ligo`**
|
||||||
```pascaligo group=b+
|
```pascaligo group=b+
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
function main (const parameter: unit ; const taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
block {skip} with ((nil : list(operation)), taco_shop_storage)
|
|
||||||
|
type return is list (operation) * taco_shop_storage
|
||||||
|
|
||||||
|
function main (const parameter : unit; const taco_shop_storage : taco_shop_storage) : return is
|
||||||
|
((nil : list (operation)), taco_shop_storage)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Populating our storage in a dry-run
|
### Populating our Storage in a dry-run
|
||||||
|
|
||||||
When dry-running a contract, it is crucial to provide a correct initial storage value - in our case the storage is type-checked as `taco_shop_storage`. Reflecting [Pedro's daily offer](tutorials/get-started/tezos-taco-shop-smart-contract.md#daily-offer), our storage's value will be defined as following:
|
When dry-running a contract, it is crucial to provide a correct
|
||||||
|
initial storage value. In our case the storage is type-checked as
|
||||||
|
`taco_shop_storage`. Reflecting
|
||||||
|
[Pedro's daily offer](tutorials/get-started/tezos-taco-shop-smart-contract.md#daily-offer),
|
||||||
|
our storage's value will be defined as follows:
|
||||||
|
|
||||||
**Storage value**
|
**Storage value**
|
||||||
```zsh
|
```zsh
|
||||||
map
|
map [
|
||||||
1n -> record
|
1n -> record [
|
||||||
current_stock = 50n;
|
current_stock = 50n;
|
||||||
max_price = 50000000mutez;
|
max_price = 50tez
|
||||||
end;
|
];
|
||||||
2n -> record
|
2n -> record [
|
||||||
current_stock = 20n;
|
current_stock = 20n;
|
||||||
max_price = 75000000mutez;
|
max_price = 75tez
|
||||||
end;
|
]
|
||||||
end
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
> Storage value is a map, with two items in it, both items are records identified by natural numbers `1n` & `2n`.
|
> The storage value is a map with two bindings (entries) distinguished
|
||||||
|
> by their keys `1n` and `2n`.
|
||||||
|
|
||||||
**Dry run command with a multi-line storage value**
|
**Dry run command with a multi-line storage value**
|
||||||
```zsh
|
```zsh
|
||||||
ligo dry-run taco-shop.ligo --syntax pascaligo main unit "map
|
ligo dry-run taco-shop.ligo --syntax pascaligo main unit "map [
|
||||||
1n -> record
|
1n -> record [
|
||||||
current_stock = 50n;
|
current_stock = 50n;
|
||||||
max_price = 50000000mutez;
|
max_price = 50tez
|
||||||
end;
|
];
|
||||||
2n -> record
|
2n -> record [
|
||||||
current_stock = 20n;
|
current_stock = 20n;
|
||||||
max_price = 75000000mutez;
|
max_price = 75tez
|
||||||
end;
|
]
|
||||||
end"
|
]"
|
||||||
```
|
```
|
||||||
|
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-2.png" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-2.png" />
|
||||||
@ -191,62 +239,85 @@ end"
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
*If everything went as expected, the `dry-run` command will return an empty list of operations and the contract's current storage, which is the map of products we've defined based on the daily offer of Pedro's taco shop.*
|
*If everything went as expected, the `dry-run` command will return an
|
||||||
|
empty list of operations and the contract's current storage, which is
|
||||||
|
the map of the products we have defined based on the daily offer of
|
||||||
|
Pedro's taco shop.*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Providing an entrypoint for buying tacos
|
## Providing another Access Function for Buying Tacos
|
||||||
|
|
||||||
Now that we have our stock well defined in form of storage, we can move on to the actual sales. We'll replace the `main` entrypoint with `buy_taco`, that takes an `id` - effectively a key from our `taco_shop_storage` map. This will allow us to calculate pricing, and if the sale is successful - then we can reduce our stock - because we have sold a taco!
|
Now that we have our stock well defined in form of storage, we can
|
||||||
|
move on to the actual sales. We will replace the `main` access
|
||||||
|
function with another, `buy_taco`, that takes a key `id` from our
|
||||||
|
`taco_shop_storage` map. This will allow us to calculate pricing, and
|
||||||
|
if the sale is successful, we will be able to reduce our stock because
|
||||||
|
we have sold a taco!
|
||||||
|
|
||||||
### Selling the tacos for free
|
### Selling the Tacos for Free
|
||||||
|
|
||||||
Let's start by customizing our contract a bit, we will:
|
Let is start by customizing our contract a bit, we will:
|
||||||
|
|
||||||
- rename the entrypoint from `main` to `buy_taco`
|
- rename the access function from `main` to `buy_taco`
|
||||||
- rename `parameter` to `taco_kind_index`
|
- rename `parameter` to `taco_kind_index`
|
||||||
- change `taco_shop_storage` to a `var` instead of a `const`, because we'll want to modify it
|
- change `taco_shop_storage` to a `var` instead of a `const`, because
|
||||||
|
we will want to modify it
|
||||||
|
|
||||||
**`taco-shop.ligo`**
|
**`taco-shop.ligo`**
|
||||||
```pascaligo group=c
|
```pascaligo group=c
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
type return is list (operation) * taco_shop_storage
|
||||||
block { skip } with ((nil : list(operation)), taco_shop_storage)
|
|
||||||
|
function buy_taco (const taco_kind_index : nat; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
|
((nil : list (operation)), taco_shop_storage)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Decreasing `current_stock` when a taco is sold
|
#### Decreasing `current_stock` when a Taco is Sold
|
||||||
|
|
||||||
In order to decrease the stock in our contract's storage for a specific taco kind, a few things needs to happen:
|
In order to decrease the stock in our contract's storage for a
|
||||||
|
specific taco kind, a few things needs to happen:
|
||||||
|
|
||||||
- retrieve the `taco_kind` from our storage, based on the `taco_kind_index` provided
|
- retrieve the `taco_kind` from our storage, based on the
|
||||||
- subtract the `taco_kind.current_stock` by `1n`
|
`taco_kind_index` provided;
|
||||||
- we can find the absolute (`nat`) value of the subtraction above by using `abs`, otherwise we'd be left with an `int`
|
- subtract the `taco_kind.current_stock` by `1n`;
|
||||||
- update the storage, and return it
|
- we can find the absolute value of the subtraction above by
|
||||||
|
calling `abs` (otherwise we would be left with an `int`);
|
||||||
|
- update the storage, and return it.
|
||||||
|
|
||||||
**`taco-shop.ligo`**
|
**`taco-shop.ligo`**
|
||||||
|
|
||||||
```pascaligo group=d
|
```pascaligo group=d
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
|
|
||||||
|
type return is list (operation) * taco_shop_storage
|
||||||
|
|
||||||
|
function buy_taco (const taco_kind_index : nat; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
|
block {
|
||||||
|
// Retrieve the taco_kind from the contract's storage or fail
|
||||||
|
const taco_kind : taco_supply =
|
||||||
|
case taco_shop_storage[taco_kind_index] of
|
||||||
|
Some (kind) -> kind
|
||||||
|
| None -> (failwith ("Unknown kind of taco.") : taco_supply)
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Decrease the stock by 1n, because we have just sold one
|
||||||
|
taco_kind.current_stock := abs (taco_kind.current_stock - 1n);
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
|
||||||
begin
|
|
||||||
// Retrieve the taco_kind from the contract's storage
|
|
||||||
const taco_kind : taco_supply = get_force(taco_kind_index, taco_shop_storage);
|
|
||||||
// Decrease the stock by 1n, because we've just sold one
|
|
||||||
taco_kind.current_stock := abs(taco_kind.current_stock - 1n);
|
|
||||||
// Update the storage with the refreshed taco_kind
|
// Update the storage with the refreshed taco_kind
|
||||||
taco_shop_storage[taco_kind_index] := taco_kind;
|
taco_shop_storage[taco_kind_index] := taco_kind
|
||||||
end with ((nil : list(operation)), taco_shop_storage)
|
} with ((nil : list (operation)), taco_shop_storage)
|
||||||
```
|
```
|
||||||
|
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-3.png" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-3.png" />
|
||||||
@ -254,67 +325,85 @@ function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_sho
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### Making sure we get paid for our tacos
|
### Making Sure We Get Paid for Our Tacos
|
||||||
|
|
||||||
In order to make Pedro's taco shop profitable, he needs to stop giving away tacos for free. When a contract is invoked via a transaction, an amount of tezzies to be sent can be specified as well. This amount is accessible within LIGO as `amount`.
|
In order to make Pedro's taco shop profitable, he needs to stop giving
|
||||||
|
away tacos for free. When a contract is invoked via a transaction, an
|
||||||
|
amount of tezzies to be sent can be specified as well. This amount is
|
||||||
|
accessible within LIGO as `amount`.
|
||||||
|
|
||||||
To make sure we get paid, we will:
|
To make sure we get paid, we will:
|
||||||
|
|
||||||
- calculate a `current_purchase_price` based on the [equation specified earlier](tutorials/get-started/tezos-taco-shop-smart-contract.md#calculating-the-current-purchase-price)
|
- calculate a `current_purchase_price` based on the
|
||||||
- check if the sent `amount` matches the `current_purchase_price`
|
[equation specified earlier](tutorials/get-started/tezos-taco-shop-smart-contract.md#calculating-the-current-purchase-price)
|
||||||
- if not, then our contract will `fail` and stop executing
|
- check if the sent `amount` matches the `current_purchase_price`:
|
||||||
- if yes, stock for the given `taco_kind` will be decreased and the payment accepted
|
- if not, then our contract will fail (`failwith`)
|
||||||
|
- otherwise, stock for the given `taco_kind` will be decreased and
|
||||||
|
the payment accepted
|
||||||
|
|
||||||
**`taco-shop.ligo`**
|
**`taco-shop.ligo`**
|
||||||
```pascaligo group=e
|
```pascaligo group=e
|
||||||
type taco_supply is record
|
type taco_supply is record [
|
||||||
current_stock : nat;
|
current_stock : nat;
|
||||||
max_price : tez;
|
max_price : tez
|
||||||
end
|
]
|
||||||
type taco_shop_storage is map(nat, taco_supply);
|
|
||||||
|
|
||||||
function buy_taco (const taco_kind_index: nat ; var taco_shop_storage : taco_shop_storage) : (list(operation) * taco_shop_storage) is
|
type taco_shop_storage is map (nat, taco_supply)
|
||||||
begin
|
|
||||||
// Retrieve the taco_kind from the contract's storage
|
type return is list (operation) * taco_shop_storage
|
||||||
const taco_kind : taco_supply = get_force(taco_kind_index, taco_shop_storage);
|
|
||||||
|
function buy_taco (const taco_kind_index : nat ; var taco_shop_storage : taco_shop_storage) : return is
|
||||||
const current_purchase_price : tez = taco_kind.max_price / taco_kind.current_stock;
|
block {
|
||||||
|
// Retrieve the taco_kind from the contract's storage or fail
|
||||||
|
const taco_kind : taco_supply =
|
||||||
|
case taco_shop_storage[taco_kind_index] of
|
||||||
|
Some (kind) -> kind
|
||||||
|
| None -> (failwith ("Unknown kind of taco.") : taco_supply)
|
||||||
|
end;
|
||||||
|
|
||||||
|
const current_purchase_price : tez =
|
||||||
|
taco_kind.max_price / taco_kind.current_stock;
|
||||||
|
|
||||||
if amount =/= current_purchase_price then
|
if amount =/= current_purchase_price then
|
||||||
// we won't sell tacos if the amount isn't correct
|
// We won't sell tacos if the amount is not correct
|
||||||
failwith("Sorry, the taco you're trying to purchase has a different price");
|
failwith ("Sorry, the taco you are trying to purchase has a different price");
|
||||||
else
|
else skip;
|
||||||
// Decrease the stock by 1n, because we've just sold one
|
|
||||||
taco_kind.current_stock := abs(taco_kind.current_stock - 1n);
|
// Decrease the stock by 1n, because we have just sold one
|
||||||
|
taco_kind.current_stock := abs (taco_kind.current_stock - 1n);
|
||||||
|
|
||||||
// Update the storage with the refreshed taco_kind
|
// Update the storage with the refreshed taco_kind
|
||||||
taco_shop_storage[taco_kind_index] := taco_kind;
|
taco_shop_storage[taco_kind_index] := taco_kind
|
||||||
end with ((nil : list(operation)), taco_shop_storage)
|
} with ((nil : list (operation)), taco_shop_storage)
|
||||||
```
|
```
|
||||||
|
|
||||||
In order to test the `amount` sent, we'll use the `--amount` option of `dry-run`:
|
In order to test the `amount` sent, we will use the `--amount` option
|
||||||
|
of `dry-run`:
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
ligo dry-run taco-shop.ligo --syntax pascaligo --amount 1 buy_taco 1n "map
|
ligo dry-run taco-shop.ligo --syntax pascaligo --amount 1 buy_taco 1n "map [
|
||||||
1n -> record
|
1n -> record [
|
||||||
current_stock = 50n;
|
current_stock = 50n;
|
||||||
max_price = 50000000mutez;
|
max_price = 50tez
|
||||||
end;
|
];
|
||||||
2n -> record
|
2n -> record [
|
||||||
current_stock = 20n;
|
current_stock = 20n;
|
||||||
max_price = 75000000mutez;
|
max_price = 75tez
|
||||||
end;
|
]
|
||||||
end"
|
]"
|
||||||
```
|
```
|
||||||
**Purchasing a taco with 1.0tz**
|
|
||||||
|
** Purchasing a Taco with 1tez **
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-4.png" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-4.png" />
|
||||||
<div style="opacity: 0.7; text-align: center; font-size: 12px; margin-top:-24px;">Stock decreases after selling a taco, if the right amount of tezzies is provided</div>
|
<div style="opacity: 0.7; text-align: center; font-size: 12px; margin-top:-24px;">Stock decreases after selling a taco, if the right amount of tezzies is provided</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
**Attempting to purchase a taco with 0.7tz**
|
**Attempting to Purchase a Taco with 0.7tez**
|
||||||
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-5.png" />
|
<img src="/img/tutorials/get-started/tezos-taco-shop-smart-contract/dry-run-5.png" />
|
||||||
<div style="opacity: 0.7; text-align: center; font-size: 12px; margin-top:-24px;">Stock does not decrease after a purchase attempt with a lower than required amount.</div>
|
<div style="opacity: 0.7; text-align: center; font-size: 12px;
|
||||||
|
margin-top:-24px;">Stock does not decrease after a purchase attempt
|
||||||
|
with an insufficient payment.</div>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
@ -322,9 +411,10 @@ end"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 💰 Bonus: *Accepting tips above the taco purchase price*
|
## 💰 Bonus: *Accepting Tips above the Taco Purchase Price*
|
||||||
|
|
||||||
If you'd like to accept tips in your contract as well, simply change the following line, depending on your preference.
|
If you would like to accept tips in your contract, simply change the
|
||||||
|
following line, depending on your preference.
|
||||||
|
|
||||||
**Without tips**
|
**Without tips**
|
||||||
```pascaligo skip
|
```pascaligo skip
|
||||||
|
@ -24,9 +24,17 @@
|
|||||||
"version-next-advanced/include",
|
"version-next-advanced/include",
|
||||||
"version-next-advanced/first-contract"
|
"version-next-advanced/first-contract"
|
||||||
],
|
],
|
||||||
"API": [
|
"API & Reference": [
|
||||||
"version-next-api/cli-commands",
|
"version-next-api/cli-commands",
|
||||||
"version-next-api/cheat-sheet"
|
"version-next-api/cheat-sheet",
|
||||||
|
"version-next-reference/big-map-reference",
|
||||||
|
"version-next-reference/bytes-reference",
|
||||||
|
"version-next-reference/crypto-reference",
|
||||||
|
"version-next-reference/current-reference",
|
||||||
|
"version-next-reference/list-reference",
|
||||||
|
"version-next-reference/map-reference",
|
||||||
|
"version-next-reference/set-reference",
|
||||||
|
"version-next-reference/string-reference"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"version-next-contributors-docs": {
|
"version-next-contributors-docs": {
|
||||||
|
@ -499,7 +499,7 @@ module Make (Token: TOKEN) : (S with module Token = Token) =
|
|||||||
| Error Token.Non_canonical_zero ->
|
| Error Token.Non_canonical_zero ->
|
||||||
fail region Non_canonical_zero
|
fail region Non_canonical_zero
|
||||||
|
|
||||||
let mk_tz state buffer =
|
let mk_tez state buffer =
|
||||||
let region, lexeme, state = sync state buffer in
|
let region, lexeme, state = sync state buffer in
|
||||||
let lexeme = Str.string_before lexeme (String.index lexeme 't') in
|
let lexeme = Str.string_before lexeme (String.index lexeme 't') in
|
||||||
let lexeme = Z.mul (Z.of_int 1_000_000) (Z.of_string lexeme) in
|
let lexeme = Z.mul (Z.of_int 1_000_000) (Z.of_string lexeme) in
|
||||||
@ -508,7 +508,7 @@ module Make (Token: TOKEN) : (S with module Token = Token) =
|
|||||||
| Error Token.Non_canonical_zero ->
|
| Error Token.Non_canonical_zero ->
|
||||||
fail region Non_canonical_zero
|
fail region Non_canonical_zero
|
||||||
|
|
||||||
let format_tz s =
|
let format_tez s =
|
||||||
match String.index s '.' with
|
match String.index s '.' with
|
||||||
index ->
|
index ->
|
||||||
let len = String.length s in
|
let len = String.length s in
|
||||||
@ -522,10 +522,11 @@ module Make (Token: TOKEN) : (S with module Token = Token) =
|
|||||||
if Z.equal Z.one should_be_1 then Some (Q.num mutez) else None
|
if Z.equal Z.one should_be_1 then Some (Q.num mutez) else None
|
||||||
| exception Not_found -> assert false
|
| exception Not_found -> assert false
|
||||||
|
|
||||||
let mk_tz_decimal state buffer =
|
let mk_tez_decimal state buffer =
|
||||||
let region, lexeme, state = sync state buffer in
|
let region, lexeme, state = sync state buffer in
|
||||||
|
let lexeme = Str.(global_replace (regexp "_") "" lexeme) in
|
||||||
let lexeme = Str.string_before lexeme (String.index lexeme 't') in
|
let lexeme = Str.string_before lexeme (String.index lexeme 't') in
|
||||||
match format_tz lexeme with
|
match format_tez lexeme with
|
||||||
None -> assert false
|
None -> assert false
|
||||||
| Some tz ->
|
| Some tz ->
|
||||||
match Token.mk_mutez (Z.to_string tz ^ "mutez") region with
|
match Token.mk_mutez (Z.to_string tz ^ "mutez") region with
|
||||||
@ -573,7 +574,7 @@ let nl = ['\n' '\r'] | "\r\n"
|
|||||||
let blank = ' ' | '\t'
|
let blank = ' ' | '\t'
|
||||||
let digit = ['0'-'9']
|
let digit = ['0'-'9']
|
||||||
let natural = digit | digit (digit | '_')* digit
|
let natural = digit | digit (digit | '_')* digit
|
||||||
let decimal = digit+ '.' digit+
|
let decimal = natural '.' natural
|
||||||
let small = ['a'-'z']
|
let small = ['a'-'z']
|
||||||
let capital = ['A'-'Z']
|
let capital = ['A'-'Z']
|
||||||
let letter = small | capital
|
let letter = small | capital
|
||||||
@ -624,9 +625,9 @@ and scan state = parse
|
|||||||
| natural 'n' { mk_nat state lexbuf |> enqueue }
|
| natural 'n' { mk_nat state lexbuf |> enqueue }
|
||||||
| natural "mutez" { mk_mutez state lexbuf |> enqueue }
|
| natural "mutez" { mk_mutez state lexbuf |> enqueue }
|
||||||
| natural "tz"
|
| natural "tz"
|
||||||
| natural "tez" { mk_tz state lexbuf |> enqueue }
|
| natural "tez" { mk_tez state lexbuf |> enqueue }
|
||||||
| decimal "tz"
|
| decimal "tz"
|
||||||
| decimal "tez" { mk_tz_decimal state lexbuf |> enqueue }
|
| decimal "tez" { mk_tez_decimal state lexbuf |> enqueue }
|
||||||
| natural { mk_int state lexbuf |> enqueue }
|
| natural { mk_int state lexbuf |> enqueue }
|
||||||
| symbol { mk_sym state lexbuf |> enqueue }
|
| symbol { mk_sym state lexbuf |> enqueue }
|
||||||
| eof { mk_eof state lexbuf |> enqueue }
|
| eof { mk_eof state lexbuf |> enqueue }
|
||||||
|
Loading…
Reference in New Issue
Block a user