# Chapter 9

Let's take a little break after a long chapter about pairs and before another long chapter about 3 essential types of Michelson: maps, big maps and sets.

In this chapter, we will learn more about less common operations that you can accomplish in a Michelson smart contract. First of all, we will check how to convert types between each other, which is called "casting". Then, we will have a look at the different possibilities to compare values and we will finish with a talk about bytes and their use cases.

# Casting types

One particularly pesky headache of using a strongly typed language is the conversion of values from one type to another. Obviously, some conversions are forbidden or impossible, it wouldn't make any sense to convert an integer to a list. However, some conversions may seem practical: after all, a value that's an integer, a natural number or a mutez is a numeric value and in some situations, it could be more convenient to change its type.

Imagine a contract with a storage that's a value of type nat, you receive 2 ints, you add them, get a new int and want to save it in the storage. But you can't, because the storage needs a nat value. Or vice-versa, the storage is an int value, you add 2 nats together and want to get an int back. In this situations, Michelson offers two instructions: ABS will return the absolute value of a number (i.e a nat value) and INT will change a nat value into an int.

storage int ;
parameter nat ;
code {
    CAR ;
    INT ;
    NIL operation ;
    PAIR
} ;

RUN %default 9 8 ;
stdout
storage int; parameter nat; code { CAR ; INT ; NIL operation ; PAIR }; RUN: use %default; drop all; push (9, 8); CAR: pop (9, 8); push 9; INT: pop 9; push 9; NIL: push []; PAIR: pop [], 9; push ([], 9);
value type
9
int
storage nat ;
parameter int ;
code {
    CAR ;
    ABS ;
    NIL operation ;
    PAIR
} ;

RUN %default 9 8 ;
stdout
storage nat; parameter int; code { CAR ; ABS ; NIL operation ; PAIR }; RUN: use %default; drop all; push (9, 8); CAR: pop (9, 8); push 9; ABS: pop 9; push 9; NIL: push []; PAIR: pop [], 9; push ([], 9);
value type
9
nat

You may also want to check if the int value you have in the stack is above zero before turning it into a nat value (because ABS will remove the negative sign of any integer). If it's the case, you can use ISNAT. If the int value is equal or greater than zero, the instruction returns (option nat) (Some value). If the int value is less than zero, the instruction returns (option nat) (None):

parameter int ;
storage nat ;
code {
    CAR ;
    ISNAT ;
    IF_SOME
        {
            NIL operation ;
            PAIR
        }
        { FAIL }
} ;

RUN %default 9 8 ;
stdout
storage nat; parameter int; code { CAR ; ISNAT ; { IF_NONE { { UNIT ; FAILWITH } } { NIL operation ; PAIR } } }; RUN: use %default; drop all; push (9, 8); CAR: pop (9, 8); push 9; ISNAT: pop 9; push (9,); IF_NONE: pop (9,); push 9; NIL: push []; PAIR: pop [], 9; push ([], 9);
value type
9
nat

Now if you change 9 for -9, you will see the contract fail as the negative integer triggers the FAIL instruction.

You can also use "hacks" to cast some values. One of the most frequently encountered is a hack based on the subtraction. Let's say you need to cast a timestamp into an int. There is no instruction for that. However, if you subtract a timestamp from another timestamp, you get an int. You can then create a timestamp equal to zero and subtract it from the timestamp you want to cast into an int:

parameter unit ;
storage int ;
code {
    DROP ;
    PUSH timestamp 0 ;
    NOW ;
    SUB ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit 0 ;
stdout
storage int; parameter unit; code { DROP ; PUSH timestamp 0 ; NOW ; SUB ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, 0); DROP: pop (Unit, 0); PUSH: push 0; NOW: push 1594026174; SUB: pop 1594026174, 0; push 1594026174; NIL: push []; PAIR: pop [], 1594026174; push ([], 1594026174);
value type
1594026174
int

Now you have an int whose value is the same as the provided timestamp!

With a little bit of imagination, it is also possible to cast other values, for example a mutez into a nat! This time, we can use EDIV to turn a mutez value into a nat value. When you divide two mutez values together, you get an optional value with a pair that contains a nat (the result) in the left field. When you divide a value by 1, the result will be the same value, so if you divide mutez 22 by mutez 1, the result will be nat 22 as illustrated below:

parameter unit ;
storage nat ;
code {
    DROP ;
    PUSH mutez 1 ;
    PUSH mutez 5566778899 ;
    EDIV ;
    IF_SOME 
        {
            CAR ;
            NIL operation ;
            PAIR ;
        }
        { FAIL }
} ;

RUN %default Unit 0 ;
stdout
storage nat; parameter unit; code { DROP ; PUSH mutez 1 ; PUSH mutez 5566778899 ; EDIV ; { IF_NONE { { UNIT ; FAILWITH } } { CAR ; NIL operation ; PAIR } } }; RUN: use %default; drop all; push (Unit, 0); DROP: pop (Unit, 0); PUSH: push 1; PUSH: push 5566778899; EDIV: pop 5566778899, 1; push ((5566778899, 0),); IF_NONE: pop ((5566778899, 0),); push (5566778899, 0); CAR: pop (5566778899, 0); push 5566778899; NIL: push []; PAIR: pop [], 5566778899; push ([], 5566778899);
value type
5566778899
nat

Finally, it is also possible to cast a contract to its address and an adress to a contract. Observe the following example:

parameter (contract unit) ;
storage address ;
code {
    CAR ;
    ADDRESS ;
    NIL operation ;
    PAIR
} ;

RUN %default "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" "0" ;
stdout
storage address; parameter (contract unit); code { CAR ; ADDRESS ; NIL operation ; PAIR }; RUN: use %default; drop all; push ('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb', '0'); CAR: pop ('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb', '0'); push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; ADDRESS: pop tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; NIL: push []; PAIR: pop [], tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; push ([], 'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb');
value type
"tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb"
address

In this example, we turn a (contract unit) type into an address. It is also possible to do the opposite:

storage (contract unit) ;
parameter address ;
code {
    CAR ;
    CONTRACT unit ;
    IF_SOME
        {
            NIL operation ;
            PAIR
        }
        { FAIL }
} ;

RUN %default "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" "0" ;
stdout
storage (contract unit); parameter address; code { CAR ; CONTRACT unit ; { IF_NONE { { UNIT ; FAILWITH } } { NIL operation ; PAIR } } }; RUN: use %default; drop all; push ('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb', '0'); CAR: pop ('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb', '0'); push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; CONTRACT: pop tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; skip check; push ('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb%default',); IF_NONE: pop ('tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb%default',); push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb%default; NIL: push []; PAIR: pop [], tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb%default; push ([], 'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb%default');
value type
"tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb%default"
contract unit

If you remember from the Chapter 5, this step of turning an address into a contract address of type (contract unit) is also a necessary step of transferring tokens.

# Working with bytes

Although a rather uncommon type, Michelson allows us to manipulate bytes. All the instructions available for strings are also available for bytes (namely COMPARE, CONCAT, SIZE and SLICE) + two extra instructions to pack (value -> bytes) and unpack (bytes -> value) bytes. Most of the times you will have to use bytes is when you will use cryptographic instructions, let's check an example:

storage bytes ;
parameter nat ;
code {
    CAR ;
    PACK ;
    SHA256 ;
    NIL operation ;
    PAIR ;
} ;

RUN %default 12345 0x ;
stdout
storage bytes; parameter nat; code { CAR ; PACK ; SHA256 ; NIL operation ; PAIR }; RUN: use %default; drop all; push (12345, b''); CAR: pop (12345, b''); push 12345; PACK: pop 12345; push 0500b9c001; SHA256: pop 0500b9c001; push cea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285; NIL: push []; PAIR: pop [], cea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285; push ([], b'\xce\xa3VC_\xf4\xee\xe1\x98\x1f.-\xdc\x81\xbe\xe2\x03\xb3\xafv|H\xf9\x7f\xd8Kd\xfav}"\x85');
value type
0xcea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285
bytes

This simple contract takes a value of type nat, converts it to bytes with the PACK instruction, gets its SHA256 hash and saves it in the storage. We can use the result of the PACK instruction to write a contract that does the inverse:

storage nat ;
parameter bytes ;
code {
    CAR ;
    UNPACK nat ;
    IF_SOME
        { NIL operation ; PAIR ; }
        { FAIL }
} ;

RUN %default 0x0500b9c001 0 ;
stdout
storage nat; parameter bytes; code { CAR ; UNPACK nat ; { IF_NONE { { UNIT ; FAILWITH } } { NIL operation ; PAIR } } }; RUN: use %default; drop all; push (b'\x05\x00\xb9\xc0\x01', 0); CAR: pop (b'\x05\x00\xb9\xc0\x01', 0); push 0500b9c001; UNPACK: pop 0500b9c001; push (12345,); IF_NONE: pop (12345,); push 12345; NIL: push []; PAIR: pop [], 12345; push ([], 12345);
value type
12345
nat

At the end of this contract, we get back our nat value of 12345 😃

Now, let's try to use another operation on bytes:

parameter (pair nat nat) ;
storage int ;
code {
    CAR ;
    UNPAIR ;
    PACK ;
    SWAP ;
    PACK ;
    COMPARE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default (Pair 44 33) 0 ;
stdout
storage int; parameter (pair nat nat); code { CAR ; { DUP ; CAR ; DIP { CDR } } ; PACK ; SWAP ; PACK ; COMPARE ; NIL operation ; PAIR }; RUN: use %default; drop all; push ((44, 33), 0); CAR: pop ((44, 33), 0); push (44, 33); DUP: push (44, 33); CAR: pop (44, 33); push 44; DIP: protect 1 item(s); CDR: pop (44, 33); push 33; restore 1 item(s); PACK: pop 44; push 05002c; SWAP: pop 05002c, 33; push 05002c; push 33; PACK: pop 33; push 050021; COMPARE: pop 050021, 05002c; push -1; NIL: push []; PAIR: pop [], -1; push ([], -1);
value type
-1
int

It is also possible to compare two byte values with the COMPARE instruction. Just like when you compare two comparable values, the instruction returns -1 is the first one is less than the second one, 0 if they are both equal and 1 if the first one is greater than the second one. Try to change the values in the pair passed in the parameter to see it working.

We can also extend our previous example and compare the sizes of the bytes:

parameter (pair nat nat) ;
storage int ;
code {
    CAR ;
    UNPAIR ;
    PACK ;
    SIZE ;
    SWAP ;
    PACK ;
    SIZE ;
    COMPARE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default (Pair 44 33) 0 ;
stdout
storage int; parameter (pair nat nat); code { CAR ; { DUP ; CAR ; DIP { CDR } } ; PACK ; SIZE ; SWAP ; PACK ; SIZE ; COMPARE ; NIL operation ; PAIR }; RUN: use %default; drop all; push ((44, 33), 0); CAR: pop ((44, 33), 0); push (44, 33); DUP: push (44, 33); CAR: pop (44, 33); push 44; DIP: protect 1 item(s); CDR: pop (44, 33); push 33; restore 1 item(s); PACK: pop 44; push 05002c; SIZE: pop 05002c; push 3; SWAP: pop 3, 33; push 3; push 33; PACK: pop 33; push 050021; SIZE: pop 050021; push 3; COMPARE: pop 3, 3; push 0; NIL: push []; PAIR: pop [], 0; push ([], 0);
value type
0
int

As you can see from the 0 returned by the COMPARE instruction, the two bytes we created have the same size.

NOTE

not all types can be packed, if you want to know which ones are and are not, you can read the Michelson reference from Nomadic Labs and check in the PA column.

add add