# Chapter 5
In this new installment of our tutorials, we are going to talk about money!
One of the main advantages of smart contracts is that they are "programmable money": they let you store, manipulate and send tez in a predictable way. Smart contracts can work as middle men and verify that a set of conditions is met before initiating a transaction, sending a certain amount of tez or doing any other modification.
In the previous tutorials, we encountered a couple of instructions that push useful information about the smart contract and its context onto the stack: you may remember NOW
that adds the current timestamp on top of the stack. This is not the only one. Michelson provides a few instructions that give you access to a lot of information. In this chapter, we are going to focus on instructions that are related to the financial aspect of smart contracts: BALANCE
, AMOUNT
, TRANFER_TOKENS
. You will also get a refresher about mutez
operations. At the end of the chapter, you will have everything you need to know in order to handle financial operations in your smart contracts 🤑
# mutez
and mutez
operations
The mutez
(or micro-tez) type is a special type that allows you to manipulate financial values in your smart contracts. mutez
are indivisible and always non-negative (as exposed below, a few restrictions are set to avoid negative values of mutez
). It may seem a little strange at the beginning, but mutez
values are NOT tez, they only represent a financial value. If you write PUSH mutez 1000
, this doesn't mean you added 1,000 microtez out of thin air in your smart contract. You merely create a value of a certain type to easily manipulate other values.
You can use different arithmetic operations for values of type mutez
: ADD
, SUB
, MUL
, EDIV
with a few restrictions:
ADD
only works with two values of typemutez
SUB
only works with two values of typemutez
AND fails if the result of the subtraction is negativeMUL
only works with one value of typemutez
and one value of typenat
, the result is of typemutez
EDIV
only works with one value of typemutez
and one value of typenat
, the result is of typeoption
,None
in case of a division by zero,(Some (pair quotient remainder))
including a pair containing the quotient on the left and the remainder on the right
In addition to these operations, it is also possible to compare two values of type mutez
. Like any other value, this comparison yields an int
between -1
and 1
.
Now let's see some examples:
We initialize a simple contract to test these operations:
storage mutez ;
parameter unit ;
BEGIN Unit 1000 ;
stdout
storage: updated parameter: updated BEGIN %default / _ => (Unit * 0.001)
Now let's separate the storage and push a new value of type mutez
(nothing new here):
CDR @storage ;
PUSH mutez 2000 ;
DUMP ;
stdout
CDR / (Unit * 0.001) => 0.001 PUSH / _ => 0.002 DUMP => [0.002, 0.001]
With the ADD
instruction, we can remove the two values of type mutez
from the stack, add them together and push the result back to the stack:
ADD ;
stdout
ADD / 0.002 : 0.001 => 0.003
After adding values together, let's try to subtract one from another! We push a new value of type mutez
and use the one already present in the stack to demonstrate SUB
:
PUSH mutez 500 ;
SWAP ;
SUB ;
stdout
PUSH / _ => 0.0005 SWAP / 0.0005 : 0.003 => 0.003 : 0.0005 SUB / 0.003 : 0.0005 => 0.0025
Did you notice the SWAP
instruction? You must make sure that the two elements are in the right order. If you try mutez 500 - mutez 3000
, the contract will fail because mutez
values cannot be negative.
Next, we can multiply mutez
values with nat
values (to ensure positive results). In this situation, the order of the values doesn't matter, so no need to swap them:
PUSH nat 4 ;
MUL ;
stdout
PUSH / _ => 4 MUL / 4 : 0.0025 => 0.01
As you can see, the value returned by MUL
is also of type mutez
.
As it was the case for the other numeric values, using EDIV
with mutez
is also a bit more complex. If you remember from the previous chapter, the result of EDIV
is an option. If you try a division by zero, the option will have the value None
. Otherwise, it has a value of type pair
that contains the quotient on the left and the remainder on the right.
We can write a small piece of code that tries to divide the value on top of the stack by the value we've just pushed. If this value is 0
, the contract fails. Otherwise, it extracts the quotient and the remainder and rearrange them in this order in the stack:
PUSH nat 10 ;
SWAP ;
EDIV ;
IF_NONE
{ PUSH string "DivisionByZero" ; FAILWITH }
{
DUP ;
CAR @quotient ;
SWAP ;
CDR @remainder ;
SWAP ;
} ;
DUMP ;
stdout
PUSH / _ => 10 SWAP / 10 : 0.01 => 0.01 : 10 EDIV / 0.01 : 10 => (0.001 * 0)? IF_NONE / (0.001 * 0)? => (0.001 * 0) DUP / (0.001 * 0) => (0.001 * 0) : (0.001 * 0) CAR / (0.001 * 0) => 0.001 SWAP / 0.001 : (0.001 * 0) => (0.001 * 0) : 0.001 CDR / (0.001 * 0) => 0 SWAP / 0 : 0.001 => 0.001 : 0 DUMP => [0.001, 0]
The two values we get on the stack are exactly the expected ones: 10000 / 10 = 1000
with a remainder of 0
.
Lastly, we can compare two values of type mutez
and check if one is equal, greater or less than the other one. This will be particularly useful when you want to verify that users send the correct amount of tez required by your contract:
PUSH mutez 500 ;
COMPARE ;
EQ ;
stdout
PUSH / _ => 0.0005 COMPARE / 0.0005 : 0.001 => -1 EQ / -1 => False
We're adding here an extra step to check if the newly added value in the stack is equal to the one already present. As they are not equal, False
is pushed onto the stack. Obviously, you can use any of the instructions we studied in Chapter three outside of EQ
.
# BALANCE
, AMOUNT
and NOW
Michelson provides a few instructions that are very useful to check the context of execution.
Every time a smart contract runs, a few "variables" are available about data related to the current execution. For example, every transaction sent to a smart contract has an amount of tez attached to it (even if this is amount may be 0
). You may want to have access to this amount, for example, to check if the users sent enough tez for the service they requested or to update their token balance.
You can easily add this piece of information to the stack by using AMOUNT
. The AMOUNT
instruction will simply push a new element on top of the stack of type mutez
whose value is equal to the amount sent along the transaction.
Here are two examples to showcase what you can do with the AMOUNT
instruction:
storage mutez ;
parameter unit ;
code {
CDR ;
AMOUNT ;
IFCMPGT
{ AMOUNT ; NIL operation ; PAIR }
{ FAIL }
} ;
RUN %default Unit 100 ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * 0.0001) CDR / (Unit * 0.0001) => 0.0001 AMOUNT / _ => 0 COMPARE / 0 : 0.0001 => -1 GT / -1 => False IF / False => _ UNIT / _ => Unit FAILWITH: Unit
stderr
FAILWITH: Unit
The contract fails as expected. Indeed, there is no tez attached to this transaction, so the value contained in AMOUNT
is not greater than the one in the storage.
Because this environment is detached from the blockchain, you have to specify yourself the value you want to give to amount
and use a non-Michelson instruction that works only in the context of these notebooks: PATCH
. Here is how it works:
storage mutez ;
parameter unit ;
code {
PATCH AMOUNT 200 ;
CDR ;
AMOUNT ;
IFCMPGT
{ AMOUNT ; NIL operation ; PAIR }
{ FAIL }
} ;
RUN %default Unit 100 ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * 0.0001) CDR / (Unit * 0.0001) => 0.0001 AMOUNT / _ => 0.0002 COMPARE / 0.0002 : 0.0001 => 1 GT / 1 => True IF / True => _ AMOUNT / _ => 0.0002 NIL / _ => [] PAIR / [] : 0.0002 => ([] * 0.0002) END %default / ([] * 0.0002) => _
You can see that the newly returned storage is mutez 200
as expected, because 200
is greater than 100
.
Other scenario: you create a smart contract and you want to prevent people from sending any tez. To do so, you must check if the amount is equal to 0
before proceeding and if it is not the case, the contract fails:
storage string ;
parameter unit ;
code {
PATCH AMOUNT ;
DROP ;
AMOUNT ;
PUSH mutez 0 ;
IFCMPNEQ
{ FAIL }
{
PUSH string "No amount!" ;
NIL operation ;
PAIR
}
} ;
RUN %default Unit "" ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * '') DROP / (Unit * '') => _ AMOUNT / _ => 0 PUSH / _ => 0 COMPARE / 0 : 0 => 0 NEQ / 0 => False IF / False => _ PUSH / _ => 'No amount!' NIL / _ => [] PAIR / [] : 'No amount!' => ([] * 'No amount!') END %default / ([] * 'No amount!') => _
You can also use PATCH AMOUNT
with no value, which will be 0
by default. We then use the IFCMPNEQ
instruction to check if the value on top of the stack (the 0
we pushed just before) is not equal to the one below (the amount). Be careful to push a value of type mutez
for the comparison.
If the result of the comparison is true (i.e the amount is not equal to 0
), the contract fails. Otherwise, we pushed the amount again (remember that every instruction starting with IF
removes the value(s) on top of the stack) and end the execution by saving the amount in the storage.
In some cases, you would like to know the current balance of the contract before running a piece of code. For example, one of your users requests to withdraw his/her share in the smart contract and you need to make sure there are enough funds before initiating the transaction. You may also want to block incoming transactions if the balance of the contract reaches a certain limit.
In these cases, you can use BALANCE
. Just like AMOUNT
, BALANCE
is an instruction that pushes a new value on top of the stack containing the current balance of the smart contract in mutez
.
Let's write a smart contract that keeps track in the storage of the last transaction sent after making sure the current balance doesn't exceed a certain limit:
storage mutez ;
parameter unit ;
code {
DROP ;
PATCH AMOUNT 10000000 ; ## manual setting of amount
PATCH BALANCE 25700000 ; ## manual setting of balance
AMOUNT ;
BALANCE ;
ADD ;
DUP ;
PUSH mutez 100000000 ; # 100 tez
IFCMPGT
{ NIL operation ; PAIR }
{ PUSH string "BalanceExceeded" ; FAILWITH }
} ;
RUN %default Unit 1000 ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * 0.001) DROP / (Unit * 0.001) => _ AMOUNT / _ => 10 BALANCE / _ => 25.7 ADD / 25.7 : 10 => 35.7 DUP / 35.7 => 35.7 : 35.7 PUSH / _ => 100 COMPARE / 100 : 35.7 => 1 GT / 1 => True IF / True => _ NIL / _ => [] PAIR / [] : 35.7 => ([] * 35.7) END %default / ([] * 35.7) => _
This is a very simple smart contract that does one simple thing: it adds the incoming amount to the balance, compares the result with the limit we manually introduce (with PUSH mutez 100000000 ;
), if the limit is greater than the addition of the amount and the balance, everything is fine, we save the amount in the storage and end the execution. However, if the limit is exceeded, we throw an error with the FAIL
instruction.
If you try to change the values (for example adding a 0
to the balance), you can make the contract fail to test that it works properly.
We already introduced NOW
in the previous chapters. The instruction pushes the current timestamp onto the stack. This is a very useful feature for time-related actions: for example, we can make a smart contract fail if a transaction comes before a certain period ended since the last transaction:
storage timestamp ;
parameter unit ;
code {
CDR ;
PUSH int 3600 ; ## 60 sec * 60 min = 1 hour interval
ADD ;
PATCH NOW 1592382823 ;
NOW ;
IFCMPGE
{ NOW ; NIL operation ; PAIR }
{ PUSH string "InvalidInterval" ; FAILWITH }
} ;
RUN %default Unit 1592379223 ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * 1592379223) CDR / (Unit * 1592379223) => 1592379223 PUSH / _ => 3600 ADD / 3600 : 1592379223 => 1592382823 NOW / _ => 1592382823 COMPARE / 1592382823 : 1592382823 => 0 GE / 0 => True IF / True => _ NOW / _ => 1592382823 NIL / _ => [] PAIR / [] : 1592382823 => ([] * 1592382823) END %default / ([] * 1592382823) => _
As you can see, the contract stores the timestamp of the last successful execution. When a transaction comes, it adds 3,600 seconds to that timestamp and compares it to the current timestamp. If one hour has passed, the new timestamp is saved into the storage. Otherwise, an error is raised and the contract fails.
In the contract, we use PATCH NOW 1592382823
in order to force a certain value for our NOW
instruction and see the contract passes. If you comment out this line (just add ##
at the very beginning of the line) and run the contract again, you can see that it fails now, because there is more than one hour between NOW
and the timestamp in the storage (by default NOW
is zero in case you are not attached to any network). The value returned by NOW
is then saved into the storage.
# Address, contract and token transfer
One of the many features of smart contracts is their ability to transfer money. You can send tez to a smart contract, you can send them from the smart contract to an address and you can even exchange tez between smart contracts! You can let anyone transfer money in and out but you can also enforce certain rules that dictate who can make transfers and how these transfers can happen. After reading this section, you will know everything you need to manage tez in your smart contracts!
Let's start from the beginning, with the address
type. address
is a type in Michelson reserved for addresses. It can be used both for implicit account (starting with tz1, tz2 or tz3) and smart contract (starting with KT1) addresses. You can type an address as a string when you want, for example, to add it onto the stack:
storage address ;
parameter unit ;
code {
DROP ;
PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
NIL operation ;
PAIR
} ;
RUN %default Unit "KT1TxqZ8QtKvLu3V3JH7Gx58n7Co8pgtpQU5" ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * KT1Txq…QU5) DROP / (Unit * KT1Txq…QU5) => _ PUSH / _ => tz1VSU…cjb NIL / _ => [] PAIR / [] : tz1VSU…cjb => ([] * tz1VSU…cjb) END %default / ([] * tz1VSU…cjb) => _
Be aware that the address
type does not ensure that the address is a valid address, only that the string follows the address format.
If you want to ensure that the address is valid, you can use the contract (param)
type. This type guarantees that the address is a valid, existing account with a parameter of type param
. In case of an implicit account, param
is equal to unit
. It is impossible to "push" a value of type contract
onto the stack but you can receive it as a parameter, you can get it with the IMPLICIT_ACCOUNT
and SELF
instructions or you have to start with a value of type address
and cast it to a value of type contract
. Let's see an example before detailing what happens:
storage (contract unit) ;
parameter unit ;
code {
DROP ;
PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
CONTRACT unit ;
IF_NONE
{ FAIL }
{ NIL operation ; PAIR }
} ;
RUN %default Unit "KT1TxqZ8QtKvLu3V3JH7Gx58n7Co8pgtpQU5" ;
stdout
storage: updated parameter: updated code: updated BEGIN %default / _ => (Unit * KT1Txq…QU5%default) DROP / (Unit * KT1Txq…QU5%default) => _ PUSH / _ => 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) => _
In this example, we convert a value of type address
to a value of type contract
and save the result in the storage. Several steps are necessary to achieve it:
- We remove the first element of the stack with
DROP
. - We add the address on top of the stack with
PUSH
. - We use a special instruction called
CONTRACT
to cast the address to a contract value. Note that we provide a parameter of typeunit
indicating that the resulting value will be an implicit account.
NOTE
smart contracts can also have a parameter of type unit
, this kind of parameter doesn't necessarily confirm that you are dealing with an implicit account.
- Two things may happen at this point: if the address is invalid or doesn't exist,
CONTRACT
returns the valueNone
of type option. We can use it to make the contract fail. If the address is valid and exists,CONTRACT
returns the value(Some (contract param))
of type option that we unwrap in the second branch of the conditional structure to get the value of type(contract unit)
. This value is then saved in the storage.
NOTE
In the Jupyter notebooks, the Contract
values are not typechecked, so they always return (Some value)
. Be careful when you use your code in a compiler as it can be a source of error.
In a real-life scenario, you wouldn't probably go through all these steps just to save the result in the storage. This operation can be particularly useful when you want to send tokens from a contract. Michelson provides an instruction that allows you to withdraw tez from the contract balance and send them to an implicit account or another contract: TRANSFER_TOKENS
. The use of this instruction is not difficult by itself but you have to make sure to get its necessary parameters in the right order in the stack before calling it. Let's see an example first to observe what TRANSFER_TOKENS
does:
storage unit ;
parameter unit ;
BEGIN Unit Unit ;
PATCH BALANCE 10000000 ;
DROP ;
stdout
storage: updated parameter: updated BEGIN %default / _ => (Unit * Unit) DROP / (Unit * Unit) => _
This initializes the storage and the parameter before dropping the first element of the stack. We also add 10 tez to the balance so we have something to send to the receiver's address.
PUSH address "tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb" ;
CONTRACT unit ;
DUMP ;
stdout
PUSH / _ => tz1VSU…cjb CONTRACT: skip type checking for tz1VSUr8wwNhLAzempoch5d6hLRiTh8Cjcjb CONTRACT / tz1VSU…cjb => tz1VSU…cjb%default? DUMP => [tz1VSU…cjb%default?]
Like in the previous contract, we push the receiver's address to the stack and convert it to a (contract unit)
type.
IF_NONE
{ PUSH string "InvalidAddress" ; FAILWITH }
{ } ;
DUMP ;
stdout
IF_NONE / tz1VSU…cjb%default? => tz1VSU…cjb%default DUMP => [tz1VSU…cjb%default]
IF_NONE
verifies that the CONTRACT
instruction succeeded and that a value has been returned. In this case, the value is the address where we will send the tez, of type (contract unit)
.
PUSH mutez 5000000 ;
DUMP ;
stdout
PUSH / _ => 5 DUMP => [5, tz1VSU…cjb%default]
We then push the amount we want to send to the receiver's address.
PUSH unit Unit ;
DUMP ;
stdout
PUSH / _ => Unit DUMP => [Unit, 5, tz1VSU…cjb%default]
Because we are sending an amount to an implicit account, the expected parameter of the value of type (contract unit)
is unit
, so we just need to provide an element of type unit
to the TRANFER_TOKENS
instruction.
Note that the three parameters necessary to transfer tokens are now on top of the stack and in the right order:
- The parameter expected for the address (
unit
for implicit accounts) - The amount to be sent in
mutez
- The receiver's address converted to a value of type
(contract param)
TRANSFER_TOKENS ;
DUMP ;
stdout
TRANSFER_TOKENS / Unit : 5 : tz1VSU…cjb%default => transaction DUMP => [transaction]
Calling the TRANSFER_TOKENS
instruction forges a new operation. Remember that contract executions in Michelson are self-contained: they do not communicate with the outside. The execution goes from its beginning to its end and only then it is possible to send new transactions.
You can see that the value we now have on top of the stack is of type operation
and contains all the information we previously added to the stack: the amount to be sent, the entrypoint to call (default
by default or in case of a transaction to an implicit account), the operation kind, the parameters (unit
for transactions to implicit accounts) and the target address. The only thing left now is to add this operation to the list of operations returned at the end of the contract.
NIL operation ;
SWAP ;
CONS ;
DUMP ;
stdout
NIL / _ => [] SWAP / [] : transaction => transaction : [] CONS / transaction : [] => [transaction] DUMP => [[transaction]]
Are you excited about finally using that list of operations we have been returning empty so far? 😅
In order to include the operation into the list of operations, you add an empty list as usual with NIL operation
, you use SWAP
because the element to add to the list must be just above in the stack and you call the CONS
instruction which prepends the value to the list. Now you can return the pair with the list of operations and the storage:
UNIT ;
SWAP ;
PAIR ;
COMMIT ;
stdout
UNIT / _ => Unit SWAP / Unit : [transaction] => [transaction] : Unit PAIR / [transaction] : Unit => ([transaction] * Unit) END %default / ([transaction] * Unit) => _
Nothing groundbreaking here, we push a new unit
on top of the stack, we use SWAP
because the list of operations must be on the left of the pair and then PAIR
.