-
Notifications
You must be signed in to change notification settings - Fork 0
/
zombiefactory.sol
217 lines (149 loc) · 9.5 KB
/
zombiefactory.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
pragma solidity >=0.5.0 <0.6.0;
//pragma solidity 0.8.17;
import "./ownable.sol";
import "./safemath.sol"; //to handle math operations and avoid over and underflows.
/*
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.
*/
contract ZombieFactory is Ownable{
/*
First we have the library keyword — libraries are similar to contracts but with a few differences. For our purposes, libraries allow us to use the using keyword, which automatically tacks on all of the library's methods to another data type:
*/
using SafeMath for uint256;
using SafeMath32 for uint32;
using SafeMath16 for uint16;
event NewZombie(uint zombieId, string name, uint dna);
uint dnaDigits = 16;
// uint x = 5 ** 2; // equal to 5^2 = 25
// Division: x / y
//Modulus / remainder: x % y (for example, 13 % 5 is 3, because if you divide 5 into 13, 3 is the remainder)
uint dnaModulus =10**dnaDigits;
/* TIME UNITS
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.
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));
}
*/
uint cooldownTime = 1 days;
//struct
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount; // Note: Remember, since we can pack uints inside structs, we want to use the smallest uints we can get away with. A uint8 is too small, since 2^8 = 256 — if our zombies attacked once per day, they could overflow this within a year. But 2^16 is 65536 — so unless a user wins or loses every day for 179 years straight, we should be safe here.
uint16 lossCount;
}
// Array with a fixed length of 2 elements:
// uint[2] fixedArray;
// another fixed Array, can contain 5 strings:
// string[5] stringArray;
// a dynamic Array - has no fixed size, can keep growing:
// uint[] dynamicArray;
//Zombie[] zombie; // dynamic Array, we can keep adding to it
Zombie[] public zombies; //You can declare an array as public, and Solidity will automatically create a getter method for it.
mapping (uint => address) public zombieToOwner; //key is uint and value is address, address that owns zombie #id
mapping (address => uint) ownerZombieCount; //how many zombies a user has
/*
function eatHamburgers(string memory _name, uint _amount) public { }
This is a function named eatHamburgers that takes 2 parameters: a string and a uint. For now the body of the function is empty. Note that we're specifying the function visibility as public. We're also providing instructions about where the _name variable should be stored- in memory. This is required for all reference types such as arrays, structs, mappings, and strings.
What is a reference type you ask?
Well, there are two ways in which you can pass an argument to a Solidity function:
By value, which means that the Solidity compiler creates a new copy of the parameter's value and passes it to your function. This allows your function to modify the value without worrying that the value of the initial parameter gets changed.
By reference, which means that your function is called with a... reference to the original variable. Thus, if your function changes the value of the variable it receives, the value of the original variable gets changed.
It's convention (but not required) to start function parameter variable names with an underscore (_) in order to differentiate them from global variables. We'll use that convention throughout our tutorial.
Call this function like: eatHamburgers("vitalik", 100);
*/
// memory _name because we want to pass it by value, so it can be changed without changing the initial value of the parameter
/*
function createZombie(string memory _name, uint _dna) public {
zombies.push(Zombie(_name, _dna));
}
*/
// .push adds an object to an array
// this function is public and This means anyone (or any other contract) can call your contract's function and execute its code.
// a private function means only other functions within our contract will be able to call this function and add to the numbers array.
function _createZombie(string memory _name, uint _dna) internal {
//zombies.push(Zombie(_name, _dna));
// and fire it here
//level = 1 see Zombie struct
//readyTime = uint32(now+cooldownTime) see Zombie struct
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now+cooldownTime), 0, 0)) - 1; //zombies.push() returns a uint of the new length of the array (-1) while adding the argument to the array zombies.
//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.
zombieToOwner[id] = msg.sender; //atribute ownership of zombie #id to address=msg.sender
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1); //increase zombie count in address=msg.sender
emit NewZombie(id, _name, _dna);
}
//convention to start private functions name with _
/*
To return a value from a function, the declaration looks like this:
string greeting = "What's up dog";
function sayHello() public returns (string memory) {
return greeting;
}
*/
// This function would be a view function because it doesnt change the state, only reads.
//function sayHello() public view returns (string memory) { return greeting;}
/*
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
This function doesn't even read from the state of the app — its return value depends only on its function parameters. So in this case we would declare the function as pure.
*/
function _generateRandomDna(string memory _str) private view returns (uint) {
uint rand = uint(keccak256(abi.encodePacked(_str)));
return rand % dnaModulus;
//rand is a uint with possibly many digits. the remainder of the integer division by 10^16 is a number with necessarily 16 or less digits (we can always count 0 on the left and state that it always has 16 digits)
// example
// 543 div 10^1 = 54 and 543 % 10^1 = 3
//3 has 1 digit, if it had 2 it would be integer divisible by 10^1 (10) and the division wouldn't be complete
//the remainder of a integer division always has one less digit than the divisor
//extra note: this gives last 16 digits of rand
}
//keccak256 is a version of SHA3, generates a 256-bit hexadecimal number from an input
function createRandomZombie(string memory _name) public {
require(ownerZombieCount[msg.sender]==0); //only runs the rest if ownerzombiecount is zero. for strings use keccak256(abi.encodePacked(_str)), solidity has no native string comparer
uint randDna = _generateRandomDna(_name);
_createZombie(_name, randDna);
}
/* SAVING GAS VIA STRUCT PACKING
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.
*/
}