
Solidity Course
Who is this for?
This is for experience 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 Solidity 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). If you aren’t already experienced in another programming language, this is not the course for you.
Get ready to experience the learning efficiency of RareSkills!
Why this course is superior (besides being free)
Courses that were created a long time ago use older versions of solidity.
As of Solidity 0.8.19 (released February 22, 2023), our course 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.
Solidity, as a language is not difficult to learn. It looks a lot like javascript (or Dart, for those of you who know that language). This course just focuses on the parts that might be unexpected or unfamiliar. We won’t go on and on about how for loops and if statements. This course 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 course is not appropriate for you. It’s for experienced coders to get up to speed in Solidity quickly.
An important part of the course is you will spend more time coding that reading material 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 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.
Don’t be fooled by the fact that this course is free. This is not low-effort content. This course 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 other courses 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 distiction 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 Java does not make you an Android developer. Simliarly, knowing Solidity does not make you a 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 course, 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
Getting Started
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.
TODO: Add Code Snipped
After you go to remix.ethereum.org, right click contracts and left click “New File”

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

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

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.

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

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

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

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

You are now ready to experiment with Solidity smart contracts!
Fixed Size Datatypes: Solidity is a typed language.
Basic Types
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.
TODO: add code snippet
In these examples, we assigned the value to a variable and then returned it. We can of course directly return the value like so.
TODO: add code snippet
It’s very important that the function signature matches the return type. The following code will produce an error
TODO: add code snippet
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 characterrs, it won’t compile.
Note that the 40 characters does not include the leading 0x.
uint256
Let’s revisit uint256 what exaclty 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.
TODO: add code snippet
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
TODO: add code snippet
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 uin64 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.
Practice Problems
Environment setup
If you have not yet set up cURL on your system you can visit the following link to do so:
TODO: add code snippet
You should see output that looks like the following
TODO: add code snippet
It’s very important that the function signature matches the return type. The following code will produce an error
TODO: add code snippet
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!

Arithmetic
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
TODO: add code snippet
Exponents are the same as in other c like langauges.
TODO: add code snippet
And so is the modulus
TODO: add code snippet
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.
TODO: add code snippet
The solution to this is to convert x * 0.1 into x * 1 / 10. This is valid and will produce the correct answer.
TODO: add code snippet
If your interest was some amount like 7.5%, then you would need to do the following
TODO: add code snippet
If you wanted to know the percentage population of a city relative to a nation, you cannot do the following.
TODO: add code snippet
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?
TODO: add code snippet
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
TODO: add code snippet
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:
TODO: add code snippet
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.
If Statements
If statements behave exactly the same as other languages
TODO: add code snippet
The argument inside the if statement must be a boolean. Note that our code above is equivalent to the following.
TODO: add code snippet
Unlike dynamic languages such as Python or javascript, you cannot do the following
TODO: add code snippet
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
For Loops
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
TODO: add code snippet
Solidity also supports the += operator if you prefer to do it that way.
TODO: add code snippet
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.
TODO: add code snippet
Introduction to arrays and strings
Unlimited data structures
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.
TODO: add code snippet
If you wanted an array of addresses or booleans, it would be the following:
TODO: add code snippet
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.
TODO: add code snippet
TODO: add code snippet
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, well talk about the exceptions to calldata later.
Here is how to use arrays of numbers with Remix.

Arrays are zero indexed like every other language
No surprises here.
TODO: add code snippet
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.
TODO: add code snippet
This is also how you can loop over an array.
TODO: add code snippet
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.
TODO: add code snippet
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.
TODO: add code snippet
And here is hello world finally.
TODO: add code snippet
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
TODO: add code snippet
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
TODO: add code snippet
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.
TODO: add code snippet
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
Nested Arrays
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.
TODO: add code snippet
Here it is running in remix.

You can also get a 1D array from a 2D array
TODO: add code snippet
To declare arrays of a fixed size, use the following syntax
TODO: add code snippet
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.
TODO: add code snippet
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
Storage Variables
Storage
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
TODO: add code snippet
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
TODO: add code snippet
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.
TODO: add code snippet
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
TODO: add code snippet
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
Storage arrays
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
TODO: add code snippet
Here it is running in remix.

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
TODO: add code snippet
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.
TODO: add code snippet
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
TODO: add code snippet
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.
TODO: add code snippet
Mappings
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.
TODO: add code snippet
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 array 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.
TODO: add code snippet
I encourage you to paste this code into remix, then plug in numbers for the key to see the zero values that come back.

By the way, ERC20 tokens use mappings to store how many tokens someone has! They map an address to how many tokens someone owns.
TODO: add code snippet
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.
TODO: add code snippet
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.
TODO: add code snippet
Surprise 3: mappings cannot be returned
The following code is invalid. Maps are not a valid return type for solidity functions.
TODO: add code snippet
Problems
Nested Mappings
Nested Mapping
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
TODO: add code snippet
Nested maps are quite common in smart contracts, unlike nested arrays. For example, you can do bookkeeping like this
TODO: add code snippet
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
msg.sender and address(this)
Msg.sender
Remember our previous example of a bad ERC20 token?
Here it is again
TODO: add code snippet
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
TODO: add code snippet
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.

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.
TODO: add code snippet
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
TODO: add code snippet
Try it out in Remix and see the address matches

Constructor
Constructor
Going back to our rolling ERC20 example, we did something a little weird, we set the banker variable directly in the contract.
TODO: add code snippet
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
TODO: add code snippet
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.
TODO: add code snippet
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.”

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.
TODO: add code snippet
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
TODO: add code snippet