Skip to main content

More examples

Function

The example below comes from Tezos Academy and represents a modification of the id of a spaceship.

So the following function takes a string my_ship as input, modifies the third character to 1 and assigns the result to a constant modified_ship.

//function_example.jsligo

type ship_code = string;
let my_ship: ship_code = "020433";
// Modify the code below
const modify_ship = (my_ship: ship_code): ship_code => {
const modified_ship: ship_code = String.sub(0 as nat, 2 as nat, my_ship) + "1" + String.sub(3 as nat, 3 as nat, my_ship);
return modified_ship;
}

You can call the function modify_ship defined above by using the LIGO dev tools:

ligo run evaluate-call function_example.jsligo --entry-point modify_ship '("020433")'
# Outputs: '021433'

Record

The structured type record is here, used to define the coordinates of a planet in the solar system.

type coordinates = {
x: int,
y: int,
z: int
};

let earth_coordinates: coordinates = {
x: 2,
y: 7,
z: 1
};

let modified_earth_coordinates = {...earth_coordinates, z: 5};

A functional update {...earth_coordinates, ... instruction takes a record data structure to be updated and a subset of the fields to update, then modifies the recorded data structure in accordance to the new specified subset of fields.

Main function

In LIGO, the design aims to have one main function called main, which dispatches the control flow according to its parameter. The functions used for those actions are called entrypoints.

In the example below:

  • The functions set_ship_code and go_to are the entrypoints.
  • Set_ship_code and Go_to are the associated actions.
type parameter =
| ["Set_ship_code", string]
| ["Go_to", string];

type storage = {
ship_code: string,
destination: string
};

type return_ = [list<operation>, storage];

const set_ship_code = (input_string: string, store: storage): return_ =>
[(list([]) as list<operation>), {...store, ship_code: input_string}];

const go_to = (input_string: string, store: storage): return_ =>
[(list([]) as list<operation>), {...store, destination: input_string}];

const main = (action: parameter, store: storage): return_ =>
match(action, {
Set_ship_code: (input_string: string) => set_ship_code(input_string, store),
Go_to: (input_string: string) => go_to(input_string, store)
});

Option

The example below comes from Tezos Academy and deals with the modification of the weapon power of a spaceship to illustrate the use of option types.

In the code below, we can see that the weapons variable are defined as a mapping between the name of each weapon and its corresponding input of power. Here, we want to increase the power of the Main Laser but the mapping returns an optional result as it might not be found in the mapping. We define the constant main_laser_power as an optional int by selecting Main Laser from the weapons mapping.

We write a pattern matching the main_laser_power:

  • If it exists, it increases the power of the Main Laser by 1 (use i as a temporary matching variable).
  • If it does not exist in the mapping, it will fail with Weapon not found.
type weapon_power = map<string, int>;

const main = (_p: unit, _store: weapon_power) : [list <operation>, weapon_power] => {
let weapons: weapon_power =
Map.literal(list([
["Main Laser", 5],
["Right Laser", 2],
["Left Laser", 3]
]));

// Type your solution below
let main_laser_power: option<int> = Map.find_opt("Main Laser", weapons);

weapons = match(main_laser_power, {
Some: i => Map.update("Main Laser", Some(i + 1), weapons),
None: () => failwith("Weapon not found")
});

return [
(list([]) as list <operation>),
weapons
];
};

Full example - FundAdvisor

This example illustrates the communication between contracts (with get_entrypoint_opt LIGO function) and lambda patterns allowing you to modify a contract that is already deployed. The example illustrated implementing, deploying and interacting with Tezos smart contracts.

The Fund and its advisor

The indice contract represents a fund value, and the advisor contract gives advice on investing in this fund.

Transaction workflow

The advisor contract can be invoked to request the fund value to the indice contract (via a transaction). The indice contract receives the request (transaction)cand will send back the requested value. When the advisor contract receives the fund value, it can apply the algorithm to check if it is worth investing!


The advisor contract can be invoked to request the fund value to the indice contract (via a transaction). The indice contract receives the request (transaction) and will send back the requested value. When the advisor contract receives the fund value, it can apply the algorithm to check if it is worth investing.

FIGURE 1: FundAdvisor

The resulting advice is stored in the storage (in the result field).

Lambda pattern

The real business logic of the advisor smart contract lies in the lambda function, which is defined in the storage. The storage can be modified so does the business logic (lambda).

Therefore an entrypoint with ChangeAlgorithm is provided to modify the algorithm that computes the worth of the investment.

Lambda pattern Changing the behavior of a smart contract can be done by customizing its implementation through the lambda functions. The idea is to implement the smart contract logic in a lambda function that can be modified after the contract is deployed. Find out more about lambda here.

Requirement

  1. A LIGO compiler, and a code editor installed. If not, go there.
  2. A sandboxed mode ready.

Sandboxed mode

This part will explain to you how to run a 'localhost-only' instance of a Tezos network.

Run a sandboxed node

For instance, if you want to run a local network with two nodes, in the first terminal, the following command will initialize a node listening for peers on port 19731 and listening for RPC on port 18731.

./src/bin_node/octez-sandboxed-node.sh 1 --connections 1

To launch the second node, run the following command in another terminal, and it will listen to port 19739 and 18739:

./src/bin_node/octez-sandboxed-node.sh 9 --connections 1

Use the sandboxed client

Once your node is running, open a new terminal and initialize the sandboxed client data in a temporary directory:

eval `./src/bin_client/octez-init-sandboxed-client.sh 1`

Activate a protocol

octez-activate-alpha

Find out more about sandboxed mode here

First contract - Indice

The indice contract represents a fund value.

Defining storage and entrypoints

Let's create a file indice_types.jsligo to put all the needed type definitions.

  • indiceStorage is an integer type that can have the value of an equity.
  • indiceEntrypoints determines how the contract will be invoked:
    • By increasing the value of the contract storage by using ["Increment",int].
    • By decreasing it with ["Decrement",int].
    • By sending its value to another contract that has called it, using ["SendValue",unit].
  • indiceFullReturn determines the return type of the main function.
// indice_types.jsligo
type indiceStorage = int;

type indiceEntrypoints =
| ["Increment", int]
| ["Decrement", int]
| ["SendValue"];

type return_ = [list <operation>, indiceStorage];

Note that the sendValue entrypoint does not take any parameter.

Defining the main function

Now let's move to the file indice.jsligo that includes the previous file indice_types.jsligo and create the main function indiceMain with writing #include "indice_types.jsligo" at the beginning of the script.

// indice.jsligo
#include "indice_types.jsligo"

const indiceMain = ([action, store] : [indiceEntrypoints, indiceStorage]) : return_ => {
return [(list([]) as list <operation>),store];
};

Let's keep the block empty for now and see if it compiles correctly.

ligo compile contract --entry-point indiceMain indice.jsligo

This should return:

{ parameter (or (or (int %decrement) (int %increment)) (unit %sendValue)) ;
storage int ;
code { UNPAIR ;
IF_LEFT { IF_LEFT { SWAP ; SUB } { ADD } } { DROP 2 ; PUSH int 0 } ;
NIL operation ;
PAIR } }

Now let's implement the three entrypoints:

// indice.jsligo
#include "indice_types.jsligo"

const indiceMain = ([action, store] : [indiceEntrypoints, indiceStorage]) : return_ => {
return match (action, {
Increment: (n: int) => increment ([store, n]),
Decrement: (n: int) => decrement ([store, n]),
SendValue: () => sendValue(store)});
};

Defining the entrypoints

Increment

The increment function takes two parameters:

  • s the initial value of the storage.
  • param of type int, which is the value that will be added to the storage,

This function's return type is return_ and returns:

  • an empty list of operations,
  • a modified storage with a new value of s + param.
//indice.jsligo

const increment = ([s, param] : [indiceStorage, int]) : return_ => [list([]) as list <operation>,s + param];

Decrement

The decrement function takes two parameters:

  • s the initial value of the storage.
  • param of type int, which is the value that will be removed from the storage,

This function's return type is indiceStorage and returns:

  • a modified storage with a new value of s - param.
//indice.jsligo

const decrement = ([s, param] : [indiceStorage, int]) : return_ => [list([]) as list <operation>,s - param];

SendValue

The sendValue function takes two parameters:

  • param of type unit, which means it takes no parameter,
  • s the initial value of the storage.

This function's return type is return_ and it returns:

  • a list of operations containing a transaction,
  • the initial storage that is not modified.

The predefined function Tezos.get_entrypoint_opt can be used to retrieve the definition of a single entry point. %receiveValue is the label of the entrypoint that will be defined in the advisor contract.

When the function get_entrypoint_opt does not find any contract at a given address, or if the contract doesn't match the type, then None is returned.

Note that the Tezos.get_entrypoint_opt function is a two-way communication solution between contract that are already deployed.

//indice.jsligo

const sendValue = (s : indiceStorage) : return_ => {
const c_opt : option<contract<int>> = Tezos.get_entrypoint_opt("%receiveValue", Tezos.get_sender());
const receiver : contract<int> =
match(c_opt,{
Some :(c) => c,
None : () => failwith("sender cannot receive indice value")
});
const op : operation = Tezos.transaction(s, 0 as mutez, receiver);
const txs : list<operation> = list([op]);
return [txs,s];
}

Let's compile again the main function to be sure we made no mistakes.

ligo compile contract --entry-point indiceMain indice.jsligo

Second contract - Advisor

Remember that the advisor contract can be invoked to request the value of the fund from the indice contract (via a transaction). The indice contract receives the request transaction and will send back the requested value. When the advisor contract receives the fund value, it can apply the algorithm to check that it is worth investing.

Defining storage and entrypoints

In a new file called advisor_types.jsligo we define:

  • advisorStorage which is a record type containing three fields:
    • indiceAddress of type address to communicate with the indice contract.
    • algorithm that takes an int as a parameter and returns a bool, depending on the business logic.
    • result that is true if the investor should invest and false otherwise.
  • advisorEntrypoints that determines how the contract will be invoked:
    • By receiving an integer value from another contract's storage with ["ReceiveValue",int].
    • By requesting this value with ["SendValue"].
    • By modifying the algorithm that computes the worth of the investment with ["ChangeAlgorithm",advisorAlgo].
  • advisorFullReturn that determines the return type of the main function.
//advisor_types.jsligo
type advisorAlgo = ((a : int) => bool);

type advisorStorage = {
indiceAddress : address,
algorithm : advisorAlgo,
result : bool,
};

type advisorEntrypoints =
| ["ReceiveValue",int]
| ["RequestValue"]
| ["ChangeAlgorithm", advisorAlgo]
;

type advisorFullReturn = [list<operation>, advisorStorage];

Defining the main function

Let's create another file advisor.jsligo that will include the previous file advisor_types.jsligo and create the main function.

//advisor.jsligo
#include "advisor_types.jsligo"

const main = (ep : advisorEntrypoints, store : advisorStorage) : advisorFullReturn =>
match(ep,{
ReceiveValue : (p) => execute(p, store),
RequestValue : () => request(store),
ChangeAlgorithm: (p) => change(p, store)
});

Defining the entrypoints

ReceiveValue

Symmetrically to the SendValue function defined for the indice contract, we define the RequestValue function, so that the two-way communication can be complete.

The request function takes one parameters:

  • s the initial value of the storage.

This function's return type is advisorFullReturn and returns:

  • a list of operations containing a transaction,
  • the initial storage that is not modified.
const request = (s : advisorStorage) : advisorFullReturn => {
const c_opt : option<contract<unit>> = Tezos.get_entrypoint_opt("%sendValue", s.indiceAddress);
const receiver : contract<unit> =
match(c_opt,{
Some :(c) => c,
None : () => failwith("indice cannot send its value")
});
const op : operation = Tezos.transaction(unit, 0 as mutez, receiver);
const txs : list<operation> = list([op]);
return [txs, s];
};

RequestValue

The execute function takes two parameters:

  • indiceVal of type int, which is the value that will be passed in the algorithm,
  • s the initial value of the storage.

This function's return type is advisorFullReturn and returns:

  • an empty list of operations,
  • a modified storage with a new value for s.result that will be the boolean return of the algorithm.
const execute = (indiceVal : int, s : advisorStorage) : advisorFullReturn => {
return [list([]), {...s, result : s.algorithm(indiceVal)}];
};

ChangeAlgorithm

change function takes two parameters:

  • p of type advisorAlgo which is the algorithm function corresponding to the wanted business logic.
  • s the initial value of the storage.

This function's return type is advisorFullReturn and returns:

  • an empty list of operations
  • a modified storage with a new value for s.algorithm.
const change = (p : advisorAlgo, s : advisorStorage) : advisorFullReturn =>
[ list([]), {...s ,algorithm : p}];

Let's compile the main function.

ligo compile contract advisor.jsligo

Dry-run, compilation and deployment

Indice contract

//indice.jsligo
#include "indice_types.jsligo"

const increment = (param : int, s : indiceStorage) : indiceFullReturn =>
[list([]), s + param];

const decrement = (param : int, s : indiceStorage) : indiceFullReturn =>
[list([]), s - param];

const sendValue = (s : indiceStorage) : indiceFullReturn => {
const c_opt : option<contract<int>> = Tezos.get_entrypoint_opt("%receiveValue", Tezos.get_sender());
const receiver : contract<int> =
match(c_opt,{
Some : (c) => c,
None : () => failwith("sender cannot receive indice value")
});
const op : operation = Tezos.transaction(s, 0 as mutez, receiver);
const txs : list<operation> = list([op]);
return [txs, s];
};

const indiceMain = (ep : indiceEntrypoints, store : indiceStorage) : indiceFullReturn =>
match(ep,{
Increment : (p) => increment(p, store),
Decrement : (p) => decrement(p, store),
SendValue : () => sendValue(store)
});

Simulation

Let's simulate the indice contract with the increment action using 5 as a parameter and initial storage of 0.

ligo run dry-run --entry-point indiceMain indice.jsligo 'Increment(5)' '0'

This should return:

( LIST_EMPTY() , 5 )

As expected, there is an empty list of operations, and the storage has been incremented by 5.

To be sure let's modify the initial value of the storage to 4.

ligo run dry-run --entry-point indiceMain indice.jsligo 'Increment(5)' '4'

This should return:

( LIST_EMPTY() , 9 )

Let's simulate another entrypoint, SendValue:

ligo run dry-run --entry-point indiceMain indice.jsligo 'SendValue()' '3'

This command should return:

failed with: "sender cannot receive indice value"

This is because the contract is not deployed yet. This is the limit of this simulation.

Compilation

Now, let's prepare the parameters and the storage.

ligo compile storage --entry-point indiceMain indice.jsligo '0'
#output: 0
ligo compile parameter --entry-point indiceMain indice.jsligo 'Increment(5)'
#output: (Left (Right 5))
ligo compile parameter --entry-point indiceMain indice.jsligo 'Decrement(5)'
#output: (Left (Left 5))
ligo compile parameter --entry-point indiceMain indice.jsligo 'SendValue()'
#output: (Right Unit)

Reminder: 0, (Left (Right 5)), (Left (Left 5)), (Right Unit) are Michelson expressions that can also be used as parameters in octez-client command lines.

We can now compile the main function and save the output in an indice.tz file.

ligo compile contract --entry-point indiceMain indice.jsligo > indice.tz

This should return:

{ parameter (or (or (int %decrement) (int %increment)) (unit %sendValue)) ;
storage int ;
code { UNPAIR ;
IF_LEFT
{ IF_LEFT { SWAP ; SUB } { ADD } ; NIL operation }
{ DROP ;
SENDER ;
CONTRACT %receiveValue int ;
IF_NONE { PUSH string "sender cannot receive indice value" ; FAILWITH } {} ;
PUSH mutez 0 ;
DUP 3 ;
TRANSFER_TOKENS ;
SWAP ;
NIL operation ;
DIG 2 ;
CONS } ;
PAIR } }

Deployment

Note : look on previous chapter about deployment to run a local sandbox

To display the list of deployed contracts, run the following command:

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
list known contracts

This should return something like this:

bootstrap5: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv
bootstrap4: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
bootstrap3: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
bootstrap2: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
bootstrap1: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx

Note that in the sandboxed mode, some users automatically created so we can easily use them.

It's time to deploy the indice smart contract.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
originate contract indice transferring 1 from bootstrap1 running 'indice.tz' --init '0' --dry-run --burn-cap 0.2

If everything works properly, run the same command without the --dry-run part.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
originate contract indice transferring 1 from bootstrap1 running 'indice.tz' --init '0' --burn-cap 0.2

The transaction is now launched, and you should have the following response:

Node is boostrapped.
...
New contract KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx originated.
Contract memorized as indice.

Let's open another terminal in order to bake this transaction using the following command line:

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
bake for bootstrap1

Tezos baking is the process of signing and appending a block of transactions to the Tezos blockchain.

Now, run the command below and see your indice contract on the list.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
list known contracts
indice: KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx
bootstrap5: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv
bootstrap4: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
bootstrap3: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
bootstrap2: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
bootstrap1: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx

Write down your indice contract address somewhere because you will need it to initialize your advisor contract.

Advisor contract

//advisor.jsligo
#include "advisor_types.jsligo"

const request = (s : advisorStorage) : advisorFullReturn => {
const c_opt : option<contract<unit>> = Tezos.get_entrypoint_opt("%sendValue", s.indiceAddress);
const receiver : contract<unit> =
match(c_opt,{
Some :(c) => c,
None : () => failwith("indice cannot send its value")
});
const op : operation = Tezos.transaction(unit, 0 as mutez, receiver);
const txs : list<operation> = list([op]);
return [txs, s];
};

const execute = (indiceVal : int, s : advisorStorage) : advisorFullReturn => {
return [list([]), {...s, result : s.algorithm(indiceVal)}];
};

const change = (p : advisorAlgo, s : advisorStorage) : advisorFullReturn =>
[ list([]), {...s ,algorithm : p}];

const main = (ep : advisorEntrypoints, store : advisorStorage) : advisorFullReturn =>
match(ep,{
ReceiveValue : (p) => execute(p,store),
RequestValue : () => request(store),
ChangeAlgorithm: (p) => change(p, store)
});

Simulation

Let's simulate the advisor contract with an initial storage of: {indiceAddress:("KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" : address), algorithm:((i : int) => false), result:false}.

Note that we choose a simple algorithm function that is ((i : int) => false) therefore the saved result is false too.

Let's simulate with ReceiveValue(5) as an action.

ligo run dry-run advisor.jsligo 'ReceiveValue(5)' '{indiceAddress:("KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" as address), algorithm:((i : int) => false), result:false}'
#output:
#( LIST_EMPTY() ,
# record[algorithm -> "[lambda of type: (lambda int bool) ]" ,
# indiceAddress -> @"KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" ,
# result -> False(unit)] )

Let's simulate again with RequestValue() as an action.

ligo run dry-run advisor.jsligo 'RequestValue()' '{indiceAddress:("KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" as address), algorithm:((i : int) => false), result:false}'
#output:
#failed with: "indice cannot send its value"

Let's simulate with ChangeAlgorithm((i : int) => (i < 10)?true:false) as action.

ligo run dry-run advisor.jsligo 'ChangeAlgorithm((i : int) => (i < 10)?true:false)' '{indiceAddress:("KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" as address), algorithm:((i : int) => false), result:false}'
#output:
#( LIST_EMPTY() ,
# record[algorithm -> "[lambda of type: (lambda int bool) ]" ,
# indiceAddress -> @"KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" ,
# result -> False(unit)] )

Compilation

Now, let's prepare the parameters and the storage.

ligo compile parameter advisor.jsligo 'ReceiveValue(5)'
#output:
#(Left (Right 5))
ligo compile parameter advisor.jsligo 'RequestValue()'
#output:
#(Right Unit)

Let's say we want to change the algorithm to a more interesting one while still using a simple business logic: if the indice of a stock (indice storage) is under 20, one should invest.

ligo compile parameter advisor.jsligo 'ChangeAlgorithm((i : int) => (i < 10)?true:false)'
#output:
#(Left (Left { PUSH int 10 ;
# SWAP ;
# COMPARE ;
# LT ;
# IF { PUSH bool True } { PUSH bool False } }))

Let's compile the storage with a similar business logic as an initial state.

ligo compile storage advisor.jsligo '{indiceAddress:("KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx" as address), algorithm:((i : int) => (i < 10)?true:false), result:false}'
#output:
#(Pair (Pair { PUSH bool False ;
# PAIR ;
# { UNPAIR ;
# SWAP ;
# PUSH int 10 ;
# SWAP ;
# COMPARE ;
# LT ;
# IF { DROP ; PUSH bool True } {} } }
# "KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx")
# False)

Once everything looks ok, we can compile the main function and save the output in an advisor.tz file.

ligo compile contract advisor.jsligo > advisor.tz

The advisor.tz file should contain:

{ parameter
(or (or (lambda %changeAlgorithm int bool) (int %receiveValue)) (unit %requestValue)) ;
storage
(pair (pair (lambda %algorithm int bool) (address %indiceAddress)) (bool %result)) ;
code { UNPAIR ;
IF_LEFT
{ IF_LEFT
{ DUP 2 ; DIG 2 ; CAR ; DIG 2 ; UPDATE 1 ; UPDATE 1 }
{ DUP 2 ; SWAP ; DIG 2 ; CAR ; CAR ; SWAP ; EXEC ; UPDATE 2 } ;
NIL operation }
{ DROP ;
DUP ;
CAR ;
CDR ;
CONTRACT %sendValue unit ;
IF_NONE { PUSH string "indice cannot send its value" ; FAILWITH } {} ;
PUSH mutez 0 ;
UNIT ;
TRANSFER_TOKENS ;
SWAP ;
NIL operation ;
DIG 2 ;
CONS } ;
PAIR } }

Deployment

As we did for the indice contract, let's run the following command in a --dry-run mode. In the --init section we put the Michelson result of the compile storage that we just ran above.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
originate contract advisor transferring 1 from bootstrap1 running 'advisor.tz' --init '(Pair (Pair { PUSH bool False ; PAIR ; { UNPAIR ; SWAP ; PUSH int 10 ; SWAP ; COMPARE ; LT ; IF { DROP ; PUSH bool True } {} } } "KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx") False)' --dry-run --burn-cap 0.2

If everything works properly, run the same command without the --dry-run part.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
originate contract advisor transferring 1 from bootstrap1 running 'advisor.tz' --init '(Pair (Pair { PUSH bool False ; PAIR ; { UNPAIR ; SWAP ; PUSH int 10 ; SWAP ; COMPARE ; LT ; IF { DROP ; PUSH bool True } {} } } "KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx") False)' --burn-cap 0.2

The transaction is now launched, and you should have the following response:

New contract KT1N95YD263Fpxjr6vUFFvLX6HrTC4Ds4Q11 originated.
Contract memorized as advisor.

Let's open another initialized terminal in order to bake this transaction using the following command line:

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
bake for bootstrap1

Now, run the command below to see your advisor contract on the list.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
list known contracts
advisor: KT1N95YD263Fpxjr6vUFFvLX6HrTC4Ds4Q11
indice: KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx
bootstrap5: tz1ddb9NMYHZi5UzPdzTZMYQQZoMub195zgv
bootstrap4: tz1b7tUupMgCNw2cCLpKTkSD1NZzB5TkP2sv
bootstrap3: tz1faswCTDciRzE4oJ9jn2Vm2dvjeyA9fUzU
bootstrap2: tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN
bootstrap1: tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx

Use

Now that the two contracts are deployed, we are able to use them. You can run the following command to access the storage of the indicated contract.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
get contract storage for advisor

This should return the same result as the compile storage command, because no transaction has been made so far.

Pair (Pair { PUSH bool False ;
PAIR ;
{ UNPAIR ;
SWAP ;
PUSH int 10 ;
SWAP ;
COMPARE ;
LT ;
IF { DROP ; PUSH bool True } {} } }
"KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx")
False

Let's check for the indice contract as well.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
get contract storage for indice
#output: 0

Now, let's make a transaction. Imagine the user bootstrap3 wants to use the advisor smart contract to know if he should invest. For that, he must make a transaction using the action RequestValue() for which the Michelson expression is (Right Unit).

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
transfer 0 from bootstrap3 to advisor --arg '(Right Unit)'

Then in another terminal, let's bake for bootstrap3,

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
bake for bootstrap3

Now let's check if it is worth investment by looking at the storage.

octez-client \
--base-dir /tmp/mockup \
--mode mockup \
get contract storage for advisor
Pair (Pair { PUSH bool False ;
PAIR ;
{ UNPAIR ;
SWAP ;
PUSH int 10 ;
SWAP ;
COMPARE ;
LT ;
IF { DROP ; PUSH bool True } {} } }
"KT1WibdmFb1TWnTTPyTUNpKF36DD5snpcjmx")
True

The result changes from False to True in the result field if it is worth investing. This is the case here because the initial value of the indice storage is still 0 and our algorithm will returns True if this value is under 10.

Now, it is your turn to play with these two smart contracts by increasing the indice storage or changing the advisor algorithm.

Remember: if you are not sure what you are doing, add --dry-run at the end of the command line to see if everything is ok.

To go further, you can find the code with a video explanation of this on our Github.