Learn Solidity lesson 18. Global variables and the Umwelt.

Umwelt is a term coined by the biosemiologist Thomas A. Sebeok that means something like ‘the environment around the living being’. Although it has nothing to do with blockchain, I really like the term, and in this lesson we are going to talk about the environment around the EVM.

A characteristic to be understood about the blockchain is that it is a closed and totally deterministic environment. A smart contract cannot access the outside of the blockchain; for instance, it cannot access an API. It can’t even generate a random number and it’s easy to understand why.

The blockchain contains a database of state variables that change through transactions. This database must be identical on all network nodes, and each node executes, locally, all transactions once validated on the network.

If it were possible to interact with an external API, perhaps one node, when executing the transaction, would record one result, while another node would record another one, because in the meantime the API service has failed and it is no longer available.

Let’s look at a new example, of a contract called the Schrödinger lottery. Such a contract has a function that accepts an amount of Ether and, based on a supposed random number generated by the system, decides whether you win the prize or not.

Someone submits a transaction to the contract, and the transaction, after being mined (or validated) will have to be executed by all nodes in the network. If the number were really random each node would generate a different number, so for some nodes you would win the prize, for others you would not. You would be Schrödinger’s rich.

I hope these arguments were enough for you to realize that the blockchain needs to be restricted to itself. It cannot interact with the outside world, and so it is the outside world that needs to be injected into the blockchain. And the world does it through global variables; at least a small part of it.

Block and transaction information via global variables

The contract is able to access some information about its surrounding environment; data of the transaction that was executed and the block in which that transaction is contained. We can get this information through global variables.

We’ve already seen one of these variables in the msg.sender property, which returns the address that invoked the function. The global variable msg has another commonly used property, msg.value, which returns the value in wei sent in the transaction.

A short list of the most used global variables and properties in Solidity follows below.

  • msg.sender. Address of who invoked the function externally. It is not necessarily the address of the account that initiated the transaction.
  • msg.value. Amount, in wei, sent in the transaction.
  • block.number. The block number that contains the transaction.
  • block.timestamp. The block date, in seconds, in Unix time. Is the number of seconds since midnight 01/01/1970.
  • gasleft(). Amount of gas remaining at the time of executing this function.
  • tx.origin. Address of the account that sent the original transaction.

The difference between msg.sender and tx.origin

The difference between msg.sender and tx.origin can be subtle, so let’s explain further. Functions in contracts can invoke functions in other contracts. When they do this, an internal transaction is created.

The tx.origin property always contains the address of the sender of the original transaction, so it is always an external address (EOA). It will never be a contract address, as contracts never initiate transactions.

msg.sender is the address of who called the function, externally. If the function has been called by some contract, through an internal transaction, then msg.sender will return the address of the contract that called the function.

Let’s see this in a contract.

pragma solidity ^0.8.7;
contract Transactions {
function directTx() public view returns(address, address) {
return (msg.sender, tx.origin);
}
function internalTx() public view returns(address, address) {
return this.iAmExternal();
}
function iAmExternal() external view returns(address, address) {
return (msg.sender, tx.origin);
}
}

Both the directTx and iAmExternal functions can be invoked directly. Thus, both msg.sender and tx.origin will have the same value, because whoever is calling the function is the same address that started the transaction (in this case, it is just a call, but it would be same if it was a transaction). We can see this in the figure below.

Msg.sender and tx.origin have the same value.

When we invoke the function internalTx, it makes a call to the function iAmExternal, but it makes a call as if it were an external call. Note that the function iAmExternal has external visibility, that is, it cannot be invoked from within the contract. That’s why we use the prefix this in the expression this.iAmExternal() and not just iAmExternal().

The result of the call can be seen in the figure below. Now msg.sender will be the address of the contract itself, while tx.origin is still the address of the account that sent the transaction.

Msg.sender indicates the address that generated the internal transaction, and tx.origin holds the address of the account that sent the original transaction.

Why is msg.sender the contract address? Remember that msg.sender is the address of the account that generated the internal transaction. It was the contract itself that generated the internal transaction to call the function, not the account that sent the transaction.

Contracts generally call functions from other contracts, not the same, externally. But to simplify the example, I used only one contract.

I also wanted to show, in this example, that it is possible to invoke a function whose visibility is external from within the contract itself. However, when we do this, it is considered an internal transaction, and the msg.sender will no longer be tx.origin, as we have seen. If the visibility were public, we could invoke the function normally, without having to generate an internal transaction.

Ps: The msg.sender of calls

To send transactions to the blockchain, you need an account, after all, transactions need to be signed by a private key. However, calls can be anonymous, as they do not propagate across the blockchain. So, who is the msg.sender of a call?

Since calls don’t change the state of the blockchain, you don’t need to worry too much about it. Even if the msg.sender value of a call is ambiguous, it will not interfere with the deterministic behavior of the blockchain.

Out of curiosity, I deployed the contract to a testnet and used etherscan to invoke the function that returns both msg.sender and tx.origin when an internal transaction is involved. The result obtained is shown in the figure below.

The tx.origin is address zero, but msg.sender takes the contract address, as it was supposed to.

The ambiguous address, which was the caller, is address zero. The msg.sender is the address of the contract, as it should be.

This session was just a curiosity, as the use of msg.sender and tx.origin makes a lot more sense in transactions than in calls, but I’m a curious person, and I hope you are too be.

Random numbers on the blockchain

As I said, it is impossible to generate truly random numbers on the blockchain, due to its deterministic behavior. What should we do when we want to generate random numbers?

One possibility is to use global variables like block.timestamp. Every Solidity tutorial does not recommend using global variables because they are not really random, but depending on the size of the project, there is not much of a problem. Especially if we use the hash of a value like block timestamp.

It’s true that the miner can manipulate the block timestamp, but unless your contract involves a lot of money, it doesn’t really matter. Also, Ethereum is migrating to PoS, so we will soon have randomly chosen validators instead of miners. Anyway, it should be clear that the timestamp is not a random number.

If you need a really random number, whatever the cost, you should use the service of an Oracle. Oracles, on blockchain, are ways for smart contracts to communicate with the outside world. They don’t cast spells, and follow the rules of the game.

We’ll talk more about oracles in the future, but let’s look at a simple way to create an oracle. By requesting a random number, a contract can emit an event, which is monitored by the outside world. Let’s say the same contract has a function that receives the random number.

The oracle, in the outside world, monitors events. Upon receiving the request for a random number, it generates the random number and sends this number to the function, through a transaction. Note that the blockchain at no time accesses the outside world, and it is the outside world that is injected into it.

Thanks for reading!

Comments and suggestions about this article are welcome.

Any contribution is welcome. www.buymeacoffee.com/jpmorais

Join Coinmonks Telegram Channel and Youtube Channel learn about crypto trading and investing

Also, Read


Learn Solidity lesson 18. Global variables and the Umwelt. was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.