From a9c388d1034039b985b6865eb7dbc47e7807c126 Mon Sep 17 00:00:00 2001 From: Dr-Electron Date: Fri, 31 Jan 2025 09:18:07 +0100 Subject: [PATCH] Apply suggestions from code review Co-authored-by: Lucas Tortora <85233773+lucas-tortora@users.noreply.github.com> --- .../standards/closed-loop-token/tutorial.mdx | 74 +++++++++---------- .../clt-tutorial/sources/clt_tutorial.move | 2 +- .../tests/clt_tutorial_tests.move | 6 +- 3 files changed, 38 insertions(+), 44 deletions(-) diff --git a/docs/content/developer/standards/closed-loop-token/tutorial.mdx b/docs/content/developer/standards/closed-loop-token/tutorial.mdx index 91fcadcbea9..d3c985e4092 100644 --- a/docs/content/developer/standards/closed-loop-token/tutorial.mdx +++ b/docs/content/developer/standards/closed-loop-token/tutorial.mdx @@ -1,19 +1,18 @@ # CLT Tutorial: A Journey from Zero to Hero This tutorial will guide you through the process of creating a Closed Loop Token (CLT) by following a journey to create a dApp for municipalities to issue vouchers for local businesses. -The use case is the following: + +The use case is: A municipality aims to promote energy efficiency among households by distributing voucher tokens that can be exchanged for energy-efficient LED light bulbs. This initiative encourages households to reduce energy consumption, thereby contributing to environmental sustainability. Let's dive right into it. We can start by minting a new token. Tokens are similar to coins in IOTA, but you if you have read the prior pages you already know that they can have their own rules and policies. But as they share similarities with coins, we can start by creating an `init` function with an OTW for our module and create a coin with a name, symbol, and description using `coin::create_currency`: -```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L24 -``` -```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L25-L33 +```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L24-L33 ``` -As tokens are special we need to talk about the token policy here. The token policy is an object which contains a `spend_balance` and the rules we define. -So it play an important role in the token lifecycle defining what the token owner can do and what not. +As tokens are special, we need to discuss the token policy. The token policy is an object that contains a `spend_balance` function and the rules we define. +So, it plays an important role in the token lifecycle: defining what the token owner can and cannot do. Let's create the policy and share it as a shared object later on: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L35 @@ -29,8 +28,8 @@ After creating the token and its policy we: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L41-L44 ``` -Now that we created the logic for initializing our module and creating the voucher token, the municipality could deploy the contract but not yet distribute the token. So let's implement a function to do that. -This is quite easy, we create a `gift_voucher` with the following header: +Now that we created the logic for initializing our module and creating the voucher token, the municipality could deploy the contract but not yet distribute the token. So, let's implement a function to do that. +This is relatively easy; we create a `gift_voucher` function that will mint the tokens and send them to the receiver: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L65-L70 ``` @@ -43,44 +42,37 @@ And then mint the token and send it to the receiver: As you can see, the `token::transfer` function returns a so called `ActionRequest`. This object can not be dropped so without passing it to another function which destroys it the contract wouldn't work. This means we have to confirm the transfer for which we have [multiple options](./action-request.mdx#approving-actions). -In this case as the caller of the `gift_voucher` function is the municipality we can approve the action directly using our treasury capability: +In this case, as the caller of the `gift_voucher` function is the municipality, we can approve the action directly using our treasury capability, as shown in the last line of the `gift_voucher` function. ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L74 -``` -:::note Interesting to know +:::info More on Move The `ActionRequest` objects gives us the chance to get a deeper understanding of Move. It represents a [Hot Potato pattern](../../iota-101/move-overview/patterns/hot-potato.mdx) where the contract can't drop the object but has to pass it to another function. -So in this case the transfer of the token is already "done", but if we don't consume the ActionRequest in the same tx, the whole transaction will fail as Move tx are atomic. +So, in this case, the token transfer is already "done," but if we don't consume the `ActionRequest` in the same transaction, the whole transaction will fail as the Move tx is atomic. ::: The municipality is now able to create a voucher and transfer it to a household. Next we need to implement a way for the household to redeem the voucher. -This is done by creating a function `buy_ledbulb`: +This is achieved by creating a `buy_led_bulb` function in which we check if we have enough voucher tokens for a LED bulb and then transfer it to the shop:: -```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L79-L84 +```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L79-L93 ``` -in which we check if we have enough voucher token for a LED bulb and then transfer it to the shop: - -```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L85-L88 -``` -But wait, remember that we said tokens are different from coins? With `token::spend` we again get a `ActionRequest` object which we have to approve. -This time we can't approve with the treasury capability as the caller is the household. And even if we would have the treasury capability, -remember how we learned that the treasury capability grants as admin rights? So it would confirm everything without checkint the rules. But here we actually want to make use of the policy rules feature. -As we should only be allowed as a household to spend the voucher token for LED bulbs in a certified shop. +Since tokens are different from coins, with `token::spend`, we again get an `ActionRequest` object, which we have to approve. +This time, we can't approve with the treasury capability as the caller is the household. Even if we did have the treasury capability, since it grants admin rights, it would confirm everything without checking the rules. So, we actually want to use the policy rules feature, as a household should only be allowed to spend the voucher token for LED bulbs in a certified shop. So let's look into rules and add some to our policy. ## Rules We can define our rules and the corresponding logic in the same module as the voucher token or create another module for a more modular approach. -We are going with the later as this allows us to reuse the rules for other projects as well. +We are going with the latter as this allows us to reuse the rules for other projects. So let's think about the rule(s) we need. We want houseowners to be able to spend the voucher token only in certified shops. -But we also don't want shops to be able to spend the voucher token. They should only be allowed to send it back to the municipality. +But we also don't want shops to be able to spend the voucher token; they should only be allowed to send it back to the municipality. With that in mind let's define our rule. A [rule](rules.mdx#rule-structure) is representet as a witness - a type with a drop ability. So let's define that first: @@ -88,7 +80,7 @@ A [rule](rules.mdx#rule-structure) is representet as a witness - a type with a d ```move file=/docs/examples/move/clt-tutorial/sources/rules.move#L24 ``` -Now let's add a function to add addresses to the rule configuration: +Next, add a function to add addresses to the rule configuration: ```move file=/docs/examples/move/clt-tutorial/sources/rules.move#L61-L75 ``` @@ -112,35 +104,37 @@ Next we have to create functionality to stamp the action request if the rule is Let's break it down: -1) We do some basic checks and task like checking if the policy has a rule config. We get the config, sender and receiver. -2) We split the verification into two parts: - - If the action is a spend_action someone is trying to return the token to the municipality. We check if the sender is in our list. If this is true, we stamp the action request. If not we abort as we only want to allow shops to return the token. - - If the action is a transfer_action someone is trying to buy a LED bulb, so we check if the sender is in our list. If this is true, that means a shop is trying to spend our token. We can't allow that, so we apport and therefore never stamp the action request. We also check if the receiver is a shop. +1) Check if the policy has a rule config. +2) Get the config, sender, and receiver. +3) We split the verification into two parts: + - If the action is a `spend_action`, someone is trying to return the token to the municipality. We check if the sender is on our list. If this is true, we stamp the action request. If not, we abort as we only want to allow shops to return the token. + - If the action is a `transfer_action`, someone is trying to buy a LED bulb. So, we check if the sender is on our list. If this is true, that means a shop is trying to spend our token. We can't allow that, so we abort and never stamp the action request. + - We also check if the receiver is a shop. -3) If we don't abort, we stamp the action request. +4) If we don't abort, we stamp the action request. -## Back to our voucher module +## Back to Our Voucher Module -Now that we created the rules modules let's use it in our voucher module. -We can import the rules like this: +Now that we created the rules modules, let's use them in our voucher module. +Since both modules belong to the same package, we can import the rules like this: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L6 ``` Now we need to register the rule for the needed actions in our `init` function. Just add the following lines between the policy creation and the sharing: -```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L37-L39 +```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L38-L39 ``` -We are defining our rule for the default `spend` and `transfer` actions here. +We defined the rule for the default `spend` and `transfer` actions. But we could also pass any other action as a string here and make the rule work for custom functions of our module. -The municipality also needs a way to register shops so we add a function for that which internally calls the `add_addresses` function from our rules module: +The municipality also needs a way to register shops. So, we should add a function which internally calls the `add_addresses` function from our rules module: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L48-L55 ``` -And now we are back to our `buy_ledbulb` function. We can now verify/stamp the action request with the rules capability: +And now we are back to our `buy_led_bulb` function. We can now verify/stamp the action request with the rules capability: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L90 ``` @@ -148,15 +142,15 @@ And now we are back to our `buy_ledbulb` function. We can now verify/stamp the a In this tutorial we are returning the action request. We could also approve it right away. So in this case in a PTB we have to call the `token_policy::confirm_request` function afterwards which will check the request for the approval stamp and make our TX succeed. -Now the shop is the owner is the owner of the voucher and the household just got a new energy-efficient LED bulb. +Now the shop owns the voucher, and the household just got a new energy-efficient LED bulb. The last thing to do is for the shop to return the voucher to the municipality. This is done by calling the `return_voucher` function: ```move file=/docs/examples/move/clt-tutorial/sources/clt_tutorial.move#L97-L107 ``` -This is similar to the `buy_ledbulb` function where we verify the rules of the `transfer` action, but in this case we use the `spend` action. -So the token will be consumed and the `spend_balance` will be added to the the action request. Once the action got confirmed the `spend_balance` will be added to the balance of the token policy. +This is similar to the `buy_led_bulb` function, where we verify the rules of the `transfer` action. But, in this case, we use the `spend` action. +So, the token will be consumed, and the `spend_balance` will be added to the action request. Once the action is confirmed, the `spend_balance` will be added to the balance of the token policy. :::note One more thing diff --git a/docs/examples/move/clt-tutorial/sources/clt_tutorial.move b/docs/examples/move/clt-tutorial/sources/clt_tutorial.move index b9b070c71e8..56a9378d4a6 100644 --- a/docs/examples/move/clt-tutorial/sources/clt_tutorial.move +++ b/docs/examples/move/clt-tutorial/sources/clt_tutorial.move @@ -76,7 +76,7 @@ module clt_tutorial::voucher { /// Buy a LED bulb using the voucher. The `LedBulb` is received, and the voucher is /// transfered to the shop address. - public fun buy_ledbulb( + public fun buy_led_bulb( token: Token, policy: &TokenPolicy, shop_address: address, diff --git a/docs/examples/move/clt-tutorial/tests/clt_tutorial_tests.move b/docs/examples/move/clt-tutorial/tests/clt_tutorial_tests.move index 15b154a4453..a3ea73a7868 100644 --- a/docs/examples/move/clt-tutorial/tests/clt_tutorial_tests.move +++ b/docs/examples/move/clt-tutorial/tests/clt_tutorial_tests.move @@ -1,7 +1,7 @@ #[test_only] module clt_tutorial::voucher_tests { use clt_tutorial::voucher::{ - buy_ledbulb, + buy_led_bulb, gift_voucher, return_voucher, register_shop, @@ -51,7 +51,7 @@ module clt_tutorial::voucher_tests { { let voucher = scenario.take_from_sender>(); let policy = scenario.take_shared>(); - let (led_bulb, action_request) = buy_ledbulb(voucher, &policy, shop, scenario.ctx()); + let (led_bulb, action_request) = buy_led_bulb(voucher, &policy, shop, scenario.ctx()); std::debug::print(&action_request); @@ -116,7 +116,7 @@ module clt_tutorial::voucher_tests { { let voucher = scenario.take_from_sender>(); let policy = scenario.take_shared>(); - let (led_bulb, action_request) = buy_ledbulb(voucher, &policy, shop2, scenario.ctx()); + let (led_bulb, action_request) = buy_led_bulb(voucher, &policy, shop2, scenario.ctx()); policy.confirm_request(action_request, scenario.ctx()); transfer::public_transfer(led_bulb, tx_context::sender(scenario.ctx()));