Skip to content

Commit

Permalink
condition do..end syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
bchamagne authored and bchamagne committed May 27, 2024
1 parent 2d267e2 commit 4c7fe13
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 262 deletions.
36 changes: 15 additions & 21 deletions docs/build/smart-contracts/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
179 changes: 82 additions & 97 deletions docs/build/smart-contracts/language/condition.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <type>, as: [
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]
condition triggered_by: oracle do
<expr that returns a boolean>
end

condition triggered_by: transaction, on: <action> do
<expr that returns a boolean>
end

# note the syntax is a bit different for the inherit
# boolean expressions form
condition inherit: [
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]
```

- `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
# ...
)
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]

# legacy: old syntax
condition oracle: [
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]
```
:::

## 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: [
# <value>
# all the transactions of this chain will be of type "contract"
type: "contract",

# <boolean>
# the content is mutable
content: true,

# <expr that returns a value>
code:
if Time.now() > 1677572714 do
"condition inherit: []"
else
previous.code
end,

# <expr that returns a boolean>
# 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.
Expand All @@ -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:

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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:

Expand Down Expand Up @@ -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.
Expand All @@ -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
```

Loading

0 comments on commit 4c7fe13

Please sign in to comment.