# Chapter 10

This chapter is going to introduce three complex types of data that are extremely important for your smart contracts.

So far, the smart contracts we have written were quite simple: they received data as input, applied some logic to the data and output a result. However, this is not what most contracts do. Smart contracts are an interesting solution to many problems because they can retain information and the new input sent to the contract can be compared and/or used against the data that's already in the contract!

In a previous chapter, we introduced the list type. Lists allow us to store multiple values of the same type in an ordered fashion. However, they lack some features that you may need for your smart contract. For example, it is not possible natively to know if a list contains a certain value (i.e without implementing a loop).

Michelson provides three other types of values that can store other values: sets, maps and big maps. Their different properties will be explained below but in a nutshell, sets are lists of unique elements, maps are tables where each value matches a unique key and big maps are maps containing a large number of values. Let's start with sets!

# Working with sets

Sets are a sorted collection of unique values. A set is like a list with the main difference of storing only unique values in a definite order. While { 1 ; 2 ; 1 ; 2 ; 1 ; 2 } is a valid value of type (list int), it wouldn't be a valid value of type (set int). Here is how to create an empty set:

storage (set int) ;
parameter unit ;
code {
    DROP ;
    EMPTY_SET int ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit { 1 };
stdout
storage (set int); parameter unit; code { DROP ; EMPTY_SET int ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {1}); DROP: pop (Unit, {1}); EMPTY_SET: push []; NIL: push []; PAIR: pop [], []; push ([], set());
value type
{}
set int

You can use the EMPTY_SET instruction to create an empty set. It takes 1 argument, the type of the elements you will store in the set. It must be a comparable type: you can, for example, put strings or integers in a set but you cannot put a map or a set inside a set.

Next, you want to save some data inside the set. You can use the UPDATE instruction to push new values in the set. This instruction requires a little setup illustrated in the example below:

storage (set int) ;
parameter unit ;
code {
    DROP ;
    EMPTY_SET int ;
    PUSH bool True;
    PUSH int 5 ;
    UPDATE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit { 1 };
stdout
storage (set int); parameter unit; code { DROP ; EMPTY_SET int ; PUSH bool True ; PUSH int 5 ; UPDATE ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {1}); DROP: pop (Unit, {1}); EMPTY_SET: push []; PUSH: push True; PUSH: push 5; UPDATE: pop 5, True, []; push {5}; NIL: push []; PAIR: pop [], {5}; push ([], {5});
value type
{ 5 }
set int

We could also have used the set in the storage:

storage (set int) ;
parameter unit ;
code {
    CDR ;
    PUSH bool True;
    PUSH int 5 ;
    UPDATE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit { 1 };
stdout
storage (set int); parameter unit; code { CDR ; PUSH bool True ; PUSH int 5 ; UPDATE ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {1}); CDR: pop (Unit, {1}); push {1}; PUSH: push True; PUSH: push 5; UPDATE: pop 5, True, {1}; push {1, 5}; NIL: push []; PAIR: pop [], {1, 5}; push ([], {1, 5});
value type
{ 5 ; 1 }
set int

Here is what happens in the stack: the UPDATE instruction requires 3 elements in the stack before proceeding:

  1. The element to add to the set
  2. A boolean value set to True to indicate that you want to add a new element to the set
  3. The set in which you want to store the new value

If the element is already present in the stack, the execution will just continue without any change. Otherwise, the new element will be pushed at the head position of the set.

If you set the boolean value to False and provide a value that already exists in the set, UPDATE will remove this element from the set:

storage (set int) ;
parameter unit ;
code {
    CDR ;
    PUSH bool False;
    PUSH int 5 ;
    UPDATE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit { 1 ; 2 ; 3 ; 4 ; 5 };
stdout
storage (set int); parameter unit; code { CDR ; PUSH bool False ; PUSH int 5 ; UPDATE ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {1, 2, 3, 4, 5}); CDR: pop (Unit, {1, 2, 3, 4, 5}); push {1, 2, 3, 4, 5}; PUSH: push False; PUSH: push 5; UPDATE: pop 5, False, {1, 2, 3, 4, 5}; push {1, 2, 3, 4}; NIL: push []; PAIR: pop [], {1, 2, 3, 4}; push ([], {1, 2, 3, 4});
value type
{ 1 ; 2 ; 3 ; 4 }
set int

Now you understand why you must set up correctly the stack before using UPDATE: the 3 required elements must be in the right order and the boolean element must be set to the right value in order to add or remove a value from the set. Here is a more complex example:

storage (set string) ;
parameter (pair string string) ;
code {
    UNPPAIAIR ;
    DIP { SWAP } ;
    PUSH bool True ;
    SWAP ;
    UPDATE ;
    SWAP ;
    PUSH bool False ;
    SWAP ;
    UPDATE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default (Pair "mango" "apple") { "banana" ; "apple" ; "strawberry" };
stdout
storage (set string); parameter (pair string string); code { { { DUP ; CAR ; DIP { CDR } } ; { DUP ; CAR ; DIP { CDR } } } ; DIP { SWAP } ; PUSH bool True ; SWAP ; UPDATE ; SWAP ; PUSH bool False ; SWAP ; UPDATE ; NIL operation ; PAIR }; RUN: use %default; drop all; push (('mango', 'apple'), {'banana', 'strawberry', 'apple'}); DUP: push (('mango', 'apple'), {'banana', 'strawberry', 'apple'}); CAR: pop (('mango', 'apple'), {'banana', 'strawberry', 'apple'}); push ('mango', 'apple'); DIP: protect 1 item(s); CDR: pop (('mango', 'apple'), {'banana', 'strawberry', 'apple'}); push {'banana', 'strawberry', 'apple'}; restore 1 item(s); DUP: push ('mango', 'apple'); CAR: pop ('mango', 'apple'); push mango; DIP: protect 1 item(s); CDR: pop ('mango', 'apple'); push apple; restore 1 item(s); DIP: protect 1 item(s); SWAP: pop apple, {'banana', 'strawberry', 'apple'}; push apple; push {'banana', 'strawberry', 'apple'}; restore 1 item(s); PUSH: push True; SWAP: pop True, mango; push True; push mango; UPDATE: pop mango, True, {'banana', 'strawberry', 'apple'}; push {'banana', 'strawberry', 'apple', 'mango'}; SWAP: pop {'banana', 'strawberry', 'apple', 'mango'}, apple; push {'banana', 'strawberry', 'apple', 'mango'}; push apple; PUSH: push False; SWAP: pop False, apple; push False; push apple; UPDATE: pop apple, False, {'banana', 'strawberry', 'apple', 'mango'}; push {'banana', 'strawberry', 'mango'}; NIL: push []; PAIR: pop [], {'banana', 'strawberry', 'mango'}; push ([], {'banana', 'strawberry', 'mango'});
value type
{ "mango" ; "banana" ; "strawberry" }
set string

First, we deconstruct the parameter with UNPPAIAIR to get our two fruits on the stack with the set of fruits below. Then, we push mango into the set of fruits before removing apple. A few SWAP instructions are necessary to put all the elements in the right order. Observe how the boolean values are set to True and False according to the effect you want to create on the set.

Before trying to remove or add a value to a set, it would be great to check if the value is already inside. This is what you can achieve with the MEM instruction:

storage (set string) ;
parameter string ;
code {
    UNPAIR ;
    DIP { DUP } ;
    DUP ;
    DIP { SWAP } ;
    MEM ;
    IF { FAIL } { PUSH bool True ; SWAP ; UPDATE } ;
    NIL operation ;
    PAIR ;
} ;

RUN %default "mango" { "banana" ; "apple" ; "strawberry" };
stdout
storage (set string); parameter string; code { { DUP ; CAR ; DIP { CDR } } ; DIP { DUP } ; DUP ; DIP { SWAP } ; MEM ; IF { { UNIT ; FAILWITH } } { PUSH bool True ; SWAP ; UPDATE } ; NIL operation ; PAIR }; RUN: use %default; drop all; push ('mango', {'banana', 'strawberry', 'apple'}); DUP: push ('mango', {'banana', 'strawberry', 'apple'}); CAR: pop ('mango', {'banana', 'strawberry', 'apple'}); push mango; DIP: protect 1 item(s); CDR: pop ('mango', {'banana', 'strawberry', 'apple'}); push {'banana', 'strawberry', 'apple'}; restore 1 item(s); DIP: protect 1 item(s); DUP: push {'banana', 'strawberry', 'apple'}; restore 1 item(s); DUP: push mango; DIP: protect 1 item(s); SWAP: pop mango, {'banana', 'strawberry', 'apple'}; push mango; push {'banana', 'strawberry', 'apple'}; restore 1 item(s); MEM: pop mango, {'banana', 'strawberry', 'apple'}; push False; IF: pop False; PUSH: push True; SWAP: pop True, mango; push True; push mango; UPDATE: pop mango, True, {'banana', 'strawberry', 'apple'}; push {'banana', 'strawberry', 'apple', 'mango'}; NIL: push []; PAIR: pop [], {'banana', 'strawberry', 'apple', 'mango'}; push ([], {'banana', 'strawberry', 'apple', 'mango'});
value type
{ "mango" ; "banana" ; "apple" ; "strawberry" }
set string

In this contract, we duplicate the values passed in the parameter because MEM is going to remove the element and the set we want to test. If the value is not in the set, it will be added, otherwise, the contract will fail. As expected, mango is not in the set, so the value gets added to the final set. If you replace mango with apple, you will see the contract fail.

As it is also the case for other types storing multiple values, you can check the size of a set by using the SIZE instruction:

storage nat ;
parameter (set string) ;
code {
    CAR ;
    SIZE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default { "banana" ; "apple" ; "strawberry" } 0;
stdout
storage nat; parameter (set string); code { CAR ; SIZE ; NIL operation ; PAIR }; RUN: use %default; drop all; push ({'banana', 'strawberry', 'apple'}, 0); CAR: pop ({'banana', 'strawberry', 'apple'}, 0); push {'banana', 'strawberry', 'apple'}; SIZE: pop {'banana', 'strawberry', 'apple'}; push 3; NIL: push []; PAIR: pop [], 3; push ([], 3);
value type
3
nat

The SIZE instruction returns a value of type nat. Add more strings to the set to see it working!

# Working with maps

Maps and big maps are probably the type of complex values you will work with most of the time. As big maps are a kind of map, we start with maps!

Maps provide a very convenient way of storing data: they are like a table with two columns, a simple value on the left side that you can use to retrieve the data on the right side. This allows to store complex data that you can find very quickly and easily. For example, in a token contract, you can associate an address with its balance and the chosen allowances. When creating a new map, you have to specifiy the type of the keys and the type of the values, as you won't be able to store data that are not of the specified type. Here is how to create an empty map:

storage (map address nat) ;
parameter unit ;
code {
    DROP ;
    EMPTY_MAP address nat ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit {} ;
stdout
storage (map address nat); parameter unit; code { DROP ; EMPTY_MAP address nat ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {}); DROP: pop (Unit, {}); EMPTY_MAP: push {}; NIL: push []; PAIR: pop [], {}; push ([], {});
value type
{}
map address nat

You can use any type you want as a key as long as it is a comparable type. Note also that map keys are lexicographically sorted.

Adding and removing values from a map is going to be a little easier than doing it with a set 😅 However, we are going to use the same instruction, UPDATE:

storage (map address nat) ;
parameter unit ;
code {
    DROP ;
    EMPTY_MAP address nat ;
    PUSH (option nat) (Some 5) ;
    PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
    UPDATE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit {} ;
stdout
storage (map address nat); parameter unit; code { DROP ; EMPTY_MAP address nat ; PUSH (option nat) (Some 5) ; PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ; UPDATE ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {}); DROP: pop (Unit, {}); EMPTY_MAP: push {}; PUSH: push (5,); PUSH: push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; UPDATE: pop tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb, (5,), {}; push {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5}; NIL: push []; PAIR: pop [], {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5}; push ([], {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5});
value type
{ Elt "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" 5 }
map address nat

In order to add a new key/pair value to a map, the stack must have three elements in the following order:

  1. The key associated to the value to add
  2. The value you want to add
  3. The map that you want to update

When the stack is correctly set up, you can call the UPDATE instruction. It is important that the value you use to update the map is an optional value with an argument whose type is going to be the expected type of the values in the map. If you use (Some type), you will add a new value to the map. However, if you use (None), you will remove the value at the provided key:

storage (map address nat) ;
parameter unit ;
code {
    CDR ;
    PUSH (option nat) (None) ;
    PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
    UPDATE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default Unit { Elt "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" 5 ; Elt "tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE" 10} ;
stdout
storage (map address nat); parameter unit; code { CDR ; PUSH (option nat) None ; PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ; UPDATE ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); CDR: pop (Unit, {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); push {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; PUSH: push None; PUSH: push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; UPDATE: pop tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb, None, {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; push {'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; NIL: push []; PAIR: pop [], {'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; push ([], {'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10});
value type
{ Elt "tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE" 10 }
map address nat

As you can see from this example, giving a (None) value to the map value before calling UPDATE will remove the corresponding key/value pair. If the key doesn't exist, this will have no effect on the map or the contract. Because of this, you may want to check first if a key exists in a map before removing it or updating it. This is what the MEM instruction is for:

storage bool ;
parameter (map address nat) ;
code {
    CAR ;
    PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
    MEM ;
    NIL operation ;
    PAIR ;
} ;

RUN %default { Elt "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" 5 ; Elt "tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE" 10} False ;
stdout
storage bool; parameter (map address nat); code { CAR ; PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ; MEM ; NIL operation ; PAIR }; RUN: use %default; drop all; push ({'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}, False); CAR: pop ({'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}, False); push {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; PUSH: push tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb; MEM: pop tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb, {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; push True; NIL: push []; PAIR: pop [], True; push ([], True);
value type
True
bool

This very simple contract checks if a single address is present in the map passed as a parameter. Please remember that MEM is going to pop the key and the map from the stack, so you should duplicate them if you want to use them later:

storage (map address nat) ;
parameter address ;
code {
    DUP ;
    DIP { UNPAIR } ;
    UNPAIR ;
    MEM ;
    IF
        { PUSH (option nat) (None) ; SWAP ; UPDATE } ## Removes key/value pair if exists
        { PUSH (option nat) (Some 1) ; SWAP ; UPDATE } ; ## Adds key/value pair if doesn't exist
    NIL operation ;
    PAIR ;
} ;

RUN %default "tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB" { Elt "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" 5 ; Elt "tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE" 10} ;
stdout
storage (map address nat); parameter address; code { DUP ; DIP { { DUP ; CAR ; DIP { CDR } } } ; { DUP ; CAR ; DIP { CDR } } ; MEM ; IF { PUSH (option nat) None ; SWAP ; UPDATE } { PUSH (option nat) (Some 1) ; SWAP ; UPDATE } ; NIL operation ; PAIR }; RUN: use %default; drop all; push ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); DUP: push ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); DIP: protect 1 item(s); DUP: push ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); CAR: pop ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); push tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB; DIP: protect 1 item(s); CDR: pop ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); push {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; restore 1 item(s); restore 1 item(s); DUP: push ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); CAR: pop ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); push tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB; DIP: protect 1 item(s); CDR: pop ('tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB', {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}); push {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; restore 1 item(s); MEM: pop tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB, {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; push False; IF: pop False; PUSH: push (1,); SWAP: pop (1,), tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB; push (1,); push tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB; UPDATE: pop tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB, (1,), {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10}; push {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10, 'tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB': 1}; NIL: push []; PAIR: pop [], {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10, 'tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB': 1}; push ([], {'tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb': 5, 'tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE': 10, 'tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB': 1});
value type
{ Elt "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" 5 ;
  Elt "tz1UVzNHTxMzkPn1uMXaSCTJYBQQc4x5dyNE" 10 ;
  Elt "tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDDrzB" 1 }
map address nat

The provided address was not a value in the map, so the contract added it and gave it a balance of 1. Now, if you change the address in the parameter for an address that's already in the map, you will see it disappear in the final map!

After checking if a key exists in a map, you probably want to get the value that's associated with it! Michelson provides the GET instruction to retrieve values bound to a key in a map:

storage string ;
parameter (pair string (map string string)) ;
code {
    CAR ;
    UNPAIR ;
    GET ;
    IF_NONE
        { PUSH string "none" }
        {} ;
    NIL operation ;
    PAIR ;
} ;

RUN %default (Pair "banana" { Elt "cherry" "red" ; Elt "banana" "yellow" ; Elt "apple" "green"}) "" ;
stdout
storage string; parameter (pair string (map string string)); code { CAR ; { DUP ; CAR ; DIP { CDR } } ; GET ; IF_NONE { PUSH string "none" } {} ; NIL operation ; PAIR }; RUN: use %default; drop all; push (('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}), ''); CAR: pop (('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}), ''); push ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); DUP: push ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); CAR: pop ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); push banana; DIP: protect 1 item(s); CDR: pop ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); push {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}; restore 1 item(s); GET: pop banana, {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}; push ('yellow',); IF_NONE: pop ('yellow',); push yellow; NIL: push []; PAIR: pop [], yellow; push ([], 'yellow');
value type
"yellow"
string

This very simple code sends a string and a map to the contract that will check if the string is a key in the map. If it is, it will save the color of the fruit in the storage. If it is not, it will save "none" in the storage instead. We could also have gone a different road and used IF_SOME to check if the GET instruction returns anything:

storage string ;
parameter (pair string (map string string)) ;
code {
    CAR ;
    UNPAIR ;
    GET ;
    IF_SOME
        {} 
        { PUSH string "none" };
    NIL operation ;
    PAIR ;
} ;

RUN %default (Pair "banana" { Elt "cherry" "red" ; Elt "banana" "yellow" ; Elt "apple" "green"}) "" ;
stdout
storage string; parameter (pair string (map string string)); code { CAR ; { DUP ; CAR ; DIP { CDR } } ; GET ; { IF_NONE { PUSH string "none" } {} } ; NIL operation ; PAIR }; RUN: use %default; drop all; push (('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}), ''); CAR: pop (('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}), ''); push ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); DUP: push ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); CAR: pop ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); push banana; DIP: protect 1 item(s); CDR: pop ('banana', {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}); push {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}; restore 1 item(s); GET: pop banana, {'cherry': 'red', 'banana': 'yellow', 'apple': 'green'}; push ('yellow',); IF_NONE: pop ('yellow',); push yellow; NIL: push []; PAIR: pop [], yellow; push ([], 'yellow');
value type
"yellow"
string

Whether you use IF_NONE or IF_SOME, if a value is found, it will be unwrapped from the optional value and dumped onto the stack. In this example, the values in the map are of type string, so GET returns (Some string) and IF_NONE/IF_SOME unwraps it and brings string to the stack.

There is a last instruction to check before ending our tour of maps. Michelson provides an instruction to know the size of a given map, you guessed it, SIZE!

storage nat ;
parameter (map string nat) ;
code {
    CAR ;
    SIZE ;
    NIL operation ;
    PAIR ;
} ;

RUN %default { Elt "cherry" 16 ; Elt "banana" 24 ; Elt "apple" 32} 0 ;
stdout
storage nat; parameter (map string nat); code { CAR ; SIZE ; NIL operation ; PAIR }; RUN: use %default; drop all; push ({'cherry': 16, 'banana': 24, 'apple': 32}, 0); CAR: pop ({'cherry': 16, 'banana': 24, 'apple': 32}, 0); push {'cherry': 16, 'banana': 24, 'apple': 32}; SIZE: pop {'cherry': 16, 'banana': 24, 'apple': 32}; push 3; NIL: push []; PAIR: pop [], 3; push ([], 3);
value type
3
nat

As usual, this kind of instruction returns a nat value.

Now that you have a better understanding of maps in Michelson, we can check big maps. Under the hood, big maps are actually maps. The only major difference is that when Michelson loads a map into memory, it goes through all the key/value pairs and get them ready to be used by your code. This consumes gas and can become very expensive if the map is huge. In the case of a big map, Michelson does not prepare the map and will only access the key/value pair you request. This will be a lot cheaper than using a map with a big drawback though: you won't get any information about the map outside of the key/value pair you are trying to access. This means instructions available on map like SIZE, as well as iterative instructions like ITER and MAP, are not possible on big_map (more on that in the next chapter). The only instructions you can use with big maps are EMPTY_BIG_MAP, GET, MEM and UPDATE. Let's try to create an example that uses all the available instructions for big maps!

storage (big_map string nat) ;
parameter unit ;
code {
    DROP ;
    EMPTY_BIG_MAP string nat ;
    PUSH nat 15 ;
    SOME ;
    PUSH string "cherry" ;
    UPDATE ;
    PUSH nat 22 ;
    SOME ;
    PUSH string "banana" ;
    UPDATE ;
    DUP ;
    PUSH string "cherry" ;
    DUP ;
    SWAP ;
    DIP { SWAP } ;
    MEM ;
    IF
        { 
            DIP { DUP } ;
            DUP ;
            DIP { SWAP } ;
            GET ;
            IF_SOME
                { PUSH nat 5 ; ADD ; SOME ; SWAP ; UPDATE }
                { DROP }
        }
        { DROP } ;
    NIL operation ;
    PAIR ;
};

RUN %default Unit {} ;
stdout
storage (big_map string nat); parameter unit; code { DROP ; EMPTY_BIG_MAP string nat ; PUSH nat 15 ; SOME ; PUSH string "cherry" ; UPDATE ; PUSH nat 22 ; SOME ; PUSH string "banana" ; UPDATE ; DUP ; PUSH string "cherry" ; DUP ; SWAP ; DIP { SWAP } ; MEM ; IF { DIP { DUP } ; DUP ; DIP { SWAP } ; GET ; { IF_NONE { DROP } { PUSH nat 5 ; ADD ; SOME ; SWAP ; UPDATE } } } { DROP } ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, -19); DROP: pop (Unit, -19); EMPTY_BIG_MAP: push -20; PUSH: push 15; SOME: pop 15; push (15,); PUSH: push cherry; UPDATE: pop cherry, (15,), -20; push -20; PUSH: push 22; SOME: pop 22; push (22,); PUSH: push banana; UPDATE: pop banana, (22,), -20; push -20; DUP: push -20; PUSH: push cherry; DUP: push cherry; SWAP: pop cherry, cherry; push cherry; push cherry; DIP: protect 1 item(s); SWAP: pop cherry, -20; push cherry; push -20; restore 1 item(s); MEM: pop cherry, -20; push True; IF: pop True; DIP: protect 1 item(s); DUP: push -20; restore 1 item(s); DUP: push cherry; DIP: protect 1 item(s); SWAP: pop cherry, -20; push cherry; push -20; restore 1 item(s); GET: pop cherry, -20; push (15,); IF_NONE: pop (15,); push 15; PUSH: push 5; ADD: pop 5, 15; push 20; SOME: pop 20; push (20,); SWAP: pop (20,), cherry; push (20,); push cherry; UPDATE: pop cherry, (20,), -20; push -20; NIL: push []; PAIR: pop [], -20; push ([], -20);
value type
1
big_map string nat

big_map action key value
1
alloc
string
nat
1
update
"cherry"
20
1
update
"banana"
22

This example is a little far-fetched but it demonstrates how you can use the different instructions available with big maps.

add add