# Chapter 13
As much as internal operations are important, they all happen within a predefined context. The Michelson contract doesn't run in a container with no contact with the outside world. A few things that may be necessary for your contract depend on variables from outside the contract. For example, you may want to verify from which address the transaction originated or you may need to forge a transaction to another contract.
Fortunately, Michelson includes several instructions that make manipulating data from the outside world easier. We are going to have a closer look first at the address
and contract
types before working with addresses available during the execution of the smart contract.
# Address and contract types
We have already encountered the address
type in different contracts. The address type can be given to a string that complies with the Tezos address format, may it be the address of an implicit account or of a smart contract. The address
type doesn't give any guarantee about the existence or validity of the address, only about its format. For example:
parameter unit ;
storage address ;
code {
DROP ;
PUSH address "tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDHrzB" ; ## this address doesn't exist
NIL operation ;
PAIR
} ;
RUN %default Unit "" ;
stdout
parameter unit; storage address; code { DROP ; PUSH address "tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDHrzB" ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, ''); DROP: pop (Unit, ''); PUSH: push tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDHrzB; NIL: push []; PAIR: pop [], tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDHrzB; push ([], 'tz1NhNv9g7rtcjyNsH8Zqu79giY5aTqDHrzB');
As you can see, the interpreter doesn't complain you are using an invalid address 😜
If you want to make sure the address is valid and existing, you can cast the address into the contract
type. The contract
type guarantees that the address is valid and exists in the network. A value of type contract
receives a parameter which matches the parameter expected by the contract. It is important here to remember that although using the word contract
, these values don't refer exclusively to smart contracts but can also be implicit accounts. An implicit account in the world of Michelson is just a value of type contract
with a parameter of type unit
. Let's see two examples:
parameter unit ;
storage (contract unit) ;
code {
DROP ;
PUSH (contract unit) "tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v" ;
NIL operation ;
PAIR
} ;
RUN %default Unit "";
stdout
parameter unit; storage (contract unit); code { DROP ; PUSH (contract unit) "tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v" ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, ''); DROP: pop (Unit, ''); PUSH:
stderr
MichelsonRuntimeError: type is not pushable: contract unit at RUN -> PUSH
That's a bummer! Values of type contract
are not pushable values, so we will have to cast an address into its contract
value:
parameter unit ;
storage (contract unit) ;
code {
DROP ;
PUSH address "tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v" ;
CONTRACT unit;
IF_NONE
{ FAIL }
{} ;
NIL operation ;
PAIR
} ;
RUN %default Unit "";
stdout
parameter unit; storage (contract unit); code { DROP ; PUSH address "tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v" ; CONTRACT unit ; IF_NONE { { UNIT ; FAILWITH } } {} ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, ''); DROP: pop (Unit, ''); PUSH: push tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v; CONTRACT: pop tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v; skip check; push ('tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v%default',); IF_NONE: pop ('tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v%default',); push tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v%default; NIL: push []; PAIR: pop [], tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v%default; push ([], 'tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v%default');
This requires a few extra steps but that's the price to pay to make sure we have a valid existing value!
First, there must be a value of type address on top of the stack (in this case, an implicit account address). Next, you use the CONTRACT
instruction followed by the type of the parameter. In the case of an implicit account, the type is unit
. Notice how the CONTRACT
instruction returns an optional
value. If the address you provided is not valid and/or doesn't exist, CONTRACT
returns (None)
. Otherwise, we can wrap things up and end the execution of the contract.
NOTE
in the context of this notebook, the CONTRACT
instruction always returns Some
until you specify RESET "mainnet"
.
Let's see how it looks like now if we want to do the same with a contract address:
parameter address ;
storage (contract (pair address nat)) ;
code {
CAR ;
CONTRACT (pair address nat) ;
IF_NONE
{ FAIL }
{} ;
NIL operation ;
PAIR
} ;
RUN %default "KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS" "" ;
stdout
parameter address; storage (contract (pair address nat)); code { CAR ; CONTRACT (pair address nat) ; IF_NONE { { UNIT ; FAILWITH } } {} ; NIL operation ; PAIR }; RUN: use %default; drop all; push ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS', ''); CAR: pop ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS', ''); push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; CONTRACT: pop KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; skip check; push ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default',); IF_NONE: pop ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default',); push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default; NIL: push []; PAIR: pop [], KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default; push ([], 'KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default');
This contract casts the provided address to a value of type contract (pair address nat)
. This kind of operation assumes that you know beforehand the type of the parameter of the contract. It will fail if the address you provided cannot be cast to the matching contract type.
As we demonstrated, it is possible to go from a value of type address
to a value of type contract
but the opposite is also possible! You can use the ADDRESS
instruction to achieve this result:
parameter address ;
storage address ;
code {
CAR ;
CONTRACT (pair address nat) ;
IF_NONE
{ FAIL }
{} ;
ADDRESS ;
NIL operation ;
PAIR
} ;
RUN %default "KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS" "" ;
stdout
parameter address; storage address; code { CAR ; CONTRACT (pair address nat) ; IF_NONE { { UNIT ; FAILWITH } } {} ; ADDRESS ; NIL operation ; PAIR }; RUN: use %default; drop all; push ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS', ''); CAR: pop ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS', ''); push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; CONTRACT: pop KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; skip check; push ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default',); IF_NONE: pop ('KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default',); push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default; ADDRESS: pop KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS%default; push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; NIL: push []; PAIR: pop [], KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; push ([], 'KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS');
The example above shows that if you cast a value of type address
to a value of type contract
before casting it again to a value of type address
, you get the original address back!
# SELF, SOURCE and SENDER
A Michelson contract runs in an environment where different variables influencing the contract are available and only one instruction away from the stack! We already encountered different instructions that push environment information onto the stack like AMOUNT
or NOW
. Three instructions are related to the topic at hand, address
and contract
types.
The first one is SELF
that you can use to push the address of the current contract as a value of type contract
. The parameter refers to the parameter of the current contract:
parameter int ;
storage (contract int) ;
code {
DROP ;
SELF ;
NIL operation ;
PAIR
} ;
RUN %default 0 "";
stdout
parameter int; storage (contract int); code { DROP ; SELF ; NIL operation ; PAIR }; RUN: use %default; drop all; push (0, ''); DROP: pop (0, ''); SELF: use %default; push KT1BEqzn5Wx8uJrZNvuS9DVHmLvG9td3fDLi%default; NIL: push []; PAIR: pop [], KT1BEqzn5Wx8uJrZNvuS9DVHmLvG9td3fDLi%default; push ([], 'KT1BEqzn5Wx8uJrZNvuS9DVHmLvG9td3fDLi%default');
Although it may be difficult to see the value of getting the address of the current contract, this will become a lot more valuable in the next chapter when we will explore inter-contract operations.
Next, we are going to explore two instructions that are similar but widely different in what they achieve and it is very important to understand their difference to avoid bugs in your contracts. SOURCE
and SENDER
are two instructions that push onto the stack the address that created the transaction. However, the difference lays in the transaction we are talking about: in the case of SOURCE
, the address you get is the address of the implicit account from which the very first transaction was sent (in general, this will be from a human user). There was nothing before the address that SOURCE
pushes onto the stack. In the case of SENDER
, the address you get on the stack is the immediate creator of the current transaction which means that in a row of transactions, the address could be the one of a smart contract. If a user sends a transaction from a dapp that will be relayed between a couple of smart contracts, SENDER
will return the instigator of the current transaction while SOURCE
will return the address that started all the subsequent transactions.
It is particularly important to understand the difference in contracts that implement some kind of address-based authentication: if they use the address provided by SENDER
, the address could be the one of a smart contract acting on someone else's behalf. If they use the address provided by SOURCE
, the address will be the one of an implicit account.
Let's see an example that verifies if the address returned by SENDER
is an implicit account:
PATCH SENDER "KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS" ;
storage unit ;
parameter unit ;
code {
SENDER ;
CONTRACT unit ;
IF_NONE
{ FAIL }
{ DROP } ;
UNIT ;
NIL operation ;
PAIR
} ;
RUN %default Unit Unit ;
RESET "mainnet" ;
stdout
PATCH: set SENDER=KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; RESET: set NETWORK='mainnet'; RESET: set NETWORK='mainnet'; PATCH: set SENDER=KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; storage unit; parameter unit; code { SENDER ; CONTRACT unit ; IF_NONE { { UNIT ; FAILWITH } } { DROP } ; UNIT ; NIL operation ; PAIR }; RUN: use %default; use mainnet; drop all; push (Unit, Unit); SENDER: push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; CONTRACT: pop KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; not found; push None; IF_NONE: pop None; UNIT: push Unit; FAILWITH: pop Unit;
stderr
MichelsonRuntimeError: Unit at RUN -> IF_NONE -> FAILWITH
The contract fails if you pass the address of a contract as parameter (note that it won't fail with a contract that accepts a parameter of type unit
, this code is only used for the sake of demonstration).
You could also compare SOURCE
with SENDER
to ensure that you are dealing with the very first transaction:
PATCH SOURCE "tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v" ;
PATCH SENDER "KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS" ;
parameter unit ;
storage unit ;
code {
DROP ;
SENDER ;
SOURCE ;
ASSERT_CMPEQ ;
UNIT ;
NIL operation ;
PAIR
} ;
RUN %default Unit Unit ;
stdout
PATCH: set SOURCE=tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v; PATCH: set SENDER=KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; parameter unit; storage unit; code { DROP ; SENDER ; SOURCE ; { { COMPARE ; EQ } ; IF {} { { UNIT ; FAILWITH } } } ; UNIT ; NIL operation ; PAIR }; RUN: use %default; drop all; push (Unit, Unit); DROP: pop (Unit, Unit); SENDER: push KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; SOURCE: push tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v; COMPARE: pop tz1SjrNeUE4zyPGSZpogDZd5tvryixNDsD2v, KT1TUx83WuwtA2Ku1pi6A9AZqov7CZfYtLUS; push 1; EQ: pop 1; push False; IF: pop False; UNIT: push Unit; FAILWITH: pop Unit;
stderr
MichelsonRuntimeError: Unit at RUN -> IF -> FAILWITH
← Chapter 12 Chapter 14 →