top of page
hero.png

Free Solidity Tutorial

Who is this for?

No email required! This is for experienced programmers who want to get to the point quickly and immediately practice the information they just gained in a highly optimized topic order. We emphasize the unexpected and unusual aspects of the Solidity language while glossing over things we can reasonably assume to be obvious to a competent developer. Aside from an occasional humorous remark, we’ve made the tutorials as short as possible (but not shorter). Although this is a solidity beginner tutorial, it is intended for experienced coders.

Get ready to experience the learning efficiency of RareSkills!

Why this tutorial is superior (besides being free)

Tutorials that were created a long time ago use older versions of solidity.

As of Solidity 0.8.19 (released February 22, 2023), our tutorial is up to date.

Another significant difference is that we teach the foundry development environment from the get-go. Previously, Truffle, then Hardhat were the industry standard. As of 2023, Foundry is the dominant development framework.

The Solidity language is not difficult to learn. It looks a lot like javascript (or Dart, for those of you who know that language). This tutorial assumes you already know how to code in a popular language and leverages that knowledge to learn Solidity quickly. You should already know what functions, integers, strings, arrays, and so on are. If you are learning to code for the first time, this tutorial is not appropriate for you. It’s for experienced coders to get up to speed in Solidity quickly.

An important part of the tutorial is you will spend more time coding than reading materials or watching videos. We will provide you just enough information to get you started, then provide some practice problems designed to enforce what you just learned. The practice problems are a very important part of learning the language. If you are just curious, it’s okay to just read the content. But if you want to actually learn Solidity, you need to do the practice problems. When you finish, check out or suggested beginner solidity projects to enforce what you've learned. 

Don’t be fooled by the fact that this class is free. This is not low-effort content. This tutorial was designed by Jeffrey Scholz, who is a two time best seller for the only expert-level Solidity courses on Udemy. The practice problems were created by a team of 3 additional experienced solidity engineers. We urge you to compare our syllabus to other paid courses to see for yourself.

We are not shy about the quality of our work.

The free content at RareSkills is superior to the paid content elsewhere.

Why are we making it free when others charge hundreds of dollars for content like this?

Because we hope later on, at least a few of you will take our Advanced Solidity Bootcamp, which is paid.

There is a distinction between knowing a language, and knowing a domain. Knowing Python doesn’t make you a data scientist, knowing Javascript doesn’t make you a frontend developer, and knowing Kotlin does not make you an Android developer. Similarly, knowing Solidity does not make you an Ethereum smart contract developer.

However, Solidity is a prerequisite for developing smart contracts. We teach smart contract development in our Advanced Solidity Bootcamp. That course, obviously, assumes you know Solidity.

This tutorial, both the material and the practice problems, is 100% free, does not require a login, a credit card, or any information from you. We hope you benefit from this valuable resource and maybe attend our training programs and join our amazing community down the road!

License

We are providing free of charge information that others charge money for, sometimes hundreds of dollars. To prevent abuse, please understand the terms of our copyright.

Although this information is free of charge, it is not free to redistribute, modify, or copy. Redistribution of any kind is not authorized. The source code in the projected is licensed under the Business Source License. Redistributing, reproducing, or creating derivative works is strictly prohibited.

If you would like to share this with others, please simply provide a hyperlink to this page.

Let's Start

Development Environment

 

To get started, we will learn solidity as a language. We won’t begin with deploying contracts on the blockchain, that will just make things more complicated.

Head over to remix.ethereum.org

You are strongly encouraged to use Remix to follow along with the examples in this course.

Let’s create a hello world.

After you go to remix.ethereum.org, right click contracts and left click “New File”

solidity remix ide create new solidity file

 

This is a solidity file, so give the file a .sol extension. The name is not important

solidity remix ide .sol file extension

 

Copy the code from above, or better yet, type it out yourself.

paste solidity code into remix ide

 

To compile the code, hit Command S on mac (ctrl S on Windows). If you see a red bubble above the solidity symbol, you have a syntax error. if you see orange, you only have warnings which you can ignore for now.

Now deploy the functions. Click the Ethereum symbol on the left, then click deploy.

how to deploy a smart contract in the remix ide

 

To test the functions, scroll down on the left menu, then click on them. They will return the values you expect them to.

test solidity smart contract in the remix ide

 

What if we want to make changes? Delete the contract with by clicking the trash icon.

use the trash icon to delete the smart contract in the remix ide

 

Now change the code, recompile with command S, then click deploy. Test the functions again.

recompile solidity smart contract in the remix ide

 

If a function requires an argument, it will be supplied next to the button.

test a solidity function that takes arguments in the remix ide

 

You are now ready to experiment with Solidity smart contracts!

Fixed Size Datatypes: Solidity is a typed language.

 

Solidity is a typed language.

Unlike javascript or python where you can assign a bool or a string or a number to a variable, each variable can only have one type, and it must be explicitly declared as such.

This applies to functions too. You must explicitly specify the argument type and the return type.

Let’s discuss the most commonly used types now:

  • The unsigned integer, or uint256

  • The boolean variable or bool

  • The address type, which stores Ethereum wallet addresses or smart contract addresses

 

Solidity has arrays, strings, structs, and other types, but they require a little different treatment, so we’ll discuss them later.

Let’s look at three different functions that return each of these types.

 

In these examples, we assigned the value to a variable and then returned it. We can of course directly return the value like so.

 

It’s very important that the function signature matches the return type. The following code will produce an error

Address

An address is represented as a hex string that has 40 characters in it, and always starts with 0x. A valid hex string contains the characters [0-9] or [a-f] inclusive.

warning: be careful when typing addresses manually. Solidity will covert 0x1 into an address with the value 0x0000000000000000000000000000000000000001. If you have an address with less than 40 hex characters, it will pad it with leading zeros.

 

If you create an address with more than 40 characters, it won’t compile.

Note that the 40 characters does not include the leading 0x.

uint256

Let’s revisit uint256 what exactly does that mean?

The u means unsigned. It cannot represent negative numbers. The 256 means it can store numbers up to 256 bits large, or 2^256-1.

Let’s plug that into python to see how big that number is.

 

That’s a very large number, big enough for pretty much everything you’ll need to do on the blockchain.

This will compile in Solidity

But if you make the number bigger, the code won’t compile.

As you can imagine, a uint128 stores unsigned numbers that are up to 2^128 - 1 in size.

Most of the time, you should only use uint256. The times you would use a smaller type like a uint64 or uint128 is a more advanced subject. Just stick to uint256 for now.

The Boolean type

This one is pretty obvious, it’s just like other languages. A bool variable holds either a true or a false. That’s it.

Setup your Environment

If you have not yet set up cURL on your system you can visit the following link to do so:

cURL setup

You should see output that looks like the following

What exactly is “forge” and “foundry” here?

You can think of it like gulp or webpack for javascript or maven for java or tox for python. Foundry is a development framework to make testing, development, and deployment easier. Without a doubt, it is the most popular framework in 2023, and absolutely worth knowing as a Solidity developer.

One thing that is really cool about it is that you can write unit tests in Solidity, so that makes testing easier. Previous tools used javascript, which forced context switching between languages and made casting types a little bit tricky.

VS Code Extension

If you haven’t already downloaded the following extension, you should!

screenshot of the solidity vscode extension

Arithmetic

 

Arithmetic in Solidity behaves exactly the same as in other languages, so we won’t belabor the point here.

You can add numbers this way

Exponents are the same as in other c like langauges.

And so is the modulus

Subtracting, multiplying, and dividing are obvious, so I won’t insult your intelligence by teaching you how to do them.

Solidity does not have floats

If you try to divide 5 by 2, you won’t get 2.5. You’ll get 2. Remember, unit256 is an unsigned Integer. So any division you do is integer division.

But what if you really want to know what 10% of 200 is? That seems very reasonable for, say, calculating interest.

 

The solution to this is to convert x * 0.1 into x * 1 / 10. This is valid and will produce the correct answer.

 

If your interest was some amount like 7.5%, then you would need to do the following

 

If you wanted to know the percentage population of a city relative to a nation, you cannot do the following.

This requires a more advanced solution we will describe later.

Note: Why doesn’t solidity support floats? Floats are not always deterministic, and blockchains must be deterministic otherwise nodes won’t agree on the outcomes of transactions. For example, if you divide 2/3, some computers will return 0.6666, and others 0.66667. This disagreement could casue the blockchain network to split up! Therefore, solidity does not allow floats.

Solidity does not underflow or overflow, it stops the execution

What happens if you try to do the following?

What happens if x is 2 and y is 5? You won’t get negative 3. Actually, what happens is the execution will halt with a revert.

Solidity doesn’t throw exceptions, but you can think of a revert as the equivalent of an uncaught exception or a panic in other languages.

It used to be the case solidity would allow overflows and underflows, but this lead to enough smart contracts breaking or getting hacked that the language built overflow and underflow protection into the language. This feature was added after Solidity version 0.8.0.

You’ve probably noticed by now a lot of solidity files have a line

This means that the source code is compiled with version 0.8.0 or later. If you see a version earlier than that, then you cannot assume overflow protection is built into the code.

If you want to allow underflow and overflow, you need to use an unchecked block

You can use an unchecked block to allow underflow and overflow. This is not recommended unless you have a very good reason to do so. An unchecked block can be used like this:

Note that anything inside the unchecked block will not revert even if it overflows or underflows. This is a very advanced feature that you should not use unless you know what you are doing.

Practice Problems

Add

Divide

Exponent

Unchecked

If Statements

 

If statements behave exactly the same as other languages

 

The argument inside the if statement must be a boolean. Note that our code above is equivalent to the following.

 

Unlike dynamic languages such as Python or javascript, you cannot do the following

 

Solidity also supports the “else if” construction, but we will assume you are already familiar with what that looks like.

 

Solidity does not have a switch statement like Java and C do.

Practice Problems

IfStatement

For Loops

 

Just like if statements, there is nothing surprising about for loops. Here is the code to add up all the numbers from 1 to 99

 

Solidity also supports the += operator if you prefer to do it that way.

 

Solidity also has while loops and do while loops but these are so rarely used that it isn’t worth mentioning them at this point.

 

A very natural usecase for for loops is iterating over an array. But we haven’t introduced arrays yet, so we’ll explain it at that point. 

 

Like other languages, you can do an early return from a function inside a for loop. This code will loop from 2 to the number until it finds a prime factor.

Practice Problems

IsPrime

Fibonacci

Introduction to arrays and strings

 

In this section we will introduce the array data structure and the string data structure. These behave differently from the solidity datatypes we discussed earlier, so we will discuss them here.

Syntax for declaring arrays

Let’s look a function that takes an array and returns an array. There is quite a bit to unpack here!

First, it should be clear that the syntax for declaring an array of numbers is uint256[]. We’ll get to “calldata” and “memory” in a moment.

 

If you wanted an array of addresses or booleans, it would be the following:

 

So what is this calldata and memory bit? First off, if you don’t include them, the code won’t compile. Here are two examples of code that doesn’t compile.

 

So what is calldata and memory?

If you are familiar with C or C++, this concept will be intuitive. Memory in solidity is like the heap in C, C++, or Rust. Arrays can have unlimited size, so storing them on the execution stack (don’t worry if you don’t know what that is), could lead to a stackoverflow error (not to be confused with the famous forum!).

Calldata is something unique to solidity. It is the actual “transaction data” that is sent when someone transmits a transaction to the blockchain.

Calldata means “refer to the data in the Ethereum transaction itself.” This is a fairly advanced concept, so don’t worry if you don’t fully understand it for now.

When in doubt: the function arguments for arrays and strings should be calldata and the function arguments for the return type should be memory.

There are some exceptions to using “calldata” in a function argument, but the return type for an array should always be memory, never calldata, or the code won’t compile. To avoid bombarding you with information, we will talk about the exceptions to calldata later.

Here is how to use arrays of numbers with Remix.

how to enter an array of numbers into the remix ide

Arrays are zero indexed like every other language

No surprises here.

 

Note that the return type is uint256, because we are returning a number, not an array.

Note that if the array was empty, the transaction will revert.

To get the length of an array, use .length

This is the same as javascript.

 

This is also how you can loop over an array.

Arrays can be declared to have a fixed length

In the previous examples, the square brackets had nothing inside of them during declaration. If you want to force an array to have a fixed size, you can put the size inside of the square brackets.

 

If the function is passed an array of any size other than 5, it will revert.

Strings

Strings behave very similar to arrays. In fact, they are arrays under the hood (but with some differences). Here is a function that returns the string you passed it.

 

And here is hello world finally.

 

Concatenating strings

Funnily enough, solidity did not support string concatenation until February 2022 when Solidity 0.8.12 was released. If you want to do string concatenation in solidity, make sure the pragma at the top of the file is at least 0.8.12

 

There is a reason support for concatenation was added so late, smart contracts usually deal with numbers, not strings.

Strings cannot be indexed

In languages like javascript or python, you can index a string like you would an array and get a character back. Solidity cannot do this. The following code won’t compile

Strings do not support length

Solidity does not support getting the length of a string. This is because unicode characters can make the length ambiguous, and solidity represents strings as a byte array, not a sequence of characters.

What we’ve left out

  • Arrays in solidity support operations like pop(), but this has side-effects which are more advanced, so we will teach this later.

  • Declaring arrays and strings inside a function, as opposed to in the argument or return value, has a different syntax

 

Practice Problems

FizzBuzz

SumArray

FilterOddNumbers

IsSorted

Mean

Nested Arrays

 

Nested arrays are rarely used in practice, but we include them here for the sake of completeness.

Nested arrays, as the name suggests, refer to arrays that are contained within another array.

 

In this example, the function is receiving a rectangular grid.

 

Here it is running in remix.

enter a nested array of numbers in a solidity function in the remix ide

 

You can also get a 1D array from a 2D array

 

To declare arrays of a fixed size, use the following syntax

 

What may be confusing is that when you access a specific item in an array, the order may feel backwards from other languages, but it makes sense if you think about it.

 

Just like 1D arrays, if you access an out-of-bound area, the transaction will revert.

Note that nested arrays are extremely rare in practice. If you feel like skipping this section, feel free to.

Problems

NestedArray

TicTacToe

Storage Variables

 

Up until this point, all of our functions have just returned values that purely depend on the function arguments. They do not depend on anything other than the immediate input. That’s why they are called pure functions. They are not aware of the blockchain state or anything that has happened in the past.

This would be quite problematic if we were keeping track of something, like how much money is owed to a certain person, or how many points they have in a game.

Now we introduce the storage variable.

 

These look like “class variables” in other languages, but don’t really behave like them. You can think of them as variables that behave like a miniature database.

Let’s look at an example

 

We have a lot to unpack here!

Variables declared outside of functions are storage variables. They keep their value after the transaction ends.

Note that getX() has the modifier view instead of pure. That’s because it views the blockchain state, I.e. what is stored in the variable x. If you change view to pure in this example, the code will not compile. You can also think of view as read-only. Also note that the return value of getX has the same type as x, both are uint256.

Second, note that setX does not have a view or a pure modifier. That’s because it is a state changing function. Functions that change storage variables, or make some other lasting change to the blockchain cannot have the view or pure modifier, this is because they are not read only and thus cannot be labelled as view, and certainly not pure.

To enforce the point, note that the following code is invalid

 

Note that the variable x itself has the modifier internal. This means other smart contracts cannot see the value.

Just because a variable is internal does not mean it is hidden. It’s still stored on the blockchain and anyone can parse the blockchain to get the value!

This is where things get confusing.

The following code is also valid, but it’s considered bad practice.

 

In this case, we removed the internal modifier to x, and it still compiles. This is considered bad practice because you aren’t being explicit about your intentions for the visibility of X.

The following code is also valid

 

When a variable is declared public, it means other smart contracts can read the value but not modify it.

This is confusing because public functions can modify variables, but public variables cannot be modified unless there is a function to change their value.

Summary

  • Storage variable are declared outside of functions

  • Public functions that do not have a view or pure modifier can change storage variables

  • Pure functions cannot access storage variables

Arrays in storage

 

You may have noticed in our section about arrays we curiously left off

  • writing to indexes in the array

  • appending to an array

  • popping from an array

 

This is because you very rarely do that to arrays that are supplied as function arguments.

However, when arrays are in storage, this operation is more common.

Here’s some example code

 

I recommend you copy and paste this code into remix so you can gain an intuition for what is happening.

Call setArray with [1,2,3,4,5,6]

Now call getLength() it returns 6, which is the length of the array.

Now call addToArray with argument 10. Call getLength() again. Now it returns 7.

Call removeFromArray() followed by getLength(). It now returns 6 as you expect.

Because myArray is public, Remix shows it as visible as a function. But it will not return the entire array. It will ask for an index and return the value at that index. The myArray function behaves like this

It is worth noting that because myArray is public, Remix shows it as visible as a function. This means that the compiler will automatically generate a function called myArray() that can be called to read the values stored in myArray.

solidity does not return an array, it can only return the index you ask for. Screenshot of the remix ide.

 

But it will not return the entire array. It will ask for an index and return the value at that index. The myArray function behaves like this

 

However, the function getEntireArray() returns the entire array.

Note that pop() does not return the value.

Removing an item

Solidity does not have a way to remove an item in the middle of a list and reduce the length by one. The following code is valid, but it does not change the length of the list.

 

If you want to remove an item and also reduce the length, you must do a “pop and swap”.

It removes the element at the index argument and swaps it with the last element in the array

 

Solidity cannot delete from the middle of the list and preserve the array’s original order.

Strings

Strings behave similarly to arrays, except when they are public they return the entire string, because strings cannot be indexed (confusing, isn’t it?). There is no pop or length operation for strings.

Practice Problems

ListOfNumbers

InsertInArray

Stack

Mappings

 

Mapping, Hashmap, associative array, maps, whatever you want to call it, solidity has it.

We’ll call it “mapping” because that’s the keyword Solidity uses. Let’s see an example.

 

This does what you think it does. Because myMapping is public, solidity wraps it with a getter function you can direclty access the values with. However, if you want to access the map through a function, you can follow the pattern in getValue.

Here is the first surprising thing

If you access an mapping with a key that has not been set, you will NOT get a revert. The mapping will just return the “zero value” of the datatype for the value

In the following example, the mapping will return false if you supply a number that hasn’t been set.

 

I encourage you to paste this code into remix, then plug in numbers for the key to see the zero values that come back.

Mappings

 

By the way, ERC20 tokens use mappings to store how many tokens someone has! They map an address to how many tokens someone owns.

 

This implementation has a flaw that anyone can invoke the public functions and send tokens between addresses willy-nilly, but we’ll fix that later.

Counterintuitively, ERC20 tokens are not stored in cryptocurrency wallets, they are simply a uint256 associated with your address in a smart contract. We think of as “ERC20 tokens” are simply a smart contract.

Here is the smart contract for USDC, an ERC20 token: https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48

And here is the token for ApeCoin, the currency of the Bored Ape Yacht Club ecosystem

https://etherscan.io/token/0x4d224452801aced8b2f0aebe155379bb5d594381

Surprise 1: Mappings can only be declared as storage, you cannot declare them inside a function

This may seem like a very odd restriction, but this has to do with how the Ethereum Virtual machine works. Blockchains in general don’t like hashmaps because of their unpredictable runtime. The following code is invalid.

Surprise 2: mappings cannot be iterated over

There is no way to iterate over the keys of a mapping. Every key is technically valid, it just defaults to zero.

Surprise 3: mappings cannot be returned

The following code is invalid. Maps are not a valid return type for solidity functions.

 

Problems

SpecialNumbers

Nested Mappings

 

In most languages, a hashmap can contain another hashmap, and solidity does this too. However, because mappings are not valid return types, you must supply all the keys the maps require.

Let’s look at an example

 

Nested maps are quite common in smart contracts, unlike nested arrays. For example, you can do bookkeeping like this

 

Note that the order matters here. In this construction, one lender can have several borrowers. If we had set borrower to be the first key, it would imply a borrower might have mutiple debtors.

The same restrictions that apply to regular mappings apply to nested mappings. You cannot iterate over the keys, declare them inside a function, or return them from a function.

Public Nested Mappings Don’t Work

Here’s yet another strange quirk of solidity. Solidity automatically creates getting functions for variables when you declare them as public. However, it the public getter functions allow you to supply the necessary arguments.

Yes. You read that right.

The solution is to make nested mappings private and wrap them in a public function that gets their value. Time to practice this!

Practice Problems

NestedMapping

TripleNestedMapping

msg.sender and address(this)

Remember our previous example of a bad ERC20 token?

Here it is again

 

The issue is that we have no idea who is calling the function.

Luckily, solidity has a mechanism to identify who is calling the smart contract: msg.sender. Msg.sender returns the address of who is invoking the smart contract function.

Try out the following code in remix

 

It will return the test address you are using in remix.

Now change the test address by hitting the “ACCOUNT” dropdown. Then try the function again. The address returned will be different.

msg.sender

 

By combining msg.sender with an if statement, you can give certain addresses special privileges.

Let’s say we want the default address in remix to be the special address.

 

The code above lets people view their balances (because balances is a public variable), but only the banker can change it.

By being a little bit clever, we can actually allow people to transfer their balance to someone else without the banker doing it for them. Consider the following example.

 

The function transfer can be called by anyone. However, it can only debit (deduct) balances from msg.sender. As an exercise for the reader, I encourage you to think about why it is impossible to steal someone else’s balance using transfer.

A natural question is, what happens if someone tries to send more amount than they have balance for? If you are using Solidity 0.8.0 or higher, nothing happens. The transaction reverts because you cannot subtract an unsigned number such that it becomes negative.

tx.origin

There is another mechanism to get the sender, tx.origin. Although it behaves similarly to msg.sender, you should not use it. To avoid bombarding you with too much information right now, we won’t explain the security issues around tx.origin yet. But the important point is, do not use tx.origin except in very specific circumstances.

address(this)

A smart contract can know its own address with the following code


Try it out in Remix and see the address matches
 

a screenshot in remix of a smart contract returning its own address

 

Problems

WhoCalledMe

Constructor

 

Going back to our rolling ERC20 example, we did something a little weird, we set the banker variable directly in the contract.

 

That’s okay, but what if someone wants to deploy the contract and set themselves to be the banker?

Smart contracts have a special function that is called at deployment time called the constructor. This is pretty similar to other object oriented programming languages. Here is what it looks like

 

Note that it’s “constructor()” and not “function constructor()” and we don’t specify public because constructors can’t be modified with things like pure, view, public, and so forth.

If you wanted the banker to be configured by the person deploying the contract, then you could use it as a function argument.

 

By the way, you’ll see this pattern variable = _variable a lot in constructors. Solidity doesn’t require you to do that, but it’s considered conventional.

When deploying a contract on Remix that has constructor arguments, you’ll have to put the arguments into the box that appears next to “deploy.”

solidity constructor arguments in remix

Unlike other functions, calldata cannot be used for arrays and strings, you must use memory

Again, for reasons we cannot get into right now, calldata cannot be used in constructor arguments. I know, it seems like a very weird and random restriction, but it will make sense later, after you understand how Ethereum works under the hood.

Here is how you would set a string during construction time

 

You may be tempted as a response to just use memory everywhere and not bothering to use calldata. But it is worth trying to remember this for now, because calldata results in cheaper transactions (I.e. lower gas fees for the user).

Also, in case you were wondering, constructors cannot return values.

Problems

Deployer

Require

 

There’s just one more essential solidity key word, and then we are ready to create our own ERC20 token.

Although we can use an if statement to check if inputs to a function are valid, or the correct msg.sender is calling the function, the elegant way is to use the require statement. The require statement forces the transaction to revert if some condition is not met.

 

Try the above code out in remix.

Note that leaving out the error message is valid, but considered bad practice, because it makes understanding the failure harder.

 

You can use this construction to ensure msg.sender is who they are supposed to be. But you can practice that in the following problems.

Practice Problems

NotEnough

Owner

ERC20 Tokens

 

We are now ready to make an ERC20 token!

ERC20 tokens typically have a name and a symbol. For example, ApeCoin has the name “ApeCoin” but the symbol “APE.” The name of the token generally doesn’t change, so we’ll set it in the constructor and not provide any functions to change it later. We’ll make these variables public so that anyone can check the name and symbol of the contract.

 

Next, we need to store everyone’s balances.

 

We say “balanceOf” because that is part of the ERC20 specification. ERC20 as a specification means that people can call the function “balanceOf” on your contract, supply an address, and get how many tokens that address owns.

Everyone’s balance is zero right now, so we need a way to bring tokens into existence. We’ll allow a special address, the person who deployed the contract, to create tokens at will.

 

It is general practice that the function mint() takes the to and amount as the parameter argument. It allows for the contract deployer to mint tokens to other accounts. For the sake of simplicity the function mint() only allows the deployer of the mint tokens into his account.

To keep track of how many tokens there are in existence, the ERC20 specification requires a public function or variable called totalSupply that tells us how many tokens have been created.

 

If you’ve used ERC20 tokens in your wallet, no doubt you’ve seen instances where you have a fraction of the coin. How does that happen when unsigned integers have no decimals?

The largest number a uint256 can represent is

115792089237316195423570985008687907853269984665640564039457584007913129639935

Let’s reduce the number a bit to make it more clear

10000000000000000000000000000000000000000000000000000000000000000000000000000

To be able to describe “decimals”, we say the 18 zeros to the right are the fractional part of the coin.

10000000000000000000000000000000000000000000000000000000000.000000000000000000

Thus, if our ERC20 has 18 decimals, we can have at most

10000000000000000000000000000000000000000000000000000000000

Full coins, with the zeros to the right being decimals. That’s 10 octodecillion coins, or for those unfamiliar with such uselessly large numbers, that’s 1 quadrillion x 1 quadrillion x 1 quadrillion x 1 trillion.

Which should be enough for most applications, even countries that go into hyperinflation.

The “units” of the currency are still integers, but the units are now very small values.

18 decimal places is pretty standard, but some coins use 6 decimal places.

The decimal of the coin should not change, it’s just a function that returns how many decimals the coin has.

 

If you are paying attention, I did throw a curve ball at you here. The number type is uint8, not uint256. uint8 can only represent numbers up to 255. However, a uint256 has 77 zeros (if you feel like counting the zeros of the numbers above, you can verify this). Thus, it isn’t possible to have more than 77 decimal places if you want to have one whole coin. So the standard dictates we use a uint8 since the number of decimals can never be very large.

Transfer

Now let’s add our transfer function back.

 

Aha, we snuck in an extra line of code there: require(to != address(0), “cannot send to address(0))

Why is this? Well, nobody “owns” the zero address, so tokens sent there are un-spendable. By convention, sending a token to the zero address should reduce the totalSupply so we want to have a separate function for that.

Now we introduce a concept of allowance.

Allowance

Allowance enables an address to spend someone else’s tokens, up to a limit that they specify.

Why would you allow someone to spend tokens for you? This is a very long story, but to summarize, think about how you would “know” someone transferred you ERC20 tokens. All that happens is a function gets executed and a mapping changed values. You didn’t “receive” the tokens, they just became associated with your address.

Now, as an entity outside the blockchain, you can inspect it for events that make you richer.

However, smart contracts cannot do that.

The established pattern for smart contracts to be recipients of transfers is to allow the smart contract to have a certain allowance, then tell that smart contract to withdraw the balance from your account.

When you want to transfer tokens to a smart contract, the typical method is to first approve the smart contract to withdraw a certain amount of tokens from your account. Then, you instruct the smart contract to withdraw the approved amount of tokens from your account. This is a common pattern used in smart contracts to enable token transfers to the contract.

Let’s add the tracker for allowance, and a way to give allowance to another user.

 

In the line allowance[msg.sender][spender] = amount;, spender refers to the address of the account that is being granted the allowance by the msg.sender. The msg.sender is giving permission to the spender to spend a certain amount of tokens from their account.

Therefore, msg.sender is the owner of the tokens and spender is someone who has been approved by the owner to spend a certain amount of tokens on their behalf.

Ah, but we don’t have a way to actually use the allowance given, it just sits there! That’s what transferFrom is for.

transferFrom

 

Let’s unpack what we just did here.

First, it is possible for the owner of the coin to call transferFrom. In that case, allowance is meaningless, so we don’t bother checking the allowance mapping, and update the balances accordingly.

Otherwise, we check to see the spender has been given enough allowance, then subtract the amount they are spending. If we didn’t subtract their spending, we would have unlimited spending power.

There is one more cleanup to do. If we read the original specification for EIP 20 it says that approve, transfer, and transferFrom must return true after they succeed. So let’s add that.

 

At the risk of throwing too much information at you, there is a cleanup to this code we can do. Note that transferFrom and transfer have duplicate code in them. What can we do about that? We could factor out the balance update code into a separate function, but we need to make sure that function isn’t public or someone can steal coins!

 

Much cleaner!

Practice Problems

  • Modify the above code so that doesn’t allow more than 1 million tokens to enter circulation, even if the owner tries to mint more

Tuples

 

We’re going to go off on a slight tangent here to introduce the tuple data type, because it is a prerequisite for upcoming sections.

If you’ve used tuples in a language like Python or Rust, no surprises here. It’s an array of fixed size, but the types inside of it can be a mixture.

Here’s an example of a function that returns a tuple

 

Note that tuples are implied. The keyword “tuple” never appears in solidity.

Tuples can also be “unpacked” to get the variables inside, like in the following example.

 

As with other languages, tuples do not need to be of length 2. It can be 3, 4 or even longer.

Practice Problems

Tupledore

Application Binary Interface Encoding (abi encoding)

 

We’re going to have to go on what seems like another random tangent before we can introduce our next piece of information.

But I want you to understand what the following things are

abi.encode

abi.decode

abi.encodeWithSignature

To motivate them, let’s create another smart contract, open the “debug” dropdown, and get a certain piece of information.

debug smart contract transaction in the remix ide

 

When we copy that, we get

0x92d62db5

What exactly is this? This is the function signature of “meaningOfLifeAndAllExistence()”. We’ll learn later how this is derived.

Whenever you “call” a smart contract, you are actually sending an ethereum transaction with some data attached so the smart contract knows which function to execute.

Let’s look at the information from another vantage point.

remix ide showing the return value of a solidity function

 

We’ve changed the return type to “bytes memory” (don’t worry that we haven’t seen that before), and returned a variable called msg.data (also don’t worry that we haven’t seen that before).

The important thing to note is that we get an identical byte sequence back!

So what is happening?

When you call a function in a smart contract, you aren’t actually doing a “function call” per se, you are sending data to the contract with some information about which function should be executed.

Makes sense right? When you fire up your browser wallet and trade ERC20 tokens, there’s no way you can “call a function” on an ERC20 contract remotely. Function calls only happen inside the same execution context. Describing transactions as functions however, is convenient. But we need to look behind the curtain to see exactly what is happening to really understand Solidity.

When you “call a smart contract” you are sending data to the contract with instructions for how to execute.

There are many data encodings, json, xml, protobufs, etc. Solidity and ethereum use the ABI encoding.

We won’t get into the specification of ABI here. But what you need to know is that it always looks like a sequence of bytes.

Functions are identified as a sequence of four bytes. Our original byte sequence (0x92d62db5) had four bytes in it: 92, d6, 2d, b5.

Remember, a byte is 8 bits, and 8 bits can be a value up to 255 (2^8 - 1). A byte, represented in hex, can go from 0x00 to 0xff. Convert 0xff to decimal, and hopefully this makes it clear.

When a function takes no arguments, sending four bytes that represent the function instructs the smart contract to execute that function.

But what would the data look like if it took an argument?

solidity msg.data containing a function argument

 

We get

0xf8689fd30000000000000000000000000000000000000000000000000000000000000007

returned to us. The f8689fd3 portion means call function “takeOneArg” and the 7 with a lot of leading zeros means pass the number 7.

It would be horribly confusing if we had to do this by hand.

Thankfully we don’t.

Watch this.

solidity function using abi.encodeWithSignature

 

We don’t need to concern ourselves with the specification of ABI encoding now, let’s just get comfortable using it.

 

Consider the following example.

how to conveniently use a solidity function in the remix ide that takes two arguments. The dropdown is highlighted.

 

Note that we are using “abi.encode” and “abi.decode”. The “withSignature” bit is when functions are involved, but that is not the case here.

In this example, the variables x and y are abi encoded into

0x0000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000f

The decimal numbers got converted to hex, which is why “5” is still “5” but 15 became “f”.

If we know in advance that this is a pair of uint256, we can “decode” it back to a pair using the function screenshotted above.

The tuple that appears as the second argument in abi.decode is instructions for how to decode the data. If you provide the wrong datatypes or the wrong length of tuple here, you’ll either get the wrong result, or the code will revert.

Problems

Encode

Decoder

Calling other contracts

 

Everything we’ve been doing up to this point is calling smart contracts directly. But it’s also possible, and in fact, desirable, for smart contracts to be able to communicate with each other.

Let’s give a minimum example of this.

 

Here’s how you can see it in action

remix ide showing the use of two smart contracts

Because we reviewed tuples, abi encoding, and bytes memory already the only surprising ting here should be call and the fact that askTheMeaningOfLife() is not a view function.

Why isn’t askTheMeaningOfLife() a view function? If you try compiling it with the view modifier, it won’t compile.

View functions are read only. When you call the function of an arbitrary smart contract, you can’t know if it is read-only or not. Therefore, solidity doesn’t let you specify a function as view if it calls another smart contract.

Also, although we can see that meaningOfLifeAndAllExistence in AnotherContract returns a uint256, we can’t know that in the general case. It could return a string.

Functions always return abi encoded bytes. How does remix know to format strings as strings and numbers as numbers? Behind the scenes, it is doing the abi.decode operation that we are doing here.

What is the bool ok portion of the tuple? Function calls to other smart contracts can fail, for example if the function reverts. To know if the external call reverted, a boolean is returned. In this implementation, the calling function, askTheMeaningOfLifeAndAllExistence, reverts also, but that is not necessarily a general requirement.

Here’s something interesting. What happens if you call a non-existent smart contract?

Try askTheMeaningOfLife(address source) with 0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db as the argument.

Don’t be lazy, try it out in the code above!

It reverts, but that’s not because the address isn’t there, but because you tried to decode empty data. If we comment out the decoding part, then the function no longer reverts when we call a non-existent address.

When you open the transaction dropdown in remix, you see the revert explanation here.

Calling other contracts

 

What if the other contract takes arguments? Here is the code to do it.

 

Be careful to not have spaces in "add(uint256,uint256)"

Ready to put this into practice?

Practice Problems

CrossContract

Token Exchange mini project

 

This is challenging

Believe it or not, you now have enough prerequisite knowledge to build a very simple token exchange smart contract! Here is your mission.

Build two ERC20 contracts: RareCoin and SkillsCoin (you can change the name if you like). Anyone can mint SkillsCoin, but the only way to obtain RareCoin is to send SkillsCoin to the RareCoin contract. You’ll need to remove the restriction that only the owner can mint SkillsCoin.

Here is the workflow

  • mint() SkillsCoin to yourself

  • SkillsCoin.approve(address rareCoinAddress, uint256 yourBalanceOfSkillsCoin) RareCoin to take coins from you.

  • RareCoin.trade() This will cause RareCoin to SkillsCoin.transferFrom(address you, address RareCoin, uint256 yourBalanceOfSkillsCoin) Remember, RareCoin can known its own address with address(this)

  • RareCoin.balanceOf(address you) should return the amount of coin you originally minted for SkillsCoin.

 

Remember ERC20 tokens(aka contract) can own other ERC20 tokens. So when you call RareCoin.trade(), it should call SkillsCoin.transferFrom and transfer your SkillsCoin to itself, I.e. address(this).

If you have the SkillsCoin address stored, it would look something like this

 

Deploy these contracts in Remix and test that they work.

If you are new to Solidity, set aside a couple days for this. A lot of engineers get weirded out by the fact that balances are stored in smart contracts, not wallets, so it takes a bit of time to get used to this. Also, trust me, you will get confused by the cross contract calls.

Payable Functions

 

Up until this point, we’ve been using tokens to represent value, but what about Ether? Let’s introduce how a smart contract can interact with Ether.

 

After you deploy this contract on Remix, you’ll note that the button for interacting with payMe turns Red. This means you can specify in the field above how much value (Ether) to send when you call the function.

sending ethereum to a payable function in a smart contract in the remix ide

 

You can send Ether in units of Wei, Gwei, Finney, or Ether.

One Wei is 1/10^18 Ether, One gwei is one billionth of an Ether, and one Finney is one tenth.

Let’s make this simple and just send one Ether.

When we click “howMuchEtherIHave” we actually get back

1000000000000000000

This doesn’t mean we created a ton of Ether out of thin air. Remember floats are not a thing on the blockchain, so Ether uses the same strategy for decimals that ERC20 tokens do. A unit of Ether is actually one Wei, and what we traditionally think of as one Ether is 10^18 Wei.

By the way, the .balance construction works on arbitrary addresses. A smart contract can determine how rich you are with the following function

 

10000000000000000000

Unless functions have the payable modifier, they will revert if they receive Ether.

Why have this construction? If someone wants to send us Ether, why not accept it?

This has been a subject of debate, but the general idea is that a function should constrained in such a way to have extremely well defined behavior. Anything outside of that should be restricted. The more constrained the behavior, the easier it is to reason about the smart contract’s functionality.

By the way, solidity provides a very convenient keyword for dealing with all the zeros involved with Ether. Both of these functions do the same thing, but one is more readable.

 

It is also valid to make a constructor payable, if you want your smart contract to begin life with privilege and a headstart. But you still need to explicitly send ether at construction time.

Just because a function is payable does not mean that the person calling the function has to send Ether.

Sending Ether

It’s clear how to send Ether if you initiate the transaction from Remix, but what if another smart contract wants to send Ether?

You will use the **call fun