Permalink
Switch branches/tags
Nothing to show
Find file Copy path
0876516 Mar 28, 2018
1 contributor

Users who have contributed to this file

335 lines (282 sloc) 14.1 KB
/// @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;
}
}