forked from cryptocopycats/awesome-cryptokitties-bubble
-
Notifications
You must be signed in to change notification settings - Fork 0
/
KittyBreeding.sol
334 lines (282 loc) · 14.1 KB
/
KittyBreeding.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
/// @title A facet of KittyCore that manages Kitty siring, gestation, and birth.
/// @author Axiom Zen (https://www.axiomzen.co)
/// @dev See the KittyCore contract documentation to understand how the various contract facets are arranged.
contract KittyBreeding is KittyOwnership {
/// @dev The Pregnant event is fired when two cats successfully breed and the pregnancy
/// timer begins for the matron.
event Pregnant(address owner, uint256 matronId, uint256 sireId, uint256 cooldownEndBlock);
/// @notice The minimum payment required to use breedWithAuto(). This fee goes towards
/// the gas cost paid by whatever calls giveBirth(), and can be dynamically updated by
/// the COO role as the gas price changes.
uint256 public autoBirthFee = 2 finney;
// Keeps track of number of pregnant kitties.
uint256 public pregnantKitties;
/// @dev The address of the sibling contract that is used to implement the sooper-sekret
/// genetic combination algorithm.
GeneScienceInterface public geneScience;
/// @dev Update the address of the genetic contract, can only be called by the CEO.
/// @param _address An address of a GeneScience contract instance to be used from this point forward.
function setGeneScienceAddress(address _address) external onlyCEO {
GeneScienceInterface candidateContract = GeneScienceInterface(_address);
// NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117
require(candidateContract.isGeneScience());
// Set the new contract address
geneScience = candidateContract;
}
/// @dev Checks that a given kitten is able to breed. Requires that the
/// current cooldown is finished (for sires) and also checks that there is
/// no pending pregnancy.
function _isReadyToBreed(Kitty _kit) internal view returns (bool) {
// In addition to checking the cooldownEndBlock, we also need to check to see if
// the cat has a pending birth; there can be some period of time between the end
// of the pregnacy timer and the birth event.
return (_kit.siringWithId == 0) && (_kit.cooldownEndBlock <= uint64(block.number));
}
/// @dev Check if a sire has authorized breeding with this matron. True if both sire
/// and matron have the same owner, or if the sire has given siring permission to
/// the matron's owner (via approveSiring()).
function _isSiringPermitted(uint256 _sireId, uint256 _matronId) internal view returns (bool) {
address matronOwner = kittyIndexToOwner[_matronId];
address sireOwner = kittyIndexToOwner[_sireId];
// Siring is okay if they have same owner, or if the matron's owner was given
// permission to breed with this sire.
return (matronOwner == sireOwner || sireAllowedToAddress[_sireId] == matronOwner);
}
/// @dev Set the cooldownEndTime for the given Kitty, based on its current cooldownIndex.
/// Also increments the cooldownIndex (unless it has hit the cap).
/// @param _kitten A reference to the Kitty in storage which needs its timer started.
function _triggerCooldown(Kitty storage _kitten) internal {
// Compute an estimation of the cooldown time in blocks (based on current cooldownIndex).
_kitten.cooldownEndBlock = uint64((cooldowns[_kitten.cooldownIndex]/secondsPerBlock) + block.number);
// Increment the breeding count, clamping it at 13, which is the length of the
// cooldowns array. We could check the array size dynamically, but hard-coding
// this as a constant saves gas. Yay, Solidity!
if (_kitten.cooldownIndex < 13) {
_kitten.cooldownIndex += 1;
}
}
/// @notice Grants approval to another user to sire with one of your Kitties.
/// @param _addr The address that will be able to sire with your Kitty. Set to
/// address(0) to clear all siring approvals for this Kitty.
/// @param _sireId A Kitty that you own that _addr will now be able to sire with.
function approveSiring(address _addr, uint256 _sireId)
external
whenNotPaused
{
require(_owns(msg.sender, _sireId));
sireAllowedToAddress[_sireId] = _addr;
}
/// @dev Updates the minimum payment required for calling giveBirthAuto(). Can only
/// be called by the COO address. (This fee is used to offset the gas cost incurred
/// by the autobirth daemon).
function setAutoBirthFee(uint256 val) external onlyCOO {
autoBirthFee = val;
}
/// @dev Checks to see if a given Kitty is pregnant and (if so) if the gestation
/// period has passed.
function _isReadyToGiveBirth(Kitty _matron) private view returns (bool) {
return (_matron.siringWithId != 0) && (_matron.cooldownEndBlock <= uint64(block.number));
}
/// @notice Checks that a given kitten is able to breed (i.e. it is not pregnant or
/// in the middle of a siring cooldown).
/// @param _kittyId reference the id of the kitten, any user can inquire about it
function isReadyToBreed(uint256 _kittyId)
public
view
returns (bool)
{
require(_kittyId > 0);
Kitty storage kit = kitties[_kittyId];
return _isReadyToBreed(kit);
}
/// @dev Checks whether a kitty is currently pregnant.
/// @param _kittyId reference the id of the kitten, any user can inquire about it
function isPregnant(uint256 _kittyId)
public
view
returns (bool)
{
require(_kittyId > 0);
// A kitty is pregnant if and only if this field is set
return kitties[_kittyId].siringWithId != 0;
}
/// @dev Internal check to see if a given sire and matron are a valid mating pair. DOES NOT
/// check ownership permissions (that is up to the caller).
/// @param _matron A reference to the Kitty struct of the potential matron.
/// @param _matronId The matron's ID.
/// @param _sire A reference to the Kitty struct of the potential sire.
/// @param _sireId The sire's ID
function _isValidMatingPair(
Kitty storage _matron,
uint256 _matronId,
Kitty storage _sire,
uint256 _sireId
)
private
view
returns(bool)
{
// A Kitty can't breed with itself!
if (_matronId == _sireId) {
return false;
}
// Kitties can't breed with their parents.
if (_matron.matronId == _sireId || _matron.sireId == _sireId) {
return false;
}
if (_sire.matronId == _matronId || _sire.sireId == _matronId) {
return false;
}
// We can short circuit the sibling check (below) if either cat is
// gen zero (has a matron ID of zero).
if (_sire.matronId == 0 || _matron.matronId == 0) {
return true;
}
// Kitties can't breed with full or half siblings.
if (_sire.matronId == _matron.matronId || _sire.matronId == _matron.sireId) {
return false;
}
if (_sire.sireId == _matron.matronId || _sire.sireId == _matron.sireId) {
return false;
}
// Everything seems cool! Let's get DTF.
return true;
}
/// @dev Internal check to see if a given sire and matron are a valid mating pair for
/// breeding via auction (i.e. skips ownership and siring approval checks).
function _canBreedWithViaAuction(uint256 _matronId, uint256 _sireId)
internal
view
returns (bool)
{
Kitty storage matron = kitties[_matronId];
Kitty storage sire = kitties[_sireId];
return _isValidMatingPair(matron, _matronId, sire, _sireId);
}
/// @notice Checks to see if two cats can breed together, including checks for
/// ownership and siring approvals. Does NOT check that both cats are ready for
/// breeding (i.e. breedWith could still fail until the cooldowns are finished).
/// TODO: Shouldn't this check pregnancy and cooldowns?!?
/// @param _matronId The ID of the proposed matron.
/// @param _sireId The ID of the proposed sire.
function canBreedWith(uint256 _matronId, uint256 _sireId)
external
view
returns(bool)
{
require(_matronId > 0);
require(_sireId > 0);
Kitty storage matron = kitties[_matronId];
Kitty storage sire = kitties[_sireId];
return _isValidMatingPair(matron, _matronId, sire, _sireId) &&
_isSiringPermitted(_sireId, _matronId);
}
/// @dev Internal utility function to initiate breeding, assumes that all breeding
/// requirements have been checked.
function _breedWith(uint256 _matronId, uint256 _sireId) internal {
// Grab a reference to the Kitties from storage.
Kitty storage sire = kitties[_sireId];
Kitty storage matron = kitties[_matronId];
// Mark the matron as pregnant, keeping track of who the sire is.
matron.siringWithId = uint32(_sireId);
// Trigger the cooldown for both parents.
_triggerCooldown(sire);
_triggerCooldown(matron);
// Clear siring permission for both parents. This may not be strictly necessary
// but it's likely to avoid confusion!
delete sireAllowedToAddress[_matronId];
delete sireAllowedToAddress[_sireId];
// Every time a kitty gets pregnant, counter is incremented.
pregnantKitties++;
// Emit the pregnancy event.
Pregnant(kittyIndexToOwner[_matronId], _matronId, _sireId, matron.cooldownEndBlock);
}
/// @notice Breed a Kitty you own (as matron) with a sire that you own, or for which you
/// have previously been given Siring approval. Will either make your cat pregnant, or will
/// fail entirely. Requires a pre-payment of the fee given out to the first caller of giveBirth()
/// @param _matronId The ID of the Kitty acting as matron (will end up pregnant if successful)
/// @param _sireId The ID of the Kitty acting as sire (will begin its siring cooldown if successful)
function breedWithAuto(uint256 _matronId, uint256 _sireId)
external
payable
whenNotPaused
{
// Checks for payment.
require(msg.value >= autoBirthFee);
// Caller must own the matron.
require(_owns(msg.sender, _matronId));
// Neither sire nor matron are allowed to be on auction during a normal
// breeding operation, but we don't need to check that explicitly.
// For matron: The caller of this function can't be the owner of the matron
// because the owner of a Kitty on auction is the auction house, and the
// auction house will never call breedWith().
// For sire: Similarly, a sire on auction will be owned by the auction house
// and the act of transferring ownership will have cleared any oustanding
// siring approval.
// Thus we don't need to spend gas explicitly checking to see if either cat
// is on auction.
// Check that matron and sire are both owned by caller, or that the sire
// has given siring permission to caller (i.e. matron's owner).
// Will fail for _sireId = 0
require(_isSiringPermitted(_sireId, _matronId));
// Grab a reference to the potential matron
Kitty storage matron = kitties[_matronId];
// Make sure matron isn't pregnant, or in the middle of a siring cooldown
require(_isReadyToBreed(matron));
// Grab a reference to the potential sire
Kitty storage sire = kitties[_sireId];
// Make sure sire isn't pregnant, or in the middle of a siring cooldown
require(_isReadyToBreed(sire));
// Test that these cats are a valid mating pair.
require(_isValidMatingPair(
matron,
_matronId,
sire,
_sireId
));
// All checks passed, kitty gets pregnant!
_breedWith(_matronId, _sireId);
}
/// @notice Have a pregnant Kitty give birth!
/// @param _matronId A Kitty ready to give birth.
/// @return The Kitty ID of the new kitten.
/// @dev Looks at a given Kitty and, if pregnant and if the gestation period has passed,
/// combines the genes of the two parents to create a new kitten. The new Kitty is assigned
/// to the current owner of the matron. Upon successful completion, both the matron and the
/// new kitten will be ready to breed again. Note that anyone can call this function (if they
/// are willing to pay the gas!), but the new kitten always goes to the mother's owner.
function giveBirth(uint256 _matronId)
external
whenNotPaused
returns(uint256)
{
// Grab a reference to the matron in storage.
Kitty storage matron = kitties[_matronId];
// Check that the matron is a valid cat.
require(matron.birthTime != 0);
// Check that the matron is pregnant, and that its time has come!
require(_isReadyToGiveBirth(matron));
// Grab a reference to the sire in storage.
uint256 sireId = matron.siringWithId;
Kitty storage sire = kitties[sireId];
// Determine the higher generation number of the two parents
uint16 parentGen = matron.generation;
if (sire.generation > matron.generation) {
parentGen = sire.generation;
}
// Call the sooper-sekret gene mixing operation.
uint256 childGenes = geneScience.mixGenes(matron.genes, sire.genes, matron.cooldownEndBlock - 1);
// Make the new kitten!
address owner = kittyIndexToOwner[_matronId];
uint256 kittenId = _createKitty(_matronId, matron.siringWithId, parentGen + 1, childGenes, owner);
// Clear the reference to sire from the matron (REQUIRED! Having siringWithId
// set is what marks a matron as being pregnant.)
delete matron.siringWithId;
// Every time a kitty gives birth counter is decremented.
pregnantKitties--;
// Send the balance fee to the person who made birth happen.
msg.sender.send(autoBirthFee);
// return the new kitten's ID
return kittenId;
}
}