Index
- Lesson 1: CryptoZombies
- Chapter 2 Contracts
- Chapter 3: State Variables & Integers
- Chapter 4: Math Operations
- Chapter 5: Structs
- Chapter 6: Arrays
- Chapter 7: Function Declarations
- Chapter 8: Working With Structs and Arrays
- Chapter 9: Private / Public Functions
- Chapter 10: More on Functions
- Chapter 11: Keccak256 and Typecasting
- Chapter 12: Putting It Together
- Chapter 13: Events
- Chapter 14: Web3.js
- Lesson 2: Zombies Attack Their Victims
- Chapter 2: Mappings and Addresses
- Chapter 3: Msg.sender
- Chapter 4: Require
- Chapter 5: Inheritance
- Chapter 6: Import
- Chapter 7: Storage vs Memory
- Chapter 8: Zombie DNA
- Chapter 9: More on Function Visibility
- Chapter 10: What Do Zombies Eat?
- Chapter 11: Using an Interface
- Chapter 12: Handling Multiple Return Values
- Chapter 13: Bonus: Kitty Genes
- Chapter 14: Wrapping It Up
- Lesson 3: Advanced Solidity Concepts
- Chapter 2: Ownable Contracts
- Chapter 3: onlyOwner Function Modifier
- Chapter 4: Gas
- Chapter 5: Time Units
- Chapter 6: Zombie Cooldowns
- Chapter 7: Public Functions & Security
- Chapter 8: More on Function Modifiers
- Chapter 9: Zombie Modifiers
- Chapter 10: Saving Gas With 'View' Functions
- Chapter 11: Storage is Expensive
- Chapter 12: For Loops
- Chapter 13: Wrapping It Up
- Lesson 4: Zombie Battle System
- Chapter 1: Payable
- Chapter 2: Withdraws
- Chapter 3: Zombie Battles
- Chapter 4: Random Numbers
- Chapter 5: Zombie Fightin'
- Chapter 6: Refactoring Common Logic
- Chapter 7: More Refactoring
- Chapter 8: Back to Attack!
- Chapter 9: Zombie Wins and Losses
- Chapter 10: Zombie Victory 😄
- Chapter 11: Zombie Loss 😞
- Lesson 5: ERC721 & Crypto-Collectibles
- Chapter 1: Tokens on Ethereum
- Chapter 2: ERC721 Standard, Multiple Inheritance
- Chapter 3: balanceOf & ownerOf
- Chapter 4: Refactoring
- Chapter 5: ERC721: Transfer Logic
- Chapter 6: ERC721: Transfer Cont'd
- Chapter 7: ERC721: Approve
- Chapter 8: ERC721: Approve
- Chapter 9: Preventing Overflows
- Chapter 10: SafeMath Part 2
- Chapter 11: SafeMath Part 3
- Chapter 12: SafeMath Part 4
- Chapter 13: Comments
- Chapter 14: Wrapping It Up
- App Front-ends & Web3.js
- Chapter 1: Intro to Web3.js
- Chapter 2: Web3 Providers
- Chapter 3: Talking to Contracts
- Chapter 4: Calling Contract Functions
- Chapter 5: Metamask & Accounts
- Chapter 6: Displaying our Zombie Army
- Chapter 7: Sending Transactions
- Chapter 8: Calling Payable Functions
- Chapter 9: Subscribing to Events
- Chapter 10: Wrapping It Up
Chapter 12: For Loops
Chapter 12: For Loops
In the previous chapter, we mentioned that sometimes you'll want to use a
for
loop to build the contents of an array in a function rather than simply saving that array to storage.Let's look at why.
For our
getZombiesByOwner
function, a naive implementation would be to store a mapping
of owners to zombie armies in the ZombieFactory
contract:mapping (address => uint[]) public ownerToZombies
Then every time we create a new zombie, we would simply use ownerToZombies[owner].push(zombieId) to add it to that owner's zombies array. And getZombiesByOwner
would be a very straightforward function:function getZombiesByOwner(address _owner) external view returns (uint[]) {
return ownerToZombies[_owner];
}
return ownerToZombies[_owner];
}
The problem with this approach
This approach is tempting for its simplicity. But let's look at what happens if we later add a function to transfer a zombie from one owner to another (which we'll definitely want to add in a later lesson!).
That transfer function would need to:
1. Push the zombie to the new owner's
ownerToZombies
array,2. Remove the zombie from the old owner's
ownerToZombies
array,3. Shift every zombie in the older owner's array up one place to fill the hole, and then
4. Reduce the array length by 1.
Step 3 would be extremely expensive gas-wise, since we'd have to do a write for every zombie whose position we shifted. If an owner has 20 zombies and trades away the first one, we would have to do 19 writes to maintain the order of the array.
Since writing to storage is one of the most expensive operations in Solidity, every call to this transfer function would be extremely expensive gas-wise. And worse, it would cost a different amount of gas each time it's called, depending on how many zombies the user has in their army and the index of the zombie being traded. So the user wouldn't know how much gas to send.
Note: Of course, we could just move the last zombie in the array to fill the missing slot and reduce the array length by one. But then we would change the ordering of our zombie army every time we made a trade.
Since
view
functions don't cost gas when called externally, we can simply use a for-loop in getZombiesByOwner
to iterate the entire zombies array and build an array of the zombies that belong to this specific owner. Then our transfer
function will be much cheaper, since we don't need to reorder any arrays in storage, and somewhat counter-intuitively this approach is cheaper overall.Using
for
loops
The syntax of
for
loops in Solidity is similar to JavaScript.Let's look at an example where we want to make an array of even numbers:
function getEvens() pure external returns(uint[]) {
uint[] memory evens = new uint[](5);
// Keep track of the index in the new array:
uint counter = 0;
// Iterate 1 through 10 with a for loop:
for (uint i = 1; i <= 10; i++) {
// If `i` is even...
if (i % 2 == 0) {
// Add it to our array
evens[counter] = i;
// Increment counter to the next empty index in `evens`:
counter++;
}
}
return evens;
}
uint[] memory evens = new uint[](5);
// Keep track of the index in the new array:
uint counter = 0;
// Iterate 1 through 10 with a for loop:
for (uint i = 1; i <= 10; i++) {
// If `i` is even...
if (i % 2 == 0) {
// Add it to our array
evens[counter] = i;
// Increment counter to the next empty index in `evens`:
counter++;
}
}
return evens;
}
This function will return an array with the contents [2, 4, 6, 8, 10]
.Put it to the test
Let's finish our
getZombiesByOwner
function by writing a for
loop that iterates through all the zombies in our DApp, compares their owner to see if we have a match, and pushes them to our result
array before returning it.1. Declare a
uint
called counter
and set it equal to 0
. We'll use this variable to keep track of the index in our result
array.2. Declare a
for
loop that starts from uint i = 0
and goes up through i < zombies.length
. This will iterate over every zombie in our array.3. Inside the
for
loop, make an if
statement that checks if zombieToOwner[i]
is equal to _owner
. This will compare the two addresses to see if we have a match.4. Inside the
if
statement:1. Add the zombie's ID to our
result
array by setting result[counter]
equal to i
.2. Increment
counter
by 1 (see the for
loop example above).That's it — the function will now return all the zombies owned by
_owner
without spending any gas.pragma solidity ^0.4.25;
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].name = _newName;
}
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].dna = _newDna;
}
function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
// Start here
uint counter = 0;
for (uint i = 0; i < zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
}
import "./zombiefeeding.sol";
contract ZombieHelper is ZombieFeeding {
modifier aboveLevel(uint _level, uint _zombieId) {
require(zombies[_zombieId].level >= _level);
_;
}
function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].name = _newName;
}
function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) {
require(msg.sender == zombieToOwner[_zombieId]);
zombies[_zombieId].dna = _newDna;
}
function getZombiesByOwner(address _owner) external view returns(uint[]) {
uint[] memory result = new uint[](ownerZombieCount[_owner]);
// Start here
uint counter = 0;
for (uint i = 0; i < zombies.length; i++) {
if (zombieToOwner[i] == _owner) {
result[counter] = i;
counter++;
}
}
return result;
}
}