Introduction
In my last article, we got started with Solidity by creating a simple contract and exploring some simple structures and concepts. We will pick up where we left off.
Mappings
After struct and arrays, we can also store data in mappings. Mappings are a key-value store. For example:
mapping(uint => string) public keyUintStringValue;
mapping(address => uint) public addressToUint;
Here, we have two public mappings. The first one has a uint key and a string value. The second has an address key and a uint value.
Addresses
The Ethereum blockchain is made up of addresses. Each account has an unique address. It takes the following form: 0x0cE440255306E921F41612C46F1v6df9Cc969183
. Each address has a certain amount of Ether, which is the cryptocurrency used on the blockchain, and can receive or send Ether to other addresses.
With that in mind, let's create a new mapping for our DeveloperFactory:
pragma solidity ^0.4.22;
contract DeveloperFactory {
event NewDeveloper(uint devId, string name, uint age);
uint maxAge = 100;
uint minAge = 5;
struct Developer {
string name;
uint id;
uint age;
}
Developer[] public developers;
mapping (address => uint) public ownerDevCount;
mapping (uint => address) public devToOwner;
function _createDeveloper( string _name, uint _id, uint _age ) private{
uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
NewDeveloper(id, _name, _age);
}
function _generateRandomId( string _str ) private pure returns (uint){
uint rand = uint(keccak256(_str));
return rand;
}
function createRandomDeveloper( string _name, uint _age ) public view {
require(_age > minAge);
require(_age < maxAge);
uint randId = _generateRandomId( _name );
_createDeveloper(_name, randId, _age );
}
}
In the first mapping, we will keep track of the number of devs each account ( address ) created. In the second, we will keep track of the owners for each dev.
msg.sender
Each contract is passive, they don't do anything until someone triggers them. msg.sender is a global variable that allows us to know which address is responsible for the triggering. It could be a account or another smart contract.
With that information, we can update our mappings appropriately. In the _createDeveloper function, we will increase the ownerDevCount for this particular address. In the devToOwner mapping, we will indicate that the newly created developer is owned by the msg.sender address.
pragma solidity ^0.4.22;
contract DeveloperFactory {
event NewDeveloper(uint devId, string name, uint age);
uint maxAge = 100;
uint minAge = 5;
struct Developer {
string name;
uint id;
uint age;
}
Developer[] public developers;
mapping (address => uint) public ownerDevCount;
mapping (uint => address) public devToOwner;
function _createDeveloper( string _name, uint _id, uint _age ) private{
uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
ownerDevCount[msg.sender]++;
devToOwner[id] = msg.sender;
NewDeveloper(id, _name, _age);
}
function _generateRandomId( string _str ) private pure returns (uint){
uint rand = uint(keccak256(_str));
return rand;
}
function createRandomDeveloper( string _name, uint _age ) public view {
require(_age > minAge);
require(_age < maxAge);
uint randId = _generateRandomId( _name );
_createDeveloper(_name, randId, _age );
}
}
Notice the ++ notation to increase the ownerDevCount[msg.sender], just live Javascript!
Inheritance and import
Contracts can inherit from other contracts. If you open a new file call DeveloperLearning.sol, you can make it inherit from DeveloperFactory.sol:
pragma solidity ^0.4.22;
import "./DeveloperFactory.sol";
contract DeveloperLearning is DeveloperFactory {
// I have now access to DeveloperFactory functions
}
Notice how we imported the DeveloperFactory contract ( assuming it was in the same folder ). To declare inheritance, we use the keyword is.
payable
In order for a contract to receive ether, we need to have the payable keyword to a function. The amount sent will be accessible in the global variable msg.value. So we could make sure that a certain amount of ether is sent to the contract before the creation of a developer:
pragma solidity ^0.4.22;
contract DeveloperFactory {
event NewDeveloper(uint devId, string name, uint age);
uint maxAge = 100;
uint minAge = 5;
struct Developer {
string name;
uint id;
uint age;
}
Developer[] public developers;
mapping (address => uint) public ownerDevCount;
mapping (uint => address) public devToOwner;
function _createDeveloper( string _name, uint _id, uint _age ) private{
uint id = developers.push( Developer( _name, _id, _age ) ) - 1;
ownerDevCount[msg.sender]++;
devToOwner[id] = msg.sender;
NewDeveloper(id, _name, _age);
}
function _generateRandomId( string _str ) private pure returns (uint){
uint rand = uint(keccak256(_str));
return rand;
}
function createRandomDeveloper( string _name, uint _age ) public payable {
require(_age > minAge);
require(_age < maxAge);
require(msg.value == 5);
uint randId = _generateRandomId( _name );
_createDeveloper(_name, randId, _age );
}
}
Memory and Storage
In Solidity, there are two places where variables are stored: in storage or in memory. A variable stored in memory is temporary, it exists while the function is used, then it is discarded. A variable stored in storage exists permanently on the blockchain. Most of the time, you don't have to worry about where to store your variables, as Solidity handles it for you.
For example, state variables ( maxAge, minAge, Developer ), declared outside of functions, are stored in storage. Variables like randId, id, rand are stored in memory.
But, in some cases, you want to explicitly declare where to store certain variables. Solidity allows you to do that with memory
and storage
keywords.