# Chapter 8

Pairs are probably one of the most important type in your Michelson tool belt. Every contract starts with a pair, every contract ends with a pair. They are a fundamental component of Michelson contracts. A pair is the smallest type of value you can use to store multiple values: it can hold two values of different types. You can also nest pairs: a pair can consist of a simple value and another pair or even two other pairs! This model allows nesting multiple pairs one into the other, like Russian dolls.

Along the previous lessons, we encountered a couple of instruction that act on pairs, namely CAR and CDR. If you remember, these two opcodes extract the left side and the right side of the pair, respectively. There is also PAIR that creates a new pair with two provided values. But that's just the tip of the iceberg! There are other macros you can use to create or manipulate pairs. By the end of this chapter, pairs will have no secret for you!

# Overview of the pair type

A pair is fundamentally a value that holds two other values. These two values can be of any type. In the documentation, you will find pair type written this way: (pair type1 type2). The value of the pair is written this way: (Pair value1 value2). Every smart contract written in Michelson starts and ends with a pair:

storage (pair int int) ;
parameter unit ;
code {
    DROP ;
    PUSH int 6 ;
    PUSH int 7 ;
    PAIR ;
    NIL operation ;
    PAIR ;
} ;

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

You can choose the types you want in a pair, even other pairs:

storage unit ;
parameter unit ;
BEGIN Unit Unit ;
DROP ;
PUSH nat 5 ;
PUSH string "hello" ;
PAIR @first_pair ;
PUSH mutez 5000 ;
NOW ;
PAIR @second_pair ;
PUSH (pair int string) (Pair 3 "hello") ;
PUSH (pair nat mutez) (Pair 5 45678) ;
PAIR @third_pair ;
DUMP ;
stdout
storage unit; parameter unit; BEGIN: use %default; drop all; push (Unit, Unit); DROP: pop (Unit, Unit); PUSH: push 5; PUSH: push hello; PAIR: pop hello, 5; push ('hello', 5); PUSH: push 5000; NOW: push 1593583442; PAIR: pop 1593583442, 5000; push (1593583442, 5000); PUSH: push (3, 'hello'); PUSH: push (5, 45678); PAIR: pop (5, 45678), (3, 'hello'); push ((5, 45678), (3, 'hello'));
value type name
Pair (Pair 5 45678) (Pair 3 "hello")
pair (pair nat mutez) (pair int string)
@third_pair
Pair 1593583442 5000
pair timestamp mutez
@second_pair
Pair "hello" 5
pair string nat
@first_pair

# Operations on pairs

A few instructions are available on pairs. The first one is an operation we have already been using since the beginning: PAIR. PAIR allows you to take the two elements on top of the stack and put them in a pair. It goes without saying that PAIR only works if there are currently two elements on top of the stack. Unlike list, you don't have to specify the type of the two elements, the first element will be on the left side of the pair and the second element on the right side. The instruction was illustrated above.

Next come two other instructions that we have also been using for a while: CAR and CDR. If you remember, CAR extracts the left side of a pair while CDR extracts the right side:

DROP_ALL ;
PUSH (pair nat string) (Pair 5 "tezos") ;
DUP ;
CAR @left_side ;
SWAP ;
CDR @right_side ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push (5, 'tezos'); DUP: push (5, 'tezos'); CAR: pop (5, 'tezos'); push 5; SWAP: pop 5, (5, 'tezos'); push 5; push (5, 'tezos'); CDR: pop (5, 'tezos'); push tezos;
value type name
"tezos"
string
@right_side
5
nat
@left_side

You should be familiar with their functioning now.
It is possible to "combine" these two instructions when working with nested pairs (i.e pairs inside pairs). If you have ever checked Michelson contracts, you probably noticed that nested pairs are pretty common. Let's observe the snippet below:

DROP_ALL ;
PUSH (pair (pair nat nat) (pair string string)) (Pair (Pair 5 6) (Pair "hello" "world")) ;
CADR ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push ((5, 6), ('hello', 'world')); CAR: pop ((5, 6), ('hello', 'world')); push (5, 6); CDR: pop (5, 6); push 6;
value type
6
nat

This is one long instruction! It created nested pairs: a root pair that contains two other pairs: on the left side, a pair with a nat on the left and a nat on the right, on the right side, a pair with a string on the left and a string on the right. As you can see from the result, the CADR macro is equal to CAR ; CDR ;, i.e it will extract the pair on the left side of a pair and then extract the right side of the nested pair. If you want to extract the left side of the nested pair, you can use CAAR:

DROP_ALL ;
PUSH (pair (pair nat nat) (pair string string)) (Pair (Pair 5 6) (Pair "hello" "world")) ;
CAAR ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push ((5, 6), ('hello', 'world')); CAR: pop ((5, 6), ('hello', 'world')); push (5, 6); CAR: pop (5, 6); push 5;
value type
5
nat

To work on the right side of the root pair, you can use CDAR (to extract its left side) or CDDR (to extract its right side):

DROP_ALL ;
PUSH (pair (pair nat nat) (pair string string)) (Pair (Pair 5 6) (Pair "hello" "world")) ;
DUP;
CDAR ;
SWAP ;
CDDR ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push ((5, 6), ('hello', 'world')); DUP: push ((5, 6), ('hello', 'world')); CDR: pop ((5, 6), ('hello', 'world')); push ('hello', 'world'); CAR: pop ('hello', 'world'); push hello; SWAP: pop hello, ((5, 6), ('hello', 'world')); push hello; push ((5, 6), ('hello', 'world')); CDR: pop ((5, 6), ('hello', 'world')); push ('hello', 'world'); CDR: pop ('hello', 'world'); push world;
value type
"world"
string
"hello"
string

We can go even further and imagine a three-level nested pair. According to the level you are trying to reach, you can put more As or more Ds between C and R to access the value you want:

DROP_ALL ;
PUSH (pair (pair nat nat) (pair int (pair string string))) (Pair (Pair 5 6) (Pair 10 (Pair "hello" "world"))) ;
CDDDR ;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push ((5, 6), (10, ('hello', 'world'))); CDR: pop ((5, 6), (10, ('hello', 'world'))); push (10, ('hello', 'world')); CDR: pop (10, ('hello', 'world')); push ('hello', 'world'); CDR: pop ('hello', 'world'); push world;
value type
"world"
string

The instructions and macros we've seen so far only extracts elements that already exist in pairs, but what about modifying elements of pairs? Imagine you have this pair: (Pair "hello" "world"). How would you go if you want to replace "world" with "Tezos"?

DROP ;
PUSH string "Tezos" ;
PUSH (pair string string) (Pair "hello" "world") ;
CAR ;
PAIR ;
DUMP ;
stdout
DROP: pop world; PUSH: push Tezos; PUSH: push ('hello', 'world'); CAR: pop ('hello', 'world'); push hello; PAIR: pop hello, Tezos; push ('hello', 'Tezos');
value type
Pair "hello" "Tezos"
pair string string

This method involves multiple instructions to extract the value you want and pair it with a second value. Michelson provides a few useful macros that just do that. The first one we are going to check is SET_CAR. SET_CAR will modify the left side of a pair. In order to use it, you must have two elements in your stack: a pair on top and the element to set on the left side below:

DROP ;
PUSH string "three" ;
PUSH (pair string string) (Pair "two" "birds") ;
SET_CAR ;
stdout
DROP: pop ('hello', 'Tezos'); PUSH: push three; PUSH: push ('two', 'birds'); CDR: pop ('two', 'birds'); push birds; SWAP: pop birds, three; push birds; push three; PAIR: pop three, birds; push ('three', 'birds');
value type
Pair "three" "birds"
pair string string

You can see that the pair we pushed has been modified and the left side that was "two" is now "three". If you want to do the same thing on the right side, you can use SET_CDR:

PUSH string "cats" ;
SWAP ; ## brings the pair on top of the element to include
SET_CDR ;
stdout
PUSH: push cats; SWAP: pop cats, ('three', 'birds'); push cats; push ('three', 'birds'); CAR: pop ('three', 'birds'); push three; PAIR: pop three, cats; push ('three', 'cats');
value type
Pair "three" "cats"
pair string string

In this new example, "birds" has been replaced with "cats". Even better, you can use macros after SET_ if you want to access nested pairs like in the example above and set their elements:

PUSH (pair string string) (Pair "four" "birds") ;
PAIR ;
PUSH string "five" ;
SWAP ;
SET_CDAR ;
stdout
PUSH: push ('four', 'birds'); PAIR: pop ('four', 'birds'), ('three', 'cats'); push (('four', 'birds'), ('three', 'cats')); PUSH: push five; SWAP: pop five, (('four', 'birds'), ('three', 'cats')); push five; push (('four', 'birds'), ('three', 'cats')); DUP: push (('four', 'birds'), ('three', 'cats')); DIP: protect 1 item(s); CDR: pop (('four', 'birds'), ('three', 'cats')); push ('three', 'cats'); CDR: pop ('three', 'cats'); push cats; SWAP: pop cats, five; push cats; push five; PAIR: pop five, cats; push ('five', 'cats'); restore 1 item(s); CAR: pop (('four', 'birds'), ('three', 'cats')); push ('four', 'birds'); PAIR: pop ('four', 'birds'), ('five', 'cats'); push (('four', 'birds'), ('five', 'cats'));
value type
Pair (Pair "four" "birds") (Pair "five" "cats")
pair (pair string string) (pair string string)

As expected with the CDAR macro, we access first the right side of the pair (the D in CDAR) then the left side (the A in CDAR) that we replace with the string we pushed earlier.

Now your brain must have warmed up and be ready to go down the rabbithole of pairs in Michelson! So far, we've seen how to set data in a pair, how to extract it and how to swap one value with another one. What about running some code on the data in a pair? 🤯

Imagine a pair containing two numbers. How would you proceed if you want to increment the number on the left?

DROP ;
PUSH (pair int int) (Pair 10 5) ;
DUP ; 
CDR ; 
DIP { CAR ; PUSH int 5 ; ADD } ;
SWAP ;
PAIR ;
stdout
DROP: pop (('four', 'birds'), ('five', 'cats')); PUSH: push (10, 5); DUP: push (10, 5); CDR: pop (10, 5); push 5; DIP: protect 1 item(s); CAR: pop (10, 5); push 10; PUSH: push 5; ADD: pop 5, 10; push 15; restore 1 item(s); SWAP: pop 5, 15; push 5; push 15; PAIR: pop 15, 5; push (15, 5);
value type
Pair 15 5
pair int int

Multiple steps are involved here to add 5 to the 10 on the left side of the pair:

  1. We duplicate the pair as we will need access to both the left field and the right field.
  2. We extract the value on the right side of the pair.
  3. DIP protects the value on top of the stack and run the following code on the second element: extract the value on the left side, push 5 and add 5 to the value that was on the left side of the pair.
  4. SWAP puts the two values in the initial order.
  5. PAIR creates a new pair.

As you can imagine, these are a lot of steps involved in modifying a single value. Fortunately, Michelson provides a very useful macro that will run this code for you under the hood: MAP_CAR. Let's take the same example and use this macro instead:

DROP ;
PUSH (pair int int) (Pair 10 5) ;
MAP_CAR { PUSH int 5 ; ADD } ;
stdout
DROP: pop (15, 5); PUSH: push (10, 5); DUP: push (10, 5); CDR: pop (10, 5); push 5; DIP: protect 1 item(s); CAR: pop (10, 5); push 10; PUSH: push 5; ADD: pop 5, 10; push 15; restore 1 item(s); SWAP: pop 5, 15; push 5; push 15; PAIR: pop 15, 5; push (15, 5);
value type
Pair 15 5
pair int int

You have to admit, that's a lot simpler than the first solution! With MAP_CAR, you only need to provide the code you want to run on the left field of the pair and the macro will do the rest for you.

As you may have guessed, we also have MAP_CDR to run code on the right side of the pair:

DROP ;
PUSH (pair nat nat) (Pair 20 3) ;
MAP_CDR { PUSH nat 6 ; SWAP ; SUB } ;
stdout
DROP: pop -2; PUSH: push (20, 3); DUP: push (20, 3); CDR: pop (20, 3); push 3; PUSH: push 6; SWAP: pop 6, 3; push 6; push 3; SUB: pop 3, 6; push -3; SWAP: pop -3, (20, 3); push -3; push (20, 3); CAR: pop (20, 3); push 20; PAIR: pop 20, -3; push (20, -3);
value type
Pair 20 -3
pair nat int

Just like the SET_, you can modify the right side of the MAP_ to access nested pairs. Let's see an example:

DROP ; 
PUSH (pair (pair string string) (pair string string)) (Pair (Pair "birds" "cats") (Pair "dogs" "cows")) ;
MAP_CADR { PUSH string " and butterflies" ; SWAP ; CONCAT } ;
stdout
DROP: pop (20, -3); PUSH: push (('birds', 'cats'), ('dogs', 'cows')); DUP: push (('birds', 'cats'), ('dogs', 'cows')); DIP: protect 1 item(s); CAR: pop (('birds', 'cats'), ('dogs', 'cows')); push ('birds', 'cats'); DUP: push ('birds', 'cats'); CDR: pop ('birds', 'cats'); push cats; PUSH: push and butterflies; SWAP: pop and butterflies, cats; push and butterflies; push cats; CONCAT: pop cats; pop and butterflies; push cats and butterflies; SWAP: pop cats and butterflies, ('birds', 'cats'); push cats and butterflies; push ('birds', 'cats'); CAR: pop ('birds', 'cats'); push birds; PAIR: pop birds, cats and butterflies; push ('birds', 'cats and butterflies'); restore 1 item(s); CDR: pop (('birds', 'cats'), ('dogs', 'cows')); push ('dogs', 'cows'); SWAP: pop ('dogs', 'cows'), ('birds', 'cats and butterflies'); push ('dogs', 'cows'); push ('birds', 'cats and butterflies'); PAIR: pop ('birds', 'cats and butterflies'), ('dogs', 'cows'); push (('birds', 'cats and butterflies'), ('dogs', 'cows'));
value type
Pair (Pair "birds" "cats and butterflies") (Pair "dogs" "cows")
pair (pair string string) (pair string string)

# Pairing and unpairing

So far, the instructions we have seen only allow us to work on one element of a pair at a time. In some situations, you may want to put some elements of the stack in a pair or remove the elements of a pair to push them on the stack. In the simplest cases, you have two elements that you want to pair together. You can use the PAIR instruction to construct a pair:

storage unit ;
parameter unit ;
BEGIN Unit Unit ;
DROP ;
PUSH int 6 ;
PUSH mutez 5559988 ;
PAIR ;
stdout
storage unit; parameter unit; BEGIN: use %default; drop all; push (Unit, Unit); DROP: pop (Unit, Unit); PUSH: push 6; PUSH: push 5559988; PAIR: pop 5559988, 6; push (5559988, 6);
value type
Pair 5559988 6
pair mutez int

You can easily deconstruct the pair and get the two elements it contains onto the stack with the UNPAIR instruction:

UNPAIR ;
DUMP ;
stdout
DUP: push (5559988, 6); CAR: pop (5559988, 6); push 5559988; DIP: protect 1 item(s); CDR: pop (5559988, 6); push 6; restore 1 item(s);
value type
5559988
mutez
6
int

Under the hood, UNPAIR is just syntactic sugar for DUP ; CAR ; DIP { CDR } ; and you can observe that the element that was on the left side of the pair is now on top and the element from the right side just below.

Now, what would happen with nested pairs? If we have two pairs in the stack and we want to pair them, we can just use PAIR as usual. It becomes more complicated if we have 4 elements in the stack and want to create an element of type (pair (pair type type) (pair type type)). Of course, we could type a few instructions to get this result (for example PAIR ; DIP { PAIR } ; PAIR ; but Michelson actually offers us a much simpler and elegant solution: pair macros. Let's have a look at an example:

DROP_ALL ;
PUSH int 6 ;
PUSH int 8 ;
PUSH nat 12 ;
PUSH nat 19 ;
PPAIPAIR;
DUMP ;
stdout
DROP_ALL: drop all; PUSH: push 6; PUSH: push 8; PUSH: push 12; PUSH: push 19; DIP: protect 2 item(s); PAIR: pop 8, 6; push (8, 6); restore 2 item(s); PAIR: pop 19, 12; push (19, 12); PAIR: pop (19, 12), (8, 6); push ((19, 12), (8, 6));
value type
Pair (Pair 19 12) (Pair 8 6)
pair (pair nat nat) (pair int int)

Although PPAIPAIR looks like my cat walked on my keyboard, it is much easier than concatenating 4 instructions together! This is a very powerful macro that looks intimidating the first time you see it but becomes very easy to use once you understand its mechanism. There are only three rules to remember:

  1. The macro starts with P and ends with R.
  2. A P inside the macro indicates a new pair.
  3. A represents the left side of the macro while I represents the right side.

This is how you can read the macro: this is a pair (PPAIPAIR), on the left side of the pair, there is a pair (PPAIPAIR) whose elements are not pairs (PPAIPAIR), on the right side, there is another pair (PPAIPAIR) whose elements are not pairs (PPAIPAIR).

Let's see two more examples:

DROP_ALL ;
PUSH int 6 ;
PUSH int 8 ;
PUSH nat 12 ;
PUSH nat 19 ;
PAPAIR ; ## A pair with a value on the left side and a pair on the right side
stdout
DROP_ALL: drop all; PUSH: push 6; PUSH: push 8; PUSH: push 12; PUSH: push 19; DIP: protect 1 item(s); PAIR: pop 12, 8; push (12, 8); restore 1 item(s); PAIR: pop 19, (12, 8); push (19, (12, 8));
value type
Pair 19 (Pair 12 8)
pair nat (pair nat int)
DROP_ALL ;
PUSH int 6 ;
PUSH int 8 ;
PUSH nat 12 ;
PUSH nat 19 ;
PUSH nat 25 ;
PAPPAIPAIR ; ## A pair with a value on the left and a pair with 2 nested pairs on the right
stdout
DROP_ALL: drop all; PUSH: push 6; PUSH: push 8; PUSH: push 12; PUSH: push 19; PUSH: push 25; DIP: protect 3 item(s); PAIR: pop 8, 6; push (8, 6); restore 3 item(s); DIP: protect 1 item(s); PAIR: pop 19, 12; push (19, 12); restore 1 item(s); DIP: protect 1 item(s); PAIR: pop (19, 12), (8, 6); push ((19, 12), (8, 6)); restore 1 item(s); PAIR: pop 25, ((19, 12), (8, 6)); push (25, ((19, 12), (8, 6)));
value type
Pair 25 (Pair (Pair 19 12) (Pair 8 6))
pair nat (pair (pair nat nat) (pair int int))

Now that you understood the principle of the macro, it is very easy to unpair nested pairs. You can follow the same mechanism to deconstruct pairs by prefixing the macro with UN. For example, let's deconstruct the pair we just created above:

UNPAPPAIPAIR ;
DUMP ;
stdout
DUP: push (25, ((19, 12), (8, 6))); CAR: pop (25, ((19, 12), (8, 6))); push 25; DIP: protect 1 item(s); CDR: pop (25, ((19, 12), (8, 6))); push ((19, 12), (8, 6)); restore 1 item(s); DIP: protect 1 item(s); DUP: push ((19, 12), (8, 6)); CAR: pop ((19, 12), (8, 6)); push (19, 12); DIP: protect 1 item(s); CDR: pop ((19, 12), (8, 6)); push (8, 6); restore 1 item(s); restore 1 item(s); DIP: protect 1 item(s); DUP: push (19, 12); CAR: pop (19, 12); push 19; DIP: protect 1 item(s); CDR: pop (19, 12); push 12; restore 1 item(s); restore 1 item(s); DIP: protect 3 item(s); DUP: push (8, 6); CAR: pop (8, 6); push 8; DIP: protect 1 item(s); CDR: pop (8, 6); push 6; restore 1 item(s); restore 3 item(s);
value type
25
nat
19
nat
12
nat
8
int
6
int

As you can see, we are left with a stack that's exactly the same as it was before we paired all the values together! This macro is going to be vital to deconstruct the storage of the contract. Imagine the following contract:

storage string ;
parameter (pair (pair int int) string) ;
code {
    UNPAIR ;
    UNPPAIIR ;
    ADD ;
    PUSH int 10 ;
    IFCMPEQ
        { NIL operation ; PAIR }
        { PUSH string "Not equal to 10!" ; FAILWITH }
} ;

RUN %default (Pair (Pair 5 5) "hello Tezos") "";
stdout
storage string; parameter (pair (pair int int) string); code { { DUP ; CAR ; DIP { CDR } } ; { { DUP ; CAR ; DIP { CDR } } ; { DUP ; CAR ; DIP { CDR } } } ; ADD ; PUSH int 10 ; { { COMPARE ; EQ } ; IF { NIL operation ; PAIR } { PUSH string "Not equal to 10!" ; FAILWITH } } }; RUN: use %default; drop all; push (((5, 5), 'hello Tezos'), ''); DUP: push (((5, 5), 'hello Tezos'), ''); CAR: pop (((5, 5), 'hello Tezos'), ''); push ((5, 5), 'hello Tezos'); DIP: protect 1 item(s); CDR: pop (((5, 5), 'hello Tezos'), ''); push ; restore 1 item(s); DUP: push ((5, 5), 'hello Tezos'); CAR: pop ((5, 5), 'hello Tezos'); push (5, 5); DIP: protect 1 item(s); CDR: pop ((5, 5), 'hello Tezos'); push hello Tezos; restore 1 item(s); DUP: push (5, 5); CAR: pop (5, 5); push 5; DIP: protect 1 item(s); CDR: pop (5, 5); push 5; restore 1 item(s); ADD: pop 5, 5; push 10; PUSH: push 10; COMPARE: pop 10, 10; push 0; EQ: pop 0; push True; IF: pop True; NIL: push []; PAIR: pop [], hello Tezos; push ([], 'hello Tezos');
value type
"hello Tezos"
string

In a first step, UNPAIR extracts the parameter and the storage values from the pair that's pushed onto the stack at the beginning of the execution. Next, UNPPAIIR unwraps the parameter and gets all its values onto the stack. The two integers that the pair on the left side contained are added together and their result is compared to 10. If it is equal, the string that was on the right side of the pair is saved in the storage. Otherwise, the contract fails.

add add