# 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: updated parameter: updated code: updated BEGIN %default / _ => (9 * 8) CAR / (9 * 8) => 9 INT / 9 => 9 NIL / _ => [] PAIR / [] : 9 => ([] * 9) END %default / ([] * 9) => _

Operations

Storage

type value
int
9
storage nat ;
parameter int ;
code {
    CAR ;
    ABS ;
    NIL operation ;
    PAIR
} ;

RUN %default 9 8 ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (9 * 8) CAR / (9 * 8) => 9 ABS / 9 => 9 NIL / _ => [] PAIR / [] : 9 => ([] * 9) END %default / ([] * 9) => _

Operations

Storage

type value
nat
9

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
parameter: updated storage: updated code: updated BEGIN %default / _ => (9 * 8) CAR / (9 * 8) => 9 ISNAT / 9 => 9? IF_NONE / 9? => 9 NIL / _ => [] PAIR / [] : 9 => ([] * 9) END %default / ([] * 9) => _

Operations

Storage

type value
nat
9

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
parameter: updated storage: updated code: updated BEGIN %default / _ => (Unit * 0) DROP / (Unit * 0) => _ PUSH / _ => 0 NOW / _ => 0 SUB / 0 : 0 => 0 NIL / _ => [] PAIR / [] : 0 => ([] * 0) END %default / ([] * 0) => _

Operations

Storage

type value
int
0

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
parameter: updated storage: updated code: updated BEGIN %default / _ => (Unit * 0) DROP / (Unit * 0) => _ PUSH / _ => 0.000001 PUSH / _ => 5566.778899 EDIV / 5566.778899 : 0.000001 => (5566778899 * 0)? IF_NONE / (5566778899 * 0)? => (5566778899 * 0) CAR / (5566778899 * 0) => 5566778899 NIL / _ => [] PAIR / [] : 5566778899 => ([] * 5566778899) END %default / ([] * 5566778899) => _

Operations

Storage

type value
nat
5566778899

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 "KT1X3zxdTzPB9DgVzA3ad6dgZe9JEamoaeRy" "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
stdout
parameter: updated storage: updated code: updated BEGIN %default / _ => (KT1X3z…eRy%default * tz1VSU…cjb) CAR / (KT1X3z…eRy%default * tz1VSU…cjb) => KT1X3z…eRy%default ADDRESS / KT1X3z…eRy%default => KT1X3z…eRy NIL / _ => [] PAIR / [] : KT1X3z…eRy => ([] * KT1X3z…eRy) END %default / ([] * KT1X3z…eRy) => _

Operations

Storage

type value
address
KT1X3zxdTzPB9DgVzA3ad6dgZe9JEamoaeRy

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" "KT1X3zxdTzPB9DgVzA3ad6dgZe9JEamoaeRy" ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (tz1VSU…cjb * KT1X3z…eRy%default) CAR / (tz1VSU…cjb * KT1X3z…eRy%default) => tz1VSU…cjb CONTRACT: skip type checking for tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb CONTRACT / tz1VSU…cjb => tz1VSU…cjb%default? IF_NONE / tz1VSU…cjb%default? => tz1VSU…cjb%default NIL / _ => [] PAIR / [] : tz1VSU…cjb%default => ([] * tz1VSU…cjb%default) END %default / ([] * tz1VSU…cjb%default) => _

Operations

Storage

type value
contract
tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb

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: updated parameter: updated code: updated BEGIN %default / _ => (12345 * 0x) CAR / (12345 * 0x) => 12345 PACK / 12345 => 0x0500b9c001 SHA256 / 0x0500b9c001 => 0xcea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285 NIL / _ => [] PAIR / [] : 0xcea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285 => ([] * 0xcea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285) END %default / ([] * 0xcea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285) => _

Operations

Storage

type value
bytes
cea356435ff4eee1981f2e2ddc81bee203b3af767c48f97fd84b64fa767d2285

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: updated parameter: updated code: updated BEGIN %default / _ => (0x0500b9c001 * 0) CAR / (0x0500b9c001 * 0) => 0x0500b9c001 UNPACK / 0x0500b9c001 => 12345? IF_NONE / 12345? => 12345 NIL / _ => [] PAIR / [] : 12345 => ([] * 12345) END %default / ([] * 12345) => _

Operations

Storage

type value
nat
12345

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
parameter: updated storage: updated code: updated BEGIN %default / _ => ((44 * 33) * 0) CAR / ((44 * 33) * 0) => (44 * 33) UNPAIR / (44 * 33) => 44 : 33 PACK / 44 => 0x05002c SWAP / 0x05002c : 33 => 33 : 0x05002c PACK / 33 => 0x050021 COMPARE / 0x050021 : 0x05002c => -1 NIL / _ => [] PAIR / [] : -1 => ([] * -1) END %default / ([] * -1) => _

Operations

Storage

type value
int
-1

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
parameter: updated storage: updated code: updated BEGIN %default / _ => ((44 * 33) * 0) CAR / ((44 * 33) * 0) => (44 * 33) UNPAIR / (44 * 33) => 44 : 33 PACK / 44 => 0x05002c SIZE / 0x05002c => 3 SWAP / 3 : 33 => 33 : 3 PACK / 33 => 0x050021 SIZE / 0x050021 => 3 COMPARE / 3 : 3 => 0 NIL / _ => [] PAIR / [] : 0 => ([] * 0) END %default / ([] * 0) => _

Operations

Storage

type value
int
0

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