diff --git a/src/frontend/src/app/App.components/Drawer/Drawer.view.tsx b/src/frontend/src/app/App.components/Drawer/Drawer.view.tsx index 0bf4d78..a0941a3 100644 --- a/src/frontend/src/app/App.components/Drawer/Drawer.view.tsx +++ b/src/frontend/src/app/App.components/Drawer/Drawer.view.tsx @@ -50,12 +50,6 @@ export const DrawerView = ({ ) else return
})} - - - hideCallback()}> - What's next? - - ) diff --git a/src/frontend/src/pages/Chapter/Chapter.data.tsx b/src/frontend/src/pages/Chapter/Chapter.data.tsx index 1de2921..795d3bc 100644 --- a/src/frontend/src/pages/Chapter/Chapter.data.tsx +++ b/src/frontend/src/pages/Chapter/Chapter.data.tsx @@ -195,27 +195,33 @@ export const chapterData = [ { pathname: '/pascal/chapter-interop', language: 'PascaLIGO', - name: '26- Pascal - Interoperability', + name: '26 - Pascal - Interoperability', data: pascalDataInterop, }, { pathname: '/pascal/chapter-preprocessor', language: 'PascaLIGO', - name: '27- Pascal - Preprocessor', + name: '27 - Pascal - Preprocessor', data: pascalDataPreprocessor, }, { pathname: '/pascal/chapter-fa2', language: 'PascaLIGO', - name: '28- Pascal - FA2', + name: '28 - Pascal - FA2', data: pascalDataFA20, }, { pathname: '/pascal/chapter-fa2-operator', language: 'PascaLIGO', - name: '29- Pascal - FA2 Operator', + name: '29 - Pascal - FA2 Operator', data: pascalDataFA20Operator, }, + { + pathname: '/pascal/chapter-fa2-hook', + language: 'PascaLIGO', + name: '30 - Pascal - FA2 Hook', + data: camelDataFA20Hook, + }, { pathname: '/camel/chapter-about', @@ -488,4 +494,10 @@ export const chapterData = [ name: '29 - Reason - FA2 Operator', data: reasonDataFA20Operator, }, + { + pathname: '/reason/chapter-fa2-hook', + language: 'ReasonLIGO', + name: '30 - Reason - FA2 Hook', + data: camelDataFA20Hook, + }, ] diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterDeployContract/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterDeployContract/course.md index d839fe8..e7cbeb5 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterDeployContract/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterDeployContract/course.md @@ -1,13 +1,12 @@ -# Chapter 22 : Deploy & Invoke +# Chapter 23 : Deploy contract Time to go live. - ## Smart contract -A smart contract is code written in Michelson langage (a low-level stack-based turing-complete language). -It contains entrypoints which describe all possible way to interact with a smart contract. -It contains prototype of each entrypoint. What kind of parameters are exepected to execute an entrypoint +A smart contract is code written in Michelson langage (a low-level stack-based turing-complete language). +It contains entrypoints which describe all possible way to interact with a smart contract. +It contains prototype of each entrypoint. What kind of parameters are exepected to execute an entrypoint It contains the description of storage. ### Storage @@ -18,21 +17,20 @@ The description of the storage is done by strongly-typing the data structure. ### Entrypoints -Entrypoints of a smart contract describe how to mutate a storage. +Entrypoints of a smart contract describe how to mutate a storage. Executing an entrypoint takes some parameters and a state of a storage and returns a new state of storage and some operations ![](/images/contract_in_out.png) - Execution of an entrypoint produces a new state of the storage of the smart contract. If executopn did not throw an exception and did not fail then the new state of storage is applied. Operations are transactions (smart contract invocation) that will be sent to some other contracts and will trigger an entryppoint of the targeted contract or a tez transfer (no invocation of entrypoint). If execution of an entrypoint produces operations (ordered list of transactions) then they are sent and executed following order of the list of operation. ## Deploy -A smart contract must be deployed to the blockchain in order to be invoked. When deploying a smart contract ot the blockchain , one must specify the initial state of the storage. +A smart contract must be deployed to the blockchain in order to be invoked. When deploying a smart contract ot the blockchain , one must specify the initial state of the storage. Deployment of a smart contract in Tezos is called "origination". -Here is the syntax of the tezos command line to deploy a smart contract : +Here is the syntax of the tezos command line to deploy a smart contract : ``` tezos-client originate contract for transferring from \ @@ -47,12 +45,11 @@ tezos-client originate contract for transferring is a Michelson expression. The --init parameter is used to specify initial state of the storage. it specifies the the maximal fee the user is willing to pay for this operation (using the --burn-cap parameter). - ## Invoke Once the smart contract has been deployed on the blockchain (contract-origination operation baked into a block), it is possible to invoke an entrypoint of the smart contract using the command line. -Here is the syntax of the tezos command line to invoke a smart contract : +Here is the syntax of the tezos command line to invoke a smart contract : ``` tezos-client transfer from to --arg '' --dry-run @@ -60,18 +57,16 @@ tezos-client transfer from to --arg ' is the quantity of tez being transfered to the contract. name given to the contract - name of the entrypoint and corresponding parameters. exemple 'Increment(5)'. + name of the entrypoint and corresponding parameters. exemple 'Increment(5)'. ⚠️ Notice the --arg parameter specifies an entrypoint call. ⚠️ Notice the --dry-run parameter simulate invocation of the entrypoint. - - ## Ligo compiler In order to produce a smart contract, a tool called transpiler (aka LIGO compiler) is used to transform LIGO code into Michelson code. -Michelson smart contract are stored in a file with .tz extension. +Michelson smart contract are stored in a file with .tz extension. This ligo compiler is also used to transform "Ligo expression" into "Michelson expression" as needed to deploy or invoke a smart contract. @@ -82,6 +77,7 @@ Here is how to transform ligo code into Michelson code using the ligo compiler i ``` ligo compile-contract code.mligo mainFunc > code.tz ``` + argument is the name of the "main function" in the .ligo file. (see Chapter "Main Function"). ⚠️ Notice the output of the command is the Michelson code. We just redirect the command output into a .tz file. @@ -96,7 +92,6 @@ ligo compile-storage [options] code.mligo mainFunc '' is a ligo expression - ### Invocation parameter Here is how to transform ligo expression into Michelson expression using the ligo compiler in command line. @@ -107,8 +102,7 @@ ligo compile-parameter [options] code.mligo mainFunc '' is a ligo expression - -### Simulating +### Simulating Here is how to simulate execution of an entrypoint using the ligo compiler in command line. @@ -116,12 +110,12 @@ Here is how to simulate execution of an entrypoint using the ligo compiler in co ligo dry-run [options] code.mligo mainFunc '' '' ``` - state of the storage when simulating execution of the entrypoint - entrypoint of the smart contract that is invoked (parameter *p* of this entrypoint is specified between parantheses). + state of the storage when simulating execution of the entrypoint + entrypoint of the smart contract that is invoked (parameter \_p* of this entrypoint is specified between parantheses). ### Ligo Expression in command line -Let's see some exemple how to transpile a storage ligo expression into a michelson one. +Let's see some exemple how to transpile a storage ligo expression into a Michelsonone. Let's consider this smart contract which associate coordinates to a planet name. @@ -148,9 +142,9 @@ let main (action,store : parameter * storage): return = #### Maps -The _Map.literal_ predifined function can be used to initialize a *map* +The _Map.literal_ predifined function can be used to initialize a _map_ -The command line *ligo compile-storage* for transpiling a map containg a tuple. +The command line _ligo compile-storage_ for transpiling a map containg a tuple. ``` ligo compile-storage starmap.mligo main 'Map.literal [("earth", (1,1,1))]' @@ -160,7 +154,7 @@ ligo compile-storage starmap.mligo main 'Map.literal [("earth", (1,1,1))]' Initialization of elements of a tuple is specified between _(_ and _)_ separated by comma _,_. -The command line *ligo compile-storage* for transpiling a map containg a tuple. +The command line _ligo compile-storage_ for transpiling a map containg a tuple. ``` ligo compile-storage starmap.mligo main 'Map.literal [("earth", (1,1,1))]' @@ -177,10 +171,10 @@ This command returns : Initialization of elements of a record is specified between _{_ and _}_ separated by comma _;_. Each element is a key/value pair seperated by _=_ and follow the syntax : ``` -{ = ; = } +{ = ; = } ``` -Let's modify our type *coordinates* to be a record instead of a tuple. +Let's modify our type _coordinates_ to be a record instead of a tuple. ``` // starmap2.mligo @@ -198,8 +192,7 @@ let main (action,store : parameter * storage): return = | DoNothing -> (([] : operation list),store) ``` - -The command line *ligo compile-storage* for transpiling a map containg a record tuple. +The command line _ligo compile-storage_ for transpiling a map containg a record tuple. ``` ligo compile-storage starmap2.mligo main 'Map.literal [("earth", {x=1;y=1;z=1})]' @@ -243,9 +236,6 @@ let main (action,store : parameter * storage): return = | DoNothing -> (([] : operation list),store) ``` - - 1- Write _compile-storage_ command and the ligo expression for initializing the *Sol* system containing planet "earth" with coordinates (2,7,1). -2- Write _dry-run_ command and the ligo expression for adding a planet "mars" with coordinates (4,15,2) in our *Sol* system. Reuse the *Sol* system of step 1. - +2- Write the _dry-run_ command and the ligo expression for adding a planet "mars" with coordinates (4,15,2) in our *Sol* system. Reuse the *Sol* system of step 1. diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterFA12/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterFA12/course.md index 95cee6f..50f88f3 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterFA12/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterFA12/course.md @@ -1,37 +1,37 @@ -# Chapter 23 : Fungible Asset 1.2 +# Chapter 25 : Financial Application 1.2 Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -A financial asset is a non-physical asset whose value is derived from a contractual claim, such as bank deposits, bonds, and stocks. Financial assets are usually more liquid than other tangible assets, such as commodities or real estate, and may be traded on financial markets. +A Financial Applicationis a non-physical asset whose value is derived from a contractual claim, such as bank deposits, bonds, and stocks. Financial assets are usually more liquid than other tangible assets, such as commodities or real estate, and may be traded on financial markets. -Financial assets are opposed to non-financial assets, property rights which include both tangible property (sometimes also called real assets) such as land, real estate or commodities and intangible assets such as intellectual property, like copyrights, patents, Trademarks etc. +Financial assets are opposed to non-financial assets, property rights which include both tangible property (sometimes also called real assets) such as land, real estate or commodities and intangible assets such as intellectual property, like copyrights, patents, Trademarks etc. ### Fungible and non-fungible -When talking about *token* or *crypto-currency*, it is a numerical asset emitted on a blockchain. +When talking about _token_ or _crypto-currency_, it is a numerical asset emitted on a blockchain. -Fungible means secable +Fungible means secable -Fungible token is a financial asset where account balance represents the value associated to an _address_. This value can be splitted into smaller parts which can be transfered to another account. +Fungible token is a Financial Applicationwhere account balance represents the value associated to an _address_. This value can be splitted into smaller parts which can be transfered to another account. -Non-fungible token (NFT) is a financial asset whose balance cannot be splitted into smaller part. Crypto-kitties is an exemple of non fungible token (on Ethereum blcockchain). For exemple, a video game avatar (such as avatar on world of warcraft) is a character having some skills/attributes (strength, dexterity, ...) one can want to sell its avatar , but cannot sell strength property of its avatar separately. It makes sense to keep tha whole avatar into a unsecable set of attributes. +Non-fungible token (NFT) is a Financial Applicationwhose balance cannot be splitted into smaller part. Crypto-kitties is an exemple of non fungible token (on Ethereum blcockchain). For exemple, a video game avatar (such as avatar on world of warcraft) is a character having some skills/attributes (strength, dexterity, ...) one can want to sell its avatar , but cannot sell strength property of its avatar separately. It makes sense to keep tha whole avatar into a unsecable set of attributes. ### Standard -A standard is a set of rules commonly accepted by the community. -The rules of financial asset describes how to create currencies (and transfer between accounts, etc). +A standard is a set of rules commonly accepted by the community. +The rules of Financial Applicationdescribes how to create currencies (and transfer between accounts, etc). Depending on the usage of the currency, many sets of rules have been commonly accepted : -* Financial asset 1.2 (FA1.2) are rules for fungible token. -* Financia asset 2.0 (FA20) are rules for non fungible token. + +- Financial Application1.2 (FA1.2) are rules for fungible token. +- Financial Application2.0 (FA20) are rules for non fungible token. For exemple, the creation of a crypto-currency is equivalent to creating a contract which supports the FA1.2 standard. All smart contracts supporting the FA12 standard can interact with account and other contracts by transfering coins of our crypto-currency. - -Similarily for ethereum, fungible token rules have been specified in a Ethereum forum blog (Ethereum Request Comment) the 20th answer was describing a good rule set and the ERC20 became the name for this standard (rule set). +Similarily for ethereum, fungible token rules have been specified in a Ethereum forum blog (Ethereum Request Comment) the 20th answer was describing a good rule set and the ERC20 became the name for this standard (rule set). ERC721 is the standard rule set for non-fungible token. ## FA1.2 (Implementation of standard) @@ -39,14 +39,13 @@ ERC721 is the standard rule set for non-fungible token. This Fungible token standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party. Possible actions : - Appove - Sender can specify an amount of token that can be spent by someone else (from his account) - Transfer - Transfer an amount a token from an account to another account (or third-party on-chain smart contract) - GetAllowance - Return the amount that can be spent by someone from sender's account - GetBalance - Returns sender's account balance - GetTotalSupply - Returns the number total of token - +Appove - Sender can specify an amount of token that can be spent by someone else (from his account) +Transfer - Transfer an amount a token from an account to another account (or third-party on-chain smart contract) +GetAllowance - Return the amount that can be spent by someone from sender's account +GetBalance - Returns sender's account balance +GetTotalSupply - Returns the number total of token -Let's see implementation in ReasonLigo of a fungible token (FA1.2) +Let's see implementation in ReasonLigo of a fungible token (FA1.2) ``` type tokens = (address, nat) big_map @@ -92,7 +91,7 @@ type action = | GetTotalSupply of getTotalSupply let transfer (p,s : transfer * storage) : operation list * storage = - let new_allowances = + let new_allowances = if Tezos.sender = p.address_from then s.allowances else let authorized_value = match Big_map.find_opt (Tezos.sender,p.address_from) s.allowances with @@ -102,7 +101,7 @@ let transfer (p,s : transfer * storage) : operation list * storage = if (authorized_value < p.value) then (failwith "Not Enough Allowance" : allowances) else Big_map.update (Tezos.sender,p.address_from) (Some (abs(authorized_value - p.value))) s.allowances - in + in let sender_balance = match Big_map.find_opt p.address_from s.tokens with Some value -> value | None -> 0n @@ -151,7 +150,7 @@ let getTotalSupply (p,s : getTotalSupply * storage) : operation list * storage = ([op],s) -let main (a,s:action * storage) = +let main (a,s:action * storage) = match a with Transfer p -> transfer (p,s) | Approve p -> approve (p,s) @@ -161,13 +160,11 @@ let main (a,s:action * storage) = ``` - - ## Your mission -Let's assume the *TezosAcamedyToken* has been deployed. +Let's assume the _TezosAcamedyToken_ has been deployed. -Consider your account is *me* (at address tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) which has been granted 1000000 token. +Consider your account is _me_ (at address tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) which has been granted 1000000 token. Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7) 1- We want you to simulate the transfer of 2 TAT (Tezos Academy Token) to *alice*. Write a ligo command line for preparing a simulated storage where you (tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) possess 1000000 of token and no allowances. @@ -176,22 +173,24 @@ Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7) 3- Write a ligo command line that simulate your invocation of previous *Approval* on storage prepared at step 1. (Don't forget to specify that you are sending this transaction). -4- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *approval*. You can consider that step 2 produced the following michelson expression: +4- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *approval*. You can consider that step 2 produced the following Michelsonexpression: + ``` (Left (Left (Left (Pair "tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7" 2)))) ``` + 5-Write a tezos command line that simulate your invocation. 6- Now that approval has been exeucted on blockchain, 2 TAT can be transfered from your address to *alice*. Write a ligo command line for preparing invocation of a *Transfer* of 2 TAT (Tezos Academy Token) from you to *alice*. - 7- Write a ligo command line for preparing a simulated storage where you (tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) possess 1000000 of token and allowances is initialized with 2 TAT that can be transfered from *me* to *alice* (tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7). 8- Write a ligo command line that simulate your invocation of previous *Transfer* on storage prepared at step 7. (Don't forget to specify that you are sending this transaction). -9- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *transfer*. You can consider that step 6 produces the following michelson expression: +9- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *transfer*. You can consider that step 6 produces the following Michelsonexpression: + ``` (Right (Pair (Pair "tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ" "tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7") 2)) ``` -10-Write a tezos command line that simulate your *Transfer* invocation. \ No newline at end of file +10-Write a tezos command line that simulate your *Transfer* invocation. diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterFA20/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterFA20/course.md index ccae59c..79e2ba5 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterFA20/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterFA20/course.md @@ -1,4 +1,4 @@ -# Chapter 28 : Financial Asset 2.0 +# Chapter 28 : Financial Application 2.0 Captain, Let's create a ship token. @@ -6,29 +6,31 @@ There are multiple dimensions and considerations while implementing a particular token smart contract. Tokens might be fungible or non-fungible. A variety of permission policies can be used to define how many tokens can be transferred, who can initiate a transfer, and who can receive tokens. A token contract can be designed to support a single token type (e.g. ERC-20 or ERC-721) or multiple token types (e.g. ERC-1155) to optimize batch transfers and atomic swaps of the tokens. -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. -In the following chapter on Financial Asset 2.0 , we will focus on *TZIP-12* which stands for the 12th Tezos Improvement Proposal (same as EIP-721 for Ethereum). +In the following chapter on Financial Application 2.0 , we will focus on _TZIP-12_ which stands for the 12th Tezos Improvement Proposal (same as EIP-721 for Ethereum). ## Architecture FA2 proposes to leave it to implementers to handle common considerations such as defining the contract’s token type(s) (e.g. non-fungible vs. fungible vs. semi-fungible), administration and whitelisting, contract upgradability, and supply operations (e.g. mint/burn). -FA2 also leaves to implementers to decide on architecture pattern for handling permissioning. Permission can be implemented -* in the the same contract as the core transfer behavior (i.e. a “monolith”), -* in a transfer hook to another contract, -* in a separate wrapper contract. +FA2 also leaves to implementers to decide on architecture pattern for handling permissioning. Permission can be implemented +- in the the same contract as the core transfer behavior (i.e. a “monolith”), +- in a transfer hook to another contract, +- in a separate wrapper contract. ## Interface and library The FA2 interface formalize a standard way to design tokens and thus describes a list of entry points (that must be implemented) and data structures related to those entry points. A more detailed decription of the interface is broken down in following sections. In addition to the FA2 interface, the FA2 standard provides helper functions to manipulate data structures involved in FA2 interface. The FA2 library contains helper functions for : -* a generic behavior and transfer hook implementation (behavior based on *permissions\_descriptor*), -* converting data structures, -* defining hooks between contracts when transfer is emitted, -* defining operators for managing allowance. + +* a generic behavior and transfer hook implementation (behavior based on *permissions\_descriptor*), + +- converting data structures, +- defining hooks between contracts when transfer is emitted, +- defining operators for managing allowance. ## Entry points @@ -46,7 +48,6 @@ type fa2_entry_points = | Is_operator of is_operator_param ``` - ### Metadata FA2 token contracts MUST implement the *token\_metadata* entry point which get the metadata for multiple token types. @@ -56,8 +57,7 @@ type fa2_entry_points = FA2 token amounts are represented by natural numbers (nat), and their granularity (the smallest amount of tokens which may be minted, burned, or transferred) is always 1. -The *decimals* property is the number of digits to use after the decimal point when displaying the token amounts. If 0, the asset is not divisible. Decimals are used for display purposes only and MUST NOT affect transfer operation. - +The _decimals_ property is the number of digits to use after the decimal point when displaying the token amounts. If 0, the asset is not divisible. Decimals are used for display purposes only and MUST NOT affect transfer operation. #### Interface @@ -78,21 +78,22 @@ type token_metadata_param = { } ``` - ### Balance of -FA2 token contracts MUST implement the _Balance of_ entry point which get the balance of multiple account/token pairs (because FA2 supports mutiple token). +FA2 token contracts MUST implement the _Balance of_ entry point which get the balance of multiple account/token pairs (because FA2 supports mutiple token). + ``` | Balance_of of balance_of_param ``` -It accepts a list of *balance\_of\_requests* and a callback and sends back to a callback contract a list of *balance\_of\_response* records. +It accepts a list of *balance\_of\_requests* and a callback and sends back to a callback contract a list of *balance\_of\_response* records. If one of the specified *token\_ids* is not defined within the FA2 contract, the entry point MUST fail with the error mnemonic "TOKEN_UNDEFINED" (see section Error Handling). #### Interface The FA2 interface defines request/response parameters as follow : + ``` type token_id = nat @@ -114,7 +115,8 @@ type balance_of_param = { ### Totalsupply -FA2 token contracts MUST implement the _Totalsupply_ entry point which get the total supply of tokens for multiple token types (because FA2 supports mutiple token). +FA2 token contracts MUST implement the _Totalsupply_ entry point which get the total supply of tokens for multiple token types (because FA2 supports mutiple token). + ``` | Total_supply of total_supply_param ``` @@ -144,33 +146,31 @@ type total_supply_param = { ### Transfer FA2 token contracts MUST implement the _Transfer_ entry point which transfer tokens between and MUST ensure following rules. + ``` | Transfer of transfer list ``` #### Rules -FA2 token contracts MUST implement the transfer logic defined by the following rules : - +FA2 token contracts MUST implement the transfer logic defined by the following rules : -1) Every transfer operation MUST be atomic. If the operation fails, all token transfers MUST be reverted, and token balances MUST remain unchanged. +1. Every transfer operation MUST be atomic. If the operation fails, all token transfers MUST be reverted, and token balances MUST remain unchanged. -2) The amount of a token transfer MUST NOT exceed the existing token owner's balance. If the transfer amount for the particular token type and token owner -exceeds the existing balance, the whole transfer operation MUST fail with the error mnemonic "INSUFFICIENT_BALANCE" +2. The amount of a token transfer MUST NOT exceed the existing token owner's balance. If the transfer amount for the particular token type and token owner + exceeds the existing balance, the whole transfer operation MUST fail with the error mnemonic "INSUFFICIENT_BALANCE" -3) Core transfer behavior MAY be extended. If additional constraints on tokens transfer are required, FA2 token contract implementation MAY invoke additional -permission policies (transfer hook is the recommended design pattern to implement core behavior extension). (See Chapter FA2 - Hook) +3. Core transfer behavior MAY be extended. If additional constraints on tokens transfer are required, FA2 token contract implementation MAY invoke additional + permission policies (transfer hook is the recommended design pattern to implement core behavior extension). (See Chapter FA2 - Hook) If the additional permission hook fails, the whole transfer operation MUST fail with a custom error mnemonic. -4) Core transfer behavior MUST update token balances exactly as the operation parameters specify it. No changes to amount values or additional transfers are -allowed. - - +4. Core transfer behavior MUST update token balances exactly as the operation parameters specify it. No changes to amount values or additional transfers are + allowed. #### Interface -It transfer tokens from a *from_* account to possibly many destination accounts where each destination transfer describes the type of token, the amount of token, and receiver address. +It transfer tokens from a _from\__ account to possibly many destination accounts where each destination transfer describes the type of token, the amount of token, and receiver address. ``` type token_id = nat @@ -199,13 +199,14 @@ type transfer_aux = { This FA2 tandard defines the set of standard errors to make it easier to integrate FA2 contracts with wallets, DApps and other generic software, and enable localization of user-visible error messages. -Each error code is a short abbreviated string mnemonic. An FA2 contract client (like another contract or a wallet) could use on-the-chain or off-the-chain registry to map the error code mnemonic to a user-readable, localized message. +Each error code is a short abbreviated string mnemonic. An FA2 contract client (like another contract or a wallet) could use on-the-chain or off-the-chain registry to map the error code mnemonic to a user-readable, localized message. A particular implementation of the FA2 contract MAY extend the standard set of errors with custom mnemonics for additional constraints. When error occurs, any FA2 contract entry point MUST fail with one of the following types: -* string value which represents an error code mnemonic. -* a Michelson pair, where the first element is a string representing error code mnemonic and the second element is a custom error data. + +- string value which represents an error code mnemonic. +- a Michelson pair, where the first element is a string representing error code mnemonic and the second element is a custom error data. #### Standard error mnemonics: @@ -229,8 +230,6 @@ Error mnemonic - Description "SENDER_HOOK_UNDEFINED" - Sender hook is required by the permission behavior, but is not implemented by a sender contract - - ## Your mission We are working on a fungible/multi-asset token compliant with the FA2 standard. We want you to complete the existing implementation of token. The *Total\_supply* entry point is not yet implemented , please finish the job ! @@ -242,4 +241,3 @@ Error mnemonic - Description 3 -If a given *token\_id* is found then the function *get\_total\_supply* must return a *total\_supply\_response* record for each given *token\_id*. As seen in the interface the *total\_supply\_response* record contains *token\_id* and *total\_supply* fields. (use v as temporary variable for the match with instruction) 4 -If a given *token\_id* is not found then the function *get\_total\_supply* must throw an exception with the predefined error messsage *token\_undefined*. - diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterFA20Hook/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterFA20Hook/course.md index 240b91d..7354903 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterFA20Hook/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterFA20Hook/course.md @@ -1,12 +1,12 @@ -# Chapter 30 : Financial Asset 2.0 - Tranfer Hook +# Chapter 30 : FA 2.0 - Tranfer Hook Captain, all space pirate should have a hook like in old times. -## ... in the previous episode +## ... in the previous episode -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. -The FA2 interface formalize a standard way to design tokens and thus describes a list of entrypoints (that must be implemented) and data structures related to those entrypoints. +The FA2 interface formalize a standard way to design tokens and thus describes a list of entrypoints (that must be implemented) and data structures related to those entrypoints. In this chapter we will focus on _transfer hook_ @@ -20,17 +20,17 @@ Although, it is recommended to implement "transfer hook design pattern" in many _Transfer hook_ is one recommended design pattern to implement FA2 that enables separation of the core token transfer logic and a permission policy. -Instead of implementing FA2 as a monolithic contract, a permission policy can be implemented as a separate contract. Permission policy contract provides an entry point invoked by the core FA2 contract to accept or reject a particular transfer operation (such an entry point is called *transfer hook*). +Instead of implementing FA2 as a monolithic contract, a permission policy can be implemented as a separate contract. Permission policy contract provides an entry point invoked by the core FA2 contract to accept or reject a particular transfer operation (such an entry point is called _transfer hook_). ![](/images/small-fa2-hook.png) -Although this approach introduces gas consumption overhead (compared to an all-in-one contract) by requiring an extra inter-contract call, it also offers some other advantages: -1) FA2 core implementation can be verified once, and certain properties (not related to permission policy) remain unchanged. -2) modification of the permission policy of an existing contract can be done by replacing a transfer hook only. No storage migration of the FA2 ledger is required. -3) Transfer hooks could be used for purposes beyond permissioning, such as implementing _custom logic_ for a particular token application +Although this approach introduces gas consumption overhead (compared to an all-in-one contract) by requiring an extra inter-contract call, it also offers some other advantages: -The transfer hook makes it possible to model different transfer permission policies like whitelists, operator lists, etc. +1. FA2 core implementation can be verified once, and certain properties (not related to permission policy) remain unchanged. +2. modification of the permission policy of an existing contract can be done by replacing a transfer hook only. No storage migration of the FA2 ledger is required. +3. Transfer hooks could be used for purposes beyond permissioning, such as implementing _custom logic_ for a particular token application +The transfer hook makes it possible to model different transfer permission policies like whitelists, operator lists, etc. #### Hook interface @@ -62,38 +62,37 @@ In addition to the hook standard, the FA2 standard provides helper functions to Some helpers functions has been gatthered in a hook library which help defining hooks when implementing a FA2 contract. This library contains following functions and type alias : -The type *fa2\_registry* is a set of address. +The type *fa2\_registry* is a set of address. the function *get\_hook\_entrypoint* retrieves the contract interface of entrypoint "%tokens\_transferred\_hook" for a given contract address -the function *register\_with\_fa2* +the function *register\_with\_fa2* * takes the address of a FA2 contract (having hooks) and register it in the registry (set of address). * calls the *Set\_transfer\_hook* entrypoint of a FA2 contract -the function *create\_register\_hook\_op* sends a transaction to a FA2 contract (having hook entrypoints). The transaction intends to invoke the entrypoint *Set\_transfer\_hook*. This entrypoint *Set\_transfer\_hook* requires as parameters : -* the contract interface of entrypoint "%tokens\_transferred\_hook" +the function *create\_register\_hook\_op* sends a transaction to a FA2 contract (having hook entrypoints). The transaction intends to invoke the entrypoint *Set\_transfer\_hook*. This entrypoint *Set\_transfer\_hook* requires as parameters : +* the contract interface of entrypoint "%tokens\_transferred\_hook" * a _permission descriptor_ the function *validate\_hook\_call* ensures an address in registered in the registry (set of address). - ##### Transfer Hooks The function *owners\_transfer\_hook* defined in the library generates a list of Tezos operations invoking sender and receiver hooks according to + the policies defined by the permissions descriptor. +The hook pattern depends on the permission policy. A transfer hook may be unwanted, optional or required. -The hook pattern depends on the permission policy. A transfer hook may be unwanted, optional or required. If the policy requires a owner hook then the token owner contract MUST implement an entry point "tokens\_received". Otherwise transfer is not allowed. If the policy optionnaly accepts a owner hook then the token owner contract MAY implement an entry point "tokens\_received". Otherwise transfer is allowed. -It is the same for permission policies including senders, the entry point *tokens\_sent* may need to be implemented. - -In case of a Transfer, if permission policies expect a hook, then the token owners MUST implement *fa2\_token\_receiver*, and *fa2\_token\_sender* interfaces. This implies that token'owner contract must have entry points *tokens\_received* and *token\_sent*. If these entry points fail the transfer is rejected. +It is the same for permission policies including senders, the entry point _tokens_sent_ may need to be implemented. +In case of a Transfer, if permission policies expect a hook, then the token owners MUST implement *fa2\_token\_receiver*, and *fa2\_token\_sender* interfaces. This implies that token'owner contract must have entry points *tokens\_received* and *token\_sent*. If these entry points fail the transfer is rejected. ##### Transfer Hooks entry points - + The library defines some helper functions The function *to\_receiver\_hook* retrieves the entry point *"%tokens\_received"* for a given _address_. It enables to check if the *fa2\_token\_receiver* interface is implemented. @@ -108,47 +107,44 @@ type hook_result = | Hook_undefined of string ``` - - #### Hook Rules FA2 implementation with the transfer hook pattern recquires following rules: -1) An FA2 token contract has a single entry point to set the hook. If a transfer hook is not set, the FA2 token contract transfer operation MUST fail. +1. An FA2 token contract has a single entry point to set the hook. If a transfer hook is not set, the FA2 token contract transfer operation MUST fail. -2) Transfer hook is to be set by the token contract administrator before any transfers can happen. +2. Transfer hook is to be set by the token contract administrator before any transfers can happen. -3) The concrete token contract implementation MAY impose additional restrictions on -who may set the hook. If the set hook operation is not permitted, it MUST fail -without changing existing hook configuration. +3. The concrete token contract implementation MAY impose additional restrictions on + who may set the hook. If the set hook operation is not permitted, it MUST fail + without changing existing hook configuration. -4) For each transfer operation, a token contract MUST invoke a transfer hook and +4. For each transfer operation, a token contract MUST invoke a transfer hook and return a corresponding operation as part of the transfer entry point result. (For more details see set\_transfer\_hook ) -5) *operator* parameter for the hook invocation MUST be set to *SENDER*. +5. _operator_ parameter for the hook invocation MUST be set to _SENDER_. 6) *from_* parameter for each *hook\_transfer* batch entry MUST be set to *Some(transfer.from_)*. 7) *to_* parameter for each *hook\_transfer* batch entry MUST be set to *Some(transfer.to_)*. -8) A transfer hook MUST be invoked, and operation returned by the hook invocation -MUST be returned by transfer entry point among other operations it might create. -*SENDER* MUST be passed as an operator parameter to any hook invocation. If an -invoked hook fails, the whole transfer transaction MUST fail. +8. A transfer hook MUST be invoked, and operation returned by the hook invocation + MUST be returned by transfer entry point among other operations it might create. + _SENDER_ MUST be passed as an operator parameter to any hook invocation. If an + invoked hook fails, the whole transfer transaction MUST fail. -9) FA2 does NOT specify an interface for mint and burn operations; however, if an -FA2 token contract implements mint and burn operations, these operations MUST -invoke a transfer hook as well. +9. FA2 does NOT specify an interface for mint and burn operations; however, if an + FA2 token contract implements mint and burn operations, these operations MUST + invoke a transfer hook as well. #### Implementation of a hook permission contract -Let's see an example of FA2 Hook pattern implementation. The following smart contract implements a hook permission contract +Let's see an example of FA2 Hook pattern implementation. The following smart contract implements a hook permission contract Owners transfer hooks are triggered by the *owners\_transfer\_hook* function. If a receiver address implements *fa2\_token\_receiver* interface, its *tokens\_received* entry point must be called. -If a sender address implements *fa2\_token\_sender* interface, its *tokens\_sent* entry point must be called. - +If a sender address implements *fa2\_token\_sender* interface, its *tokens\_sent* entry point must be called. ``` (** @@ -169,7 +165,7 @@ type entry_points = | Tokens_transferred_hook of transfer_descriptor_param_michelson | Register_with_fa2 of fa2_with_hook_entry_points contract - let main (param, s : entry_points * storage) + let main (param, s : entry_points * storage) : (operation list) * storage = match param with | Tokens_transferred_hook pm -> @@ -199,12 +195,11 @@ let own_policy : permissions_descriptor = { Notice this Hook Permission contract contains an entry point *Register\_with\_fa2* to register with the FA2 core contract. -Notice this Hook Permission contract contains an entry point *Tokens\_transferred\_hook* triggered when FA2 core contract receive a transfer request. This entry point triggers the owner hook transfer (sending hooks to sender and receiver and waiting for their approval or rejection). - +Notice this Hook Permission contract contains an entry point *Tokens\_transferred\_hook* triggered when FA2 core contract receive a transfer request. This entry point triggers the owner hook transfer (sending hooks to sender and receiver and waiting for their approval or rejection). ## Your mission -We are working on a Fungible token which can handle multiple assets. We decided to implement a Hook pattern. A FA2 core contract handle all fa2 entry points (BalanceOf, Transfer, ...) and a hook permission contract which implements the validation of a transfer with some custom rules. +We are working on a Fungible token which can handle multiple assets. We decided to implement a Hook pattern. A FA2 core contract handle all fa2 entry points (BalanceOf, Transfer, ...) and a hook permission contract which implements the validation of a transfer with some custom rules. ![](/images/small-fa2-hook-exercise.png) @@ -214,12 +209,11 @@ We are working on a Fungible token which can handle multiple assets. We decided If a receiver address implements *fa2\_token\_receiver* interface, its *tokens\_received* entry point must be called. - -Complete the hook permission smart contract by implementing our custom rules on receivers. Transfer is permitted if receiver address implements *fa2\_token\_receiver* interface OR a receiver address is in the receiver white list. +Complete the hook permission smart contract by implementing our custom rules on receivers. Transfer is permitted if receiver address implements *fa2\_token\_receiver* interface OR a receiver address is in the receiver white list. 1- Find receiver hook - Check if a receiver _r_ implements *fa2\_token\_receiver* interface, using *to\_receiver\_hook* function and a _match_ operator. -2- Retrieve hook - if the receiver _r_ implements *fa2\_token\_receiver* interface, introduce variable _h_ as hook entry point. +2- Retrieve hook - if the receiver _r_ implements *fa2\_token\_receiver* interface, introduce variable _h_ as hook entry point. 3- Prepare parameter - cast parameter _p_ into type *transfer\_descriptor\_param\_to\_michelson* and store the result in a new variable _pm_ @@ -229,9 +223,8 @@ We are working on a Fungible token which can handle multiple assets. We decided 6- if the receiver _r_ does not implement *fa2\_token\_receiver* interface, response of *to\_receiver\_hook* provided an error message with variable _err_. -7- Check if receiver _r_ is registered in the whitelist _wl_. +7- Check if receiver _r_ is registered in the whitelist _wl_. -8- If it is the case , everything is fine, just return the returned list of operation _ops_. +8- If it is the case , everything is fine, just return the returned list of operation _ops_. 9- Otherwise throw an exception with _err_ message. Don't forget to cast the exception. - diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterFA20Operator/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterFA20Operator/course.md index f580f61..bae197e 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterFA20Operator/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterFA20Operator/course.md @@ -1,10 +1,10 @@ -# Chapter 29 : Financial Asset 2.0 - Operators and Permissions +# Chapter 29 : FA 2.0 - Operators and Permissions Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. In this chapter we will focus on _Operators_ and _Permissions_. @@ -27,6 +27,7 @@ type fa2_entry_points = ### Operators #### Definition + _Operator_ can be seen as delegate role. _Operator_ is a Tezos address that initiates token transfer operation on behalf of the owner. @@ -67,45 +68,42 @@ type is_operator_param = { Notice the parameter _is\_operator\_param_ given to *Is\_operator* entry point contains a *callback* property used to send back a response to the calling contract. -Notice entry point *Update\_operators* expectes a list of *update\_operator\_michelson*. The fa2 convertor helper provide the *operator\_param\_to\_michelson* function to convert *operator\_param* format into *update\_operator\_michelson* format. - +Notice entry point *Update\_operators* expectes a list of *update\_operator\_michelson*. The fa2 convertor helper provide the *operator\_param\_to\_michelson* function to convert *operator\_param* format into *update\_operator\_michelson* format. #### FA2 standard operator library Some helpers functions has been implemented in the FA2 library which help manipulating _operator_. This library contains following functions and type alias : - an _operator_ is a relationship between two address (owner address and operator address) function *is\_operator* returns to a contract caller whether an operator address is associated to an owner address function *update\_operators* allows to Add or Remove an operator in the list of operators. -function *validate\_update\_operators\_by\_owner*, it ensures the given adress is owner of an _operator_ +function *validate\_update\_operators\_by\_owner*, it ensures the given adress is owner of an _operator_ function *validate\_operator* validates operators for all transfers in the batch at once, depending on given *operator\_transfer\_policy* - - ### FA2 Permission Policies and Configuration -Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. +Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. Such logic (called _Permission Policy_) could validate who initiates a transfer, the transfer amount, and who can receive tokens. This FA2 standard defines a framework to compose and configure such permission policies from the standard behaviors and configuration APIs. FA2 defines : -* the default core transfer behavior, that MUST always be implemented -* a set of predefined permission policies that are optional +- the default core transfer behavior, that MUST always be implemented +- a set of predefined permission policies that are optional #### Permissions descriptor FA2 specifies an interface permissions_descriptor allowing external contracts to discover an FA2 contract's permission policy and to configure it. *permissions\_descriptor* serves as a modular approach to define consistent and non-self-contradictory policies. -The *permission descriptor* indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. +The _permission descriptor_ indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. + +The FA2 standard defines a special metadata entry point _permission descriptor_ containing standard permission policies. -The FA2 standard defines a special metadata entry point *permission descriptor* containing standard permission policies. ``` type permissions_descriptor = { operator : operator_transfer_policy; @@ -115,10 +113,9 @@ type permissions_descriptor = { } ``` - #### Operator transfer policy -*operator\_transfer\_policy* - defines who can transfer tokens. Tokens can be transferred by the token owner or an operator (some address that is authorized to transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). +*operator\_transfer\_policy* - defines who can transfer tokens. Tokens can be transferred by the token owner or an operator (some address that is authorized to transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). The FA2 standard defines two entry points to manage and inspect operators associated with the token owner address (*update\_operators*, *is\_operator*). Once an operator is added, it can manage all of its associated owner's tokens. @@ -151,39 +148,36 @@ type custom_permission_policy = { } ``` - #### Permission Policy Formulae Each concrete implementation of the permission policy can be described by a formula which combines permission behaviors in the following form: + ``` Operator(?) * Receiver(?) * Sender(?) ``` This formula describes the policy which allows only token owners to transfer their own tokens : + ``` Operator(Owner_transfer) * Receiver(Owner_no_hook) * Sender(Owner_no_hook) ``` - - - - ## Your mission We are working on a non_fungible/single-asset token. Our NFT "token" is almost ready but to allow a new rule. We need Bob to transfert a token taken from Vera account and send it to Alice account. - * Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" - * Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" - * Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" +- Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" +- Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" +- Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" 1- First we want you to prepare the initial state of storage. Modify the _ligo compile-storage_ command for the *token* contract with following recommandations : - * Vera account is owner of the token 1 +- Vera account is owner of the token 1 2- Complete the _ligo dry-run_ command for authorizing Bob to transfer token taken from Vera account, transaction emitted by Vera. (reuse the storage you made on step 1). You can use *operator\_update\_to\_michelson* function to convert your parameters into the format expected by *Update\_operators* entry point. - 3- Complete the _ligo dry-run_ command for simulating the transfer of 1 token from Vera'account to Alice's account, transaction emitted by Bob. The transfered token id is number 1 (token\_id and and amount must be 1). You can use the *transfer\_to\_michelson* function to convert your parameters into the format expected by *Transfer* entry point. + You will have to modify the storage to in the state where "Vera account is owner of the token 1" (step 1) and Bob is authorized to transfer token taken from Vera account (step 2). diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterFA20Permission/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterFA20Permission/course.md index 74e1abc..7555720 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterFA20Permission/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterFA20Permission/course.md @@ -1,10 +1,10 @@ -# Chapter 24 : Financial Asset 2.0 - Operators and Permissions +# Chapter 24 : Financial Application 2.0 - Operators and Permissions Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. In this chapter we will focus on _Operators_ and _Permissions_. @@ -27,6 +27,7 @@ type fa2_entry_points = ### Operators #### Definition + _Operator_ can be seen as delegate role. _Operator_ is a Tezos address that initiates token transfer operation on behalf of the owner. @@ -45,7 +46,8 @@ FA2 interface specifies two entry points to update and inspect operators. Once p | Is_operator of is_operator_param ``` -where parameter type *update_operator* and *is_operator_param* are : +where parameter type _update_operator_ and _is_operator_param_ are : + ``` type update_operator = | Add_operator_p of operator_param @@ -57,46 +59,44 @@ type is_operator_param = { } ``` -Notice the *update_operator* can only Add or Remove an _operator_ (an allowance between an operator address and a token owner address). +Notice the _update_operator_ can only Add or Remove an _operator_ (an allowance between an operator address and a token owner address). -Notice the parameter _is_operator_param_ given to *Is_operator* entry point contains a *callback* property used to send back a response to the calling contract. +Notice the parameter _is_operator_param_ given to _Is_operator_ entry point contains a _callback_ property used to send back a response to the calling contract. #### FA2 standard operator library Some helpers functions has been implemented in the FA2 library which help manipulating _operator_. This library contains following functions and type alias : - an _operator_ is a relationship between two address (owner address and operator address) -function *is_operator* returns to a contract caller whether an operator address is associated to an owner address - -function *update_operators* allows to Add or Remove an operator in the list of operators. +function _is_operator_ returns to a contract caller whether an operator address is associated to an owner address -function *validate_update_operators_by_owner*, it ensures the given adress is owner of an _operator_ - -the function *validate_operator* validates operators for all transfers in the batch at once, depending on given operator_transfer_policy +function _update_operators_ allows to Add or Remove an operator in the list of operators. +function _validate_update_operators_by_owner_, it ensures the given adress is owner of an _operator_ +the function _validate_operator_ validates operators for all transfers in the batch at once, depending on given operator_transfer_policy ### FA2 Permission Policies and Configuration -Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. +Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. Such logic (called _Permission Policy_) could validate who initiates a transfer, the transfer amount, and who can receive tokens. This FA2 standard defines a framework to compose and configure such permission policies from the standard behaviors and configuration APIs. FA2 defines : -* the default core transfer behavior, that MUST always be implemented -* a set of predefined permission policies that are optional +- the default core transfer behavior, that MUST always be implemented +- a set of predefined permission policies that are optional #### permissions_descriptor -FA2 specifies an interface permissions_descriptor allowing external contracts to discover an FA2 contract's permission policy and to configure it. *permissions_descriptor* serves as a modular approach to define consistent and non-self-contradictory policies. +FA2 specifies an interface permissions_descriptor allowing external contracts to discover an FA2 contract's permission policy and to configure it. _permissions_descriptor_ serves as a modular approach to define consistent and non-self-contradictory policies. + +The _permission descriptor_ indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. -The *permission descriptor* indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. +The FA2 standard defines a special metadata entry point _permission descriptor_ containing standard permission policies. -The FA2 standard defines a special metadata entry point *permission descriptor* containing standard permission policies. ``` type permissions_descriptor = { operator : operator_transfer_policy; @@ -106,7 +106,6 @@ type permissions_descriptor = { } ``` - #### operator_transfer_policy operator_transfer_policy - defines who can transfer tokens. Tokens can be @@ -114,8 +113,8 @@ transferred by the token owner or an operator (some address that is authorized t transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). The FA2 standard defines two entry points to manage and inspect operators associated -with the token owner address (*update_operators*, -*is_operator*). Once an operator is added, it can manage all of +with the token owner address (_update_operators_, +_is_operator_). Once an operator is added, it can manage all of its associated owner's tokens. ``` @@ -128,8 +127,8 @@ type operator_transfer_policy = #### owner_hook_policy owner_hook_policy - defines if sender/receiver hooks should be called or -not. Each token owner contract MAY implement either an *fa2_token_sender* or -*fa2_token_receiver* hook interface. Those hooks MAY be called when a transfer sends +not. Each token owner contract MAY implement either an _fa2_token_sender_ or +_fa2_token_receiver_ hook interface. Those hooks MAY be called when a transfer sends tokens from the owner account or the owner receives tokens. The hook can either accept a transfer transaction or reject it by failing. @@ -155,24 +154,21 @@ type custom_permission_policy = { } ``` - #### Permission Policy Formulae Each concrete implementation of the permission policy can be described by a formula which combines permission behaviors in the following form: + ``` Operator(?) * Receiver(?) * Sender(?) ``` This formula describes the policy which allows only token owners to transfer their own tokens : + ``` Operator(Owner_transfer) * Receiver(Owner_no_hook) * Sender(Owner_no_hook) ``` - - - - ## Your mission We are working on a non_fungible/multi-asset token. @@ -180,19 +176,17 @@ Our NFT "token" is almost ready but to allow a new rule. We need Bob to transfer 1- First we have to set the right operator policy to authorize delegation when deploying the contract. We want you to prepare the initial state of storage. Write the _ligo compile-storage_ command for the *token* contract with following recommandations : - * Jay 's account address is "tz1UK81V9ccgpDjq8MVUE9uP4mnmNiSZQm9J" - * Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" - * Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" - * Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" - * operator transfer is authorized, - * Bob account has no token - * Vera account is owner of the token 1 - * Alice account has no token - * Jay is the administrator of the contract - * the token type transfered is 0 (token_id) - +- Jay 's account address is "tz1UK81V9ccgpDjq8MVUE9uP4mnmNiSZQm9J" +- Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" +- Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" +- Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" +- operator transfer is authorized, +- Bob account has no token +- Vera account is owner of the token 1 +- Alice account has no token +- Jay is the administrator of the contract +- the token type transfered is 0 (token_id) 2- Write the _ligo dry-run_ command for authorizing Bob to transfer token taken from Vera account, transaction emitted by Jay. (reuse the storage you made on step 1) - 3- Write the _ligo dry-run_ command for simulating the transfer of 1 mutez from Vera'account to Alice's account, transaction emitted by A. You will have to modify the storage to take step 2 into account. diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterInterop/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterInterop/course.md index cb05d4f..ecff8fc 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterInterop/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterInterop/course.md @@ -2,21 +2,22 @@ We need to hack aliens, decompile their code to understand how their informatic works - -LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types. +LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types. ## Annotations ### Michelson types and annotations -Michelson types consist of *or*'s and *pair*'s, combined with field annotations. Field annotations add contraints on a Michelson type, for example a _pair_ of *(pair (int %foo) (string %bar))* will only work with the exact equivalence or the same type without the field annotations. +Michelson types consist of _or_'s and _pair_'s, combined with field annotations. Field annotations add contraints on a Michelson type, for example a _pair_ of _(pair (int %foo) (string %bar))_ will only work with the exact equivalence or the same type without the field annotations. + +For example, the following _pair_ -For example, the following _pair_ ``` (pair (int %foo) (string %bar)) ``` -will accept these definitions and fail with the ones that does not respect the typing or the order of pair fields: +will accept these definitions and fail with the ones that does not respect the typing or the order of pair fields: + ``` (pair (int %foo) (string %bar)) // OK (pair int string) // OK @@ -24,21 +25,20 @@ will accept these definitions and fail with the ones that does not respect the t (pair (string %bar) (int %foo)) // KO ``` - ### Entrypoints and annotations -As seen in chapter Polymorphism, a contract can be called by another contract. Using the predefined function *Tezos.get\_entrypoint\_opt* allows to a calling contract ot point to a specific entry point of the called contract. +As seen in chapter Polymorphism, a contract can be called by another contract. Using the predefined function *Tezos.get\_entrypoint\_opt* allows to a calling contract ot point to a specific entry point of the called contract. Here is an exemple. Let's consider the following "Counter" contract : ``` type storage = int -type parameter = +type parameter = | Left of int | Right of int -let main ((p, x): (parameter * storage)): (operation list * storage) = +let main ((p, x): (parameter * storage)): (operation list * storage) = (([]: operation list), (match p with | Left i -> x - i | Right i -> x + i @@ -55,18 +55,18 @@ type parameter = int type x = Left of int let main (p, s: parameter * storage): operation list * storage = ( - let contract: x contract = + let contract: x contract = match ((Tezos.get_entrypoint_opt "%left" ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address)): x contract option) with | Some c -> c | None -> (failwith "contract does not match": x contract) - in + in (([ Tezos.transaction (Left 2) 2mutez contract; ]: operation list), s) ) ``` -⚠️ Notice how we directly use the *%left* entrypoint without mentioning the *%right* entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to. +⚠️ Notice how we directly use the _%left_ entrypoint without mentioning the _%right_ entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to. These annotations works for _or_'s or _variant_ types in LIGO. @@ -75,16 +75,17 @@ These annotations works for _or_'s or _variant_ types in LIGO. To interop with existing Michelson code or for compatibility with certain development tooling, LIGO has two special interop types: *michelson\_or* and *michelson\_pair*. These types give the flexibility to model the exact Michelson output, including field annotations. Take for example the following Michelson type that we want to interop with: + ``` -(or +(or (unit %z) - (or %other - (unit %y) - (pair %other - (string %x) - (pair %other - (int %w) - (nat %v))))) + (or %other + (unit %y) + (pair %other + (string %x) + (pair %other + (int %w) + (nat %v))))) ``` To reproduce this type we can use the following LIGO code: @@ -98,7 +99,7 @@ type z_or = (unit, "z", y_or, "other") michelson_or If you don't want to have an annotation, you need to provide an empty string. -To use variables of type michelson_or you have to use *M_left* and *M_right*. *M_left* picks the left _or_ case while *M_right* picks the right _or_ case. For *michelson_pair* you need to use tuples. +To use variables of type michelson*or you have to use *M_left* and *M_right*. *M_left* picks the left \_or* case while _M_right_ picks the right _or_ case. For _michelson_pair_ you need to use tuples. ``` let z: z_or = (M_left (unit) : z_or) @@ -113,9 +114,9 @@ let x: z_or = (M_right (y_1) : z_or) ## Helper functions -Conversions from Ligo types to michelson types requires a precise knowledge of data structures representation. +Conversions from Ligo types to Michelsontypes requires a precise knowledge of data structures representation. -So it becomes even more relevant with nested pairs that there are many possible decomposition of a record in pairs of pairs. +So it becomes even more relevant with nested pairs that there are many possible decomposition of a record in pairs of pairs. The following record structure @@ -127,10 +128,10 @@ type l_record = { } ``` -can be transformed in a left combed data structure +can be transformed in a left combed data structure ``` - (pair %other + (pair %other (pair %other (string %s) (int %w) @@ -139,19 +140,19 @@ can be transformed in a left combed data structure ) ``` - or a right combed data structure +or a right combed data structure - ``` - (pair %other - (string %s) - (pair %other - (int %w) - (nat %v) - ) - ) - ``` +``` + (pair %other + (string %s) + (pair %other + (int %w) + (nat %v) + ) + ) +``` -Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually. +Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually. ### Converting left combed Michelson data structures @@ -162,7 +163,7 @@ Converting between different LIGO types and data structures can happen in two wa Here's an example of a left combed Michelson data structure using pairs: ``` - (pair %other + (pair %other (pair %other (string %s) (int %w) @@ -181,27 +182,29 @@ type l_record = { } ``` -This snippet of code shows -\* how to use *Layout.convert\_from\_left\_comb* to transform a michelson type into a record type. -\* how to use *Layout.convert\_to\_left\_comb* to transform a record type into a michelson type. +This snippet of code shows + +\* how to use *Layout.convert\_from\_left\_comb* to transform a Michelsontype into a record type. +\* how to use *Layout.convert\_to\_left\_comb* to transform a record type into a Michelsontype. ``` -type michelson = l_record michelson_pair_left_comb +type Michelson= l_record michelson_pair_left_comb -let of_michelson (f: michelson) : l_record = +let of_michelson (f: michelson) : l_record = let p: l_record = Layout.convert_from_left_comb f in p -let to_michelson (f: l_record) : michelson = +let to_michelson (f: l_record) : Michelson= let p = Layout.convert_to_left_comb (f: l_record) in p ``` #### Variant + In the case of a left combed Michelson or data structure, that you want to translate to a variant, you can use the *michelson\_or\_left\_comb* type. ``` -type vari = +type vari = | Foo of int | Bar of nat | Other of bool @@ -212,7 +215,7 @@ type r = vari michelson_or_left_comb And then use these types in *Layout.convert\_from\_left\_comb* or *Layout.convert\_to\_left\_comb*, similar to the pairs example above ``` -let of_michelson_or (f: r) : vari = +let of_michelson_or (f: r) : vari = let p: vari = Layout.convert_from_left_comb f in p @@ -233,7 +236,7 @@ The following code can be used as inspiration: ``` type z_to_v = -| Z +| Z | Y | X | W @@ -253,7 +256,7 @@ type test = { } let make_concrete_sum (r: z_to_v) : z_or = - match r with + match r with | Z -> (M_left (unit) : z_or) | Y -> (M_right (M_left (unit): y_or) : z_or ) | X -> (M_right (M_right (M_left (unit): x_or): y_or) : z_or ) @@ -261,19 +264,19 @@ let make_concrete_sum (r: z_to_v) : z_or = | V -> (M_right (M_right (M_right (M_right (unit): w_or_v): x_or): y_or) : z_or ) let make_concrete_record (r: test) : (string * int * string * bool * int) = - (r.z, r.y, r.x, r.w, r.v) + (r.z, r.y, r.x, r.w, r.v) -let make_abstract_sum (z_or: z_or) : z_to_v = - match z_or with +let make_abstract_sum (z_or: z_or) : z_to_v = + match z_or with | M_left n -> Z - | M_right y_or -> - (match y_or with + | M_right y_or -> + (match y_or with | M_left n -> Y | M_right x_or -> ( - match x_or with + match x_or with | M_left n -> X | M_right w_or -> ( - match w_or with + match w_or with | M_left n -> W | M_right n -> V))) @@ -281,12 +284,8 @@ let make_abstract_record (z: string) (y: int) (x: string) (w: bool) (v: int) : t { z = z; y = y; x = x; w = w; v = v } ``` - - ## Your mission -We want you to modify our "inventory" contract. As you can see the storage is mainly composed of an item inventory where each item is a right combed nested pairs. The contract possess a single entry point AddInventory. This *AddInventory* function adds each element in the inventory (don't worry about duplicates it has already been taken care of). +We want you to modify our "inventory" contract. As you can see the storage is mainly composed of an item inventory where each item is a right combed nested pairs. The contract possess a single entry point AddInventory. This _AddInventory_ function adds each element in the inventory (don't worry about duplicates it has already been taken care of). 1- Complete the implementation of the *update\_inventory* lambda function. This function takes a list of item as parameter and must transform each item in a combed pair structure and add this transformed structure in the storage inventory. (When naming your temporary variables, use *acc* for the accumulator name and *i* for the current item) - - diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterLambda/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterLambda/course.md index e076719..0caa863 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterLambda/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterLambda/course.md @@ -1,36 +1,41 @@ -# Chapter 23 : Versioning and Lambda (anonymous function) +# Chapter 22 : Lambda (anonymous function) red alert the humans are here battle station surrender dirty humans or die we are the master of this universe and we will easily destroy you hahahaha - ## Versioning -Tezos as a public blockchain expects that contracts should have same behaviour for all users. In theory once a contract is deployed, it should be changed. +Tezos as a public blockchain expects that contracts should have the same behaviour for all users. In theory, once a contract is deployed, it should not be changed. + +We call _antipattern_ when a smart contract has a special role (admin) or may be evolving (changing the rules of the smart contract). -We call *antipattern* when a smart contract have special role (admin) or smart contract that may be evolving (changing rules of the smart contract). +The need to modify the behaviour of a smart contract emerges when for exemple the laws of a country have changed and you need to apply the same changes to the rules in your smart contract. +One could write a new smart contract (V2) and deploy it but it would imply that all existing information stored in the storage of the old smart contract (V1) would be lost. This problem can be solved by : +1- migrating storage information through transactions, +2- or by forcing the new contract to request storage data from the old contract, +3- or by customizing the contract implementation. -The need to modify the behaviour of a smart contract emerges when for exemple the law of the country has changed and you need to apply the same changes to the rules of your smart contract. -One could write a new smart contract (V2) and deploy it but it would imply that all existing information stored in the storage of the old smart contract (V1) would be lost. This problem can be solved by migrating sstorage information through transactions, or by forcing the new contract to request storage data from from the old contract or by customizing the contract implementation. In this chapter we will focus on the third solution. +In this chapter we will focus on the third solution. ### Versioning by re-emission -Versioning can be done by writing a new smart contract and emitting transactions from the old contract (V1) to migrate storage information to the new contract (V2). This can require a lot of transactions and thus a lot of fee spent (resulting in a non negligeable price). This price could be paid by the smart contract that would emit transactions or by each user which would invoke a "migrate" entrypoint of V1 contract to send storage information to the new contract. Transaction emission has been seen in chapter 17 with the _Tezos.transaction_ predefined function. +Versioning can be done by writing a new smart contract and emitting transactions from the old contract (V1) to migrate storage information to the new contract (V2). This may require a lot of transactions and thus a lot of fees (resulting in a significant price). This price could be paid by the smart contract that would emit transactions or by each user which would invoke a "migrate" entrypoint of V1 contract to send storage information to the new contract. Transaction emission has been seen in chapter 17 with the _Tezos.transaction_ predefined function. ### Versioning by contract communication -Versioning can be done by writing a new smart contract that can ask information to the old contract. This pattern need a 2-way communication where the new contract sends a "request" transaction and reacts when the old contract is sending back the requested information. Execution of entrypoint would become asynchronous (due to 2-way transactions). The old contract V1 must be implemented in a way to allow to send transaction to a not yet deployed contract (see chapter Polymorphism). +Versioning can be done by writing a new smart contract that can ask information to the old contract. This pattern needs a 2-way communication where the new contract sends a "request" transaction and reacts when the old contract is sending back the requested information. Execution of entrypoint would become asynchronous (due to the 2-way transactions). The old contract V1 must be implemented in a way that allows to send transaction to a not yet deployed contract (see chapter Polymorphism). ### Versioning by lambda -Versioning can be done by writing a single smart contract that can change its properties and functions (lambdas). This implies to be able to forecast what kind of change might be needed. It also implies a special admin role who is allowed to change the behavior of smart contract. The admin role could be a multi-signature pattern that allow changing behavior of the smart contract if enough user agreed on the proposed change. +Versioning can be done by writing a single smart contract that can change its properties and functions (lambdas). This implies to be able to forecast what kind of change might be needed. It also implies a special admin role who is allowed to change the behavior of the smart contract. The admin role could be a multi-signature pattern that allows changing the behavior of the smart contract if enough user agree on the proposed change. ## Lambda -Changing the behavior of a smart contract can be done by customizing the implementation through lambda's function. +Changing the behavior of a smart contract can be done by customizing the implementation through lambda functions. -So the idea is to define an anonymous function in the storage which is called in entrypoint and write an entrypoint that allow to change implementation of this anonymous function. +The idea is to define an anonymous function in the storage which is called by an entrypoint and writes a new entrypoint that allows to change the implementation of this anonymous function. Let's consider the "starmap" smart contract : + ``` // starmap.ligo type coordinates = { x : int; y : int; z : int } @@ -61,23 +66,26 @@ let main ((action, store) : (parameter * storage)) : return = | DoNothing -> (([] : operation list),store) ``` - ## Lambda prototype -Defining the prototype of an anonymous function (lambda) follow the syntax : +The prototype of a lambda follows the following syntax : + ``` (,) -> ``` -In "starmap" smart contract the type of *func* is +In the "starmap" smart contract, the type of _func_ is + ``` (coordinates) -> coordinates ``` -⚠️ Note that *func* is transforming coordinates of a planet into coordinates of a planet. + +⚠️ Note that _func_ is transforming coordinates of a planet into coordinates of a planet as well. ## Lambda call -Anonymous functions can be called like other functions. Here in our exemple, the lambda *func* is called in function *addPlanet* to transform planet's coordinates : +Anonymous functions can be called like other functions. Here in our exemple, the lambda _func_ is called in _addPlanet_ to transform the planet's coordinates : + ``` Map.add input.0 (store.func input.1) store.systemplanets ``` @@ -85,126 +93,84 @@ Map.add input.0 (store.func input.1) store.systemplanets ## Lambda definition Defining a lambda in a ligo expression follows the syntax : + ``` fun ( : ) -> ``` -The implementation of the lambda can be change with the *changeFunc* function which assigns new code to *func*. Here is an exemple of execution of the *ChangeFunc* entrypoint with the simulation ligo command line : +The implementation of the lambda can be changed with the _changeFunc_ function which assigns new code to _func_. Here is an exemple of execution of the _ChangeFunc_ entrypoint with the simulation ligo command line : + ``` ligo dry-run lambda.mligo main 'ChangeFunc(fun (c : coordinates) -> {x=c.x*100;y=c.y;z=c.z})' '{name="Sol";func=(fun (c : coordinates) -> {x=c.x*10;y=c.y;z=c.z});systemplanets=Map.literal [("earth", {x=2;y=7;z=1})] }' ``` ⚠️ Notice the returned type of the lambda is not specified -``` fun (c : coordinates) -> {x=c.x*100;y=c.y;z=c.z} ``` -⚠️ Notice the new implementation of *func* multiplies 'x' coordinate by 100 (defined as parameter of *ChangeFunc* entrypoint) - -⚠️ Notice the old implementation of *func* multiplies 'x' coordinate by 10 (defined in storage) +``` fun (c : coordinates) -> {x=c.x*100;y=c.y;z=c.z} ``` +⚠️ Notice the new implementation of _func_ multiplies the 'x' coordinate by 100 (defined as parameter of _ChangeFunc_ entrypoint) +⚠️ Notice the old implementation of _func_ multiplies the 'x' coordinate by 10 (defined in storage) ## Your mission -We have a smart contract that reference planets is the Sol system. Since the beginning of the project , all celestial bodies were considered as planets. -Since 2006, the IAU decided that celetial bodies with a mass under 100 are not considered as a planet but as a dwarf-planet. Hopefully we forecasted this kind of change ! A *DeduceCategoryChange* entrypoint allows us to change the lambda which determines the category of a celestial body. (All we have to do is define the new rule and all registered celestial bodies will be updated). - -Take a look at the starmap contract : - -``` -// starmap.ligo -type coordinates = { x : int; y : int; z : int } -type planet_type = PLANET | ASTEROID | STAR -type planet = { - position : coordinates; - mass : nat; - category : planet_type -} -type planets = (string, planet) map -type storage = { - name : string; - func : (planet) -> planet_type; - celestialbodies : planets -} -type return = (operation list * storage) - -type parameter = DeduceCategoryChange of (planet) -> planet_type | AddPlanet of (string * planet) | DoNothing - -let addPlanet (input, store : (string \* planet) \* storage) : return = - let modified : planets = match Map.find_opt input.0 store.celestialbodies with - Some (p) -> (failwith("planet already exist") : planets) - | None -> Map.add input.0 {position=input.1.position;mass=input.1.mass;category=store.func input.1} store.celestialbodies - in - (([] : operation list), {name=store.name;func=store.func;celestialbodies=modified}) +We have a smart contract that reference planets is the Sol system. Since the beginning of the project, all celestial bodies were considered as planets. +Since 2006, the IAU decided that celetial bodies with a mass under 100 are not considered as a planet but as a dwarf-planet. Hopefully we forecasted this kind of change! A _DeduceCategoryChange_ entrypoint allows us to change the lambda which determines the category of a celestial body. All we have to do is define the new rule and all registered celestial bodies will be updated. -let deduceCategoryChange (f,store : ((planet) -> planet_type) * storage) : return = - let applyDeduceCatg = fun (name,p : string * planet) -> - {position=p.position;mass=p.mass;category=f p} in - let modified : planets = Map.map applyDeduceCatg store.celestialbodies in - (([] : operation list), {name=store.name;func=f;celestialbodies=modified}) +Take a look at the starmap contract in the editor tabs. -let main ((action, store) : (parameter * storage)) : return = - match (action) with - AddPlanet (input) -> addPlanet ((input,store)) - | DeduceCategoryChange (f) -> deduceCategoryChange ((f,store)) - | DoNothing -> (([] : operation list),store) -``` +⚠️ Notice that the function _deduceCategoryChange_ allows to specify a new deduction function _f_ which is assign to the lambda _func_ with : -⚠️ Notice in the function *deduceCategoryChange* allows to specify a new deduction function *f* which is assign to the lambda *func* with : ``` {name=store.name;func=f;celestialbodies=modified} ``` -⚠️ Notice in the function *deduceCategoryChange* the sub-function *applyDeduceCatg* apply the new category deduction to a planet (_category=f(p)_). +⚠️ Notice in the function _deduceCategoryChange_ the sub-function _applyDeduceCatg_ which applies the new category deduction to a planet (_category=f(p)_). + ``` let applyDeduceCatg = fun (name,p : string * planet) -> {position=p.position;mass=p.mass;category=f p} in ``` -⚠️ Notice in the function *deduceCategoryChange* the *applyDeduceCatg* function is used to update all entries of the *celestialbodies* map with : - ``` -Map.map applyDeduceCatg store.celestialbodies - ``` - - - - - - -We want you to update our "starmap" contract in order to take this new rule into account. +⚠️ Notice in the function _deduceCategoryChange_ the _applyDeduceCatg_ function that is used to update all entries of the _celestialbodies_ map with : +``` +Map.map applyDeduceCatg store.celestialbodies +``` -1- Write _dry-run_ command and the associated invocation (entrypoint) for taking the new rule into account. +We want you to update our "starmap" contract in order to take this new rule into account. -2- First rule : if coordinates of a planet is (0,0,0) then celestial body is considered as a STAR. +1- Write the _dry-run_ command and the associated invocation (entrypoint) for taking the new rule into account. -3- Second rule : if mass of a planet is above 100 then celestial body is considered as a PLANET. +2- First rule : if the coordinates of a planet is (0,0,0) then the celestial body is considered as a STAR. -4- Third rule : if mass of a planet is under 100 then celestial body is considered as an ASTEROID. +3- Second rule : if the mass of a planet is above 100 then the celestial body is considered as a PLANET. +4- Third rule : if the mass of a planet is under 100 then the celestial body is considered as an ASTEROID. +expected storage after simulation : -expected storage after simulation : ``` ( LIST_EMPTY() , record[celestialbodies -> MAP_ADD("earth" , - record[category -> PLANET(unit) , - mass -> +1000 , - position -> record[x -> 2 , - y -> 7 , - z -> 1]] , - MAP_ADD("pluto" , - record[category -> ASTEROID(unit) , - mass -> +10 , - position -> record[x -> 200 , - y -> 750 , - z -> 100]] , - MAP_ADD("sun" , - record[category -> STAR(unit) , - mass -> +1000000 , - position -> record[x -> 0 , - y -> 0 , - z -> 0]] , - MAP_EMPTY()))) , + record[category -> PLANET(unit) , + mass -> +1000 , + position -> record[x -> 2 , + y -> 7 , + z -> 1]] , + MAP_ADD("pluto" , + record[category -> ASTEROID(unit) , + mass -> +10 , + position -> record[x -> 200 , + y -> 750 , + z -> 100]] , + MAP_ADD("sun" , + record[category -> STAR(unit) , + mass -> +1000000 , + position -> record[x -> 0 , + y -> 0 , + z -> 0]] , + MAP_EMPTY()))) , func -> "[lambda of type: (lambda\n (pair (pair (or %category (or (unit %aSTEROID) (unit %pLANET)) (unit %sTAR)) (nat %mass))\n (pair %position (pair (int %x) (int %y)) (int %z)))\n (or (or (unit %aSTEROID) (unit %pLANET)) (unit %sTAR))) ]" , name -> "Sol"] ) -``` \ No newline at end of file +``` diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterLambda/index.ts b/src/frontend/src/pages/Chapters/Camel/ChapterLambda/index.ts index cf687e8..403dc7a 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterLambda/index.ts +++ b/src/frontend/src/pages/Chapters/Camel/ChapterLambda/index.ts @@ -7,5 +7,8 @@ import exercise from "!raw-loader!./exercise.ligo"; /* eslint import/no-webpack-loader-syntax: off */ // @ts-ignore import solution from "!raw-loader!./solution.ligo"; +/* eslint import/no-webpack-loader-syntax: off */ +// @ts-ignore +import starmap from "!raw-loader!./starmap.mligo"; -export const data = { course, exercise, solution, supports: {} }; +export const data = { course, exercise, solution, supports: { starmap } }; diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterMultisig/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterMultisig/course.md index a1db3ef..8376727 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterMultisig/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterMultisig/course.md @@ -1,41 +1,39 @@ -# Chapter 25 : Multi-signature pattern +# Chapter 24 : Multi-signature pattern Captain, we should warm up the weapons while we are still in FTL, we don't know what awaits us on the other side. -Before any nuke strike, the admiral and the president of Galatic Union must agree on nuclear usage. We need the approval of both for nuclear weapons usage. +Before any nuke strike, the admiral and the president of Galatic Union must agree on nuclear usage. We need the approval of both for nuclear weapons usage. - -In some case one may want to execute an action only if many users approve this action. This kind of pattern is called _multi-signature_. +In some case one may want to execute an action only if many users approve this action. This kind of pattern is called _multi-signature_. ## Multi-signature When invoking a smart contract, an entrypoint is called and usually an action is executed (triggering a storage modification and/or transactions emmission). -The purpose of a multi-signature pattern is to execute an action when all preconditions has been verified. The action that need to be executed depends on the smart contract logic. +The purpose of a multi-signature pattern is to execute an action when all preconditions has been verified. The action that need to be executed depends on the smart contract logic. The mutli-signature implementation can be done in a single contract with the smart contract logic or in a separated contract like a proxy contract (which emits transactions to the contract containg the logic). ### Rules The multi-signature pattern can be described with this set of rules : -* a user can propose an action -* a user can approve an action (proposed by someone else) -* a user can cancel his approval on an action. -* an action is automatically executed when it has been approved by enough users (a threshold of number of approvals must be defined) -* the smart contract must also handle a list of user allowed to approve an action +- a user can propose an action +- a user can approve an action (proposed by someone else) +- a user can cancel his approval on an action. +- an action is automatically executed when it has been approved by enough users (a threshold of number of approvals must be defined) +- the smart contract must also handle a list of user allowed to approve an action optionnaly -* the smart contract can also handle the number of approval per user and set maximum number of approvals. -* the smart contract can also handle an inner state. Everytime an action is executed the inner state of the multi-signature contract is updated for tracability purpose +- the smart contract can also handle the number of approval per user and set maximum number of approvals. +- the smart contract can also handle an inner state. Everytime an action is executed the inner state of the multi-signature contract is updated for tracability purpose More complex rules can be added these basic ones. - ### Implementation of multisig Let's consider this implementation of the multi-signature pattern. This implementation takes all previously rules into account. -The smart contract *MultisigProxy* accepts a proposed message (parameter typed _string_)), when number of approvals is reached the string is used to generate transaction to an other contract *Counter*. -This smart contract *MultisigProxy* intends to play the role of a proxy pattern for *Counter* contract. -The *Counter* contract (the exemple at https://ide.ligolang.org/p/-hNqhvMFDFdsTULXq4K-KQ) has been deployed at address : KT1CFBbdhRCNAzNkX56v361XZToHCAtjSsVS +The smart contract _MultisigProxy_ accepts a proposed message (parameter typed _string_)), when number of approvals is reached the string is used to generate transaction to an other contract _Counter_. +This smart contract _MultisigProxy_ intends to play the role of a proxy pattern for _Counter_ contract. +The _Counter_ contract (the exemple at https://ide.ligolang.org/p/-hNqhvMFDFdsTULXq4K-KQ) has been deployed at address : KT1CFBbdhRCNAzNkX56v361XZToHCAtjSsVS ``` // Counter contract types @@ -69,8 +67,8 @@ type parameter = | Withdraw of message | Default of unit -// Function executed when {threshold} approvals has been reached -let execute_action (str, s : (string * storage) ) : operation list = +// Function executed when {threshold} approvals has been reached +let execute_action (str, s : (string * storage) ) : operation list = if String.sub 1n 1n str = "3" then let ci_opt : action contract option = Tezos.get_contract_opt s.target_contract in let op : operation = match (ci_opt) with @@ -79,34 +77,34 @@ let execute_action (str, s : (string * storage) ) : operation list = in let listop : operation list = [ op ] in listop - else ([] : operation list) + else ([] : operation list) let send (param,s : (message * storage)) : return = // check sender against the authorized addresses - if (Set.mem Tezos.sender s.authorized_addresses = false) then + if (Set.mem Tezos.sender s.authorized_addresses = false) then (failwith("Unauthorized address") : return) else // check message size against the stored limit - let msg : message = param in + let msg : message = param in let packed_msg : bytes = Bytes.pack (msg) in - if (Bytes.length packed_msg > s.max_message_size) then + if (Bytes.length packed_msg > s.max_message_size) then ( failwith ("Message size exceed maximum limit") : return ) - else + else // compute the new set of addresses associated with the message and update counters let voters_opt : addr_set option = Map.find_opt packed_msg s.message_store in let (new_store, proposal_counters_updated) : (addr_set * proposal_counters) = match (voters_opt) with - Some (voters) -> + Some (voters) -> // The message is already stored. Increment the counter only if the sender is not already associated with the message. if (Set.mem Tezos.sender voters) then ((Set.empty : addr_set), s.proposal_counters) - else + else let updated : proposal_counters = match (Map.find_opt Tezos.sender s.proposal_counters) with | Some(count) -> Map.update Tezos.sender (Some(count + 1n)) s.proposal_counters | None -> Map.add Tezos.sender 1n s.proposal_counters in ( Set.add Tezos.sender voters, updated) - - | None -> + + | None -> // the message has never been received before let updated : proposal_counters = match (Map.find_opt Tezos.sender s.proposal_counters) with Some(count) -> Map.update Tezos.sender (Some(count + 1n)) s.proposal_counters @@ -123,9 +121,9 @@ let send (param,s : (message * storage)) : return = //let sender_proposal_counter : nat = get_force (Tezos.sender, proposal_counters_updated); if (sender_proposal_counter > s.max_proposal) then (failwith ("Maximum number of proposal reached") : return ) - else + else // check the threshold - if (Set.cardinal new_store >= s.threshold) then + if (Set.cardinal new_store >= s.threshold) then //remove packed_msg from map s.message_store; let message_store_updated : message_store = Map.update packed_msg (None : addr_set option) s.message_store in // trigger action execution @@ -135,9 +133,9 @@ let send (param,s : (message * storage)) : return = // decrement the counters let decrement = fun (addr,ctr: (address * nat)) -> if (Set.mem addr new_store) then abs(ctr - 1n) else ctr in let decremented_proposal_counters : proposal_counters = Map.map decrement proposal_counters_updated in - + (ret_ops, {s with proposal_counters=decremented_proposal_counters; state_hash=new_state_hash; message_store=message_store_updated}) - else + else //update map s.message_store with (packed_msg, new_store); (([]:operation list), {s with proposal_counters=proposal_counters_updated; message_store=Map.update packed_msg (Some(new_store)) s.message_store}) @@ -148,8 +146,8 @@ let withdraw (param, s : (message * storage)) : return = match (Map.find_opt packed_msg s.message_store) with Some (voters) -> // The message is stored let new_set : addr_set = Set.remove Tezos.sender voters in - // Decrement the counter only if the sender was already associated with the message - let proposal_counters_updated : proposal_counters = + // Decrement the counter only if the sender was already associated with the message + let proposal_counters_updated : proposal_counters = if (Set.cardinal voters <> Set.cardinal (new_set)) then //s.proposal_counters[Tezos.sender] := abs (get_force (Tezos.sender, s.proposal_counters) - 1n) let updated : proposal_counters = match (Map.find_opt Tezos.sender s.proposal_counters) with @@ -157,21 +155,21 @@ let withdraw (param, s : (message * storage)) : return = | None -> Map.add Tezos.sender 1n s.proposal_counters in updated - else + else s.proposal_counters in // If the message is left without any associated addresses, remove the corresponding message_store field - let message_store_updated : message_store = + let message_store_updated : message_store = if (Set.cardinal (new_set) = 0n) then //remove packed_msg from map s.message_store Map.update packed_msg (None : addr_set option) s.message_store - else + else //s.message_store[packed_msg] := new_set Map.update packed_msg (Some(new_set)) s.message_store in (([] : operation list),{s with message_store=message_store_updated; proposal_counters=proposal_counters_updated}) | None -> (([] : operation list), s) // The message is not stored, ignore. - + let default (p,s : (unit * storage)) : return = (([] : operation list), s) @@ -179,21 +177,22 @@ let main (param,s : (parameter * storage)) : return = match (param) with // Propagate message p if the number of authorized addresses having voted for the same message p equals the threshold. Send p -> send (p, s) - // Withdraw vote for message p + // Withdraw vote for message p | Withdraw p -> withdraw (p, s) - // Use this action to transfer tez to the contract + // Use this action to transfer tez to the contract | Default p -> default (p, s) ``` -Notice in the *Send* function the number of voters is compared to the threshold. If threshold is reached : +Notice in the _Send_ function the number of voters is compared to the threshold. If threshold is reached : * the message *packed\_msg* is removed from *message\_storage* -* the action is executed and takes the _string_ as parameter -* the inner state *state\_hash* of the contract is updated by creating a hash key of old state + treated message -* the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. + +- the action is executed and takes the _string_ as parameter +* the inner state *state\_hash* of the contract is updated by creating a hash key of old state + treated message +- the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. ``` -if (Set.cardinal new_store >= s.threshold) then +if (Set.cardinal new_store >= s.threshold) then //remove packed_msg from map s.message_store; let message_store_updated : message_store = Map.update packed_msg (None : addr_set option) s.message_store in // trigger action execution @@ -203,18 +202,17 @@ if (Set.cardinal new_store >= s.threshold) then // decrement the counters let decrement = fun (addr,ctr: (address * nat)) -> if (Set.mem addr new_store) then abs(ctr - 1n) else ctr in let decremented_proposal_counters : proposal_counters = Map.map decrement proposal_counters_updated in - + (ret_ops, {s with proposal_counters=decremented_proposal_counters; state_hash=new_state_hash; message_store=message_store_updated}) -else +else //update map s.message_store with (packed_msg, new_store); (([]:operation list), {s with proposal_counters=proposal_counters_updated; message_store=Map.update packed_msg (Some(new_store)) s.message_store}) ``` -Notice in the *Withdraw* function : - -* if a message proposal has no voters then it is removed -* the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. +Notice in the _Withdraw_ function : +- if a message proposal has no voters then it is removed +- the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. ## Your mission @@ -224,4 +222,4 @@ Notice in the *Withdraw* function : 2- Modify *increment* function to modify the reputation of a given *addr* address by granting a point of reputation. (use *count* as temporary variable for the _switch_ operator). If the voter is not registered yet in the *reputation* register then add him otherwise update its reputation by incrementing by one its actual level !. It is recommanded to use Map.add and Map.update when modifying a _map_. - 3- Modify *reputation_updated* variable (representing the new state of reputations) by iterating on voters with a _Set.fold_ operation and applying *increment* function on reputation. + 3- Modify *reputation_updated* variable (representing the new state of reputations) by iterating on voters with a _Set.fold_ operation and applying *increment* function on reputation. diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterPolymorphism/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterPolymorphism/course.md index 85b0725..444ad83 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterPolymorphism/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterPolymorphism/course.md @@ -1,32 +1,30 @@ # Chapter 21 : Polymorphism - - -When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using inclusion. +When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using inclusion. But a problem arises when creating a new contract which must communicate way and back (two-way communication) with an already deployed contract. The deployed contract cannot know the signature of a contract not yet created ! -Hopefully a solution exists to this problem of polymorphism, it is *Tezos.get\_entrypoint\_opt* function. +Fortunately a solution exists to this problem of polymorphism: the *Tezos.get\_entrypoint\_opt* function. ## Way and back communication between contracts -Let's consider two contracts _A_ and _B_. Contract _A_ ask some information from contract _B_ and they communicate between each other with transactions. -A sends a request to B, (it means _A_ calls an entry point of _B_, so contract _A_ includes type definition of _B_) -B receives the request, process the request and sends an response back to _A_ (it means _B_ calls an entry point of _A_, , so contract _B_ includes type definition of _A_) -Once they are deployed, we cannot change their includes. +Let's consider two contracts _A_ and _B_. Contract _A_ asks some information from contract _B_ and they communicate between each other with transactions. +A sends a request to B, meanings _A_ calls an entry point of _B_, so contract _A_ includes type definition of _B_. +B receives the request, processesthe request and sends a response back to _A_, meaning _B_ calls an entry point of _A_, so contract _B_ includes type definition of _A_. +Once they are deployed, we cannot change their _includes_. Now let's consider a third smart contract _C_ which will communicate with _B_. (Like _A_) -Since we can't change _B_ (already deployed) , then _C_ must have same definition of _A_ to be able to receive transactions from _B_. +Since we can't change _B_ (already deployed) , then _C_ must have the same definition of _A_ to be able to receive transactions from _B_. -The problem is coming from the fact that _B_ must know the whole definition of _A_ parameter but it actually only needs one entry point used for the transaction. If _C_ implements the same entry point than _A_ (for receiving a message from _B_) then transaction will match the entry point and problems solved ! +The problem is coming from the fact that _B_ must know the whole definition of _A_ parameters but it actually only need one entry point used for the transaction. If _C_ implements the same entry point than _A_ (for receiving a message from _B_) then the transaction will match the entry point and solve our problem! ## Retrieving entry points For this purpose, the predefined function *Tezos.get\_entrypoint\_opt* can be used to retrieve the definition of a single entry point (from the whole variant). -The predefined function *Tezos.get\_entrypoint\_opt* can be used in replacement of the *Tezos.get\_contract\_opt* function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special michelson syntax) and the address of the contract. +The predefined function *Tezos.get\_entrypoint\_opt* can be used in replacement of the *Tezos.get\_contract\_opt* function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special Michelsonsyntax) and the address of the contract. The predefined function *Tezos.get\_entrypoint\_opt* has the following syntax : @@ -34,46 +32,39 @@ The problem is coming from the fact that _B_ must know the whole definition of _ let : option(contract()) = Tezos.get_entrypoint_opt(, ); ``` -When function *get\_entrypoint\_opt* does not find any contract at a given _address_ or the contract doesn't match the type, then _None_ is returned. - -As for *Tezos.get\_contract\_opt* function, *Tezos.get\_entrypoint\_opt* function returns an _option_ type. +When the function *get\_entrypoint\_opt* does not find any contract at a given _address_ or the contract doesn't match the type, then _None_ is returned. +As for the *Tezos.get\_contract\_opt* function, the *Tezos.get\_entrypoint\_opt* function returns an _option_ .type. ## Entry point naming convention - Entry point name is a double-quoted string with first character is _%_ followed by the name of the entry point (and its first letter must not be capitalized) exemple: for an entry point *FooBar* the corresponding entry point name is *%fooBar*. - -Entry point names are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. +An entry point name is a double-quoted string where the first character is _%_ followed by the name of the entry point (and its first letter must not be capitalized), e.g. for an entry point *FooBar* the corresponding entry point name is *%fooBar*. +Entry point names are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. ## Entry point naming convention - *entrypoint\_name* is a double-quoted string with first character is % followed by the name of the entry point (and its first letter must not be capitalized) exemple: for an entry point *FooBar* the corresponding *entrypoint\_name* is *"%fooBar"* + *entrypoint\_name* is a double-quoted string where the first character is % followed by the name of the entry point (and its first letter must not be capitalized), e.g. for an entry point *FooBar* the corresponding *entrypoint\_name* is *"%fooBar"* To get a contract from an address and entry point, we can use _Tezos.get_entrypoint_opt(,
)_ -Entrypoints are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. Notice we change the case of the first letter. - - - +Entrypoints are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. Notice we change the case of the first letter. ## Your mission -Consider the following smart contracts : Squadron and Central. +Consider the following smart contracts : Squadron and Central (Exercice). -Central contract acts as an inventory of ships (an entry point *RegisterShip* is provided to register a ship). -Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point *RetrieveShip* is provided to query a ship). -Squadron contract provides an entry point *ModuleRequest* to ask ship information to the central contract. -Squadron contract provides an entry point *ModuleResponse* which is called by the central contract when sending back the expected ship information. - -As you can see, the entry point *RetrieveShip* calls the function *sendTx* which is responsible to send a transaction to a the calling contract. The implementation of Central contract has not been finished. We need you to finish the *sendTx* function ! +The Central contract acts as an inventory of ships (an entry point _RegisterShip_ is provided to register a ship). +The Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point _RetrieveShip_ is provided to query a ship). +The Squadron contract provides an entry point _ModuleRequest_ to ask for ship information to the central contract. +The Squadron contract provides an entry point _ModuleResponse_ which is called by the central contract when sending back the expected ship information. +As you can see, the entry point _RetrieveShip_ calls the function _sendTx_ which is responsible to send a transaction to a the calling contract. The implementation of the Central contract has not been finished. We need you to finish the _sendTx_ function! 1- Try to retrieve the entry point %moduleResponse of the given *callbackAddress* and store the result in a variable called *contractInterfaceOpt* of type _option(contract(actionSquadron))_ 2- Use a _switch_ operator to extract the entry point if it exists (use temporary variable name *ci* in the switch). Otherwise throw an exception with error message "Entrypoint not found in contract Squadron". The extracted entry point must be stored in a variable called *contractInterface*. -3- In order to prepare the ship information that need to be sent back to the Squadron contract. Check the expected type of entry point _moduleResponse_ and prepare a variable *ee* containing the expected ship *e*. - -4- Send a transaction to the retrieved entry point of squadron contract. the transaction must point to the moduleResponse entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an _operation_ type that you can store in a variable *sendbackOperation*. +3- In order to prepare the ship information that need to be sent back to the Squadron contract. Check the expected type of entry point _moduleResponse_ and prepare a variable *ee* containing the expected ship *e*. +4- Send a transaction to the retrieved entry point of the Squadron contract. The transaction must point to the _moduleResponse_ entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an _operation_ type that you can store in a variable *sendbackOperation*. diff --git a/src/frontend/src/pages/Chapters/Camel/ChapterStrings/course.md b/src/frontend/src/pages/Chapters/Camel/ChapterStrings/course.md index 01e12f7..15685ab 100644 --- a/src/frontend/src/pages/Chapters/Camel/ChapterStrings/course.md +++ b/src/frontend/src/pages/Chapters/Camel/ChapterStrings/course.md @@ -40,4 +40,4 @@ let length: nat = String.length name // length = 14 ## Your mission -1- Reassign *my\_ship* by modifying the engine attribute (third number) from 0 to 1. Use substrings for the attributes before and after to make sure they are untouched. +1 - Reassign *my\_ship* by modifying the engine attribute (third number) from 0 to 1. Use substrings for the attributes before and after to make sure they are untouched. diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterDeployContract/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterDeployContract/course.md index 8bee78e..6424bc0 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterDeployContract/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterDeployContract/course.md @@ -1,13 +1,12 @@ -# Chapter 22 : Deploy & Invoke +# Chapter 23 : Deploy contract Time to go live. - ## Smart contract -A smart contract is code written in Michelson langage (a low-level stack-based turing-complete language). -It contains entrypoints which describe all possible way to interact with a smart contract. -It contains prototype of each entrypoint. What kind of parameters are exepected to execute an entrypoint +A smart contract is code written in Michelson langage (a low-level stack-based turing-complete language). +It contains entrypoints which describe all possible way to interact with a smart contract. +It contains prototype of each entrypoint. What kind of parameters are exepected to execute an entrypoint It contains the description of storage. ### Storage @@ -18,7 +17,7 @@ The description of the storage is done by strongly-typing the data structure. ### Entrypoints -Entrypoints of a smart contract describe how to mutate a storage. +Entrypoints of a smart contract describe how to mutate a storage. Executing an entrypoint takes some parameters and a state of a storage and returns a new state of storage and some operations ![](/images/contract_in_out.png) @@ -29,9 +28,9 @@ Operations are transactions (smart contract invocation) that will be sent to som ## Deploy -A smart contract must be deployed to the blockchain in order to be invoked. When deploying a smart contract ot the blockchain , one must specify the initial state of the storage. +A smart contract must be deployed to the blockchain in order to be invoked. When deploying a smart contract ot the blockchain , one must specify the initial state of the storage. Deployment of a smart contract in Tezos is called "origination". -Here is the syntax of the tezos command line to deploy a smart contract : +Here is the syntax of the tezos command line to deploy a smart contract : ``` tezos-client originate contract for transferring from \ @@ -46,12 +45,11 @@ tezos-client originate contract for transferring is a Michelson expression. The --init parameter is used to specify initial state of the storage. it specifies the the maximal fee the user is willing to pay for this operation (using the --burn-cap parameter). - ## Invoke Once the smart contract has been deployed on the blockchain (contract-origination operation baked into a block), it is possible to invoke an entrypoint of the smart contract using the command line. -Here is the syntax of the tezos command line to invoke a smart contract : +Here is the syntax of the tezos command line to invoke a smart contract : ``` tezos-client transfer from to --arg '' --dry-run @@ -59,18 +57,16 @@ tezos-client transfer from to --arg ' is the quantity of tez being transfered to the contract. name given to the contract - name of the entrypoint and corresponding parameters. exemple 'Increment(5)'. + name of the entrypoint and corresponding parameters. exemple 'Increment(5)'. ⚠️ Notice the --arg parameter specifies an entrypoint call. ⚠️ Notice the --dry-run parameter simulate invocation of the entrypoint. - - ## Ligo compiler In order to produce a smart contract, a tool called transpiler (aka LIGO compiler) is used to transform LIGO code into Michelson code. -Michelson smart contract are stored in a file with .tz extension. +Michelson smart contract are stored in a file with .tz extension. This ligo compiler is also used to transform "Ligo expression" into "Michelson expression" as needed to deploy or invoke a smart contract. @@ -96,7 +92,6 @@ ligo compile-storage [options] code.ligo mainFunc '' is a ligo expression - ### Invocation parameter Here is how to transform ligo expression into Michelson expression using the ligo compiler in command line. @@ -107,8 +102,7 @@ ligo compile-parameter [options] code.ligo mainFunc '' is a ligo expression - -### Simulating +### Simulating Here is how to simulate execution of an entrypoint using the ligo compiler in command line. @@ -116,12 +110,12 @@ Here is how to simulate execution of an entrypoint using the ligo compiler in co ligo dry-run [options] code.ligo mainFunc '' '' ``` - state of the storage when simulating execution of the entrypoint - entrypoint of the smart contract that is invoked (parameter *p* of this entrypoint is specified between parantheses). + state of the storage when simulating execution of the entrypoint + entrypoint of the smart contract that is invoked (parameter \_p* of this entrypoint is specified between parantheses). ### Ligo Expression in command line -Let's see some exemple how to transpile a storage ligo expression into a michelson one. +Let's see some exemple how to transpile a storage ligo expression into a Michelsonone. Let's consider this smart contract which associate coordinates to a planet name. @@ -150,15 +144,15 @@ block { skip } with case action of #### Maps -The _map_ _end_ keywords can be used to initialize a *map* +The _map_ _end_ keywords can be used to initialize a _map_ -The command line *ligo compile-storage* for transpiling a map containg a tuple. +The command line _ligo compile-storage_ for transpiling a map containg a tuple. ``` ligo compile-storage starmap.ligo main 'map [ "earth" -> (1,1,1) ]' ``` -When specifying an empty map, one must cast the *map []* into the expected type. +When specifying an empty map, one must cast the _map []_ into the expected type. ``` ligo compile-storage starmap.ligo main '(map []: map(string,coordinates))' @@ -168,7 +162,7 @@ ligo compile-storage starmap.ligo main '(map []: map(string,coordinates))' Initialization of elements of a tuple is specified between _(_ and _)_ separated by comma _,_. -The command line *ligo compile-storage* for transpiling a map containg a tuple. +The command line _ligo compile-storage_ for transpiling a map containg a tuple. ``` ligo compile-storage starmap.ligo main 'map [ "earth" -> (1,1,1) ]' @@ -188,7 +182,7 @@ Initialization of elements of a record is specified between _record[_ and _]_ se record[ -> ; -> ] ``` -Let's modify our type *coordinates* to be a record instead of a tuple. +Let's modify our type _coordinates_ to be a record instead of a tuple. ``` // starmap2.mligo @@ -207,8 +201,7 @@ block { skip } with case action of end ``` - -The command line *ligo compile-storage* for transpiling a map containg a record tuple. +The command line _ligo compile-storage_ for transpiling a map containg a record tuple. ``` ligo compile-storage starmap2.ligo main 'map [ "earth" -> record [x=1;y=1;z=1] ]' @@ -257,9 +250,6 @@ block { skip } with case action of end ``` - - 1- Write _compile-storage_ command and the ligo expression for initializing the *Sol* system containing planet "earth" with coordinates (2,7,1). -2- Write _dry-run_ command and the ligo expression for adding a planet "mars" with coordinates (4,15,2) in our *Sol* system. Reuse the *Sol* system of step 1. - +2- Write the _dry-run_ command and the ligo expression for adding a planet "mars" with coordinates (4,15,2) in our *Sol* system. Reuse the *Sol* system of step 1. diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterFA12/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterFA12/course.md index e14d814..e64557f 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterFA12/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterFA12/course.md @@ -1,37 +1,37 @@ -# Chapter 23 : Fungible Asset 1.2 +# Chapter 25 : Financial Application 1.2 Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -A financial asset is a non-physical asset whose value is derived from a contractual claim, such as bank deposits, bonds, and stocks. Financial assets are usually more liquid than other tangible assets, such as commodities or real estate, and may be traded on financial markets. +A Financial Applicationis a non-physical asset whose value is derived from a contractual claim, such as bank deposits, bonds, and stocks. Financial assets are usually more liquid than other tangible assets, such as commodities or real estate, and may be traded on financial markets. -Financial assets are opposed to non-financial assets, property rights which include both tangible property (sometimes also called real assets) such as land, real estate or commodities and intangible assets such as intellectual property, like copyrights, patents, Trademarks etc. +Financial assets are opposed to non-financial assets, property rights which include both tangible property (sometimes also called real assets) such as land, real estate or commodities and intangible assets such as intellectual property, like copyrights, patents, Trademarks etc. ### Fungible and non-fungible -When talking about *token* or *crypto-currency*, it is a numerical asset emitted on a blockchain. +When talking about _token_ or _crypto-currency_, it is a numerical asset emitted on a blockchain. -Fungible means secable +Fungible means secable -Fungible token is a financial asset where account balance represents the value associated to an _address_. This value can be splitted into smaller parts which can be transfered to another account. +Fungible token is a Financial Applicationwhere account balance represents the value associated to an _address_. This value can be splitted into smaller parts which can be transfered to another account. -Non-fungible token (NFT) is a financial asset whose balance cannot be splitted into smaller part. Crypto-kitties is an exemple of non fungible token (on Ethereum blcockchain). For exemple, a video game avatar (such as avatar on world of warcraft) is a character having some skills/attributes (strength, dexterity, ...) one can want to sell its avatar , but cannot sell strength property of its avatar separately. It makes sense to keep tha whole avatar into a unsecable set of attributes. +Non-fungible token (NFT) is a Financial Applicationwhose balance cannot be splitted into smaller part. Crypto-kitties is an exemple of non fungible token (on Ethereum blcockchain). For exemple, a video game avatar (such as avatar on world of warcraft) is a character having some skills/attributes (strength, dexterity, ...) one can want to sell its avatar , but cannot sell strength property of its avatar separately. It makes sense to keep tha whole avatar into a unsecable set of attributes. ### Standard -A standard is a set of rules commonly accepted by the community. -The rules of financial asset describes how to create currencies (and transfer between accounts, etc). +A standard is a set of rules commonly accepted by the community. +The rules of Financial Applicationdescribes how to create currencies (and transfer between accounts, etc). Depending on the usage of the currency, many sets of rules have been commonly accepted : -* Financial asset 1.2 (FA1.2) are rules for fungible token. -* Financia asset 2.0 (FA20) are rules for non fungible token. + +- Financial Application1.2 (FA1.2) are rules for fungible token. +- Financial Application2.0 (FA20) are rules for non fungible token. For exemple, the creation of a crypto-currency is equivalent to creating a contract which supports the FA1.2 standard. All smart contracts supporting the FA12 standard can interact with account and other contracts by transfering coins of our crypto-currency. - -Similarily for ethereum, fungible token rules have been specified in a Ethereum forum blog (Ethereum Request Comment) the 20th answer was describing a good rule set and the ERC20 became the name for this standard (rule set). +Similarily for ethereum, fungible token rules have been specified in a Ethereum forum blog (Ethereum Request Comment) the 20th answer was describing a good rule set and the ERC20 became the name for this standard (rule set). ERC721 is the standard rule set for non-fungible token. ## FA1.2 (Implementation of standard) @@ -39,14 +39,13 @@ ERC721 is the standard rule set for non-fungible token. This Fungible token standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party. Possible actions : - Appove - Sender can specify an amount of token that can be spent by someone else (from his account) - Transfer - Transfer an amount a token from an account to another account (or third-party on-chain smart contract) - GetAllowance - Return the amount that can be spent by someone from sender's account - GetBalance - Returns sender's account balance - GetTotalSupply - Returns the number total of token - +Appove - Sender can specify an amount of token that can be spent by someone else (from his account) +Transfer - Transfer an amount a token from an account to another account (or third-party on-chain smart contract) +GetAllowance - Return the amount that can be spent by someone from sender's account +GetBalance - Returns sender's account balance +GetTotalSupply - Returns the number total of token -Let's see implementation in PascalLigo of a fungible token (FA1.2) +Let's see implementation in PascalLigo of a fungible token (FA1.2) ``` // This is an implimentation of the FA1.2 specification in PascaLIGO @@ -70,7 +69,7 @@ type contract_storage is record ledger: big_map(address, account); end -function isAllowed ( const src : address ; const value : amt ; var s : contract_storage) : bool is +function isAllowed ( const src : address ; const value : amt ; var s : contract_storage) : bool is begin var allowed: bool := False; if sender =/= source then block { @@ -89,12 +88,12 @@ function isAllowed ( const src : address ; const value : amt ; var s : contract_ // The balance of accountFrom is decreased by amount // The balance of destination is increased by amount function transfer (const accountFrom : address ; const destination : address ; const value : amt ; var s : contract_storage) : contract_storage is - begin + begin // If accountFrom = destination transfer is not necessary if accountFrom = destination then skip; else block { // Is sender allowed to spend value in the name of source - case isAllowed(accountFrom, value, s) of + case isAllowed(accountFrom, value, s) of | False -> failwith ("Sender not allowed to spend token from source") | True -> skip end; @@ -103,7 +102,7 @@ function transfer (const accountFrom : address ; const destination : address ; c const src: account = get_force(accountFrom, s.ledger); // Check that the source can spend that much - if value > src.balance + if value > src.balance then failwith ("Source balance is too low"); else skip; @@ -114,7 +113,7 @@ function transfer (const accountFrom : address ; const destination : address ; c s.ledger[accountFrom] := src; // Fetch dst account or add empty dst account to ledger - var dst: account := record + var dst: account := record balance = 0n; allowances = (map end : map(address, amt)); end; @@ -184,7 +183,7 @@ function getTotalSupply (const contr : contract(amt) ; var s : contract_storage) function main (const p : action ; const s : contract_storage) : (list(operation) * contract_storage) is - block { + block { // Reject any transaction that try to transfer token to this contract if amount =/= 0tz then failwith ("This contract do not accept token"); else skip; @@ -197,13 +196,11 @@ function main (const p : action ; const s : contract_storage) : end ``` - - ## Your mission -Let's assume the *TezosAcamedyToken* has been deployed. +Let's assume the _TezosAcamedyToken_ has been deployed. -Consider your account is *me* (at address tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) which has been granted 1000000 token. +Consider your account is _me_ (at address tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) which has been granted 1000000 token. Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7) 1- We want you to simulate the transfer of 2 TAT (Tezos Academy Token) to *alice*. Write a ligo command line for preparing a simulated storage where you (tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) possess 1000000 of token and no allowances. @@ -212,10 +209,12 @@ Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7) 3- Write a ligo command line that simulate your invocation of previous *Approval* on storage prepared at step 1. (Don't forget to specify that you are sending this transaction). -4- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *approval*. You can consider that step 2 produced the following michelson expression: +4- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *approval*. You can consider that step 2 produced the following Michelsonexpression: + ``` (Left (Left (Left (Pair "tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7" 2)))) ``` + 5-Write a tezos command line that simulate your invocation. 6- Now that approval has been exeucted on blockchain, 2 TAT can be transfered from your address to *alice*. Write a ligo command line for preparing invocation of a *Transfer* of 2 TAT (Tezos Academy Token) from you to *alice*. @@ -224,9 +223,10 @@ Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7) 8- Write a ligo command line that simulate your invocation of previous *Transfer* on storage prepared at step 7. (Don't forget to specify that you are sending this transaction). -9- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *transfer*. You can consider that step 6 produces the following michelson expression: +9- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *transfer*. You can consider that step 6 produces the following Michelsonexpression: + ``` (Right (Pair (Pair "tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ" "tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7") 2)) ``` -10-Write a tezos command line that simulate your *Transfer* invocation. \ No newline at end of file +10-Write a tezos command line that simulate your *Transfer* invocation. diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterFA20/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterFA20/course.md index b21d382..8d660cf 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterFA20/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterFA20/course.md @@ -1,4 +1,4 @@ -# Chapter 28 : Financial Asset 2.0 +# Chapter 28 : Financial Application 2.0 Captain, Let's create a ship token. @@ -6,29 +6,31 @@ There are multiple dimensions and considerations while implementing a particular token smart contract. Tokens might be fungible or non-fungible. A variety of permission policies can be used to define how many tokens can be transferred, who can initiate a transfer, and who can receive tokens. A token contract can be designed to support a single token type (e.g. ERC-20 or ERC-721) or multiple token types (e.g. ERC-1155) to optimize batch transfers and atomic swaps of the tokens. -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. -In the following chapter on Financial Asset 2.0 , we will focus on *TZIP-12* which stands for the 12th Tezos Improvement Proposal (same as EIP-721 for Ethereum). +In the following chapter on Financial Application 2.0 , we will focus on _TZIP-12_ which stands for the 12th Tezos Improvement Proposal (same as EIP-721 for Ethereum). ## Architecture FA2 proposes to leave it to implementers to handle common considerations such as defining the contract’s token type(s) (e.g. non-fungible vs. fungible vs. semi-fungible), administration and whitelisting, contract upgradability, and supply operations (e.g. mint/burn). -FA2 also leaves to implementers to decide on architecture pattern for handling permissioning. Permission can be implemented -* in the the same contract as the core transfer behavior (i.e. a “monolith”), -* in a transfer hook to another contract, -* in a separate wrapper contract. +FA2 also leaves to implementers to decide on architecture pattern for handling permissioning. Permission can be implemented +- in the the same contract as the core transfer behavior (i.e. a “monolith”), +- in a transfer hook to another contract, +- in a separate wrapper contract. ## Interface and library The FA2 interface formalize a standard way to design tokens and thus describes a list of entry points (that must be implemented) and data structures related to those entry points. A more detailed decription of the interface is broken down in following sections. In addition to the FA2 interface, the FA2 standard provides helper functions to manipulate data structures involved in FA2 interface. The FA2 library contains helper functions for : -* a generic behavior and transfer hook implementation (behavior based on *permissions\_descriptor*), -* converting data structures, -* defining hooks between contracts when transfer is emitted, -* defining operators for managing allowance. + +* a generic behavior and transfer hook implementation (behavior based on *permissions\_descriptor*), + +- converting data structures, +- defining hooks between contracts when transfer is emitted, +- defining operators for managing allowance. ## Entry points @@ -44,21 +46,22 @@ type fa2_entry_points is | Is_operator of is_operator_params ``` - ### Balance of -FA2 token contracts MUST implement the _Balance of_ entry point which get the balance of multiple account/token pairs (because FA2 supports mutiple token). +FA2 token contracts MUST implement the _Balance of_ entry point which get the balance of multiple account/token pairs (because FA2 supports mutiple token). + ``` | Balance_of of balance_of_params ``` -It accepts a list of *balance\_of\_requests* and a callback and sends back to a callback contract a list of *balance\_of\_response* records. +It accepts a list of *balance\_of\_requests* and a callback and sends back to a callback contract a list of *balance\_of\_response* records. If one of the specified *token\_ids* is not defined within the FA2 contract, the entry point MUST fail with the error mnemonic "TOKEN_UNDEFINED" (see section Error Handling). #### Interface The FA2 interface defines request/response parameters as follow : + ``` type token_id is nat @@ -80,38 +83,34 @@ type balance_of_params_ is record end ``` - - ### Transfer FA2 token contracts MUST implement the _Transfer_ entry point which transfer tokens between and MUST ensure following rules. + ``` Transfer of transfer_params ``` #### Rules -FA2 token contracts MUST implement the transfer logic defined by the following rules : - +FA2 token contracts MUST implement the transfer logic defined by the following rules : -1) Every transfer operation MUST be atomic. If the operation fails, all token transfers MUST be reverted, and token balances MUST remain unchanged. +1. Every transfer operation MUST be atomic. If the operation fails, all token transfers MUST be reverted, and token balances MUST remain unchanged. -2) The amount of a token transfer MUST NOT exceed the existing token owner's balance. If the transfer amount for the particular token type and token owner -exceeds the existing balance, the whole transfer operation MUST fail with the error mnemonic "INSUFFICIENT_BALANCE" +2. The amount of a token transfer MUST NOT exceed the existing token owner's balance. If the transfer amount for the particular token type and token owner + exceeds the existing balance, the whole transfer operation MUST fail with the error mnemonic "INSUFFICIENT_BALANCE" -3) Core transfer behavior MAY be extended. If additional constraints on tokens transfer are required, FA2 token contract implementation MAY invoke additional -permission policies (transfer hook is the recommended design pattern to implement core behavior extension). (See Chapter FA2 - Hook) +3. Core transfer behavior MAY be extended. If additional constraints on tokens transfer are required, FA2 token contract implementation MAY invoke additional + permission policies (transfer hook is the recommended design pattern to implement core behavior extension). (See Chapter FA2 - Hook) If the additional permission hook fails, the whole transfer operation MUST fail with a custom error mnemonic. -4) Core transfer behavior MUST update token balances exactly as the operation parameters specify it. No changes to amount values or additional transfers are -allowed. - - +4. Core transfer behavior MUST update token balances exactly as the operation parameters specify it. No changes to amount values or additional transfers are + allowed. #### Interface -It transfer tokens from a *from_* account to possibly many destination accounts where each destination transfer describes the type of token, the amount of token, and receiver address. +It transfer tokens from a _from\__ account to possibly many destination accounts where each destination transfer describes the type of token, the amount of token, and receiver address. ``` type token_id = nat @@ -140,13 +139,14 @@ type transfer_aux = { This FA2 tandard defines the set of standard errors to make it easier to integrate FA2 contracts with wallets, DApps and other generic software, and enable localization of user-visible error messages. -Each error code is a short abbreviated string mnemonic. An FA2 contract client (like another contract or a wallet) could use on-the-chain or off-the-chain registry to map the error code mnemonic to a user-readable, localized message. +Each error code is a short abbreviated string mnemonic. An FA2 contract client (like another contract or a wallet) could use on-the-chain or off-the-chain registry to map the error code mnemonic to a user-readable, localized message. A particular implementation of the FA2 contract MAY extend the standard set of errors with custom mnemonics for additional constraints. When error occurs, any FA2 contract entry point MUST fail with one of the following types: -* string value which represents an error code mnemonic. -* a Michelson pair, where the first element is a string representing error code mnemonic and the second element is a custom error data. + +- string value which represents an error code mnemonic. +- a Michelson pair, where the first element is a string representing error code mnemonic and the second element is a custom error data. #### Standard error mnemonics: @@ -170,15 +170,13 @@ Error mnemonic - Description "SENDER_HOOK_UNDEFINED" - Sender hook is required by the permission behavior, but is not implemented by a sender contract - - ## Your mission We are working on a fungible token compliant with the FA2 standard. We want you to complete the existing implementation of token. The *Balance\_Of* entry point is not yet implemented , please finish the job ! The function *retreive\_balance* is responsible for processing each request and providing a response to each request.As you can see, a request is of type *balance\_of\_request* -1- Declare a variable *retreived\_balance* of type natural initialized to 0. +1- Declare a variable *retreived\_balance* of type natural initialized to 0. 2- Retrieve balance associated to the request owner in the ledger. and store it in the variable *retreived\_balance*. In the _case_ instruction use *ledger_balance* as temporary name for the _Some_. If no balance is retrieve do nothing (do not modify *retreived\_balance*). @@ -186,19 +184,12 @@ Error mnemonic - Description 4- The function *retreive\_balance* must return a type *balance\_of\_response*. You can use the *convert\_to\_right\_comb* function (seen in Chapter Interop) to convert constant *response* into the right format. Don't forget to cast *response* as type *balance\_of\_response\_*. - - - - - - - - -## WIP +## WIP ### Totalsupply -FA2 token contracts MUST implement the _Totalsupply_ entry point which get the total supply of tokens for multiple token types (because FA2 supports mutiple token). +FA2 token contracts MUST implement the _Totalsupply_ entry point which get the total supply of tokens for multiple token types (because FA2 supports mutiple token). + ``` | Total_supply of total_supply_param ``` @@ -234,8 +225,7 @@ type total_supply_param = { FA2 token amounts are represented by natural numbers (nat), and their granularity (the smallest amount of tokens which may be minted, burned, or transferred) is always 1. -The *decimals* property is the number of digits to use after the decimal point when displaying the token amounts. If 0, the asset is not divisible. Decimals are used for display purposes only and MUST NOT affect transfer operation. - +The _decimals_ property is the number of digits to use after the decimal point when displaying the token amounts. If 0, the asset is not divisible. Decimals are used for display purposes only and MUST NOT affect transfer operation. #### Interface @@ -255,4 +245,4 @@ type token_metadata_param is record token_ids : list(token_id); callback : contract(list(token_metadata)) ; end -``` \ No newline at end of file +``` diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Hook/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Hook/course.md index 17cbeb8..219a491 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Hook/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Hook/course.md @@ -1,12 +1,12 @@ -# Chapter 30 : Financial Asset 2.0 - Tranfer Hook +# Chapter 30 : FA 2.0 - Tranfer Hook Captain, all space pirate should have a hook like in old times. -## ... in the previous episode +## ... in the previous episode -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. -The FA2 interface formalize a standard way to design tokens and thus describes a list of entrypoints (that must be implemented) and data structures related to those entrypoints. +The FA2 interface formalize a standard way to design tokens and thus describes a list of entrypoints (that must be implemented) and data structures related to those entrypoints. In this chapter we will focus on _transfer hook_ @@ -20,17 +20,17 @@ Although, it is recommended to implement "transfer hook design pattern" in many _Transfer hook_ is one recommended design pattern to implement FA2 that enables separation of the core token transfer logic and a permission policy. -Instead of implementing FA2 as a monolithic contract, a permission policy can be implemented as a separate contract. Permission policy contract provides an entry point invoked by the core FA2 contract to accept or reject a particular transfer operation (such an entry point is called *transfer hook*). +Instead of implementing FA2 as a monolithic contract, a permission policy can be implemented as a separate contract. Permission policy contract provides an entry point invoked by the core FA2 contract to accept or reject a particular transfer operation (such an entry point is called _transfer hook_). ![](/images/small-fa2-hook.png) -Although this approach introduces gas consumption overhead (compared to an all-in-one contract) by requiring an extra inter-contract call, it also offers some other advantages: -1) FA2 core implementation can be verified once, and certain properties (not related to permission policy) remain unchanged. -2) modification of the permission policy of an existing contract can be done by replacing a transfer hook only. No storage migration of the FA2 ledger is required. -3) Transfer hooks could be used for purposes beyond permissioning, such as implementing _custom logic_ for a particular token application +Although this approach introduces gas consumption overhead (compared to an all-in-one contract) by requiring an extra inter-contract call, it also offers some other advantages: -The transfer hook makes it possible to model different transfer permission policies like whitelists, operator lists, etc. +1. FA2 core implementation can be verified once, and certain properties (not related to permission policy) remain unchanged. +2. modification of the permission policy of an existing contract can be done by replacing a transfer hook only. No storage migration of the FA2 ledger is required. +3. Transfer hooks could be used for purposes beyond permissioning, such as implementing _custom logic_ for a particular token application +The transfer hook makes it possible to model different transfer permission policies like whitelists, operator lists, etc. #### Hook interface @@ -63,46 +63,43 @@ In addition to the hook standard, the FA2 standard provides helper functions to Some helpers functions has been gatthered in a hook library which help defining hooks when implementing a FA2 contract. This library contains following functions and type alias : -The type *fa2\_registry* is a _set_ of _address_. +The type *fa2\_registry* is a _set_ of _address_. the function *get\_hook\_entrypoint* retrieves the contract interface of entrypoint "%tokens\_transferred\_hook" for a given contract address -the function *register\_with\_fa2* +the function *register\_with\_fa2* * takes the address of a FA2 contract (having hooks) and register it in the registry (set of address). * calls the *Set\_transfer\_hook* entrypoint of a FA2 contract -the function *create\_register\_hook\_op* sends a transaction to a FA2 contract (having hook entrypoints). The transaction intends to invoke the entrypoint *Set\_transfer\_hook*. This entrypoint *Set\_transfer\_hook* requires as parameters : -* the contract interface of entrypoint "%tokens\_transferred\_hook" +the function *create\_register\_hook\_op* sends a transaction to a FA2 contract (having hook entrypoints). The transaction intends to invoke the entrypoint *Set\_transfer\_hook*. This entrypoint *Set\_transfer\_hook* requires as parameters : +* the contract interface of entrypoint "%tokens\_transferred\_hook" * a _permission descriptor_ the function *validate\_hook\_call* ensures an address in registered in the registry (set of address). - ##### Transfer Hooks The function *owners\_transfer\_hook* defined in the library generates a list of Tezos operations invoking sender and receiver hooks according to the policies defined by the permissions descriptor. -The hook pattern depends on the permission policy. A transfer hook may be unwanted, optional or required. +The hook pattern depends on the permission policy. A transfer hook may be unwanted, optional or required. + If the policy requires a owner hook then the token owner contract MUST implement an entry point "tokens\_received". Otherwise transfer is not allowed. If the policy optionnaly accepts a owner hook then the token owner contract MAY implement an entry point "tokens\_received". Otherwise transfer is allowed. -It is the same for permission policies including senders, the entry point *tokens\_sent* may need to be implemented. - -In case of a Transfer, if permission policies expect a hook, then the token owners MUST implement *fa2\_token\_receiver*, and *fa2\_token\_sender* interfaces. This implies that token'owner contract must have entry points *tokens\_received* and *token\_sent*. If these entry points fail the transfer is rejected. +It is the same for permission policies including senders, the entry point _tokens_sent_ may need to be implemented. +In case of a Transfer, if permission policies expect a hook, then the token owners MUST implement *fa2\_token\_receiver*, and *fa2\_token\_sender* interfaces. This implies that token'owner contract must have entry points *tokens\_received* and *token\_sent*. If these entry points fail the transfer is rejected. ##### Transfer Hooks entry points - + The library defines some helper functions The function *to\_receiver\_hook* retrieves the entry point *"%tokens\_received"* for a given _address_. It enables to check if the *fa2\_token\_receiver* interface is implemented. The function *to\_sender\_hook* retrieves the entry point *"%tokens\_sent"* for a given _address_. It enables to check if the *fa2\_token\_sender* interface is implemented. - - - //// NOT IMPLEMENTED START/// + These two functions return a variant *hook\_result* type. If variant value is *Hook\_contract* then the entrypoint exists an is provided. If variant value is *Hook\_undefined* then the entry point is not implemented and a message error is provided. ``` @@ -110,48 +107,47 @@ type hook_result = | Hook_contract of transfer_descriptor_param_michelson contract | Hook_undefined of string ``` -//// NOT IMPLEMENTED END/// +//// NOT IMPLEMENTED END/// #### Hook Rules FA2 implementation with the transfer hook pattern recquires following rules: -1) An FA2 token contract has a single entry point to set the hook. If a transfer hook is not set, the FA2 token contract transfer operation MUST fail. +1. An FA2 token contract has a single entry point to set the hook. If a transfer hook is not set, the FA2 token contract transfer operation MUST fail. -2) Transfer hook is to be set by the token contract administrator before any transfers can happen. +2. Transfer hook is to be set by the token contract administrator before any transfers can happen. -3) The concrete token contract implementation MAY impose additional restrictions on -who may set the hook. If the set hook operation is not permitted, it MUST fail -without changing existing hook configuration. +3. The concrete token contract implementation MAY impose additional restrictions on + who may set the hook. If the set hook operation is not permitted, it MUST fail + without changing existing hook configuration. -4) For each transfer operation, a token contract MUST invoke a transfer hook and +4. For each transfer operation, a token contract MUST invoke a transfer hook and return a corresponding operation as part of the transfer entry point result. (For more details see set\_transfer\_hook ) -5) *operator* parameter for the hook invocation MUST be set to *SENDER*. +5. _operator_ parameter for the hook invocation MUST be set to _SENDER_. 6) *from_* parameter for each *hook\_transfer* batch entry MUST be set to *Some(transfer.from_)*. 7) *to_* parameter for each *hook\_transfer* batch entry MUST be set to *Some(transfer.to_)*. -8) A transfer hook MUST be invoked, and operation returned by the hook invocation -MUST be returned by transfer entry point among other operations it might create. -*SENDER* MUST be passed as an operator parameter to any hook invocation. If an -invoked hook fails, the whole transfer transaction MUST fail. +8. A transfer hook MUST be invoked, and operation returned by the hook invocation + MUST be returned by transfer entry point among other operations it might create. + _SENDER_ MUST be passed as an operator parameter to any hook invocation. If an + invoked hook fails, the whole transfer transaction MUST fail. -9) FA2 does NOT specify an interface for mint and burn operations; however, if an -FA2 token contract implements mint and burn operations, these operations MUST -invoke a transfer hook as well. +9. FA2 does NOT specify an interface for mint and burn operations; however, if an + FA2 token contract implements mint and burn operations, these operations MUST + invoke a transfer hook as well. #### Implementation of a hook permission contract -Let's see an example of FA2 Hook pattern implementation. The following smart contract implements a hook permission contract +Let's see an example of FA2 Hook pattern implementation. The following smart contract implements a hook permission contract Owners transfer hooks are triggered by the *owners\_transfer\_hook* function. If a receiver address implements *fa2\_token\_receiver* interface, its *tokens\_received* entry point must be called. -If a sender address implements *fa2\_token\_sender* interface, its *tokens\_sent* entry point must be called. - +If a sender address implements *fa2\_token\_sender* interface, its *tokens\_sent* entry point must be called. ``` (** @@ -171,14 +167,14 @@ type entry_points is | Tokens_transferred_hook of transfer_descriptor_param | Register_with_fa2 of contract(fa2_with_hook_entry_points) -function tokens_transferred_hook(const pm : transfer_descriptor_param; const s : storage) : list(operation) * storage is +function tokens_transferred_hook(const pm : transfer_descriptor_param; const s : storage) : list(operation) * storage is block { const p : transfer_descriptor_param_ = Layout.convert_to_right_comb (pm); const u : unit = validate_hook_call (Tezos.sender, s.fa2_registry); const ops : list(operation) = owners_transfer_hook(record [ligo_param = p; michelson_param = pm], s.descriptor); } with (ops, s) -function register(const fa2 : contract(fa2_with_hook_entry_points); const s : storage) : list(operation) * storage is +function register(const fa2 : contract(fa2_with_hook_entry_points); const s : storage) : list(operation) * storage is block { const ret : list(operation) * set(address) = register_with_fa2 (fa2, s.descriptor, s.fa2_registry); s.fa2_registry := ret.1; @@ -204,12 +200,11 @@ const own_policy : permissions_descriptor = record [ Notice this Hook Permission contract contains an entry point *Register\_with\_fa2* to register with the FA2 core contract. -Notice this Hook Permission contract contains an entry point *Tokens\_transferred\_hook* triggered when FA2 core contract receive a transfer request. This entry point triggers the owner hook transfer (sending hooks to sender and receiver and waiting for their approval or rejection). - +Notice this Hook Permission contract contains an entry point *Tokens\_transferred\_hook* triggered when FA2 core contract receive a transfer request. This entry point triggers the owner hook transfer (sending hooks to sender and receiver and waiting for their approval or rejection). ## Your mission -We are working on a Fungible token which can handle multiple assets. We decided to implement a Hook pattern. A FA2 core contract handle all fa2 entry points (BalanceOf, Transfer, ...) and a hook permission contract which implements the validation of a transfer with some custom rules. +We are working on a Fungible token which can handle multiple assets. We decided to implement a Hook pattern. A FA2 core contract handle all fa2 entry points (BalanceOf, Transfer, ...) and a hook permission contract which implements the validation of a transfer with some custom rules. ![](/images/small-fa2-hook-exercise.png) @@ -219,13 +214,11 @@ Rule 1 - we want to accept a transfer if transfer receiver is registered in a wh If a receiver address implements *fa2\_token\_receiver* interface, its *tokens\_received* entry point must be called. +Complete the hook permission smart contract by implementing our custom rules on receivers. Transfer is permitted if receiver address implements *fa2\_token\_receiver* interface OR a receiver address is in the receiver white list. -Complete the hook permission smart contract by implementing our custom rules on receivers. Transfer is permitted if receiver address implements *fa2\_token\_receiver* interface OR a receiver address is in the receiver white list. - -- As you can see the function *check\_receiver* verifies if a receiver _r_ implements *fa2\_token\_receiver* interface, using *to\_receiver\_hook* function and a _case_ operator. If the receiver _r_ implements *fa2\_token\_receiver* interface, the function *create\_hook\_receiver\_operation* is called with _h_ as hook entry point. +- As you can see the function *check\_receiver* verifies if a receiver _r_ implements *fa2\_token\_receiver* interface, using *to\_receiver\_hook* function and a _case_ operator. If the receiver _r_ implements *fa2\_token\_receiver* interface, the function *create\_hook\_receiver\_operation* is called with _h_ as hook entry point. - -1- Prepare parameter - cast parameter _p_ into type *transfer\_descriptor\_param* and store the result in a new variable _pm_. You can check the *fa2\_interface.ligo* for type definition of *transfer\_descriptor\_param* and use the *Layout.convert\_to\_right\_comb* function for conversion. +1 - Prepare parameter - cast parameter _p_ into type *transfer\_descriptor\_param* and store the result in a new variable _pm_. You can check the *fa2\_interface.ligo* for type definition of *transfer\_descriptor\_param* and use the *Layout.convert\_to\_right\_comb* function for conversion. 2- Call the entry point - create a variable _op_ of type *operation* which is a transaction sending variable _pm_ and no mutez to the retrieved hook entry point _h_ @@ -233,9 +226,8 @@ Rule 1 - we want to accept a transfer if transfer receiver is registered in a wh - if the receiver _r_ does not implement *fa2\_token\_receiver* interface, the function *verify\_receiver\_in\_whitelist* is called. Modify function *verify\_receiver\_in\_whitelist* with following implementation requirements: -4- Check if receiver _r_ is registered in the whitelist _wl_. +4- Check if receiver _r_ is registered in the whitelist _wl_. -5- If it is the case , everything is fine, just return the returned list of operation _ops_. +5- If it is the case , everything is fine, just return the returned list of operation _ops_. 6- Otherwise throw an exception with "Not in whitelist" message. Don't forget to cast the exception. - diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Operator/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Operator/course.md index f3307aa..07dfcd4 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Operator/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterFA20Operator/course.md @@ -1,10 +1,10 @@ -# Chapter 29 : Financial Asset 2.0 - Operators and Permissions +# Chapter 29 : FA 2.0 - Operators and Permissions Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. In this chapter we will focus on _Operators_ and _Permissions_. @@ -25,6 +25,7 @@ type fa2_entry_points is ### Operators #### Definition + _Operator_ can be seen as delegate role. _Operator_ is a Tezos address that initiates token transfer operation on behalf of the owner. @@ -78,41 +79,38 @@ type is_operator_params is michelson_pair_right_comb(is_operator_params_) Notice the parameter _is\_operator\_params_ given to *Is\_operator* entry point contains a *callback* property used to send back a response to the calling contract. -Notice entry point *Update\_operators* expectes a list of *update\_operator\_param*. - +Notice entry point *Update\_operators* expectes a list of *update\_operator\_param*. #### FA2 standard operator library Some helpers functions has been implemented in the FA2 library which help manipulating _operator_. This library contains following functions and type alias : - an _operator_ is a relationship between two address (owner address and operator address) function *update\_operators* allows to Add or Remove an operator in the list of operators. function *validate\_operator* validates operators for all transfers in the batch at once, depending on given *operator\_transfer\_policy* - - ### FA2 Permission Policies and Configuration -Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. +Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. Such logic (called _Permission Policy_) could validate who initiates a transfer, the transfer amount, and who can receive tokens. This FA2 standard defines a framework to compose and configure such permission policies from the standard behaviors and configuration APIs. FA2 defines : -* the default core transfer behavior, that MUST always be implemented -* a set of predefined permission policies that are optional +- the default core transfer behavior, that MUST always be implemented +- a set of predefined permission policies that are optional #### Permissions descriptor FA2 specifies an interface permissions_descriptor allowing external contracts to discover an FA2 contract's permission policy and to configure it. *permissions\_descriptor* serves as a modular approach to define consistent and non-self-contradictory policies. -The *permission descriptor* indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. +The _permission descriptor_ indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. + +The FA2 standard defines a special metadata entry point _permission descriptor_ containing standard permission policies. -The FA2 standard defines a special metadata entry point *permission descriptor* containing standard permission policies. ``` type permissions_descriptor_ is record operator : operator_transfer_policy @@ -125,7 +123,7 @@ end #### Interface FA2 token contract MUST implement the *Permissions\_descriptor* entry point which provides token policies. - + ``` | Permissions_descriptor of permissions_descriptor_params ``` @@ -139,10 +137,9 @@ type permissions_descriptor_params is contract (permissions_descriptor) ``` - #### Operator transfer policy -*operator\_transfer\_policy* - defines who can transfer tokens. Tokens can be transferred by the token owner or an operator (some address that is authorized to transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). +*operator\_transfer\_policy* - defines who can transfer tokens. Tokens can be transferred by the token owner or an operator (some address that is authorized to transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). The FA2 standard defines two entry points to manage and inspect operators associated with the token owner address (*update\_operators*, *is\_operator*). Once an operator is added, it can manage all of its associated owner's tokens. @@ -175,39 +172,36 @@ type custom_permission_policy = { } ``` - #### Permission Policy Formulae Each concrete implementation of the permission policy can be described by a formula which combines permission behaviors in the following form: + ``` Operator(?) * Receiver(?) * Sender(?) ``` This formula describes the policy which allows only token owners to transfer their own tokens : + ``` Operator(Owner_transfer) * Receiver(Owner_no_hook) * Sender(Owner_no_hook) ``` - - - - ## Your mission We are working on a non_fungible/single-asset token. Our NFT "token" is almost ready but to allow a new rule. We need Bob to transfert a token taken from Vera account and send it to Alice account. - * Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" - * Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" - * Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" +- Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" +- Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" +- Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" 1- First we want you to prepare the initial state of storage. Modify the _ligo compile-storage_ command for the *token* contract with following recommandations : - * Vera account is owner of the token 1 +- Vera account is owner of the token 1 2- Complete the _ligo dry-run_ command for authorizing Bob to transfer token taken from Vera account, transaction emitted by Vera. (reuse the storage you made on step 1). You can use *Layout.convert\_to\_right\_comb* function to convert your parameters into the format expected by *Update\_operators* entry point. - 3- Complete the _ligo dry-run_ command for simulating the transfer of 1 token from Vera'account to Alice's account, transaction emitted by Bob. The transfered token id is number 1 (token\_id and and amount must be 1). You can use the *Layout.convert\_to\_right\_comb* function to convert your parameters into the format expected by *Transfer* entry point. + You will have to modify the storage to in the state where "Vera account is owner of the token 1" (step 1) and Bob is authorized to transfer token taken from Vera account (step 2). diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterInterop/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterInterop/course.md index a59edff..fdb4b3c 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterInterop/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterInterop/course.md @@ -2,22 +2,21 @@ We need to hack aliens, decompile their code to understand how their informatic works - -LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types. +LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types. ## Annotations ### Michelson types and annotations -Michelson types consist of *or*'s and *pair*'s, combined with field annotations. Field annotations add contraints on a Michelson type, for example a _pair_ of *(pair (int %foo) (string %bar))* will only work with the exact equivalence or the same type without the field annotations. +Michelson types consist of _or_'s and _pair_'s, combined with field annotations. Field annotations add contraints on a Michelson type, for example a _pair_ of _(pair (int %foo) (string %bar))_ will only work with the exact equivalence or the same type without the field annotations. -For example, the following _pair_ +For example, the following _pair_ ``` (pair (int %foo) (string %bar)) ``` -will accept these definitions and fail with the ones that does not respect the typing or the order of pair fields: +will accept these definitions and fail with the ones that does not respect the typing or the order of pair fields: ``` (pair (int %foo) (string %bar)) // OK @@ -26,17 +25,16 @@ will accept these definitions and fail with the ones that does not respect the t (pair (string %bar) (int %foo)) // KO ``` - ### Entrypoints and annotations -As seen in chapter Polymorphism, a contract can be called by another contract. Using the predefined function *Tezos.get\_entrypoint\_opt* allows to a calling contract ot point to a specific entry point of the called contract. +As seen in chapter Polymorphism, a contract can be called by another contract. Using the predefined function *Tezos.get\_entrypoint\_opt* allows to a calling contract ot point to a specific entry point of the called contract. Here is an exemple. Let's consider the following contract : ``` type storage is int -type parameter is +type parameter is | Left of int | Right of int @@ -57,8 +55,8 @@ type parameter is int type x is Left of int function main (const p: parameter; const s: storage): (list(operation) * storage) is block { - const contract: contract(x) = - case (Tezos.get_entrypoint_opt("%left", ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx":address)): option(contract(x))) of + const contract: contract(x) = + case (Tezos.get_entrypoint_opt("%left", ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx":address)): option(contract(x))) of | Some (c) -> c | None -> (failwith("not a correct contract") : contract(x)) end; @@ -67,7 +65,7 @@ function main (const p: parameter; const s: storage): (list(operation) * storage } with result ``` -⚠️ Notice how we directly use the *%left* entrypoint without mentioning the *%right* entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to. +⚠️ Notice how we directly use the _%left_ entrypoint without mentioning the _%right_ entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to. These annotations works for _or_'s or _variant_ types in LIGO. @@ -78,15 +76,15 @@ These annotations works for _or_'s or _variant_ types in LIGO. Take for example the following Michelson type that we want to interop with: ``` -(or +(or (unit %z) - (or %other - (unit %y) - (pair %other - (string %x) - (pair %other - (int %w) - (nat %v))))) + (or %other + (unit %y) + (pair %other + (string %x) + (pair %other + (int %w) + (nat %v))))) ``` To reproduce this type we can use the following LIGO code: @@ -115,9 +113,9 @@ const x: z_or = (M_right (y_1) : z_or); ## Helper functions -Conversions from Ligo types to michelson types requires a precise knowledge of data structures representation. +Conversions from Ligo types to Michelsontypes requires a precise knowledge of data structures representation. -So it becomes even more relevant with nested pairs that there are many possible decomposition of a record in pairs of pairs. +So it becomes even more relevant with nested pairs that there are many possible decomposition of a record in pairs of pairs. The following record structure @@ -129,10 +127,10 @@ type l_record is record [ ] ``` -can be transformed in a left combed data structure +can be transformed in a left combed data structure ``` - (pair %other + (pair %other (pair %other (string %s) (int %w) @@ -141,19 +139,19 @@ can be transformed in a left combed data structure ) ``` - or a right combed data structure +or a right combed data structure - ``` - (pair %other - (string %s) - (pair %other - (int %w) - (nat %v) - ) - ) - ``` +``` + (pair %other + (string %s) + (pair %other + (int %w) + (nat %v) + ) + ) +``` -Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually. +Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually. ### Converting left combed Michelson data structures @@ -164,7 +162,7 @@ Converting between different LIGO types and data structures can happen in two wa Here's an example of a left combed Michelson data structure using pairs: ``` - (pair %other + (pair %other (pair %other (string %s) (int %w) @@ -183,31 +181,33 @@ type l_record is record [ ] ``` -This snippet of code shows -\* how to use *Layout.convert\_from\_left\_comb* to transform a michelson type into a record type. -\* how to use *Layout.convert\_to\_left\_comb* to transform a record type into a michelson type. +This snippet of code shows + +\* how to use *Layout.convert\_from\_left\_comb* to transform a Michelsontype into a record type. +\* how to use *Layout.convert\_to\_left\_comb* to transform a record type into a Michelsontype. ``` -type michelson is michelson_pair_left_comb(l_record) +type Michelsonis michelson_pair_left_comb(l_record) -function of_michelson (const f: michelson) : l_record is +function of_michelson (const f: michelson) : l_record is block { const p: l_record = Layout.convert_from_left_comb(f) - } + } with p -function to_michelson (const f: l_record) : michelson is +function to_michelson (const f: l_record) : Michelsonis block { - const p: michelson = Layout.convert_to_left_comb ((f: l_record)) + const p: Michelson= Layout.convert_to_left_comb ((f: l_record)) } with p ``` #### Variant + In the case of a left combed Michelson or data structure, that you want to translate to a variant, you can use the *michelson\_or\_left\_comb* type. ``` -type vari is +type vari is | Foo of int | Bar of nat | Other of bool @@ -218,13 +218,13 @@ type r is michelson_or_left_comb(vari) And then use these types in *Layout.convert\_from\_left\_comb* or *Layout.convert\_to\_left\_comb*, similar to the pairs example above ``` -function of_michelson_or (const f: r) : vari is +function of_michelson_or (const f: r) : vari is block { const p: vari = Layout.convert_from_left_comb(f) - } + } with p -function to_michelson_or (const f: vari) : r is +function to_michelson_or (const f: vari) : r is block { const p: r = Layout.convert_to_left_comb((f: vari)) } @@ -243,7 +243,7 @@ The following code can be used as inspiration: ``` type z_to_v is -| Z +| Z | Y | X | W @@ -251,8 +251,8 @@ type z_to_v is type w_or_v is michelson_or(unit, "w", unit, "v") type x_or is michelson_or(unit, "x", w_or_v, "other") -type y_or is michelson_or(unit, "y", x_or, "other") -type z_or is michelson_or(unit, "z", y_or, "other") +type y_or is michelson_or(unit, "y", x_or, "other") +type z_or is michelson_or(unit, "z", y_or, "other") type test is record [ z: string; @@ -264,7 +264,7 @@ type test is record [ function make_concrete_sum (const r: z_to_v) : z_or is block { const z: z_or = (M_left (unit) : z_or); - + const y_1: y_or = (M_left (unit): y_or); const y: z_or = (M_right (y_1) : z_or); @@ -282,7 +282,7 @@ function make_concrete_sum (const r: z_to_v) : z_or is block { const v_1: y_or = (M_right (v_2): y_or); const v: z_or = (M_right (v_1) : z_or); } - with (case r of + with (case r of | Z -> z | Y -> y | X -> x @@ -292,12 +292,12 @@ function make_concrete_sum (const r: z_to_v) : z_or is block { function make_concrete_record (const r: test) : (string * int * string * bool * int) is - (r.z, r.y, r.x, r.w, r.v) + (r.z, r.y, r.x, r.w, r.v) function make_abstract_sum (const z_or: z_or) : z_to_v is (case z_or of | M_left (n) -> Z - | M_right (y_or) -> + | M_right (y_or) -> (case y_or of | M_left (n) -> Y | M_right (x_or) -> @@ -310,18 +310,14 @@ function make_abstract_sum (const z_or: z_or) : z_to_v is end) end) end) - end) + end) function make_abstract_record (const z: string; const y: int; const x: string; const w: bool; const v: int) : test is record [ z = z; y = y; x = x; w = w; v = v ] ``` - - ## Your mission -We want you to modify our "inventory" contract. As you can see the storage is mainly composed of an item inventory where each item is a right combed nested pairs. The contract possess a single entry point AddInventory. This *AddInventory* function adds each element in the inventory (don't worry about duplicates it has already been taken care of). +We want you to modify our "inventory" contract. As you can see the storage is mainly composed of an item inventory where each item is a right combed nested pairs. The contract possess a single entry point AddInventory. This _AddInventory_ function adds each element in the inventory (don't worry about duplicates it has already been taken care of). 1- Complete the implementation of the *update_inventory* lambda function. This function takes a list of item as parameter and must transform each item in a combed pair structure and add this transformed structure in the storage inventory. (When naming your temporary variables, use *acc* for the accumulator name and *i* for the current item) - - diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/course.md index 70c965d..387f081 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/course.md @@ -1,34 +1,38 @@ -# Chapter 23 : Versioning and Lambda (anonymous function) +# Chapter 22 : Lambda (anonymous function) red alert the humans are here battle station surrender dirty humans or die we are the master of this universe and we will easily destroy you hahahaha - ## Versioning -Tezos as a public blockchain expects that contracts should have same behaviour for all users. In theory once a contract is deployed, it should be changed. +Tezos as a public blockchain expects that contracts should have the same behaviour for all users. In theory, once a contract is deployed, it should not be changed. + +We call _antipattern_ when a smart contract has a special role (admin) or may be evolving (changing the rules of the smart contract). -We call *antipattern* when a smart contract have special role (admin) or smart contract that may be evolving (changing rules of the smart contract). +The need to modify the behaviour of a smart contract emerges when for exemple the laws of a country have changed and you need to apply the same changes to the rules in your smart contract. +One could write a new smart contract (V2) and deploy it but it would imply that all existing information stored in the storage of the old smart contract (V1) would be lost. This problem can be solved by : +1- migrating storage information through transactions, +2- or by forcing the new contract to request storage data from the old contract, +3- or by customizing the contract implementation. -The need to modify the behaviour of a smart contract emerges when for exemple the law of the country has changed and you need to apply the same changes to the rules of your smart contract. -One could write a new smart contract (V2) and deploy it but it would imply that all existing information stored in the storage of the old smart contract (V1) would be lost. This problem can be solved by migrating sstorage information through transactions, or by forcing the new contract to request storage data from from the old contract or by customizing the contract implementation. In this chapter we will focus on the third solution. +In this chapter we will focus on the third solution. ### Versioning by re-emission -Versioning can be done by writing a new smart contract and emitting transactions from the old contract (V1) to migrate storage information to the new contract (V2). This can require a lot of transactions and thus a lot of fee spent (resulting in a non negligeable price). This price could be paid by the smart contract that would emit transactions or by each user which would invoke a "migrate" entrypoint of V1 contract to send storage information to the new contract. Transaction emission has been seen in chapter 17 with the _Tezos.transaction_ predefined function. +Versioning can be done by writing a new smart contract and emitting transactions from the old contract (V1) to migrate storage information to the new contract (V2). This may require a lot of transactions and thus a lot of fees (resulting in a significant price). This price could be paid by the smart contract that would emit transactions or by each user which would invoke a "migrate" entrypoint of V1 contract to send storage information to the new contract. Transaction emission has been seen in chapter 17 with the _Tezos.transaction_ predefined function. ### Versioning by contract communication -Versioning can be done by writing a new smart contract that can ask information to the old contract. This pattern need a 2-way communication where the new contract sends a "request" transaction and reacts when the old contract is sending back the requested information. Execution of entrypoint would become asynchronous (due to 2-way transactions). The old contract V1 must be implemented in a way to allow to send transaction to a not yet deployed contract (see chapter Polymorphism). +Versioning can be done by writing a new smart contract that can ask information to the old contract. This pattern needs a 2-way communication where the new contract sends a "request" transaction and reacts when the old contract is sending back the requested information. Execution of entrypoint would become asynchronous (due to the 2-way transactions). The old contract V1 must be implemented in a way that allows to send transaction to a not yet deployed contract (see chapter Polymorphism). ### Versioning by lambda -Versioning can be done by writing a single smart contract that can change its properties and functions (lambdas). This implies to be able to forecast what kind of change might be needed. It also implies a special admin role who is allowed to change the behavior of smart contract. The admin role could be a multi-signature pattern that allow changing behavior of the smart contract if enough user agreed on the proposed change. +Versioning can be done by writing a single smart contract that can change its properties and functions (lambdas). This implies to be able to forecast what kind of change might be needed. It also implies a special admin role who is allowed to change the behavior of the smart contract. The admin role could be a multi-signature pattern that allows changing the behavior of the smart contract if enough user agree on the proposed change. ## Lambda -Changing the behavior of a smart contract can be done by customizing the implementation through lambda's function. +Changing the behavior of a smart contract can be done by customizing the implementation through lambda functions. -So the idea is to define an anonymous function in the storage which is called in entrypoint and write an entrypoint that allow to change implementation of this anonymous function. +The idea is to define an anonymous function in the storage which is called by an entrypoint and writes a new entrypoint that allows to change the implementation of this anonymous function. Let's consider the "starmap" smart contract : @@ -54,7 +58,7 @@ block { } with ((nil : list(operation)), record [name=store.name;func=store.func;systemplanets=modified]) function changeFunc (const f : (coordinates) -> coordinates; const store : storage) : return is -block { skip } +block { skip } with ((nil : list(operation)), record [name=store.name;func=f;systemplanets=store.systemplanets]) function main (const action : parameter; const store : storage) : return is @@ -65,146 +69,101 @@ block { skip } with case action of end ``` - ## Lambda prototype -Defining the prototype of an anonymous function (lambda) follow the syntax : +The prototype of a lambda follows the following syntax : + ``` (,) -> ``` -In "starmap" smart contract the type of *func* is +In the "starmap" smart contract, the type of _func_ is + ``` (coordinates) -> coordinates ``` -⚠️ Note that *func* is transforming coordinates of a planet into coordinates of a planet. + +⚠️ Note that _func_ is transforming coordinates of a planet into coordinates of a planet as well. ## Lambda call -Anonymous functions can be called like other functions. Here in our exemple, the lambda *func* is called in function *addPlanet* to transform planet's coordinates : +Anonymous functions can be called like other functions. Here in our exemple, the lambda _func_ is called in _addPlanet_ to transform the planet's coordinates : + ``` Map.add (input.0, store.func(input.1), store.systemplanets) ``` ## Lambda definition -The implementation of the lambda can be change with the *changeFunc* function which assigns new code to *func*. Here is an exemple of execution of the *ChangeFunc* entrypoint with the simulation ligo command line : +The implementation of the lambda can be changed with the _changeFunc_ function which assigns new code to _func_. Here is an exemple of execution of the _ChangeFunc_ entrypoint with the simulation ligo command line : + ``` ligo dry-run lambda.ligo main 'ChangeFunc(function (const c : coordinates) : coordinates is record[x=c.x*100;y=c.y;z=c.z])' 'record[name="Sol";func=(function (const c : coordinates) : coordinates is record[x=c.x*10;y=c.y;z=c.z]);systemplanets=map "earth" -> record [x=2;y=7;z=1] end]' ``` -⚠️ Notice the new implementation of *func* multiplies 'x' coordinate by 100 (defined as parameter of *ChangeFunc* entrypoint) - -⚠️ Notice the old implementation of *func* multiplies 'x' coordinate by 10 (defined in storage) - +⚠️ Notice the new implementation of _func_ multiplies the 'x' coordinate by 100 (defined as parameter of _ChangeFunc_ entrypoint) +⚠️ Notice the old implementation of _func_ multiplies the 'x' coordinate by 10 (defined in storage) ## Your mission -We have a smart contract that reference planets is the Sol system. Since the beginning of the project , all celestial bodies were considered as planets. -Since 2006, the IAU decided that celetial bodies with a mass under 100 are not considered as a planet but as a dwarf-planet. Hopefully we forecasted this kind of change ! A *DeduceCategoryChange* entrypoint allows us to change the lambda which determines the category of a celestial body. (All we have to do is define the new rule and all registered celestial bodies will be updated). +We have a smart contract that reference planets is the Sol system. Since the beginning of the project, all celestial bodies were considered as planets. +Since 2006, the IAU decided that celetial bodies with a mass under 100 are not considered as a planet but as a dwarf-planet. Hopefully we forecasted this kind of change! A _DeduceCategoryChange_ entrypoint allows us to change the lambda which determines the category of a celestial body. All we have to do is define the new rule and all registered celestial bodies will be updated. -Take a look at the starmap contract : +Take a look at the starmap contract in the editor tabs. -``` -// starmap.ligo -type coordinates is record [ - x : int; - y : int; - z : int -] -type planet_type is PLANET | ASTEROID | STAR -type planet is record [ - position : coordinates; - mass : nat; - category : planet_type -] -type planets is map (string, planet) -type storage is record[ - name : string; - func : (planet) -> planet_type; - celestialbodies : planets -] -type return is (list(operation) * storage) +⚠️ Notice that the function _deduceCategoryChange_ allows to specify a new deduction function _f_ which is assign to the lambda _func_ with : -type parameter is DeduceCategoryChange of (planet) -> planet_type | AddPlanet of (string * planet) | DoNothing - -function addPlanet (const input : (string * planet); const store : storage) : return is -block { - const modified : planets = case Map.find_opt(input.0, store.celestialbodies) of - Some (p) -> (failwith("planet already exist") : planets) - | None -> Map.add (input.0, record[position=input.1.position;mass=input.1.mass;category=store.func(input.1)], store.celestialbodies) - end; -} with ((nil : list(operation)), record [name=store.name;func=store.func;celestialbodies=modified]) - -function deduceCategoryChange (const f : (planet) -> planet_type; const store : storage) : return is -block { - function applyDeduceCatg (const name : string; const p : planet) : planet is - record [position=p.position;mass=p.mass;category=f(p)]; - const modified : planets = Map.map (applyDeduceCatg, store.celestialbodies); -} with ((nil : list(operation)), record [name=store.name;func=f;celestialbodies=modified]) - -function main (const action : parameter; const store : storage) : return is -block { skip } with case action of - AddPlanet (input) -> addPlanet (input,store) - | DeduceCategoryChange (f) -> deduceCategoryChange (f,store) - | DoNothing -> ((nil : list(operation)),store) - end -``` - -⚠️ Notice in the function *deduceCategoryChange* allows to specify a new deduction function *f* which is assign to the lambda *func* with : ``` record [name=store.name;func=f;celestialbodies=modified] ``` -⚠️ Notice in the function *deduceCategoryChange* the sub-function *applyDeduceCatg* apply the new category deduction to a planet (_category=f(p)_). +⚠️ Notice in the function _deduceCategoryChange_ the sub-function _applyDeduceCatg_ which applies the new category deduction to a planet (_category=f(p)_). + ``` function applyDeduceCatg (const name : string; const p : planet) : planet is record [position=p.position;mass=p.mass;category=f(p)]; ``` -⚠️ Notice in the function *deduceCategoryChange* the *applyDeduceCatg* function is used to update all entries of the *celestialbodies* map with : - ``` - Map.map (applyDeduceCatg, store.celestialbodies); - ``` - - - -We want you to update our "starmap" contract in order to take this new rule into account. +⚠️ Notice in the function _deduceCategoryChange_ the _applyDeduceCatg_ function that is used to update all entries of the _celestialbodies_ map with : +``` +Map.map (applyDeduceCatg, store.celestialbodies); +``` -1- Write _dry-run_ command and the associated invocation (entrypoint) for taking the new rule into account. +We want you to update our "starmap" contract in order to take this new rule into account. -2- First rule : if coordinates of a planet is (0,0,0) then celestial body is considered as a STAR. +1- Write the _dry-run_ command and the associated invocation (entrypoint) for taking the new rule into account. -3- Second rule : if mass of a planet is above 100 then celestial body is considered as a PLANET. +2- First rule : if the coordinates of a planet is (0,0,0) then the celestial body is considered as a STAR. -4- Third rule : if mass of a planet is under 100 then celestial body is considered as an ASTEROID. +3- Second rule : if the mass of a planet is above 100 then the celestial body is considered as a PLANET. +4- Third rule : if the mass of a planet is under 100 then the celestial body is considered as an ASTEROID. +expected storage after simulation : -expected storage after simulation : ``` ( LIST_EMPTY() , record[celestialbodies -> MAP_ADD("earth" , - record[category -> PLANET(unit) , - mass -> +1000 , - position -> record[x -> 2 , - y -> 7 , - z -> 1]] , - MAP_ADD("pluto" , - record[category -> ASTEROID(unit) , - mass -> +10 , - position -> record[x -> 200 , - y -> 750 , - z -> 100]] , - MAP_ADD("sun" , - record[category -> STAR(unit) , - mass -> +1000000 , - position -> record[x -> 0 , - y -> 0 , - z -> 0]] , - MAP_EMPTY()))) , + record[category -> PLANET(unit) , + mass -> +1000 , + position -> record[x -> 2 , + y -> 7 , + z -> 1]] , + MAP_ADD("pluto" , + record[category -> ASTEROID(unit) , + mass -> +10 , + position -> record[x -> 200 , + y -> 750 , + z -> 100]] , + MAP_ADD("sun" , + record[category -> STAR(unit) , + mass -> +1000000 , + position -> record[x -> 0 , + y -> 0 , + z -> 0]] , + MAP_EMPTY()))) , func -> "[lambda of type: (lambda\n (pair (pair (or %category (or (unit %aSTEROID) (unit %pLANET)) (unit %sTAR)) (nat %mass))\n (pair %position (pair (int %x) (int %y)) (int %z)))\n (or (or (unit %aSTEROID) (unit %pLANET)) (unit %sTAR))) ]" , name -> "Sol"] ) -``` \ No newline at end of file +``` diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/index.ts b/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/index.ts index cf687e8..0ccec4c 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/index.ts +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterLambda/index.ts @@ -7,5 +7,8 @@ import exercise from "!raw-loader!./exercise.ligo"; /* eslint import/no-webpack-loader-syntax: off */ // @ts-ignore import solution from "!raw-loader!./solution.ligo"; +/* eslint import/no-webpack-loader-syntax: off */ +// @ts-ignore +import starmap from "!raw-loader!./starmap.ligo"; -export const data = { course, exercise, solution, supports: {} }; +export const data = { course, exercise, solution, supports: { starmap } }; diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterMultisig/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterMultisig/course.md index 86a5f00..6f484f3 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterMultisig/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterMultisig/course.md @@ -1,47 +1,45 @@ -# Chapter 25 : Multi-signature pattern +# Chapter 24 : Multi-signature pattern Captain, we should warm up the weapons while we are still in FTL, we don't know what awaits us on the other side. -Before any nuke strike, the admiral and the president of Galatic Union must agree on nuclear usage. We need the approval of both for nuclear weapons usage. +Before any nuke strike, the admiral and the president of Galatic Union must agree on nuclear usage. We need the approval of both for nuclear weapons usage. - -In some case one may want to execute an action only if many users approve this action. This kind of pattern is called _multi-signature_. +In some case one may want to execute an action only if many users approve this action. This kind of pattern is called _multi-signature_. ## Multi-signature When invoking a smart contract, an entrypoint is called and usually an action is executed (triggering a storage modification and/or transactions emmission). -The purpose of a multi-signature pattern is to execute an action when all preconditions has been verified. The action that need to be executed depends on the smart contract logic. +The purpose of a multi-signature pattern is to execute an action when all preconditions has been verified. The action that need to be executed depends on the smart contract logic. The mutli-signature implementation can be done in a single contract with the smart contract logic or in a separated contract like a proxy contract (which emits transactions to the contract containg the logic). ### Rules The multi-signature pattern can be described with this set of rules : -* a user can propose an action -* a user can approve an action (proposed by someone else) -* a user can cancel his approval on an action. -* an action is automatically executed when it has been approved by enough users (a threshold of number of approvals must be defined) -* the smart contract must also handle a list of user allowed to approve an action +- a user can propose an action +- a user can approve an action (proposed by someone else) +- a user can cancel his approval on an action. +- an action is automatically executed when it has been approved by enough users (a threshold of number of approvals must be defined) +- the smart contract must also handle a list of user allowed to approve an action optionnaly -* the smart contract can also handle the number of approval per user and set maximum number of approvals. -* the smart contract can also handle an inner state. Everytime an action is executed the inner state of the multi-signature contract is updated for tracability purpose +- the smart contract can also handle the number of approval per user and set maximum number of approvals. +- the smart contract can also handle an inner state. Everytime an action is executed the inner state of the multi-signature contract is updated for tracability purpose More complex rules can be added these basic ones. - ### Implementation of multisig Let's consider this implementation of the multi-signature pattern. This implementation takes all previously rules into account. -The smart contract *MultisigProxy* accepts a proposed message (parameter typed _string_)), when number of approvals is reached the string is used to generate transaction to an other contract *Counter*. -This smart contract *MultisigProxy* intends to play the role of a proxy pattern for *Counter* contract. -The *Counter* contract (the exemple at https://ide.ligolang.org/p/-hNqhvMFDFdsTULXq4K-KQ) has been deployed at address : KT1CFBbdhRCNAzNkX56v361XZToHCAtjSsVS +The smart contract _MultisigProxy_ accepts a proposed message (parameter typed _string_)), when number of approvals is reached the string is used to generate transaction to an other contract _Counter_. +This smart contract _MultisigProxy_ intends to play the role of a proxy pattern for _Counter_ contract. +The _Counter_ contract (the exemple at https://ide.ligolang.org/p/-hNqhvMFDFdsTULXq4K-KQ) has been deployed at address : KT1CFBbdhRCNAzNkX56v361XZToHCAtjSsVS ``` // Counter contract types type action is | Increment of int -| Decrement of int +| Decrement of int // MultisigProxy storage type type addr_set is set (address) @@ -186,12 +184,13 @@ function main (const param : parameter; const s : storage) : return is ``` -Notice in the *Send* function the number of voters is compared to the threshold. If threshold is reached : +Notice in the _Send_ function the number of voters is compared to the threshold. If threshold is reached : * the message *packed\_msg* is removed from *message\_storage* -* the action is executed and takes the _string_ as parameter -* the inner state *state\_hash* of the contract is updated by creating a hash key of old state + treated message -* the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. + +- the action is executed and takes the _string_ as parameter +* the inner state *state\_hash* of the contract is updated by creating a hash key of old state + treated message +- the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. ``` if Set.cardinal (new_store) >= s.threshold then { @@ -209,11 +208,10 @@ Notice in the *Send* function the number of voters is compared to the threshold. } ``` -Notice in the *Withdraw* function : - -* if a message proposal has no voters the it is removed -* the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. +Notice in the _Withdraw_ function : +- if a message proposal has no voters the it is removed +- the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. ## Your mission @@ -224,5 +222,3 @@ Notice in the *Withdraw* function : 2- Iterate on voters with a _for_ loop (use *addr* as temporary variable for the loop). 3- Modify *reputation* in a single instruction using a _case_ operator to verify if voter has already a reputation account (use *count* as temporary variable for the _Some_). If the voter is not registered yet in the *reputation* register then add him otherwise update its reputation by incrementing by one its actual level !. It is recommanded to use Map.add and Map.update when modifying a _map_. - - diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterOption/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterOption/course.md index ed74d0a..7900fe4 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterOption/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterOption/course.md @@ -13,7 +13,7 @@ function div (const a : nat; const b : nat) : option (nat) is ## Map Access and Option evaluation -Use the postfix [] operator to read a value of the map. When accessing to an element of a map (using [] operator), the returned result is the associated value or _None_ if the given key does not exist. This is the reason why the [] operator returns an _option_ of the expected type (and not just the expected type). +Use the postfix [] operator to read a value of the map. When accessing to an element of a map (using [] operator), the returned result is the associated value or _None_ if the given key does not exist. This is the reason why the [] operator returns an _option_ .of the expected type (and not just the expected type). The keyword _Some_ can be used to create an _option_ variable for a given value. The keyword _None_ can be used to create an _option_ variable with no given value. diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterPolymorphism/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterPolymorphism/course.md index be73e76..e3858a3 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterPolymorphism/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterPolymorphism/course.md @@ -1,32 +1,30 @@ # Chapter 21 : Polymorphism - - -When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using inclusion. +When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using inclusion. But a problem arises when creating a new contract which must communicate way and back (two-way communication) with an already deployed contract. The deployed contract cannot know the signature of a contract not yet created ! -Hopefully a solution exists to this problem of polymorphism, it is *Tezos.get\_entrypoint\_opt* function. +Fortunately a solution exists to this problem of polymorphism: the *Tezos.get\_entrypoint\_opt* function. ## Way and back communication between contracts -Let's consider two contracts _A_ and _B_. Contract _A_ ask some information from contract _B_ and they communicate between each other with transactions. -A sends a request to B, (it means _A_ calls an entry point of _B_, so contract _A_ includes type definition of _B_) -B receives the request, process the request and sends an response back to _A_ (it means _B_ calls an entry point of _A_, , so contract _B_ includes type definition of _A_) -Once they are deployed, we cannot change their includes. +Let's consider two contracts _A_ and _B_. Contract _A_ asks some information from contract _B_ and they communicate between each other with transactions. +A sends a request to B, meanings _A_ calls an entry point of _B_, so contract _A_ includes type definition of _B_. +B receives the request, processesthe request and sends a response back to _A_, meaning _B_ calls an entry point of _A_, so contract _B_ includes type definition of _A_. +Once they are deployed, we cannot change their _includes_. Now let's consider a third smart contract _C_ which will communicate with _B_. (Like _A_) -Since we can't change _B_ (already deployed) , then _C_ must have same definition of _A_ to be able to receive transactions from _B_. +Since we can't change _B_ (already deployed) , then _C_ must have the same definition of _A_ to be able to receive transactions from _B_. -The problem is coming from the fact that _B_ must know the whole definition of _A_ parameter but it actually only needs one entry point used for the transaction. If _C_ implements the same entry point than _A_ (for receiving a message from _B_) then transaction will match the entry point and problems solved ! +The problem is coming from the fact that _B_ must know the whole definition of _A_ parameters but it actually only need one entry point used for the transaction. If _C_ implements the same entry point than _A_ (for receiving a message from _B_) then the transaction will match the entry point and solve our problem! ## Retrieving entry points For this purpose, the predefined function *Tezos.get\_entrypoint\_opt* can be used to retrieve the definition of a single entry point (from the whole variant). -The predefined function *Tezos.get\_entrypoint\_opt* can be used in replacement of the *Tezos.get\_contract\_opt* function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special michelson syntax) and the address of the contract. +The predefined function *Tezos.get\_entrypoint\_opt* can be used in replacement of the *Tezos.get\_contract\_opt* function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special Michelson syntax) and the address of the contract. The predefined function *Tezos.get\_entrypoint\_opt* has the following syntax : @@ -34,37 +32,31 @@ The problem is coming from the fact that _B_ must know the whole definition of _ let : option(contract()) = Tezos.get_entrypoint_opt(, ); ``` -When function *get\_entrypoint\_opt* does not find any contract at a given _address_ or the contract doesn't match the type, then _None_ is returned. - -As for *Tezos.get\_contract\_opt* function, *Tezos.get\_entrypoint\_opt* function returns an _option_ type. +When the function *get\_entrypoint\_opt* does not find any contract at a given _address_ or the contract doesn't match the type, then _None_ is returned. +As for the *Tezos.get\_contract\_opt* function, the *Tezos.get\_entrypoint\_opt* function returns an _option_ .type. ## Entry point naming convention - Entry point name is a double-quoted string with first character is _%_ followed by the name of the entry point (and its first letter must not be capitalized) exemple: for an entry point *FooBar* the corresponding entry point name is *%fooBar*. - -Entry point names are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. - - +An entry point name is a double-quoted string where the first character is _%_ followed by the name of the entry point (and its first letter must not be capitalized), e.g. for an entry point *FooBar* the corresponding entry point name is *%fooBar*. +Entry point names are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. ## Your mission -Consider the following smart contracts : Squadron and Central. - -Central contract acts as an inventory of ships (an entry point *RegisterShip* is provided to register a ship). -Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point *RetrieveShip* is provided to query a ship). -Squadron contract provides an entry point *ModuleRequest* to ask ship information to the central contract. -Squadron contract provides an entry point *ModuleResponse* which is called by the central contract when sending back the expected ship information. +Consider the following smart contracts : Squadron and Central (Exercice). -As you can see, the entry point *RetrieveShip* calls the function *sendTx* which is responsible to send a transaction to a the calling contract. The implementation of Central contract has not been finished. We need you to finish the *sendTx* function ! +The Central contract acts as an inventory of ships (an entry point _RegisterShip_ is provided to register a ship). +The Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point _RetrieveShip_ is provided to query a ship). +The Squadron contract provides an entry point _ModuleRequest_ to ask for ship information to the central contract. +The Squadron contract provides an entry point _ModuleResponse_ which is called by the central contract when sending back the expected ship information. +As you can see, the entry point _RetrieveShip_ calls the function _sendTx_ which is responsible to send a transaction to a the calling contract. The implementation of the Central contract has not been finished. We need you to finish the _sendTx_ function! 1- Try to retrieve the entry point %moduleResponse of the given *callbackAddress* and store the result in a variable called *contractInterfaceOpt* of type _option(contract(actionSquadron))_ 2- Use a _switch_ operator to extract the entry point if it exists (use temporary variable name *ci* in the switch). Otherwise throw an exception with error message "Entrypoint not found in contract Squadron". The extracted entry point must be stored in a variable called *contractInterface*. -3- In order to prepare the ship information that need to be sent back to the Squadron contract. Check the expected type of entry point _moduleResponse_ and prepare a variable *ee* containing the expected ship *e*. - -4- Send a transaction to the retrieved entry point of squadron contract. the transaction must point to the moduleResponse entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an _operation_ type that you can store in a variable *sendbackOperation*. +3- In order to prepare the ship information that need to be sent back to the Squadron contract. Check the expected type of entry point _moduleResponse_ and prepare a variable *ee* containing the expected ship *e*. +4- Send a transaction to the retrieved entry point of the Squadron contract. The transaction must point to the _moduleResponse_ entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an _operation_ type that you can store in a variable *sendbackOperation*. diff --git a/src/frontend/src/pages/Chapters/Pascal/ChapterStrings/course.md b/src/frontend/src/pages/Chapters/Pascal/ChapterStrings/course.md index d9ade59..e775afc 100644 --- a/src/frontend/src/pages/Chapters/Pascal/ChapterStrings/course.md +++ b/src/frontend/src/pages/Chapters/Pascal/ChapterStrings/course.md @@ -40,4 +40,4 @@ const length: nat = String.length(name) // length = 14 ## Your mission -1- Reassign *my\_ship* by modifying the engine attribute (third number) from 0 to 1. Use substrings for the attributes before and after to make sure they are untouched. +1 - Reassign *my\_ship* by modifying the engine attribute (third number) from 0 to 1. Use substrings for the attributes before and after to make sure they are untouched. diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterDeployContract/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterDeployContract/course.md index 178fd45..4790c1e 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterDeployContract/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterDeployContract/course.md @@ -1,13 +1,12 @@ -# Chapter 22 : Deploy & Invoke +# Chapter 23 : Deploy contract Time to go live. - ## Smart contract -A smart contract is code written in Michelson langage (a low-level stack-based turing-complete language). -It contains entrypoints which describe all possible way to interact with a smart contract. -It contains prototype of each entrypoint. What kind of parameters are exepected to execute an entrypoint +A smart contract is code written in Michelson langage (a low-level stack-based turing-complete language). +It contains entrypoints which describe all possible way to interact with a smart contract. +It contains prototype of each entrypoint. What kind of parameters are exepected to execute an entrypoint It contains the description of storage. ### Storage @@ -18,7 +17,7 @@ The description of the storage is done by strongly-typing the data structure. ### Entrypoints -Entrypoints of a smart contract describe how to mutate a storage. +Entrypoints of a smart contract describe how to mutate a storage. Executing an entrypoint takes some parameters and a state of a storage and returns a new state of storage and some operations ![](/images/contract_in_out.png) @@ -29,9 +28,10 @@ Operations are transactions (smart contract invocation) that will be sent to som ## Deploy -A smart contract must be deployed to the blockchain in order to be invoked. When deploying a smart contract ot the blockchain , one must specify the initial state of the storage. +A smart contract must be deployed to the blockchain in order to be invoked. When deploying a smart contract ot the blockchain , one must specify the initial state of the storage. Deployment of a smart contract in Tezos is called "origination". -Here is the syntax of the tezos command line to deploy a smart contract : +Here is the syntax of the tezos command line to deploy a smart contract : + ``` tezos-client originate contract for transferring from \ running \ @@ -45,39 +45,39 @@ tezos-client originate contract for transferring is a Michelson expression. The --init parameter is used to specify initial state of the storage. it specifies the the maximal fee the user is willing to pay for this operation (using the --burn-cap parameter). - ## Invoke Once the smart contract has been deployed on the blockchain (contract-origination operation baked into a block), it is possible to invoke an entrypoint of the smart contract using the command line. -Here is the syntax of the tezos command line to invoke a smart contract : +Here is the syntax of the tezos command line to invoke a smart contract : + ``` tezos-client transfer from to --arg '' --dry-run ``` \ is the quantity of tez being transfered to the contract. name given to the contract - name of the entrypoint and corresponding parameters. exemple 'Increment(5)'. + name of the entrypoint and corresponding parameters. exemple 'Increment(5)'. ⚠️ Notice the --arg parameter specifies an entrypoint call. ⚠️ Notice the --dry-run parameter simulate invocation of the entrypoint. - - ## Ligo compiler In order to produce a smart contract, a tool called transpiler (aka LIGO compiler) is used to transform LIGO code into Michelson code. -Michelson smart contract are stored in a file with .tz extension. +Michelson smart contract are stored in a file with .tz extension. This ligo compiler is also used to transform "Ligo expression" into "Michelson expression" as needed to deploy or invoke a smart contract. ### Compile Here is how to transform ligo code into Michelson code using the ligo compiler in command line. + ``` ligo compile-contract code.religo mainFunc > code.tz ``` + argument is the name of the "main function" in the .ligo file. (see Chapter "Main Function"). ⚠️ Notice the output of the command is the Michelson code. We just redirect the command output into a .tz file. @@ -85,36 +85,37 @@ ligo compile-contract code.religo mainFunc > code.tz ### Initial storage Here is how to transform ligo expression into Michelson expression using the ligo compiler in command line. + ``` ligo compile-storage [options] code.religo mainFunc '' ``` is a ligo expression - ### Invocation parameter Here is how to transform ligo expression into Michelson expression using the ligo compiler in command line. + ``` ligo compile-parameter [options] code.religo mainFunc '' ``` is a ligo expression - -### Simulating +### Simulating Here is how to simulate execution of an entrypoint using the ligo compiler in command line. + ``` ligo dry-run [options] code.religo mainFunc '' '' ``` - state of the storage when simulating execution of the entrypoint - entrypoint of the smart contract that is invoked (parameter *p* of this entrypoint is specified between parantheses). + state of the storage when simulating execution of the entrypoint + entrypoint of the smart contract that is invoked (parameter \_p* of this entrypoint is specified between parantheses). ### Ligo Expression in command line -Let's see some exemple how to transpile a storage ligo expression into a michelson one. +Let's see some exemple how to transpile a storage ligo expression into a Michelsonone. Let's consider this smart contract which associate coordinates to a planet name. @@ -144,9 +145,9 @@ let main = ((action,store) : (parameter,storage)): return => #### Maps -The _Map.literal_ predifined function can be used to initialize a *map* +The _Map.literal_ predifined function can be used to initialize a _map_ -The command line *ligo compile-storage* for transpiling a map containg a tuple. +The command line _ligo compile-storage_ for transpiling a map containg a tuple. ``` ligo compile-storage starmap.religo main 'Map.literal ([("earth", (1,1,1))])' @@ -156,7 +157,7 @@ ligo compile-storage starmap.religo main 'Map.literal ([("earth", (1,1,1))])' Initialization of elements of a tuple is specified between _(_ and _)_ separated by comma _,_. -The command line *ligo compile-storage* for transpiling a map containg a tuple. +The command line _ligo compile-storage_ for transpiling a map containg a tuple. ``` ligo compile-storage starmap.religo main 'Map.literal ([("earth", (1,1,1))])' @@ -171,11 +172,12 @@ This command returns : #### Record Initialization of elements of a record is specified between _{_ and _}_ separated by comma _,_. Each element is a key/value pair seperated by _:_ and follow the syntax : + ``` -{ : , : } +{ : , : } ``` -Let's modify our type *coordinates* to be a record instead of a tuple. +Let's modify our type _coordinates_ to be a record instead of a tuple. ``` // starmap2.religo @@ -194,7 +196,7 @@ let main = ((action,store) : (parameter,storage)): return => }; ``` -The command line *ligo compile-storage* for transpiling a map containg a record tuple. +The command line _ligo compile-storage_ for transpiling a map containg a record tuple. ``` ligo compile-storage starmap2.religo main 'Map.literal ([("earth", {x:1,y:1,z:1})])' @@ -240,9 +242,6 @@ let main = ((action,store) : (parameter,storage)): return => }; ``` - - 1- Write _compile-storage_ command and the ligo expression for initializing the *Sol* system containing planet "earth" with coordinates (2,7,1). -2- Write _dry-run_ command and the ligo expression for adding a planet "mars" with coordinates (4,15,2) in our *Sol* system. Reuse the *Sol* system of step 1. - +2- Write the _dry-run_ command and the ligo expression for adding a planet "mars" with coordinates (4,15,2) in our *Sol* system. Reuse the *Sol* system of step 1. diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterFA12/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterFA12/course.md index e90ff7c..e0ced1b 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterFA12/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterFA12/course.md @@ -1,37 +1,37 @@ -# Chapter 23 : Fungible Asset 1.2 +# Chapter 25 : Financial Application 1.2 Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -A financial asset is a non-physical asset whose value is derived from a contractual claim, such as bank deposits, bonds, and stocks. Financial assets are usually more liquid than other tangible assets, such as commodities or real estate, and may be traded on financial markets. +A Financial Applicationis a non-physical asset whose value is derived from a contractual claim, such as bank deposits, bonds, and stocks. Financial assets are usually more liquid than other tangible assets, such as commodities or real estate, and may be traded on financial markets. -Financial assets are opposed to non-financial assets, property rights which include both tangible property (sometimes also called real assets) such as land, real estate or commodities and intangible assets such as intellectual property, like copyrights, patents, Trademarks etc. +Financial assets are opposed to non-financial assets, property rights which include both tangible property (sometimes also called real assets) such as land, real estate or commodities and intangible assets such as intellectual property, like copyrights, patents, Trademarks etc. ### Fungible and non-fungible -When talking about *token* or *crypto-currency*, it is a numerical asset emitted on a blockchain. +When talking about _token_ or _crypto-currency_, it is a numerical asset emitted on a blockchain. -Fungible means secable +Fungible means secable -Fungible token is a financial asset where account balance represents the value associated to an _address_. This value can be splitted into smaller parts which can be transfered to another account. +Fungible token is a Financial Applicationwhere account balance represents the value associated to an _address_. This value can be splitted into smaller parts which can be transfered to another account. -Non-fungible token (NFT) is a financial asset whose balance cannot be splitted into smaller part. Crypto-kitties is an exemple of non fungible token (on Ethereum blcockchain). For exemple, a video game avatar (such as avatar on world of warcraft) is a character having some skills/attributes (strength, dexterity, ...) one can want to sell its avatar , but cannot sell strength property of its avatar separately. It makes sense to keep tha whole avatar into a unsecable set of attributes. +Non-fungible token (NFT) is a Financial Applicationwhose balance cannot be splitted into smaller part. Crypto-kitties is an exemple of non fungible token (on Ethereum blcockchain). For exemple, a video game avatar (such as avatar on world of warcraft) is a character having some skills/attributes (strength, dexterity, ...) one can want to sell its avatar , but cannot sell strength property of its avatar separately. It makes sense to keep tha whole avatar into a unsecable set of attributes. ### Standard -A standard is a set of rules commonly accepted by the community. -The rules of financial asset describes how to create currencies (and transfer between accounts, etc). +A standard is a set of rules commonly accepted by the community. +The rules of Financial Applicationdescribes how to create currencies (and transfer between accounts, etc). Depending on the usage of the currency, many sets of rules have been commonly accepted : -* Financial asset 1.2 (FA1.2) are rules for fungible token. -* Financia asset 2.0 (FA20) are rules for non fungible token. + +- Financial Application1.2 (FA1.2) are rules for fungible token. +- Financial Application2.0 (FA20) are rules for non fungible token. For exemple, the creation of a crypto-currency is equivalent to creating a contract which supports the FA1.2 standard. All smart contracts supporting the FA12 standard can interact with account and other contracts by transfering coins of our crypto-currency. - -Similarily for ethereum, fungible token rules have been specified in a Ethereum forum blog (Ethereum Request Comment) the 20th answer was describing a good rule set and the ERC20 became the name for this standard (rule set). +Similarily for ethereum, fungible token rules have been specified in a Ethereum forum blog (Ethereum Request Comment) the 20th answer was describing a good rule set and the ERC20 became the name for this standard (rule set). ERC721 is the standard rule set for non-fungible token. ## FA1.2 (Implementation of standard) @@ -39,14 +39,13 @@ ERC721 is the standard rule set for non-fungible token. This Fungible token standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party. Possible actions : - Appove - Sender can specify an amount of token that can be spent by someone else (from his account) - Transfer - Transfer an amount a token from an account to another account (or third-party on-chain smart contract) - GetAllowance - Return the amount that can be spent by someone from sender's account - GetBalance - Returns sender's account balance - GetTotalSupply - Returns the number total of token +Appove - Sender can specify an amount of token that can be spent by someone else (from his account) +Transfer - Transfer an amount a token from an account to another account (or third-party on-chain smart contract) +GetAllowance - Return the amount that can be spent by someone from sender's account +GetBalance - Returns sender's account balance +GetTotalSupply - Returns the number total of token - -Let's see implementation in ReasonLigo of a fungible token (FA1.2) +Let's see implementation in ReasonLigo of a fungible token (FA1.2) ``` type tokens = big_map (address, nat) @@ -92,7 +91,7 @@ type action = | GetTotalSupply ( getTotalSupply ) let transfer = ((p,s) : (transfer, storage)) : (list (operation), storage) => { - let new_allowances = + let new_allowances = if (Tezos.sender == p.address_from) { s.allowances; } else { let authorized_value = switch (Big_map.find_opt ((Tezos.sender,p.address_from), s.allowances)) { @@ -156,7 +155,7 @@ let getTotalSupply = ((p,s) : (getTotalSupply, storage)) : (list (operation), st }; -let main = ((a,s): (action, storage)) => +let main = ((a,s): (action, storage)) => switch a { | Transfer p => transfer ((p,s)) | Approve p => approve ((p,s)) @@ -167,13 +166,11 @@ let main = ((a,s): (action, storage)) => ``` - - ## Your mission -Let's assume the *TezosAcamedyToken* has been deployed. +Let's assume the _TezosAcamedyToken_ has been deployed. -Consider your account is *me* (at address tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ). +Consider your account is _me_ (at address tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ). Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7). 1- We want you to simulate the transfer of 2 TAT (Tezos Academy Token) to *alice*. Write a ligo command line for preparing a simulated storage where you (tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ) possess 1000000 of token and no allowances. @@ -182,10 +179,12 @@ Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7). 3- Write a ligo command line that simulate your invocation of previous *Approval* on storage prepared at step 1. (Don't forget to specify that you are sending this transaction). -4- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *approval*. You can consider that step 2 produced the following michelson expression: +4- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *approval*. You can consider that step 2 produced the following Michelsonexpression: + ``` (Left (Left (Left (Pair "tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7" 2)))) ``` + 5-Write a tezos command line that simulate your invocation. 6- Now that approval has been exeucted on blockchain, 2 TAT can be transfered from your address to *alice*. Write a ligo command line for preparing invocation of a *Transfer* of 2 TAT (Tezos Academy Token) from you to *alice*. @@ -194,9 +193,10 @@ Consider alice account (at address tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7). 8- Write a ligo command line that simulate your invocation of previous *Transfer* on storage prepared at step 7. (Don't forget to specify that you are sending this transaction). -9- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *transfer*. You can consider that step 6 produces the following michelson expression: +9- Now that ligo compiler ensured us that simulation is good, we will try to simulate it with the tezos-client command line in order to know the right amount of gas needed to run execute *transfer*. You can consider that step 6 produces the following Michelsonexpression: + ``` (Right (Pair (Pair "tz1SdT62G8tQp9fdHh4f2m4VtL8aGG6NUcmJ" "tz1NiAGZgRV8F1E3qYFEPgajntzTRDYkU9h7") 2)) ``` -10-Write a tezos command line that simulate your *Transfer* invocation. \ No newline at end of file +10-Write a tezos command line that simulate your *Transfer* invocation. diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterFA20/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterFA20/course.md index 020e61d..a046f40 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterFA20/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterFA20/course.md @@ -1,4 +1,4 @@ -# Chapter 28 : Financial Asset 2.0 +# Chapter 28 : Financial Application 2.0 Captain, Let's create a ship token. @@ -6,37 +6,40 @@ There are multiple dimensions and considerations while implementing a particular token smart contract. Tokens might be fungible or non-fungible. A variety of permission policies can be used to define how many tokens can be transferred, who can initiate a transfer, and who can receive tokens. A token contract can be designed to support a single token type (e.g. ERC-20 or ERC-721) or multiple token types (e.g. ERC-1155) to optimize batch transfers and atomic swaps of the tokens. -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. -In the following chapter on Financial Asset 2.0 , we will focus on *TZIP-12* which stands for the 12th Tezos Improvement Proposal (same as EIP-721 for Ethereum). +In the following chapter on Financial Application 2.0 , we will focus on _TZIP-12_ which stands for the 12th Tezos Improvement Proposal (same as EIP-721 for Ethereum). ## Architecture FA2 proposes to leave it to implementers to handle common considerations such as defining the contract’s token type(s) (e.g. non-fungible vs. fungible vs. semi-fungible), administration and whitelisting, contract upgradability, and supply operations (e.g. mint/burn). -FA2 also leaves to implementers to decide on architecture pattern for handling permissioning. Permission can be implemented -* in the the same contract as the core transfer behavior (i.e. a “monolith”), -* in a transfer hook to another contract, -* in a separate wrapper contract. +FA2 also leaves to implementers to decide on architecture pattern for handling permissioning. Permission can be implemented +- in the the same contract as the core transfer behavior (i.e. a “monolith”), +- in a transfer hook to another contract, +- in a separate wrapper contract. ## Interface and library The FA2 interface formalize a standard way to design tokens and thus describes a list of entry points (that must be implemented) and data structures related to those entry points. A more detailed decription of the interface is broken down in following sections. In addition to the FA2 interface, the FA2 standard provides helper functions to manipulate data structures involved in FA2 interface. The FA2 library contains helper functions for : -* a generic behavior and transfer hook implementation (behavior based on *permissions\_descriptor*), -* converting/manipulating data structures, -* defining hooks between contracts when transfer is emitted, -* defining operators for managing allowance. + +* a generic behavior and transfer hook implementation (behavior based on *permissions\_descriptor*), + +- converting/manipulating data structures, +- defining hooks between contracts when transfer is emitted, +- defining operators for managing allowance. ## Entry points Token contract implementing the FA2 standard MUST have the following entry points. TODO should be renamed fa2_entry_points + ``` -type parameter = +type parameter = | Transfer(transferParameter) | Balance_of(balanceOfParameterMichelson) | Permissions_descriptor(permissionsDescriptorParameter) @@ -46,14 +49,15 @@ type parameter = ### Balance of -The FA2 client (contracts using our token) may need to know the balance of a owner. The FA2 standard specifies an entry point _Balance of_ which use a callback in order to send the balance information to the calling contract. +The FA2 client (contracts using our token) may need to know the balance of a owner. The FA2 standard specifies an entry point _Balance of_ which use a callback in order to send the balance information to the calling contract. + +FA2 token contracts MUST implement the _Balance of_ entry point which get the balance of multiple account/token pairs (because FA2 supports mutiple token). -FA2 token contracts MUST implement the _Balance of_ entry point which get the balance of multiple account/token pairs (because FA2 supports mutiple token). ``` | Balance_of of balance_of_param ``` -It accepts a list of *balance\_of\_requests* and a callback and sends back to a callback contract a list of *balance\_of\_response* records. +It accepts a list of *balance\_of\_requests* and a callback and sends back to a callback contract a list of *balance\_of\_response* records. If one of the specified *token\_ids* is not defined within the FA2 contract, the entry point MUST fail with the error mnemonic "TOKEN_UNDEFINED" (see section Error Handling). @@ -98,8 +102,6 @@ type balanceOfParameterAuxiliary = { type balanceOfParameterMichelson = michelson_pair_right_comb(balanceOfParameterAuxiliary); ``` - - ### Transfer Most basic feature of a token is to provide a way to exchange tokens between owners. The FA2 standard speficies an entry point _Transfer_ for this. @@ -112,26 +114,23 @@ FA2 token contracts MUST implement the _Transfer_ entry point which transfer tok #### Rules -FA2 token contracts MUST implement the transfer logic defined by the following rules : +FA2 token contracts MUST implement the transfer logic defined by the following rules : +1. Every transfer operation MUST be atomic. If the operation fails, all token transfers MUST be reverted, and token balances MUST remain unchanged. -1) Every transfer operation MUST be atomic. If the operation fails, all token transfers MUST be reverted, and token balances MUST remain unchanged. +2. The amount of a token transfer MUST NOT exceed the existing token owner's balance. If the transfer amount for the particular token type and token owner + exceeds the existing balance, the whole transfer operation MUST fail with the error mnemonic "INSUFFICIENT_BALANCE" -2) The amount of a token transfer MUST NOT exceed the existing token owner's balance. If the transfer amount for the particular token type and token owner -exceeds the existing balance, the whole transfer operation MUST fail with the error mnemonic "INSUFFICIENT_BALANCE" - -3) Core transfer behavior MAY be extended. If additional constraints on tokens transfer are required, FA2 token contract implementation MAY invoke additional -permission policies (transfer hook is the recommended design pattern to implement core behavior extension). (See Chapter FA2 - Hook) +3. Core transfer behavior MAY be extended. If additional constraints on tokens transfer are required, FA2 token contract implementation MAY invoke additional + permission policies (transfer hook is the recommended design pattern to implement core behavior extension). (See Chapter FA2 - Hook) If the additional permission hook fails, the whole transfer operation MUST fail with a custom error mnemonic. -4) Core transfer behavior MUST update token balances exactly as the operation parameters specify it. No changes to amount values or additional transfers are allowed. - - +4. Core transfer behavior MUST update token balances exactly as the operation parameters specify it. No changes to amount values or additional transfers are allowed. #### Interface -It transfer tokens from a *from_* account to possibly many destination accounts where each destination transfer describes the type of token, the amount of token, and receiver address. +It transfer tokens from a _from\__ account to possibly many destination accounts where each destination transfer describes the type of token, the amount of token, and receiver address. ``` type tokenId = nat; @@ -168,7 +167,7 @@ type transferParameter = list(transferMichelson); ### Metadata -The metadata section deals with token definition which specifies the name, and asset caracteristics such as the range od ids (for non-fungible tokens) or total supply of assets (for fungible tokens). +The metadata section deals with token definition which specifies the name, and asset caracteristics such as the range od ids (for non-fungible tokens) or total supply of assets (for fungible tokens). FA2 token contracts MUST implement the *token\_metadata* entry point which get the metadata for multiple token types. @@ -177,8 +176,7 @@ The metadata section deals with token definition which specifies the name, and a FA2 token amounts are represented by natural numbers (nat), and their granularity (the smallest amount of tokens which may be minted, burned, or transferred) is always 1. -The *decimals* property is the number of digits to use after the decimal point when displaying the token amounts. If 0, the asset is not divisible. Decimals are used for display purposes only and MUST NOT affect transfer operation. - +The _decimals_ property is the number of digits to use after the decimal point when displaying the token amounts. If 0, the asset is not divisible. Decimals are used for display purposes only and MUST NOT affect transfer operation. #### Interface @@ -222,19 +220,19 @@ let tokenMetadataRegistry = ((tokenMetadataRegistryParameter, storage): (tokenMe } ``` - ### Error Handling This FA2 tandard defines the set of standard errors to make it easier to integrate FA2 contracts with wallets, DApps and other generic software, and enable localization of user-visible error messages. -Each error code is a short abbreviated string mnemonic. An FA2 contract client (like another contract or a wallet) could use on-the-chain or off-the-chain registry to map the error code mnemonic to a user-readable, localized message. +Each error code is a short abbreviated string mnemonic. An FA2 contract client (like another contract or a wallet) could use on-the-chain or off-the-chain registry to map the error code mnemonic to a user-readable, localized message. A particular implementation of the FA2 contract MAY extend the standard set of errors with custom mnemonics for additional constraints. When error occurs, any FA2 contract entry point MUST fail with one of the following types: -* string value which represents an error code mnemonic. -* a Michelson pair, where the first element is a string representing error code mnemonic and the second element is a custom error data. + +- string value which represents an error code mnemonic. +- a Michelson pair, where the first element is a string representing error code mnemonic and the second element is a custom error data. #### Standard error mnemonics: @@ -258,20 +256,16 @@ Error mnemonic - Description "SENDER_HOOK_UNDEFINED" - Sender hook is required by the permission behavior, but is not implemented by a sender contract - - ## Your mission We are working on a fungible token compliant with the FA2 standard. We want you to complete the existing implementation of token. The *Balance\_Of* entry point is not yet implemented , please finish the job ! The function *balanceOfRequestsIterator* is responsible for processing each request and providing a response to each request.As you can see, a request is of type *balanceOfRequestMichelson* - 1- First, we need to retieve information from the request. convert the request *balanceOfRequestMichelson* into a variable named *balanceOfRequest* of type _balanceOfRequest_. You can use the *convert\_from\_right\_comb* function (seen in Chapter Interop) -2- Now that request is readable, call the *getTokenBalance* function in order to retrieve the balance of the specified owner. Store the result in a variable *tokenBalance* of type _tokenBalance_. +2- Now that request is readable, call the *getTokenBalance* function in order to retrieve the balance of the specified owner. Store the result in a variable *tokenBalance* of type _tokenBalance_. 3- Now, we need to build a response for the balanceOfRequest. Declare a variable *balanceOfResponseAuxiliary* ot type _balanceOfResponseAuxiliary_ which contains the request and the retrieved balance *tokenBalance* (defined in the previous line). 4- Convert this response of type _balanceOfResponseAuxiliary_ into a variable *balanceOfResponseMichelson* of type _balanceOfResponseMichelson_. You can use the *convert\_from\_right\_comb* function (seen in Chapter Interop) - diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterFA20Operator/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterFA20Operator/course.md index c0c9129..365fc1c 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterFA20Operator/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterFA20Operator/course.md @@ -1,10 +1,10 @@ -# Chapter 29 : Financial Asset 2.0 - Operators and Permissions +# Chapter 29 : FA 2.0 - Operators and Permissions Captain, why are you trying to change the part yourself? Just write a function on the terminal and send it to a droid. ## Definition -The FA2 standard proposes a *unified token contract interface* that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. +The FA2 standard proposes a _unified token contract interface_ that accommodates all mentioned concerns. It aims to provide significant expressivity to contract developers to create new types of tokens while maintaining a common interface standard for wallet integrators and external developers. In this chapter we will focus on _Operators_ and _Permissions_. @@ -13,7 +13,7 @@ In this chapter we will focus on _Operators_ and _Permissions_. Token contract implementing the FA2 standard MUST have the following entry points. ``` -type parameter = +type parameter = | Transfer(transferParameter) | Balance_of(balanceOfParameterMichelson) | Permissions_descriptor(permissionsDescriptorParameter) @@ -23,6 +23,7 @@ type parameter = ### Operators #### Definition + _Operator_ can be seen as delegate role. _Operator_ is a Tezos address that initiates token transfer operation on behalf of the owner. @@ -49,7 +50,7 @@ type operatorParameter = { } type updateOperatorsAddOrRemove = -// There's an extra '_p' in the constructors below to avoid 'redundant constructor' error +// There's an extra '_p' in the constructors below to avoid 'redundant constructor' error // due to the interop type conversions below | Add_operator_p(operatorParameter) | Remove_operator_p(operatorParameter) @@ -67,19 +68,17 @@ type updateOperatorsParameter = list(updateOperatorsAddOrRemoveMichelson); Notice the *updateOperatorsAddOrRemove* can only Add or Remove an _operator_ (an allowance between an operator address and a token owner address). -Notice entry point *Update\_operators* expects a list of *updateOperatorsAddOrRemoveMichelson*. The fa2 convertor helper provide the *updateOperatorsIterator* function to iterate *updateOperatorsAddOrRemoveMichelson* format. - +Notice entry point *Update\_operators* expects a list of *updateOperatorsAddOrRemoveMichelson*. The fa2 convertor helper provide the *updateOperatorsIterator* function to iterate *updateOperatorsAddOrRemoveMichelson* format. #### FA2 standard operator library Some helpers functions has been implemented in the FA2 library which help manipulating _operator_. This library contains following functions and type alias : - an _operator_ is a relationship between two address (owner address and operator address) function *updateOperators* allows to Add or Remove an operator in the list of operators. -Some helper function can be added such as *canUpdateOperators*, it ensures the given address is owner of an _operator_ +Some helper function can be added such as *canUpdateOperators*, it ensures the given address is owner of an _operator_ ``` let canUpdateOperators = ((tokenOwner, storage): (tokenOwner, storage)): unit => { @@ -89,7 +88,7 @@ let canUpdateOperators = ((tokenOwner, storage): (tokenOwner, storage)): unit => } ``` -Some helper function can be added such as *isOperator*, verifies if a given address is registered as operator for a given owner +Some helper function can be added such as *isOperator*, verifies if a given address is registered as operator for a given owner ``` let isOperator = ((tokenOwner, tokenOperator, storage): (tokenOwner, tokenOperator, storage)): bool => { @@ -102,29 +101,28 @@ let isOperator = ((tokenOwner, tokenOperator, storage): (tokenOwner, tokenOperat ``` // TOTO : verify equivalent ! -function *validate\_operator* validates operators for all transfers in the batch at once, depending on given *operatorTransferPolicy* - +function *validate\_operator* validates operators for all transfers in the batch at once, depending on given *operatorTransferPolicy* ### FA2 Permission Policies and Configuration -Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. +Most token standards specify logic that validates a transfer transaction and can either approve or reject a transfer. Such logic (called _Permission Policy_) could validate who initiates a transfer, the transfer amount, and who can receive tokens. This FA2 standard defines a framework to compose and configure such permission policies from the standard behaviors and configuration APIs. FA2 defines : -* the default core transfer behavior, that MUST always be implemented -* a set of predefined permission policies that are optional +- the default core transfer behavior, that MUST always be implemented +- a set of predefined permission policies that are optional #### Permissions descriptor FA2 specifies an interface *permissions\_descriptor* allowing external contracts to discover an FA2 contract's permission policy and to configure it. *permissions\_descriptor* serves as a modular approach to define consistent and non-self-contradictory policies. -The *permission descriptor* indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. +The _permission descriptor_ indicates which standard permission policies are implemented by the FA2 contract and can be used by off-chain and on-chain tools to discover the properties of the particular FA2 contract implementation. -The FA2 standard defines a special metadata entry point *permission descriptor* containing standard permission policies. +The FA2 standard defines a special metadata entry point _permission descriptor_ containing standard permission policies. ``` | Permissions_descriptor(permissionsDescriptorParameter) @@ -139,10 +137,9 @@ type permissionsDescriptor = { } ``` - #### Operator transfer policy -*operatorTransferPolicy* - defines who can transfer tokens. Tokens can be transferred by the token owner or an operator (some address that is authorized to transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). +*operatorTransferPolicy* - defines who can transfer tokens. Tokens can be transferred by the token owner or an operator (some address that is authorized to transfer tokens on behalf of the token owner). A special case is when neither owner nor operator can transfer tokens (can be used for non-transferable tokens). The FA2 standard defines an entry point to manage operators associated with the token owner address (*update\_operators*). Once an operator is added, it can manage all of its associated owner's tokens. @@ -176,7 +173,6 @@ type customPermissionPolicy = { } ``` - #### Permission Policy Formulae Each concrete implementation of the permission policy can be described by a formula which combines permission behaviors in the following form: @@ -192,7 +188,7 @@ tokens : Operator(Owner_transfer) * Receiver(Owner_no_hook) * Sender(Owner_no_hook) ``` -#### Interface +#### Interface Updating permissions requires specific parameters, and thus follow these type definitions. @@ -213,24 +209,21 @@ type permissionsDescriptorMichelson = michelson_pair_right_comb(permissionsDescr type permissionsDescriptorParameter = contract(permissionsDescriptorMichelson); ``` - - - ## Your mission We are working on a non_fungible/single-asset token. Our NFT "token" is almost ready but to allow a new rule. We need Bob to transfert a token taken from Vera account and send it to Alice account. - * Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" - * Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" - * Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" +- Alice's account address is "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN" +- Bob's account address is "tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU" +- Vera's account address is "tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv" 1- First we want you to prepare the initial state of storage. Modify the _ligo compile-storage_ command for the *token* contract with following recommandations : - * Vera account is owner of the token 1 +- Vera account is owner of the token 1 2- Complete the _ligo dry-run_ command for authorizing Bob to transfer token taken from Vera account, transaction emitted by Vera. (reuse the storage you made on step 1). You can use *Layout.convert\_to\_right\_comb* function to convert your parameters into the format expected by *Update\_operators* entry point. - 3- Complete the _ligo dry-run_ command for simulating the transfer of 1 token from Vera'account to Alice's account, transaction emitted by Bob. The transfered token id is number 1 (token\_id and and amount must be 1). You can use the *Layout.convert\_to\_right\_comb* function to convert your parameters into the format expected by *Transfer* entry point. + You will have to modify the storage to in the state where "Vera account is owner of the token 1" (step 1) and Bob is authorized to transfer token taken from Vera account (step 2). diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterInterop/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterInterop/course.md index 148f089..3c88eae 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterInterop/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterInterop/course.md @@ -2,22 +2,21 @@ We need to hack aliens, decompile their code to understand how their informatic works - -LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types. +LIGO can work together with other smart contract languages on Tezos. However data structures might have different representations in Michelson and not correctly match the standard LIGO types. ## Annotations ### Michelson types and annotations -Michelson types consist of *or*'s and *pair*'s, combined with field annotations. Field annotations add contraints on a Michelson type, for example a _pair_ of *(pair (int %foo) (string %bar))* will only work with the exact equivalence or the same type without the field annotations. +Michelson types consist of _or_'s and _pair_'s, combined with field annotations. Field annotations add contraints on a Michelson type, for example a _pair_ of _(pair (int %foo) (string %bar))_ will only work with the exact equivalence or the same type without the field annotations. -For example, the following _pair_ +For example, the following _pair_ ``` (pair (int %foo) (string %bar)) ``` -will accept these definitions and fail with the ones that does not respect the typing or the order of pair fields: +will accept these definitions and fail with the ones that does not respect the typing or the order of pair fields: ``` (pair (int %foo) (string %bar)) // OK @@ -26,17 +25,16 @@ will accept these definitions and fail with the ones that does not respect the t (pair (string %bar) (int %foo)) // KO ``` - ### Entrypoints and annotations -As seen in chapter Polymorphism, a contract can be called by another contract. Using the predefined function *Tezos.get\_entrypoint\_opt* allows to a calling contract ot point to a specific entry point of the called contract. +As seen in chapter Polymorphism, a contract can be called by another contract. Using the predefined function *Tezos.get\_entrypoint\_opt* allows to a calling contract ot point to a specific entry point of the called contract. Here is an exemple. Let's consider the following "Counter" contract : ``` type storage = int -type parameter = +type parameter = | Left(int) | Right(int) @@ -58,7 +56,7 @@ type parameter = int; type x = Left(int); let main = ((p, s): (parameter, storage)): (list(operation), storage) => { - let contract: contract(x) = + let contract: contract(x) = switch (Tezos.get_entrypoint_opt("%left", ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx": address)): option(contract(x))) { | Some c => c | None => (failwith ("contract does not match"): contract(x)) @@ -69,7 +67,7 @@ let main = ((p, s): (parameter, storage)): (list(operation), storage) => { }; ``` -⚠️ Notice how we directly use the *%left* entrypoint without mentioning the *%right* entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to. +⚠️ Notice how we directly use the _%left_ entrypoint without mentioning the _%right_ entrypoint. This is done with the help of annotations. Without annotations it wouldn't be clear what our int would be referring to. These annotations works for _or_'s or _variant_ types in LIGO. @@ -78,16 +76,17 @@ These annotations works for _or_'s or _variant_ types in LIGO. To interop with existing Michelson code or for compatibility with certain development tooling, LIGO has two special interop types: *michelson\_or* and *michelson\_pair*. These types give the flexibility to model the exact Michelson output, including field annotations. Take for example the following Michelson type that we want to interop with: + ``` -(or +(or (unit %z) - (or %other - (unit %y) - (pair %other - (string %x) - (pair %other - (int %w) - (nat %v))))) + (or %other + (unit %y) + (pair %other + (string %x) + (pair %other + (int %w) + (nat %v))))) ``` To reproduce this type we can use the following LIGO code: @@ -116,9 +115,9 @@ let x: z_or = (M_right (y_1) : z_or) ## Helper functions -Conversions from Ligo types to michelson types requires a precise knowledge of data structures representation. +Conversions from Ligo types to Michelsontypes requires a precise knowledge of data structures representation. -So it becomes even more relevant with nested pairs that there are many possible decomposition of a record in pairs of pairs. +So it becomes even more relevant with nested pairs that there are many possible decomposition of a record in pairs of pairs. The following record structure @@ -130,10 +129,10 @@ type l_record = { } ``` -can be transformed in a left combed data structure +can be transformed in a left combed data structure ``` - (pair %other + (pair %other (pair %other (string %s) (int %w) @@ -142,19 +141,19 @@ can be transformed in a left combed data structure ) ``` - or a right combed data structure +or a right combed data structure - ``` - (pair %other - (string %s) - (pair %other - (int %w) - (nat %v) - ) - ) - ``` +``` + (pair %other + (string %s) + (pair %other + (int %w) + (nat %v) + ) + ) +``` -Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually. +Converting between different LIGO types and data structures can happen in two ways. The first way is to use the provided layout conversion functions, and the second way is to handle the layout conversion manually. ### Converting left combed Michelson data structures @@ -165,7 +164,7 @@ Converting between different LIGO types and data structures can happen in two wa Here's an example of a left combed Michelson data structure using pairs: ``` - (pair %other + (pair %other (pair %other (string %s) (int %w) @@ -184,29 +183,31 @@ type l_record = { } ``` -This snippet of code shows -* how to use *Layout.convert\_from\_left\_comb* to transform a michelson type into a record type. -* how to use *Layout.convert\_to\_left\_comb* to transform a record type into a michelson type. +This snippet of code shows + +* how to use *Layout.convert\_from\_left\_comb* to transform a Michelsontype into a record type. +* how to use *Layout.convert\_to\_left\_comb* to transform a record type into a Michelsontype. ``` -type michelson = michelson_pair_left_comb(l_record); +type Michelson= michelson_pair_left_comb(l_record); -let of_michelson = (f: michelson) : l_record => { +let of_michelson = (f: michelson) : l_record => { let p: l_record = Layout.convert_from_left_comb(f); p }; -let to_michelson = (f: l_record) : michelson => { +let to_michelson = (f: l_record) : Michelson=> { let p = Layout.convert_to_left_comb(f: l_record); p } ``` #### Variant + In the case of a left combed Michelson or data structure, that you want to translate to a variant, you can use the *michelson\_or\_left\_comb* type. ``` -type vari = +type vari = | Foo(int) | Bar(nat) | Other(bool) @@ -217,7 +218,7 @@ type r = michelson_or_left_comb(vari) And then use these types in *Layout.convert\_from\_left\_comb* or *Layout.convert\_to\_left\_comb*, similar to the pairs example above ``` -let of_michelson_or = (f: r) : vari => { +let of_michelson_or = (f: r) : vari => { let p: vari = Layout.convert_from_left_comb(f); p }; @@ -240,7 +241,7 @@ The following code can be used as inspiration: ``` type z_to_v = -| Z +| Z | Y | X | W @@ -269,7 +270,7 @@ let make_concrete_sum = (r: z_to_v) : z_or => } let make_concrete_record = (r: test) : (string, int, string, bool, int) => - (r.z, r.y, r.x, r.w, r.v) + (r.z, r.y, r.x, r.w, r.v) let make_abstract_sum = (z_or: z_or) : z_to_v => switch (z_or) { @@ -292,15 +293,11 @@ let make_abstract_sum = (z_or: z_or) : z_to_v => let make_abstract_record = (z: string, y: int, x: string, w: bool, v: int) : test => { z : z, y, x, w, v } - -``` - +``` ## Your mission -We want you to modify our "inventory" contract. As you can see the storage is mainly composed of an item inventory where each item is a right combed nested pairs. The contract possess a single entry point AddInventory. This *AddInventory* function adds each element in the inventory (don't worry about duplicates it has already been taken care of). +We want you to modify our "inventory" contract. As you can see the storage is mainly composed of an item inventory where each item is a right combed nested pairs. The contract possess a single entry point AddInventory. This _AddInventory_ function adds each element in the inventory (don't worry about duplicates it has already been taken care of). 1- Complete the implementation of the *update_inventory* lambda function. This function takes a list of item as parameter and must transform each item in a combed pair structure and add this transformed structure in the storage inventory. (When naming your temporary variables, use *acc* for the accumulator name and *i* for the current item) - - diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterLambda/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterLambda/course.md index 0a812c9..aabeabb 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterLambda/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterLambda/course.md @@ -1,34 +1,38 @@ -# Chapter 23 : Versioning and Lambda (anonymous function) +# Chapter 22 : Lambda (anonymous function) red alert the humans are here battle station surrender dirty humans or die we are the master of this universe and we will easily destroy you hahahaha - ## Versioning -Tezos as a public blockchain expects that contracts should have same behaviour for all users. In theory once a contract is deployed, it should be changed. +Tezos as a public blockchain expects that contracts should have the same behaviour for all users. In theory, once a contract is deployed, it should not be changed. + +We call _antipattern_ when a smart contract has a special role (admin) or may be evolving (changing the rules of the smart contract). -We call *antipattern* when a smart contract have special role (admin) or smart contract that may be evolving (changing rules of the smart contract). +The need to modify the behaviour of a smart contract emerges when for exemple the laws of a country have changed and you need to apply the same changes to the rules in your smart contract. +One could write a new smart contract (V2) and deploy it but it would imply that all existing information stored in the storage of the old smart contract (V1) would be lost. This problem can be solved by : +1- migrating storage information through transactions, +2- or by forcing the new contract to request storage data from the old contract, +3- or by customizing the contract implementation. -The need to modify the behaviour of a smart contract emerges when for exemple the law of the country has changed and you need to apply the same changes to the rules of your smart contract. -One could write a new smart contract (V2) and deploy it but it would imply that all existing information stored in the storage of the old smart contract (V1) would be lost. This problem can be solved by migrating sstorage information through transactions, or by forcing the new contract to request storage data from from the old contract or by customizing the contract implementation. In this chapter we will focus on the third solution. +In this chapter we will focus on the third solution. ### Versioning by re-emission -Versioning can be done by writing a new smart contract and emitting transactions from the old contract (V1) to migrate storage information to the new contract (V2). This can require a lot of transactions and thus a lot of fee spent (resulting in a non negligeable price). This price could be paid by the smart contract that would emit transactions or by each user which would invoke a "migrate" entrypoint of V1 contract to send storage information to the new contract. Transaction emission has been seen in chapter 17 with the _Tezos.transaction_ predefined function. +Versioning can be done by writing a new smart contract and emitting transactions from the old contract (V1) to migrate storage information to the new contract (V2). This may require a lot of transactions and thus a lot of fees (resulting in a significant price). This price could be paid by the smart contract that would emit transactions or by each user which would invoke a "migrate" entrypoint of V1 contract to send storage information to the new contract. Transaction emission has been seen in chapter 17 with the _Tezos.transaction_ predefined function. ### Versioning by contract communication -Versioning can be done by writing a new smart contract that can ask information to the old contract. This pattern need a 2-way communication where the new contract sends a "request" transaction and reacts when the old contract is sending back the requested information. Execution of entrypoint would become asynchronous (due to 2-way transactions). The old contract V1 must be implemented in a way to allow to send transaction to a not yet deployed contract (see chapter Polymorphism). +Versioning can be done by writing a new smart contract that can ask information to the old contract. This pattern needs a 2-way communication where the new contract sends a "request" transaction and reacts when the old contract is sending back the requested information. Execution of entrypoint would become asynchronous (due to the 2-way transactions). The old contract V1 must be implemented in a way that allows to send transaction to a not yet deployed contract (see chapter Polymorphism). ### Versioning by lambda -Versioning can be done by writing a single smart contract that can change its properties and functions (lambdas). This implies to be able to forecast what kind of change might be needed. It also implies a special admin role who is allowed to change the behavior of smart contract. The admin role could be a multi-signature pattern that allow changing behavior of the smart contract if enough user agreed on the proposed change. +Versioning can be done by writing a single smart contract that can change its properties and functions (lambdas). This implies to be able to forecast what kind of change might be needed. It also implies a special admin role who is allowed to change the behavior of the smart contract. The admin role could be a multi-signature pattern that allows changing the behavior of the smart contract if enough user agree on the proposed change. ## Lambda -Changing the behavior of a smart contract can be done by customizing the implementation through lambda's function. +Changing the behavior of a smart contract can be done by customizing the implementation through lambda functions. -So the idea is to define an anonymous function in the storage which is called in entrypoint and write an entrypoint that allow to change implementation of this anonymous function. +The idea is to define an anonymous function in the storage which is called by an entrypoint and writes a new entrypoint that allows to change the implementation of this anonymous function. Let's consider the "starmap" smart contract : @@ -65,147 +69,101 @@ let main = ((action, store) : (parameter, storage)) : return => } ``` - ## Lambda prototype -Defining the prototype of an anonymous function (lambda) follow the syntax : +The prototype of a lambda follows the following syntax : + ``` ((,) => ) ``` -In "starmap" smart contract the type of *func* is +In the "starmap" smart contract, the type of _func_ is + ``` ((coordinates) => coordinates) ``` -⚠️ Note that *func* is transforming coordinates of a planet into coordinates of a planet. + +⚠️ Note that _func_ is transforming coordinates of a planet into coordinates of a planet as well. ## Lambda call -Anonymous functions can be called like other functions. Here in our exemple, the lambda *func* is called in function *addPlanet* to transform planet's coordinates : +Anonymous functions can be called like other functions. Here in our exemple, the lambda _func_ is called in _addPlanet_ to transform the planet's coordinates : + ``` Map.add (input[0], store.func(input[1]), store.systemplanets) ``` ## Lambda definition -The implementation of the lambda can be change with the *changeFunc* function which assigns new code to *func*. Here is an exemple of execution of the *ChangeFunc* entrypoint with the simulation ligo command line : +The implementation of the lambda can be changed with the _changeFunc_ function which assigns new code to _func_. Here is an exemple of execution of the _ChangeFunc_ entrypoint with the simulation ligo command line : + ``` ligo dry-run lambda.religo main 'ChangeFunc((c : coordinates) : coordinates => {x:c.x*100,y:c.y,z:c.z})' '{name:"Sol",func:((c : coordinates) : coordinates => {x:c.x*10,y:c.y,z:c.z}),systemplanets:Map.literal([("earth", {x:2,y:7,z:1})])}' ``` -⚠️ Notice the new implementation of *func* multiplies 'x' coordinate by 100 (defined as parameter of *ChangeFunc* entrypoint) - -⚠️ Notice the old implementation of *func* multiplies 'x' coordinate by 10 (defined in storage) - +⚠️ Notice the new implementation of _func_ multiplies the 'x' coordinate by 100 (defined as parameter of _ChangeFunc_ entrypoint) +⚠️ Notice the old implementation of _func_ multiplies the 'x' coordinate by 10 (defined in storage) ## Your mission -We have a smart contract that reference planets is the Sol system. Since the beginning of the project , all celestial bodies were considered as planets. -Since 2006, the IAU decided that celetial bodies with a mass under 100 are not considered as a planet but as a dwarf-planet. Hopefully we forecasted this kind of change ! A *DeduceCategoryChange* entrypoint allows us to change the lambda which determines the category of a celestial body. (All we have to do is define the new rule and all registered celestial bodies will be updated). +We have a smart contract that reference planets is the Sol system. Since the beginning of the project, all celestial bodies were considered as planets. +Since 2006, the IAU decided that celetial bodies with a mass under 100 are not considered as a planet but as a dwarf-planet. Hopefully we forecasted this kind of change! A _DeduceCategoryChange_ entrypoint allows us to change the lambda which determines the category of a celestial body. All we have to do is define the new rule and all registered celestial bodies will be updated. -Take a look at the starmap contract : +Take a look at the starmap contract in the editor tabs. -``` -// starmap.ligo -type coordinates = { x : int, y : int, z : int } -type planet_type = PLANET | ASTEROID | STAR -type planet = { - position : coordinates, - mass : nat, - category : planet_type -} -type planets = map (string, planet) -type storage = { - name : string, - func : ((planet) => planet_type), - celestialbodies : planets -} -type return = (list(operation), storage) +⚠️ Notice that the function _deduceCategoryChange_ allows to specify a new deduction function _f_ which is assign to the lambda _func_ with : -type parameter = DeduceCategoryChange ((planet) => planet_type) | AddPlanet ((string, planet)) | DoNothing - -let addPlanet = ((input, store) : ((string, planet), storage)) : return => -{ - let modified : planets = switch (Map.find\_opt(input[0], store.celestialbodies)) { - | Some (p) => (failwith("planet already exist") : planets) - | None => Map.add (input[0], {position:input[1].position,mass:input[1].mass,category:store.func(input[1])}, store.celestialbodies) - }; - (([] : list(operation)), {name:store.name,func:store.func,celestialbodies:modified}); -} - -let deduceCategoryChange = ((f,store) : (((planet) => planet_type), storage)) : return => -{ - let applyDeduceCatg = ((name,p) : (string, planet)) : planet => - {position:p.position,mass:p.mass,category:f(p)}; - let modified : planets = Map.map (applyDeduceCatg, store.celestialbodies); - (([] : list(operation)), {name:store.name,func:f,celestialbodies:modified}) -} - -let main = ((action, store) : (parameter, storage)) : return => - switch (action) { - | AddPlanet (input) => addPlanet ((input,store)) - | DeduceCategoryChange (f) => deduceCategoryChange ((f,store)) - | DoNothing => (([] : list(operation)),store) - } -``` - -⚠️ Notice in the function *deduceCategoryChange* allows to specify a new deduction function *f* which is assign to the lambda *func* with : ``` {name:store.name, func:f, celestialbodies:modified} ``` -⚠️ Notice in the function *deduceCategoryChange* the sub-function *applyDeduceCatg* apply the new category deduction to a planet (_category:f(p)_). +⚠️ Notice in the function _deduceCategoryChange_ the sub-function _applyDeduceCatg_ which applies the new category deduction to a planet (_category:f(p)_). + ``` let applyDeduceCatg = ((name,p) : (string, planet)) : planet => {position:p.position, mass:p.mass, category:f(p)}; ``` -⚠️ Notice in the function *deduceCategoryChange* the *applyDeduceCatg* function is used to update all entries of the *celestialbodies* map with : - ``` -Map.map (applyDeduceCatg, store.celestialbodies); - ``` - - - - - - -We want you to update our "starmap" contract in order to take this new rule into account. +⚠️ Notice in the function _deduceCategoryChange_ the _applyDeduceCatg_ function that is used to update all entries of the _celestialbodies_ map with : +``` +Map.map (applyDeduceCatg, store.celestialbodies); +``` -1- Write _dry-run_ command and the associated invocation (entrypoint) for taking the new rule into account. +We want you to update our "starmap" contract in order to take this new rule into account. -2- First rule : if coordinates of a planet is (0,0,0) then celestial body is considered as a STAR. +1- Write the _dry-run_ command and the associated invocation (entrypoint) for taking the new rule into account. -3- Second rule : if mass of a planet is above 100 then celestial body is considered as a PLANET. +2- First rule : if the coordinates of a planet is (0,0,0) then the celestial body is considered as a STAR. -4- Third rule : if mass of a planet is under 100 then celestial body is considered as an ASTEROID. +3- Second rule : if the mass of a planet is above 100 then the celestial body is considered as a PLANET. +4- Third rule : if the mass of a planet is under 100 then the celestial body is considered as an ASTEROID. +expected storage after simulation : -expected storage after simulation : ``` ( LIST_EMPTY() , record[celestialbodies -> MAP_ADD("earth" , - record[category -> PLANET(unit) , - mass -> +1000 , - position -> record[x -> 2 , - y -> 7 , - z -> 1]] , - MAP_ADD("pluto" , - record[category -> ASTEROID(unit) , - mass -> +10 , - position -> record[x -> 200 , - y -> 750 , - z -> 100]] , - MAP_ADD("sun" , - record[category -> STAR(unit) , - mass -> +1000000 , - position -> record[x -> 0 , - y -> 0 , - z -> 0]] , - MAP_EMPTY()))) , + record[category -> PLANET(unit) , + mass -> +1000 , + position -> record[x -> 2 , + y -> 7 , + z -> 1]] , + MAP_ADD("pluto" , + record[category -> ASTEROID(unit) , + mass -> +10 , + position -> record[x -> 200 , + y -> 750 , + z -> 100]] , + MAP_ADD("sun" , + record[category -> STAR(unit) , + mass -> +1000000 , + position -> record[x -> 0 , + y -> 0 , + z -> 0]] , + MAP_EMPTY()))) , func -> "[lambda of type: (lambda\n (pair (pair (or %category (or (unit %aSTEROID) (unit %pLANET)) (unit %sTAR)) (nat %mass))\n (pair %position (pair (int %x) (int %y)) (int %z)))\n (or (or (unit %aSTEROID) (unit %pLANET)) (unit %sTAR))) ]" , name -> "Sol"] ) -``` \ No newline at end of file +``` diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterLambda/index.ts b/src/frontend/src/pages/Chapters/Reason/ChapterLambda/index.ts index cf687e8..fdbed17 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterLambda/index.ts +++ b/src/frontend/src/pages/Chapters/Reason/ChapterLambda/index.ts @@ -7,5 +7,8 @@ import exercise from "!raw-loader!./exercise.ligo"; /* eslint import/no-webpack-loader-syntax: off */ // @ts-ignore import solution from "!raw-loader!./solution.ligo"; +/* eslint import/no-webpack-loader-syntax: off */ +// @ts-ignore +import starmap from "!raw-loader!./starmap.religo"; -export const data = { course, exercise, solution, supports: {} }; +export const data = { course, exercise, solution, supports: { starmap } }; diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterMultisig/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterMultisig/course.md index 77b96d6..a5d17e5 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterMultisig/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterMultisig/course.md @@ -1,47 +1,45 @@ -# Chapter 25 : Multi-signature pattern +# Chapter 24 : Multi-signature pattern Captain, we should warm up the weapons while we are still in FTL, we don't know what awaits us on the other side. -Before any nuke strike, the admiral and the president of Galatic Union must agree on nuclear usage. We need the approval of both for nuclear weapons usage. +Before any nuke strike, the admiral and the president of Galatic Union must agree on nuclear usage. We need the approval of both for nuclear weapons usage. - -In some case one may want to execute an action only if many users approve this action. This kind of pattern is called _multi-signature_. +In some case one may want to execute an action only if many users approve this action. This kind of pattern is called _multi-signature_. ## Multi-signature When invoking a smart contract, an entrypoint is called and usually an action is executed (triggering a storage modification and/or transactions emmission). -The purpose of a multi-signature pattern is to execute an action when all preconditions has been verified. The action that need to be executed depends on the smart contract logic. +The purpose of a multi-signature pattern is to execute an action when all preconditions has been verified. The action that need to be executed depends on the smart contract logic. The mutli-signature implementation can be done in a single contract with the smart contract logic or in a separated contract like a proxy contract (which emits transactions to the contract containg the logic). ### Rules The multi-signature pattern can be described with this set of rules : -* a user can propose an action -* a user can approve an action (proposed by someone else) -* a user can cancel his approval on an action. -* an action is automatically executed when it has been approved by enough users (a threshold of number of approvals must be defined) -* the smart contract must also handle a list of user allowed to approve an action +- a user can propose an action +- a user can approve an action (proposed by someone else) +- a user can cancel his approval on an action. +- an action is automatically executed when it has been approved by enough users (a threshold of number of approvals must be defined) +- the smart contract must also handle a list of user allowed to approve an action optionnaly -* the smart contract can also handle the number of approval per user and set maximum number of approvals. -* the smart contract can also handle an inner state. Everytime an action is executed the inner state of the multi-signature contract is updated for tracability purpose +- the smart contract can also handle the number of approval per user and set maximum number of approvals. +- the smart contract can also handle an inner state. Everytime an action is executed the inner state of the multi-signature contract is updated for tracability purpose More complex rules can be added these basic ones. - ### Implementation of multisig Let's consider this implementation of the multi-signature pattern. This implementation takes all previously rules into account. -The smart contract *MultisigProxy* accepts a proposed message (parameter typed _string_)), when number of approvals is reached the string is used to generate transaction to an other contract *Counter*. -This smart contract *MultisigProxy* intends to play the role of a proxy pattern for *Counter* contract. -The *Counter* contract (the exemple at https://ide.ligolang.org/p/-hNqhvMFDFdsTULXq4K-KQ) has been deployed at address : KT1CFBbdhRCNAzNkX56v361XZToHCAtjSsVS +The smart contract _MultisigProxy_ accepts a proposed message (parameter typed _string_)), when number of approvals is reached the string is used to generate transaction to an other contract _Counter_. +This smart contract _MultisigProxy_ intends to play the role of a proxy pattern for _Counter_ contract. +The _Counter_ contract (the exemple at https://ide.ligolang.org/p/-hNqhvMFDFdsTULXq4K-KQ) has been deployed at address : KT1CFBbdhRCNAzNkX56v361XZToHCAtjSsVS ``` // Counter contract types type action = | Increment (int) -| Decrement (int) +| Decrement (int) // MulitsigProxy storage type type addr_set = set (address) @@ -69,8 +67,8 @@ type parameter = | Withdraw (message) | Default (unit) -// Function executed when {threshold} approvals has been reached -let execute_action = ((str, s) : (string, storage) ) : list(operation) => +// Function executed when {threshold} approvals has been reached +let execute_action = ((str, s) : (string, storage) ) : list(operation) => { if (String.sub(1n,1n,str) == "3") { let ci_opt : option(contract(action)) = Tezos.get_contract_opt(s.target_contract); @@ -87,15 +85,15 @@ let execute_action = ((str, s) : (string, storage) ) : list(operation) => let send = ((param,s) : (message ,storage)) : return => { // check sender against the authorized addresses - if (Set.mem (Tezos.sender, s.authorized_addresses) == false) { + if (Set.mem (Tezos.sender, s.authorized_addresses) == false) { (failwith("Unauthorized address") : return) - } else { + } else { // check message size against the stored limit let msg : message = param; let packed_msg : bytes = Bytes.pack (msg); - if (Bytes.length (packed_msg) > s.max_message_size) { + if (Bytes.length (packed_msg) > s.max_message_size) { ( failwith ("Message size exceed maximum limit") : return ) - } else { + } else { // compute the new set of addresses associated with the message and update counters let voters_opt : option(addr_set) = Map.find_opt (packed_msg, s.message_store); let (new_store, proposal_counters_updated) : (addr_set, proposal_counters) = switch (voters_opt) { @@ -129,7 +127,7 @@ let send = ((param,s) : (message ,storage)) : return => //let sender_proposal_counter : nat = get_force (Tezos.sender, proposal_counters_updated); if (sender_proposal_counter > s.max_proposal) { (failwith ("Maximum number of proposal reached") : return ) - } else { + } else { // check the threshold if (Set.cardinal (new_store) >= s.threshold) { //remove packed_msg from map s.message_store; @@ -141,16 +139,16 @@ let send = ((param,s) : (message ,storage)) : return => // decrement the counters let decrement = ((addr,ctr): (address, nat)) => if (Set.mem (addr, new_store)) { abs(ctr - 1n) } else { ctr }; let decremented_proposal_counters : proposal_counters = Map.map (decrement, proposal_counters_updated); - + (ret_ops, {...s,proposal_counters:decremented_proposal_counters,state_hash:new_state_hash,message_store:message_store_updated}) - } else { + } else { //update map s.message_store with (packed_msg, new_store); (([]:list(operation)), {...s,proposal_counters:proposal_counters_updated,message_store:Map.update(packed_msg, Some(new_store), s.message_store)}) } }; }; }; -} +} let withdraw = ((param, s) : (message, storage)) : return => { @@ -161,8 +159,8 @@ let withdraw = ((param, s) : (message, storage)) : return => | Some (voters) => { // The message is stored let new_set : addr_set = Set.remove (Tezos.sender, voters); - // Decrement the counter only if the sender was already associated with the message - let proposal_counters_updated : proposal_counters = + // Decrement the counter only if the sender was already associated with the message + let proposal_counters_updated : proposal_counters = if (Set.cardinal (voters) != Set.cardinal (new_set)) { //s.proposal_counters[Tezos.sender] := abs (get_force (Tezos.sender, s.proposal_counters) - 1n) let updated : proposal_counters = switch (Map.find_opt (Tezos.sender, s.proposal_counters)) { @@ -174,20 +172,20 @@ let withdraw = ((param, s) : (message, storage)) : return => else { s.proposal_counters }; // If the message is left without any associated addresses, remove the corresponding message_store field - let message_store_updated : message_store = + let message_store_updated : message_store = if (Set.cardinal (new_set) == 0n) { //remove packed_msg from map s.message_store Map.update(packed_msg, (None : option(addr_set)), s.message_store); } - else { + else { //s.message_store[packed_msg] := new_set Map.update(packed_msg, Some(new_set), s.message_store) }; (([] : list (operation)),{...s,message_store:message_store_updated,proposal_counters:proposal_counters_updated}) } | None => (([] : list (operation)), s) // The message is not stored, ignore. - } -} + } +} let default = ((p,s) : (unit, storage)) : return => (([] : list (operation)), s) @@ -196,24 +194,25 @@ let main = ((param,s) : (parameter, storage)) : return => switch (param) { // Propagate message p if the number of authorized addresses having voted for the same message p equals the threshold. | Send (p) => send ((p, s)) - // Withdraw vote for message p + // Withdraw vote for message p | Withdraw (p) => withdraw ((p, s)) - // Use this action to transfer tez to the contract + // Use this action to transfer tez to the contract | Default (p) => default ((p, s)) } ``` -Notice in the *Send* function the number of voters is compared to the threshold. If threshold is reached : +Notice in the _Send_ function the number of voters is compared to the threshold. If threshold is reached : * the message *packed\_msg* is removed from *message\_storage* -* the action is executed and takes the _string_ as parameter -* the inner state *state\_hash* of the contract is updated by creating a hash key of old state + treated message -* the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. + +- the action is executed and takes the _string_ as parameter +* the inner state *state\_hash* of the contract is updated by creating a hash key of old state + treated message +- the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. ``` if (sender_proposal_counter > s.max_proposal) { (failwith ("Maximum number of proposal reached") : return ) -} else { +} else { // check the threshold if (Set.cardinal (new_store) >= s.threshold) { //remove packed_msg from map s.message_store; @@ -225,20 +224,19 @@ if (sender_proposal_counter > s.max_proposal) { // decrement the counters let decrement = ((addr,ctr): (address, nat)) => if (Set.mem (addr, new_store)) { abs(ctr - 1n) } else { ctr }; let decremented_proposal_counters : proposal_counters = Map.map (decrement, proposal_counters_updated); - + (ret_ops, {...s,proposal_counters:decremented_proposal_counters,state_hash:new_state_hash,message_store:message_store_updated}) - } else { + } else { //update map s.message_store with (packed_msg, new_store); (([]:list(operation)), {...s,proposal_counters:proposal_counters_updated,message_store:Map.update(packed_msg, Some(new_store), s.message_store)}) } }; ``` -Notice in the *Withdraw* function : - -* if a message proposal has no voters the it is removed -* the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. +Notice in the _Withdraw_ function : +- if a message proposal has no voters the it is removed +- the counter (of number of proposals) is updated. This is used to compute the limit of maximum of proposal. ## Your mission @@ -248,4 +246,4 @@ Notice in the *Withdraw* function : 2- Modify *increment* function to modify the reputation of a given *addr* address by granting a point of reputation. (use *count* as temporary variable for the _switch_ operator). If the voter is not registered yet in the *reputation* register then add him otherwise update its reputation by incrementing by one its actual level !. It is recommanded to use Map.add and Map.update when modifying a _map_. - 3- Modify *reputation\_updated* variable (representing the new state of reputations) by iterating on voters with a _Set.fold_ operation and applying *increment* function on reputation. + 3- Modify *reputation\_updated* variable (representing the new state of reputations) by iterating on voters with a _Set.fold_ operation and applying *increment* function on reputation. diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterPolymorphism/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterPolymorphism/course.md index be73e76..329af0e 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterPolymorphism/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterPolymorphism/course.md @@ -1,32 +1,30 @@ # Chapter 21 : Polymorphism - - -When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using inclusion. +When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using inclusion. But a problem arises when creating a new contract which must communicate way and back (two-way communication) with an already deployed contract. The deployed contract cannot know the signature of a contract not yet created ! -Hopefully a solution exists to this problem of polymorphism, it is *Tezos.get\_entrypoint\_opt* function. +Fortunately a solution exists to this problem of polymorphism: the *Tezos.get\_entrypoint\_opt* function. ## Way and back communication between contracts -Let's consider two contracts _A_ and _B_. Contract _A_ ask some information from contract _B_ and they communicate between each other with transactions. -A sends a request to B, (it means _A_ calls an entry point of _B_, so contract _A_ includes type definition of _B_) -B receives the request, process the request and sends an response back to _A_ (it means _B_ calls an entry point of _A_, , so contract _B_ includes type definition of _A_) -Once they are deployed, we cannot change their includes. +Let's consider two contracts _A_ and _B_. Contract _A_ asks some information from contract _B_ and they communicate between each other with transactions. +A sends a request to B, meanings _A_ calls an entry point of _B_, so contract _A_ includes type definition of _B_. +B receives the request, processesthe request and sends a response back to _A_, meaning _B_ calls an entry point of _A_, so contract _B_ includes type definition of _A_. +Once they are deployed, we cannot change their _includes_. Now let's consider a third smart contract _C_ which will communicate with _B_. (Like _A_) -Since we can't change _B_ (already deployed) , then _C_ must have same definition of _A_ to be able to receive transactions from _B_. +Since we can't change _B_ (already deployed) , then _C_ must have the same definition of _A_ to be able to receive transactions from _B_. -The problem is coming from the fact that _B_ must know the whole definition of _A_ parameter but it actually only needs one entry point used for the transaction. If _C_ implements the same entry point than _A_ (for receiving a message from _B_) then transaction will match the entry point and problems solved ! +The problem is coming from the fact that _B_ must know the whole definition of _A_ parameters but it actually only need one entry point used for the transaction. If _C_ implements the same entry point than _A_ (for receiving a message from _B_) then the transaction will match the entry point and solve our problem! ## Retrieving entry points For this purpose, the predefined function *Tezos.get\_entrypoint\_opt* can be used to retrieve the definition of a single entry point (from the whole variant). -The predefined function *Tezos.get\_entrypoint\_opt* can be used in replacement of the *Tezos.get\_contract\_opt* function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special michelson syntax) and the address of the contract. +The predefined function *Tezos.get\_entrypoint\_opt* can be used in replacement of the *Tezos.get\_contract\_opt* function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special Michelsonsyntax) and the address of the contract. The predefined function *Tezos.get\_entrypoint\_opt* has the following syntax : @@ -34,37 +32,31 @@ The problem is coming from the fact that _B_ must know the whole definition of _ let : option(contract()) = Tezos.get_entrypoint_opt(, ); ``` -When function *get\_entrypoint\_opt* does not find any contract at a given _address_ or the contract doesn't match the type, then _None_ is returned. - -As for *Tezos.get\_contract\_opt* function, *Tezos.get\_entrypoint\_opt* function returns an _option_ type. +When the function *get\_entrypoint\_opt* does not find any contract at a given _address_ or the contract doesn't match the type, then _None_ is returned. +As for the *Tezos.get\_contract\_opt* function, the *Tezos.get\_entrypoint\_opt* function returns an _option_ .type. ## Entry point naming convention - Entry point name is a double-quoted string with first character is _%_ followed by the name of the entry point (and its first letter must not be capitalized) exemple: for an entry point *FooBar* the corresponding entry point name is *%fooBar*. - -Entry point names are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. - - +An entry point name is a double-quoted string where the first character is _%_ followed by the name of the entry point (and its first letter must not be capitalized), e.g. for an entry point *FooBar* the corresponding entry point name is *%fooBar*. +Entry point names are written in the form of: _%myEntryPoint_ for the entry point _MyEntryPoint_. ## Your mission -Consider the following smart contracts : Squadron and Central. - -Central contract acts as an inventory of ships (an entry point *RegisterShip* is provided to register a ship). -Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point *RetrieveShip* is provided to query a ship). -Squadron contract provides an entry point *ModuleRequest* to ask ship information to the central contract. -Squadron contract provides an entry point *ModuleResponse* which is called by the central contract when sending back the expected ship information. +Consider the following smart contracts : Squadron and Central (Exercice). -As you can see, the entry point *RetrieveShip* calls the function *sendTx* which is responsible to send a transaction to a the calling contract. The implementation of Central contract has not been finished. We need you to finish the *sendTx* function ! +The Central contract acts as an inventory of ships (an entry point _RegisterShip_ is provided to register a ship). +The Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point _RetrieveShip_ is provided to query a ship). +The Squadron contract provides an entry point _ModuleRequest_ to ask for ship information to the central contract. +The Squadron contract provides an entry point _ModuleResponse_ which is called by the central contract when sending back the expected ship information. +As you can see, the entry point _RetrieveShip_ calls the function _sendTx_ which is responsible to send a transaction to a the calling contract. The implementation of the Central contract has not been finished. We need you to finish the _sendTx_ function! 1- Try to retrieve the entry point %moduleResponse of the given *callbackAddress* and store the result in a variable called *contractInterfaceOpt* of type _option(contract(actionSquadron))_ 2- Use a _switch_ operator to extract the entry point if it exists (use temporary variable name *ci* in the switch). Otherwise throw an exception with error message "Entrypoint not found in contract Squadron". The extracted entry point must be stored in a variable called *contractInterface*. -3- In order to prepare the ship information that need to be sent back to the Squadron contract. Check the expected type of entry point _moduleResponse_ and prepare a variable *ee* containing the expected ship *e*. - -4- Send a transaction to the retrieved entry point of squadron contract. the transaction must point to the moduleResponse entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an _operation_ type that you can store in a variable *sendbackOperation*. +3- In order to prepare the ship information that need to be sent back to the Squadron contract. Check the expected type of entry point _moduleResponse_ and prepare a variable *ee* containing the expected ship *e*. +4- Send a transaction to the retrieved entry point of the Squadron contract. The transaction must point to the _moduleResponse_ entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an _operation_ type that you can store in a variable *sendbackOperation*. diff --git a/src/frontend/src/pages/Chapters/Reason/ChapterStrings/course.md b/src/frontend/src/pages/Chapters/Reason/ChapterStrings/course.md index d369204..bfc7104 100644 --- a/src/frontend/src/pages/Chapters/Reason/ChapterStrings/course.md +++ b/src/frontend/src/pages/Chapters/Reason/ChapterStrings/course.md @@ -40,4 +40,4 @@ let length: nat = String.length(name); // length = 14 ## Your mission -1- Reassign *my\_ship* by modifying the engine attribute (third number) from 0 to 1. Use substrings for the attributes before and after to make sure they are untouched. +1 - Reassign *my\_ship* by modifying the engine attribute (third number) from 0 to 1. Use substrings for the attributes before and after to make sure they are untouched.