0% found this document useful (0 votes)
117 views

Solidity Notes

The document discusses various concepts in Solidity including events, mappings, msg.sender, require statements, inheritance, storage vs memory, internal and external functions, Ownable contracts, constructors, function modifiers, and gas optimization.

Uploaded by

Sahil Bansal
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
117 views

Solidity Notes

The document discusses various concepts in Solidity including events, mappings, msg.sender, require statements, inheritance, storage vs memory, internal and external functions, Ownable contracts, constructors, function modifiers, and gas optimization.

Uploaded by

Sahil Bansal
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 16

Events

Events are a way for your contract to communicate that something happened


on the blockchain to your app front-end, which can be 'listening' for certain
events and take action when they happen.

Mapping
In Lesson 1 we looked at structs and arrays. Mappings are another way of
storing organized data in Solidity.

Defining a mapping looks like this:

// For a financial app, storing a uint that holds the user's account balance:
mapping (address => uint) public accountBalance;
// Or could be used to store / lookup usernames based on userId
mapping (uint => string) userIdToName;

A mapping is essentially a key-value store for storing and looking up data. In


the first example, the key is an address and the value is a uint, and in the
second example the key is a uint and the value a string.

Msg.sender
In Solidity, there are certain global variables that are available to all functions.
One of these is msg.sender, which refers to the address of the person (or smart
contract) who called the current function.

Note: In Solidity, function execution always needs to start with an external


caller. A contract will just sit on the blockchain doing nothing until someone
calls one of its functions. So there will always be a  msg.sender.

Require statement
 require makes it so that the function will throw an error and stop executing if
some condition is not true:

function sayHiToVitalik(string memory _name) public returns (string memory) {


// Compares if _name equals "Vitalik". Throws an error and exits if not true.
// (Side note: Solidity doesn't have native string comparison, so we
// compare their keccak256 hashes to see if the strings are equal)
require(keccak256(abi.encodePacked(_name)) ==
keccak256(abi.encodePacked("Vitalik")));
// If it's true, proceed with the function:
return "Hi!";
}
If you call this function with sayHiToVitalik("Vitalik"), it will return "Hi!". If you
call it with any other input, it will throw an error and not execute.

Thus require is quite useful for verifying certain conditions that must be true
before running a function.

Inheritance:
One feature of Solidity that makes this more manageable is
contract inheritance:
contract Doge {
function catchphrase() public returns (string memory) {
return "So Wow CryptoDoge";
}
}

contract BabyDoge is Doge {


function anotherCatchphrase() public returns (string memory) {
return "Such Moon BabyDoge";
}
}

BabyDoge inherits from Doge. That means if you compile and deploy BabyDoge, it


will have access to both catchphrase() and anotherCatchphrase() (and any
other public functions we may define on Doge).

This can be used for logical inheritance (such as with a subclass, a Cat is
an Animal). But it can also be used simply for organizing your code by
grouping similar logic together into different contracts.

Storage vs Memory(storage location)


In Solidity, there are two locations you can store variables — in storage and
in memory.

Storage refers to variables stored permanently on the


blockchain. Memory variables are temporary, and are erased between
external function calls to your contract. Think of it like your computer's hard
disk vs RAM.

Most of the time you don't need to use these keywords because Solidity
handles them by default. State variables (variables declared outside of
functions) are by default storage and written permanently to the blockchain,
while variables declared inside functions are memory and will disappear when
the function call ends.
However, there are times when you do need to use these keywords, namely
when dealing with structs and arrays within functions:

contract SandwichFactory {
struct Sandwich {
string name;
string status;
}

Sandwich[] sandwiches;

function eatSandwich(uint _index) public {


// Sandwich mySandwich = sandwiches[_index];

// ^ Seems pretty straightforward, but solidity will give you a warning


// telling you that you should explicitly declare `storage` or `memory` here.

// So instead, you should declare with the `storage` keyword, like:


Sandwich storage mySandwich = sandwiches[_index];
// ...in which case `mySandwich` is a pointer to `sandwiches[_index]`
// in storage, and...
mySandwich.status = "Eaten!";
// ...this will permanently change `sandwiches[_index]` on the blockchain.

// If you just want a copy, you can use `memory`:


Sandwich memory anotherSandwich = sandwiches[_index + 1];
// ...in which case `anotherSandwich` will simply be a copy of the
// data in memory, and...
anotherSandwich.status = "Eaten!";
// ...will just modify the temporary variable and have no effect
// on `sandwiches[_index + 1]`. But you can do this:
sandwiches[_index + 1] = anotherSandwich;
// ...if you want to copy the changes back into blockchain storage.
}
}

Don't worry if you don't fully understand when to use which one yet —
throughout this tutorial we'll tell you when to use storage and when to
use memory, and the Solidity compiler will also give you warnings to let you
know when you should be using one of these keywords.

For now, it's enough to understand that there are cases where you'll need to
explicitly declare storage or memory!

Internal and external


In addition to public and private, Solidity has two more types of visibility for
functions: internal and external.

internal is the same as private, except that it's also accessible to contracts
that inherit from this contract. (Hey, that sounds like what we want here!).
external is similar to public, except that these functions can ONLY be called
outside the contract — they can't be called by other functions inside that
contract. We'll talk about why you might want to use external vs public later.

Ownable contracts
setKittyContractAddress is external, so anyone can call it! That means
anyone who called the function could change the address of the CryptoKitties
contract, and break our app for all its users.

We do want the ability to update this address in our contract, but we don't
want everyone to be able to update it.

To handle cases like this, one common practice that has emerged is to make
contracts Ownable — meaning they have an owner (you) who has special
privileges.

 Constructors:
constructor() is a constructor, which is an optional special function that
has the same name as the contract. It will get executed only one time,
when the contract is first created.

 Function Modifiers: 
modifier onlyOwner(). Modifiers are kind of half-functions that are used to
modify other functions, usually to check some requirements prior to
execution. In this case, onlyOwner can be used to limit access
so only the owner of the contract can run this function. We'll talk more
about function modifiers in the next chapter, and what that weird _; does.

 indexed keyword:
don't worry about this one, we don't need it yet.

So the Ownable contract basically does the


following:
1. When a contract is created, its constructor sets
the owner to msg.sender (the person who deployed it)
2. It adds an onlyOwner modifier, which can restrict access to certain
functions to only the owner
3. It allows you to transfer the contract to a new owner

onlyOwner is such a common requirement for contracts that most Solidity


DApps start with a copy/paste of this Ownable contract, and then their first
contract inherits from it.

Since we want to limit setKittyContractAddress to onlyOwner, we're going to do


the same for our contract.

Gas — the fuel Ethereum DApps run on


In Solidity, your users have to pay every time they execute a function on your
DApp using a currency called gas. Users buy gas with Ether (the currency on
Ethereum), so your users have to spend ETH in order to execute functions on
your DApp.

How much gas is required to execute a function depends on how complex that
function's logic is. Each individual operation has a gas cost based roughly on
how much computing resources will be required to perform that operation (e.g.
writing to storage is much more expensive than adding two integers). The
total gas cost of your function is the sum of the gas costs of all its individual
operations.

Because running functions costs real money for your users, code optimization
is much more important in Ethereum than in other programming languages. If
your code is sloppy, your users are going to have to pay a premium to execute
your functions — and this could add up to millions of dollars in unnecessary
fees across thousands of users

Why is gas necessary?


Ethereum is like a big, slow, but extremely secure computer. When you
execute a function, every single node on the network needs to run that same
function to verify its output — thousands of nodes verifying every function
execution is what makes Ethereum decentralized, and its data immutable and
censorship-resistant.

The creators of Ethereum wanted to make sure someone couldn't clog up the
network with an infinite loop, or hog all the network resources with really
intensive computations. So they made it so transactions aren't free, and users
have to pay for computation time as well as storage.
Struct packing to save gas
In Lesson 1, we mentioned that there are other types
of uints: uint8, uint16, uint32, etc.

Normally there's no benefit to using these sub-types because Solidity reserves


256 bits of storage regardless of the uint size. For example,
using uint8 instead of uint (uint256) won't save you any gas.

But there's an exception to this: inside structs.

If you have multiple uints inside a struct, using a smaller-sized uint when


possible will allow Solidity to pack these variables together to take up less
storage. For example:
struct NormalStruct {
uint a;
uint b;
uint c;
}

struct MiniMe {
uint32 a;
uint32 b;
uint c;
}

// `mini` will cost less gas than `normal` because of struct packing
NormalStruct normal = NormalStruct(10, 20, 30);
MiniMe mini = MiniMe(10, 20, 30);

For this reason, inside a struct you'll want to use the smallest integer sub-
types you can get away with.

You'll also want to cluster identical data types together (i.e. put them next to
each other in the struct) so that Solidity can minimize the required storage
space. For example, a struct with fields uint c; uint32 a; uint32 b; will cost
less gas than a struct with fields uint32 a; uint c; uint32 b; because
the uint32 fields are clustered together.

Time units
Solidity provides some native units for dealing with time.

The variable now will return the current unix timestamp of the latest block (the
number of seconds that have passed since January 1st 1970). The unix time
as I write this is 1515527488.
Note: Unix time is traditionally stored in a 32-bit number. This will lead to the
"Year 2038" problem, when 32-bit unix timestamps will overflow and break a
lot of legacy systems. So if we wanted our DApp to keep running 20 years
from now, we could use a 64-bit number instead — but our users would have
to spend more gas to use our DApp in the meantime. Design decisions!

Solidity also contains the time


units seconds, minutes, hours, days, weeks and years. These will convert to
a uint of the number of seconds in that length of time. So 1 minutes is 60, 1
hours is 3600 (60 seconds x 60 minutes), 1 days is 86400 (24 hours x 60
minutes x 60 seconds), etc.

Here's an example of how these time units can be useful:


uint lastUpdated;

// Set `lastUpdated` to `now`


function updateTimestamp() public {
lastUpdated = now;
}

// Will return `true` if 5 minutes have passed since `updateTimestamp` was


// called, `false` if 5 minutes have not passed
function fiveMinutesHavePassed() public view returns (bool) {
return (now >= (lastUpdated + 5 minutes));
}

We can use these time units for our Zombie cooldown feature.

Passing structs as arguments


You can pass a storage pointer to a struct as an argument to
a private or internal function. This is useful, for example, for passing around
our Zombie structs between functions.

The syntax looks like this:

function _doStuff(Zombie storage _zombie) internal {


// do stuff with _zombie
}

This way we can pass a reference to our zombie into a function instead of
passing in a zombie ID and looking it up.

View functions don't cost gas


view functions don't cost any gas when they're called externally by a user.
This is because view functions don't actually change anything on the
blockchain – they only read the data. So marking a function
with view tells web3.js that it only needs to query your local Ethereum node to
run the function, and it doesn't actually have to create a transaction on the
blockchain (which would need to be run on every single node, and cost gas).

We'll cover setting up web3.js with your own node later. But for now the big
takeaway is that you can optimize your DApp's gas usage for your users by
using read-only external view functions wherever possible.

Note: If a  view function is called internally from another function in the same


contract that is not a  view function, it will still cost gas. This is because the
other function creates a transaction on Ethereum, and will still need to be
verified from every node. So  view functions are only free when they're called
externally.

Declaring arrays in memory


You can use the memory keyword with arrays to create a new array inside a
function without needing to write anything to storage. The array will only exist
until the end of the function call, and this is a lot cheaper gas-wise than
updating an array in storage — free if it's a view function called externally.

Here's how to declare an array in memory:

function getArray() external pure returns(uint[] memory) {


// Instantiate a new array in memory with a length of 3
uint[] memory values = new uint[](3);

// Put some values to it


values[0] = 1;
values[1] = 2;
values[2] = 3;

return values;
}

This is a trivial example just to show you the syntax, but in the next chapter
we'll look at combining this with for loops for real use-cases.

Note: memory arrays must be created with a length argument (in this


example,  3). They currently cannot be resized like storage arrays can
with  array.push(), although this may be changed in a future version of
Solidity.

FUNCTION MODIFIERES
We have visibility modifiers that control when and where the function can
be called from: private means it's only callable from other functions inside
the contract; internal is like private but can also be called by contracts
that inherit from this one; external can only be called outside the contract;
and finally public can be called anywhere, both internally and externally.

1. We also have state modifiers, which tell us how the function interacts
with the BlockChain: view tells us that by running the function, no data
will be saved/changed. pure tells us that not only does the function not
save any data to the blockchain, but it also doesn't read any data from
the blockchain. Both of these don't cost any gas to call if they're called
externally from outside the contract (but they do cost gas if called
internally by another function).

2. Then we have custom modifiers, which we learned about in Lesson


3: onlyOwner and aboveLevel, for example. For these we can define
custom logic to determine how they affect a function.

These modifiers can all be stacked together on a function definition as follows:

function test() external view onlyOwner anotherModifier { /* ... */ }

In this chapter, we're going to introduce one more function modifier: payable.

The payable Modifier
payable functions are part of what makes Solidity and Ethereum so cool —
they are a special type of function that can receive Ether.

Let that sink in for a minute. When you call an API function on a normal web
server, you can't send US dollars along with your function call — nor can you
send Bitcoin.

But in Ethereum, because both the money (Ether), the data (transaction
payload), and the contract code itself all live on Ethereum, it's possible for you
to call a function and pay money to the contract at the same time.

This allows for some really interesting logic, like requiring a certain payment to
the contract in order to execute a function.

Let's look at an example


contract OnlineStore {
function buySomething() external payable {
// Check to make sure 0.001 ether was sent to the function call:
require(msg.value == 0.001 ether);
// If so, some logic to transfer the digital item to the caller of the
function:
transferThing(msg.sender);
}
}

Here, msg.value is a way to see how much Ether was sent to the contract,
and ether is a built-in unit.

What happens here is that someone would call the function from web3.js
(from the DApp's JavaScript front-end) as follows:

// Assuming `OnlineStore` points to your contract on Ethereum:


OnlineStore.buySomething({from: web3.eth.defaultAccount, value:
web3.utils.toWei(0.001)})

Notice the value field, where the javascript function call specifies how


much ether to send (0.001). If you think of the transaction like an envelope,
and the parameters you send to the function call are the contents of the letter
you put inside, then adding a value is like putting cash inside the envelope —
the letter and the money get delivered together to the recipient.

It is important to note that you cannot transfer Ether to an address unless that
address is of type address payable. But the _owner variable is of type uint160,
meaning that we must explicitly cast it to address payable.

Once you cast the address from uint160 to address payable, you can transfer
Ether to that address using the transfer function,
and address(this).balance will return the total balance stored on the contract.
So if 100 users had paid 1 Ether to our contract, address(this).balance would
equal 100 Ether.

You can use transfer to send funds to any Ethereum address. For example,
you could have a function that transfers Ether back to the msg.sender if they
overpaid for an item:
uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

Or in a contract with a buyer and a seller, you could save the seller's address
in storage, then when someone purchases his item, transfer him the fee paid
by the buyer: seller.transfer(msg.value).

These are some examples of what makes Ethereum programming really cool
— you can have decentralized marketplaces like this that aren't controlled by
anyone.

Random number generation via keccak256


The best source of randomness we have in Solidity is the keccak256 hash
function.

We could do something like the following to generate a random number:


// Generate a random number between 1 and 100:
uint randNonce = 0;
uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;
randNonce++;
uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) %
100;

What this would do is take the timestamp of now, the msg.sender, and an


incrementing nonce (a number that is only ever used once, so we don't run the
same hash function with the same input parameters twice).

It would then "pack" the inputs and use keccak to convert them to a random
hash. Next, it would convert that hash to a uint, and then use % 100 to take
only the last 2 digits. This will give us a totally random number between 0 and
99.

This method is vulnerable to attack by a dishonest node

In Ethereum, when you call a function on a contract, you broadcast it to a


node or nodes on the network as a transaction. The nodes on the network
then collect a bunch of transactions, try to be the first to solve a
computationally-intensive mathematical problem as a "Proof of Work", and
then publish that group of transactions along with their Proof of Work (PoW)
as a block to the rest of the network.

Once a node has solved the PoW, the other nodes stop trying to solve the
PoW, verify that the other node's list of transactions are valid, and then accept
the block and move on to trying to solve the next block.

This makes our random number function exploitable.

Let's say we had a coin flip contract — heads you double your money, tails
you lose everything. Let's say it used the above random function to determine
heads or tails. (random >= 50 is heads, random < 50 is tails).

If I were running a node, I could publish a transaction only to my own


node and not share it. I could then run the coin flip function to see if I won —
and if I lost, choose not to include that transaction in the next block I'm solving.
I could keep doing this indefinitely until I finally won the coin flip and solved the
next block, and profit.
So how do we generate random numbers safely in
Ethereum?
Because the entire contents of the blockchain are visible to all participants,
this is a hard problem, and its solution is beyond the scope of this tutorial. You
can read this StackOverflow thread for some ideas. One idea would be to use
an oracle to access a random number function from outside of the Ethereum
blockchain.

Of course, since tens of thousands of Ethereum nodes on the network are


competing to solve the next block, my odds of solving the next block are
extremely low. It would take me a lot of time or computing resources to exploit
this profitably — but if the reward were high enough (like if I could bet
$100,000,000 on the coin flip function), it would be worth it for me to attack.

So while this random number generation is NOT secure on Ethereum, in


practice unless our random function has a lot of money on the line, the users
of your game likely won't have enough resources to attack it.

Because we're just building a simple game for demo purposes in this tutorial
and there's no real money on the line, we're going to accept the tradeoffs of
using a random number generator that is simple to implement, knowing that it
isn't totally secure.

In a future lesson, we may cover using oracles (a secure way to pull data in


from outside of Ethereum) to generate secure random numbers from outside
the blockchain.

Tokens
A token on Ethereum is basically just a smart contract that follows some
common rules — namely it implements a standard set of functions that all
other token contracts share, such as transferFrom(address _from, address
_to, uint256 _tokenId) and balanceOf(address _owner).

Internally the smart contract usually has a mapping, mapping(address =>


uint256) balances, that keeps track of how much balance each address has.

So basically a token is just a contract that keeps track of who owns how much
of that token, and some functions so those users can transfer their tokens to
other addresses.

Since all ERC20 tokens share the same set of functions with the same names,
they can all be interacted with in the same ways.
This means if you build an application that is capable of interacting with one
ERC20 token, it's also capable of interacting with any ERC20 token. That way
more tokens can easily be added to your app in the future without needing to
be custom coded. You could simply plug in the new token contract address,
and boom, your app has another token it can use.

One example of this would be an exchange. When an exchange adds a new


ERC20 token, really it just needs to add another smart contract it talks to.
Users can tell that contract to send tokens to the exchange's wallet address,
and the exchange can tell the contract to send the tokens back out to users
when they request a withdraw.

The exchange only needs to implement this transfer logic once, then when it
wants to add a new ERC20 token, it's simply a matter of adding the new
contract address to its database.

ERC721 tokens are not interchangeable since each one is assumed to be


unique, and are not divisible. You can only trade them in whole units, and
each one has a unique ID. So these are a perfect fit for making our zombies
tradeable.

Chapter 2: ERC721 Standard, Multiple Inheritance

contract ERC721 {
event Transfer(address indexed _from, address indexed _to, uint256 indexed
_tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256
indexed _tokenId);

function balanceOf(address _owner) external view returns (uint256);


function ownerOf(uint256 _tokenId) external view returns (address);
function transferFrom(address _from, address _to, uint256 _tokenId) external
payable;
function approve(address _approved, uint256 _tokenId) external payable;
}

This is the list of methods we'll need to implement, which we'll be doing over
the coming chapters in pieces.

It looks like a lot, but don't get overwhelmed! We're here to walk you through
it.
Implementing a token contract
When implementing a token contract, the first thing we do is copy the interface
to its own Solidity file and import it, import "./erc721.sol";. Then we have our
contract inherit from it, and we override each method with a function definition.

But wait — ZombieOwnership is already inheriting from ZombieAttack — how can


it also inherit from ERC721?

Luckily in Solidity, your contract can inherit from multiple contracts as follows:
contract SatoshiNakamoto is NickSzabo, HalFinney {
// Omg, the secrets of the universe revealed!
}

As you can see, when using multiple inheritance, you just separate the
multiple contracts you're inheriting from with a comma, ,. In this case, our
contract is inheriting from NickSzabo and HalFinney.

balanceOf & ownerOf


Great, let's dive into the ERC721 implementation!

We've gone ahead and copied the empty shell of all the functions you'll be
implementing in this lesson.

In this chapter, we're going to implement the first two


methods: balanceOf and ownerOf.

balanceOf
function balanceOf(address _owner) external view returns (uint256 _balance);

This function simply takes an address, and returns how many tokens
that address owns.

In our case, our "tokens" are Zombies. Do you remember where in our DApp
we stored how many zombies an owner has?

ownerOf
function ownerOf(uint256 _tokenId) external view returns (address
_owner);

This function takes a token ID (in our case, a Zombie ID), and returns
the address of the person who owns it.
Again, this is very straightforward for us to implement, since we already have
a mapping in our DApp that stores this information. We can implement this
function in one line, just a return statement.

Note: Remember,  uint256 is equivalent to  uint. We've been using  uint in our
code up until now, but we're using  uint256 here because we copy/pasted from
the spec.

Contract security enhancements: Overflows and Underflows


We're going to look at one major security feature you should be aware of
when writing smart contracts: Preventing overflows and underflows.

What's an overflow?

Let's say we have a uint8, which can only have 8 bits. That means the largest
number we can store is binary 11111111 (or in decimal, 2^8 - 1 = 255).

Take a look at the following code. What is number equal to at the end?

uint8 number = 255;


number++;

In this case, we've caused it to overflow — so number is counterintuitively now


equal to 0 even though we increased it. (If you add 1 to binary 11111111, it
resets back to 00000000, like a clock going from 23:59 to 00:00).

An underflow is similar, where if you subtract 1 from a uint8 that equals 0, it


will now equal 255 (because uints are unsigned, and cannot be negative).

While we're not using uint8 here, and it seems unlikely that a uint256 will


overflow when incrementing by 1 each time (2^256 is a really big number), it's
still good to put protections in our contract so that our DApp never has
unexpected behavior in the future.

Using SafeMath
To prevent this, OpenZeppelin has created a library called SafeMath that
prevents these issues by default.

But before we get into that... What's a library?

A library is a special type of contract in Solidity. One of the things it is useful


for is to attach functions to native data types.
For example, with the SafeMath library, we'll use the syntax using SafeMath
for uint256. The SafeMath library has 4 functions — add, sub, mul, and div.
And now we can access these functions from uint256 as follows:

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

We'll look at what these functions do in the next chapter, but for now let's add
the SafeMath library to our contract.

Assert and require


assert issimilar to require, where it will throw an error if false. The difference
between assert and require is that require will refund the user the rest of their
gas when a function fails, whereas assert will not. So most of the time you
want to use require in your code; assert is typically used when something has
gone horribly wrong with the code (like a uint overflow).

You might also like