From 4c7fe13a7dc624b58985797bee7c05ef15de7622 Mon Sep 17 00:00:00 2001 From: bchamagne Date: Fri, 24 May 2024 16:19:49 +0200 Subject: [PATCH] condition do..end syntax --- docs/build/smart-contracts/examples.md | 36 ++-- .../smart-contracts/language/condition.md | 179 ++++++++---------- .../smart-contracts/language/examples.md | 137 -------------- .../smart-contracts/language/triggers.md | 28 ++- 4 files changed, 118 insertions(+), 262 deletions(-) delete mode 100644 docs/build/smart-contracts/language/examples.md diff --git a/docs/build/smart-contracts/examples.md b/docs/build/smart-contracts/examples.md index 5234e2a27..8913b203d 100644 --- a/docs/build/smart-contracts/examples.md +++ b/docs/build/smart-contracts/examples.md @@ -31,9 +31,10 @@ The Smart Contract's chain must define a token. ```elixir @version 1 -condition triggered_by: transaction, on: buyToken(recipient_address), as: [ - uco_transfers: check_amount(transaction.uco_movements) -] +condition triggered_by: transaction, on: buyToken(recipient_address) do + transfered_amount = Map.get(transaction.uco_movements, contract.address) + transfered_amount != nil && transfered_amount > 0 +end actions triggered_by: transaction, on: buyToken(recipient_address) do transfers = get_transfered_amount() @@ -62,11 +63,6 @@ fun get_transfered_amount() do Map.get(transaction.uco_transfers, contract.address) end -fun check_amount(transfers) do - transfered_amount = Map.get(transfers, contract.address) - transfered_amount != nil && transfered_amount > 0 -end - fun token_address() do Chain.get_genesis_address(contract.address) end @@ -103,20 +99,18 @@ A public function is available to be able to easily query the number of votes in ```elixir @version 1 -condition triggered_by: transaction, on: vote(candidate), as: [ - content: ( - # check incoming vote - valid_candidate? = List.in?(["Miss Scarlett", "Colonel Mustard"], candidate) - - # check incoming voter - valid_voter? = !List.in?( - State.get("voters_genesis_addresses", []), - Chain.get_genesis_address(transaction.address) - ) - - valid_candidate? && valid_voter? +condition triggered_by: transaction, on: vote(candidate) do + # check incoming vote + valid_candidate? = List.in?(["Miss Scarlett", "Colonel Mustard"], candidate) + + # check incoming voter + valid_voter? = !List.in?( + State.get("voters_genesis_addresses", []), + Chain.get_genesis_address(transaction.address) ) -] + + valid_candidate? && valid_voter? +end actions triggered_by: transaction, on: vote(candidate) do scarlett_votes = State.get("Miss Scarlett", 0) diff --git a/docs/build/smart-contracts/language/condition.md b/docs/build/smart-contracts/language/condition.md index 120767a2f..9b65003b1 100644 --- a/docs/build/smart-contracts/language/condition.md +++ b/docs/build/smart-contracts/language/condition.md @@ -5,79 +5,74 @@ sidebar_label: Condition sidebar_position: 2 --- -Conditions are blocks which purpose is to check the validity of a transaction. +Conditions are blocks which purpose is to check the validity of a transaction. -There are 3 types of `condition` block: -- [inherit](#condition-inherit) +There are 3 types of `condition` block: +- [inherit](#condition-inherit) (optional) - [transaction](#condition-transaction) - [oracle](#condition-oracle) -The condition blocks are not code block, they are maps of what we call "boolean expressions". - -In this map, the keys are the transaction property, and the values are expressions that must return a boolean or a value. - -Conditions `transaction` and `oracle` are mandatory if the associated trigger is used. Condition `inherit` is optional. - -Pseudo-code: +There are 2 differents forms. You may use whichever is the more suitable for you. ```elixir -condition triggered_by: , as: [ - : , - : , - : , - : -] +condition triggered_by: oracle do + +end + +condition triggered_by: transaction, on: do + +end -# note the syntax is a bit different for the inherit +# boolean expressions form condition inherit: [ : , : , : , : ] -``` -- `type` is either `transaction` or `oracle`. -- `prop` is a property of the transaction or `origin_family`. See [Action's Appendix 1](/build/smart-contracts/language/actions#appendix-1-the-transaction-map) for the available properties. -- `expr` is a code expression. - -:::tip -If you wish use a complex expression for condition property. You can use multi-lines block enclosed by parenthesis - -```elixir +# legacy: before there were named actions, there was only one possible action condition transaction: [ - content: ( - # Complex logic - # ... - ) + : , + : , + : , + : +] + +# legacy: old syntax +condition oracle: [ + : , + : , + : , + : ] ``` -::: + +## Boolean expressions + +It is a map where the keys are the [transaction's fields](/build/smart-contracts/language/actions#appendix-1-the-transaction-map), and the values are expressions that must return a boolean or a value. +They work for any conditions type, but we suggest to use them with the inherit conditions only. Example: ```elixir condition inherit: [ # + # all the transactions of this chain will be of type "contract" type: "contract", # + # the content is mutable content: true, - # - code: - if Time.now() > 1677572714 do - "condition inherit: []" - else - previous.code - end, - # + # all the transactions must contains at least 1 UCO transfers + # here uco_transfers is automatically given as 1st argument of Map.size() uco_transfers: Map.size() > 0 ] ``` -## Rules +### Rules 1. **All "boolean expressions" must pass for the transaction to be valid.** 1. If the expression returns true, this "boolean expression" passes. @@ -91,14 +86,7 @@ In these blocks, there is also some sugar to automatically add the property as a ## Condition inherit The `condition inherit` purpose is to check the next transaction generated by the smart contract after its code execution. It ensures the next transaction respects specific rules so the smart contract chain cannot be compromised. - -```elixir -condition inherit: [...] -``` - -:::caution -If the map is empty `[]`, it means the transaction chain is closed: no transaction will be accepted anymore. -::: +It is automatically forwarded from transaction to transaction unless it is manually overriden. There are 2 global variables for this condition block: @@ -108,7 +96,9 @@ There are 2 global variables for this condition block: See [Action's Appendix 1](/build/smart-contracts/language/actions#appendix-1-the-transaction-map) for the details of the transaction map. :::caution -A specific rule is applied for inherit condition, if a field is not specified in the conditions, it assumes that it must have the same value as the previous transaction. ie. `code: previous.code == next.code` +A specific rule is applied for inherit condition, if a field is not specified in the conditions, it assumes that it must have the same value as the previous transaction. Example, if code is ommited, this is assumed: `code: previous.code == next.code` + +This means that an empty inherit condition (`condition inherit: []`) means no changes is accepted, resulting in locking the chain. ::: ### Examples @@ -136,7 +126,7 @@ Pass only if chain has been closed (the code part) and there is a 2 UCO transfer ```elixir condition inherit: [ code: "condition inherit: []", - uco_transfers: + uco_transfers: if Time.now() >= 1674564088 do ["00003bafdfb7a8e66b59de5692b79088063853bbd69a7d555faec906e6215e57ff98": 2] else @@ -148,34 +138,32 @@ condition inherit: [ Pass only if the key "index" of the content is greater than the previous one: ```elixir -# Note that we have to embed a block code inside parenthesis as we create complex code -# like create/assign variables with multilines conditions statements -condition inherit: [ - content: ( - json_path = "$.index" - if Json.path_match?(next.content, json_path) do - previous_index = Json.path_extract(previous.content, json_path) - new_index = Json.path_extract(next.content, json_path) - - new_index > previous_index - else - false - end - ) -] +condition inherit do + json_path = "$.index" + if Json.path_match?(next.content, json_path) do + previous_index = Json.path_extract(previous.content, json_path) + new_index = Json.path_extract(next.content, json_path) + + new_index > previous_index + else + false + end +end ``` -## Condition transaction +## Condition triggered by a transaction -The `condition transaction` purpose is to check the transaction that triggered the contract (a transaction with the contract address in the recipients). +Its purpose is to check the transaction that triggered an action on the contract. It must return a boolean. ```elixir -condition triggered_by: transaction, as: [...] -``` +condition triggered_by: transaction do + false +end -:::caution -If the map is empty `[]`, it means any transaction can trigger the contract. -::: +condition triggered_by: transaction, on: refund() do + true +end +``` There are 2 global variables for this condition block: @@ -205,27 +193,31 @@ condition triggered_by: transaction, as: [ Pass only if the candidate is in the list: ```elixir -condition triggered_by: transaction, on: vote(candidate), as: [ - content: List.in?(["Peter", "Sofia", "Claire"], candidate) -] +condition triggered_by: transaction, on: vote(candidate) do + List.in?(["Peter", "Sofia", "Claire"], candidate) +end ``` -:::info Did you notice? -We wrote the expression in the `content` and we did not use this field. We'll soon introduce a new syntax to avoid this "hack". -::: +Pass only if the secret is correct and the lock_time is in the future: + +```elixir +condition triggered_by: transaction, on: withdraw(secret) do + valid_secret = Crypto.hash(secret) == State.get("secret_hash") + valid_time = Time.now() < State.get("lock_time") + valid_secret && valid_time +end +``` -## Condition oracle +## Condition triggered by an oracle -The `condition oracle` purpose is to check the oracle transaction that triggered the contract. +Its purpose is to check the oracle transaction that triggered the contract. It must return a boolean. It is very useful because not every oracle transaction contains the data you need. ```elixir -condition triggered_by: oracle, as: [...] +condition triggered_by: oracle do + true +end ``` -:::caution -If the map is empty `[]`, it means any transaction can trigger the contract. -::: - There are 2 global variables for this condition block: 1. `contract` is the transaction of the current contract. @@ -239,20 +231,13 @@ Pass only if the transaction's content is a JSON string including the UCO price ```elixir condition triggered_by: oracle, as: [ - content: Json.path_match?(transaction.content, "$.uco.usd") + content: Json.path_match?("$.uco.usd") ] ``` -## Legacy syntax - -:::caution Deprecated -The syntax still works, but we'd like everyone to use the new syntax. -::: - -The condition `oracle` & `transaction` used to have the same syntax as `inherit`. - +Pass only if the transaction's content is a JSON string including the Vancouver's weather and if it's raining there. ```elixir -condition transaction: [...] -condition oracle: [...] +condition triggered_by: oracle do + Json.path_match?(transaction.content, "$.canada.vancouver.raining?") +end ``` - diff --git a/docs/build/smart-contracts/language/examples.md b/docs/build/smart-contracts/language/examples.md deleted file mode 100644 index e34569544..000000000 --- a/docs/build/smart-contracts/language/examples.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -id: examples -title: Smart Contracts - Examples -sidebar_label: Examples -sidebar_position: 5 ---- - -## Hello World - -This simple contract will be triggered once, at the specified unix timestamp. -It will create a new transaction with a content "Hello world!". - -```elixir -@version 1 - -actions triggered_by: datetime, at: 1689857160 do - Contract.set_content("Hello world!") -end -``` - -## ICO (Initial Coin Offering) - -This contract the crowdsale of an ICO. - -Users can send UCOs to this contract and will receive 10000 times the amount as token from this contract. - -It is possible for users to define a different receive address. - -The Smart Contract's chain must define a token. - -```elixir -@version 1 - -condition triggered_by: transaction, on: buyToken(recipient_address), as: [ - uco_transfers: check_amount(transaction.uco_movements) -] - -actions triggered_by: transaction, on: buyToken(recipient_address) do - transfers = get_transfered_amount() - - # Get the amount of UCO sent to this contract - amount_send = number_of_uco_sent() - - # Convert UCO to the number of tokens to credit. Each UCO worth 10000 token - token_to_credit = number_of_tokens(amount_send) - - Contract.set_type("transfer") - - # Users can specify to send the token in a different address - Contract.add_token_transfer(to: recipient_address, token_address: token_address(), amount: token_to_credit) -end - -fun number_of_tokens(uco_amount) do - uco_amount * 10000 -end - -fun number_of_uco_sent() do - Map.get(transaction.uco_movements, contract.address) -end - -fun get_transfered_amount() do - Map.get(transaction.uco_transfers, contract.address) -end - -fun check_amount(transfers) do - transfered_amount = Map.get(transfers, contract.address) - transfered_amount != nil && transfered_amount > 0 -end - -fun token_address() do - Chain.get_genesis_address(contract.address) -end - -``` - -## Recurring ICO - -This contract will automatically call the ICO contract with 2 UCOs every hours. -The tokens will be transfered in a different chain. - -```elixir -@version 1 - -actions triggered_by: interval, at: "0 * * * *" do - - ico_contract = 0x0000993F3BE0CE40541E47735AA083854ECAC7785B39435C90D8456C777B9E9D81F1 - - Contract.set_type("transfer") - Contract.add_recipient(address: ico_contract, action: "buyToken", args: [0x0000A3A066DD64FBD51AE384F2383684B3803BC72012BFEAA9CD1C93AB7C60F584DC]) - Contract.add_uco_transfer(to: ico_contract, amount: 2) -end -``` - -## Vote system - -This contract counts the number of votes for list of candidates (Mr.X and Mrs.Y). - -For each vote request, it increments the number of votes for the given candidate, -and create a new transaction out of it, with the new state in the `content` field of the transaction as JSON document. - -:::info -A public function is available to be able to easily query the number of votes in the system -::: - -```elixir -@version 1 -condition triggered_by: transaction, on: vote(candidate), as: [ - content: List.in?(["X", "Y"], candidate) -] - -actions triggered_by: transaction, on: vote(candidate) do - votes = [] - - if contract.content == "" do - votes = add_vote([x: 0, y: 0], candidate) - else - votes = get_votes() - votes = add_vote(votes, candidate) - end - - Contract.set_content(Json.to_string(votes)) -end - -fun add_vote(votes, candidate) do - if candidate == "X" do - Map.set(votes, "x", votes.x + 1) - else - Map.set(votes, "y", votes.y + 1) - end -end - -export fun get_votes() do - count_x = Json.path_extract(contract.content, "$.x") - count_y = Json.path_extract(contract.content, "$.y") - [x: count_x, y: count_y] -end -``` diff --git a/docs/build/smart-contracts/language/triggers.md b/docs/build/smart-contracts/language/triggers.md index 2320303fe..609be1490 100644 --- a/docs/build/smart-contracts/language/triggers.md +++ b/docs/build/smart-contracts/language/triggers.md @@ -11,7 +11,7 @@ To trigger a SC execution, the incoming transaction must add the SC address in t :::tip -This allows to activate a SC without sending funds to it, or to send funds to it without activating the code execution. +This allows to activate a SC without sending funds to it, or to send funds to it without activating the code execution. Because the `recipients` field is a list, you can trigger multiple contracts in the same transaction :wink: ::: @@ -23,9 +23,9 @@ In this scenario: ```elixir @version 1 -condition triggered_by: transaction, as: [ +condition triggered_by: transaction do ... -] +end actions triggered_by: transaction do ... @@ -44,9 +44,9 @@ In this scenario: ```elixir @version 1 -condition triggered_by: transaction, on: vote_for_class_president(firstname, lastname), as: [ +condition triggered_by: transaction, on: vote_for_class_president(firstname, lastname) do ... -] +end actions triggered_by: transaction, on: vote_for_class_president(firstname, lastname) do ... @@ -104,9 +104,9 @@ In this scenario: ```elixir @version 1 -condition triggered_by: oracle, as: [ +condition triggered_by: oracle do # use transaction.content to make sure the oracle has the data you require -] +end actions triggered_by: oracle do # do something with transaction.content end @@ -179,3 +179,17 @@ actions triggered_by: datetime, at: 1693605600 do ... end ``` + +DON'T: + +```elixir +@version 1 + +actions triggered_by: transaction, on: lock(x, y) do + ... +end + +actions triggered_by: transaction, on: lock(x, y) do + ... +end +```