지난 번 5번째 컨트랙트 KittyOwnership에서는 주로 토큰(ERC721)에 대한 기본적인 함수에 대해서만 나와있었다. 이제 진짜 크립토 키티스러운..컨트랙트인 KittyBreeding을 살펴본다.
KittyCore <- KittyMinting <- KittyAuction <- KittyBreeding <- KittyOwnership <- KittyBase, ERC721
KittyBase <- KittyAccessControl
고양이를 Breeding(번식) 시키기 위한 내용이 기술되어 있다. KittyBreeding 컨트랙트 이외에 GeneScienceInterface 컨트랙트를 사용하고 있지만, 내용은 안나와있다. 나중에 GeneScienceInterface 내용을 용이하게 변경하고 숨기기?(뭔가) 위해서 그런 듯하다. 현재 GeneScienceInterface의 주소는 (https://etherscan.io/address/0xf97e0a5b616dffc913e72455fde9ea8bbe946a2b)여기로 되어 있다.
KittyCore와 GeneScienceInterface의 각각의 생성 날짜는 다음과 같다.
- KittyCore : (https://etherscan.io/tx/0x691f348ef11e9ef95d540a2da2c5f38e36072619aa44db0827e1b8a276f120f4)로 처음에 생성, 2017년 11월 23일
- GeneScienceInterface : (https://etherscan.io/tx/0x7e92d7c83d542ebebc491dc214a05d3dfb320d05d9140e2b51d2b0b1f32db78d)로 2017년 11월 23일 생성
동일한 날짜에 생성되었고 그때 생성된 GeneScienceAddress가 그대로 지금까지 사용되고 있는 것을 보면 업데이트는 없었던 듯하다. 내용만 숨기려고? 하는 듯한데 나중에 시간이 되면 디컴파일을 해볼까 한다. (아마 누군가 했을 듯하지만..)
그런데 게임 특성상(랜덤성?) 어느 고양이끼리 교배를 시켜야(다시 말하지만 암컷과 수컷의 개념은 없다) 희귀종?의 고양이가 생성되는지를 비밀로 하고 싶었나보다.
breedWithAuto(uint256 _matronId, uint256 _sireId)
함수에서 실제 교배하는 내용이 정의되어 있다. 먼저 앞부분에서 다음과 같은 각종 체크를 한다.
- 최소 0.002이더 (2 Finney, 2 Mili) 이상인지 확인
- 함수를 실행하는 사람이 matron 주인인지 확인(임신 시키기 위함 엄마 고양이 주인인지 확인)
- 현재 pregnat 상태인지 cooldown 상태가 아닌지 체크
- 동일하게 sire주인인지 확인(아빠 고양이 주인 확인)
- 현재 pregnat 상태인지 cooldown 상태가 아닌지 체크
- 두 고양이가 유효한 고양이인지 체크(동일한 고양이끼리 인지, 부모와 교배를 하려고 하는 건지, 형제간에 교배하려는 지 등)
- 그리고 실제로
_breedWith
함수로 교배!
그러면 실질적으로 교배가 일어나는 _breedWidth
함수를 보도록 하자
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);
}
여기를 보면 breedWith
함수는 엄마 고양이의 siringWidthId
를 입력받은 _sireId
로 한다. 즉, 엄마 고양이에 아빠 고양이의 ID를 넣어 임신중인 것을 나타내는 변수이다. 0일때는 임신하고 있지 않은 상태이다.
그리고 _triggerCooldown
함수로 엄마,아빠 고양이의 coolDown 상태로 만든다. 어느정도 coolDown인지는 크립토 키티 구조체의 cooldownIndex에 따라 다르다. 마지막으로 임신중인 키티 수를 +1 증가시키고 event log를 출력하고 끝난다.
KittyCore
컨트랙트에 정의되어 있는 function withdrawBalance() external onlyCFO
함수를 통해 임신 중이 고양이 숫자+1만큼 authBirthFee를 곱한 값을 제외한 값을 모두 CFO 지갑으로 보내게 되어있다. 이건 임신을 시키기 위해 선지급한 돈을 나중에 giveBirth
로 새끼 고양이가 태어나면 돌려주게 되어서 그렇다.
실제로 얼마만큼 크립토 키티는 벌어들인 것인지 (물론 나중에 auction 등이 나오지만 일단 먼저 궁금해서 알아봤다)
주소는 (https://etherscan.io/txsinternal?ps=100&zero=false&a=0x2041bb7d8b49f0bde3aa1fa7fb506ac6c539394c&valid=true)를 보면 크립토키티에서 CFO로 입금된 내역이다 (크립토 키트 컨트랙트를 동작하기 위한 fee는 제외시키고)
총 합계는 대충 8,869 이더이다. 1이더를 70만원으로 환산하면 대략 62억이다.....잘 믿기지 않지만 그렇다. 원래 게임에 돈을 쓰고 캐릭터에 돈을 쓰는 사람들의 심리는 잘 모르는 사람으로서는 이해가 잘 안가지만 상당히 많은 돈이 오고가고 했는 듯하다.
가장 많았을 때는 (작년 12월 경) 1,600이더를 가져온 것이 기록에 있다. 아마 저때가 피크이지 않았나 싶다.
다시 본론으로 들어가서 보자. 임신을 시켰으면 출산을 해야한다. 그 출산에 해당하는 함수는 giveBirth(uint256 _matronId)
로 정의되어 있다.
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;
}
먼저 엄마 고양이가 새끼 고양이를 출산해도 좋은 상태인지 _isReadyToGiveBirth
에서 체크한다. 임신중인지와 blockNumber로 시간이 일정시간이 지난 지 체크한다.
그리고 geneScience.mixGenes()
함수로 새끼 고양이의 유전자를 결정하고 새끼 고양이의 소유권을 엄마 고양이를 가진 사람에게 준다. 그리고 KittyBase 컨트랙트 내에 있던 _createKitty()
를 호출해서 고양이 데이터를 생성한다. KittyBase에 대한 내용은 여기를 참고하기 바란다.
마지막으로 breedWithAuto
에서 선지급한 autoBirthFee를 giveBirth
함수를 호출한 사람에게 autoBirthFee
=0.002이더를 보낸다.
한번 직접한번 교배시켜 새끼를 낳아봤다. 제일 왼쪽이 새로 낳은 고양이이고 가운데(아빠)와 오른쪽(엄마)이 부모 고양이다. 뭔가 외형적으로 유전자(?)가 물려받은 듯이 보인다.