티스토리 뷰

나의 공부방

[블록체인] 이더리움(Ethereum)에 크립토키티(CryptoKitties)와 같은 게임 개발하기

망나니개발자 2020. 7. 22. 20:41
반응형

아래의 내용은 How to Code Your Own CryptoKitties-Style Game on Ethereum의 내용을 번역/의역 및 추가 정리 한 것입니다.

 

1. CryptoKitties(크립토키티) 소개


[ CryptoKitties(크립토키티) 소개 ]

CryptoKitties는 디지털 고양이를 사고, 팔고, 교배시키는 게임이다.

https://www.cryptokitties.co/?startMeow=visible

 

CryptoKitties | Collect and breed digital cats!

Collect and trade CryptoKitties in one of the world’s first blockchain games. Breed your rarest cats to create the purrfect furry friend. The future is meow!

www.cryptokitties.co

각각의 고양이들은 서로 다른 유전자를 지니며, 그렇기 때문에 모습이 모두 다르다. 만약 2개의 고양이를 교배시키면 유전자가 결합되어 unique한 방법으로 자식 고양이가 생기게 되고, 탄생한 자식 고양이를 팔거나 교배시킬 수 있다.

 

 

[ CryptoKitties 소스코드 ]

크립토키티 코드의 대부분은 오픈소스로 공개되어 있기 때문에, 소스코드를 읽어서 동장 방식을 이해하는 것이 최선의 방법이다. 크립토키티의 코드는 아래의 링크에 공개되어 있다. 크립토키티의 소스코드는 약 2000줄 정도로 적지 않기 때문에, 중요한 부분에 대해서만 분석하고 넘어가도록 하자.

https://ethfiddle.com/09YbyJRfiI

 

EthFiddle - Solidity in the Browser. Powered By Loom Network

#1 Solidity code sharing site. Share Solidity code snippets with friends, or check out cool code snippets from around the web.

ethfiddle.com

 

크립토키티의 코드는 아래와 같은 여러 개의 작은 컨트랙트로 분할된다. 크립토키티 관련 컨트랙트의 상속 관계를 보면 아래와 같다.

contract KittyAccessControl
contract KittyBase is KittyAccessControl
contract KittyOwnership is KittyBase, ERC721
contract KittyBreeding is KittyOwnership
contract KittyAuction is KittyBreeding
contract KittyMinting is KittyAuction
contract KittyCore is KittyMinting

결국 KittyCore 컨트랙트 주소가 앱의 최종적인 주소가 되며, 하위 컨트랙트로부터 데이터와 메소드를 상속받아 가지고 있다. 그렇기 때문에 KittyAccessControl부터 KittyCore까지 순서대로 살펴보도록 하자.

 

 

2. CryptoKitties 코드 상세


[ 1. KittyAccessControl: Who Controls the Contract? ]

 

KittyAccessControl은 다양한 address들을 관리하며, CEO, CFO, COO 직책을 가진 사람들만 실행가능하도록 기능을 제한한다.

 

KittyAccessControl은 다른 contract들을 관리하기 위한 contract이며 게임의 메커니즘과는 관계가 없다. 이 컨트랙트는 기본적으로 "CEO", "COO, "CFO"에 대한 seter 메소드만을 갖는다. "CEO", "COO, "CFO"는 컨트랙트의 기능들과 소유권을 갖는 이더리움 주소를 가리킨다.

또한 KittyAccessControl은 onlyCEO(CEO만 실행시킬 수 있는 함수)와 같은 modifiers 함수들을 정의하고 있으며, 추가로 컨트랙트를 puase/unpause하는 함수와 funds를 withdraw하는 함수를 가지고 있다.

modifier onlyCLevel() {
    require(
        msg.sender == cooAddress ||
        msg.sender == ceoAddress ||
        msg.sender == cfoAddress
    );
    _;
}
//...some other stuff
// Only the CEO, COO, and CFO can execute this function:
function pause() external onlyCLevel whenNotPaused {
    paused = true;
}

 

위와 같이 구현된 Pause함수는 예상치 못한 버그가 있을 경우에, 시스템을 멈추고 새로운 버전으로 업데이트를 하기 위해 추가되었을 것이다. 이 함수는 개발자가 contract를 완전히 멈추게 함으로써, 어느 누구도 kitties를 사고 팔거나, 교배시킬 수 없도록 한다. 개발자들은 이것을 당연히 실행시키고 싶지 않겠지만, 이더리움 기반의 DAPP이 개발자에 의한 통제를 받을 수 있다는 것은 상당히 흥미로운 사실이다.

 

 

 

[ 2. KittyBase: What is a Kitty, Reall? ]

 

KittyBase 컨트랙트는 핵심적인 기능 전반에 걸쳐 공유되는 기본적인 코드가 정의되어 있다. 이것은 핵심 데이터 저장소, 변수, 데이터 타입 그리고 이런 항목들을 관리하기 위한 internal 함수를 포함한다.

 

KittyBase는 App의 많은 핵심 데이터를 정의하고 있다.

첫번째로, 아래와 같은 Kitty 구조체를 정의하고 있다. Kitty는 unsinged int로만 구성되어 있다.

struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
}

Kitty의 각 항목들을 살펴보면 아래와 같다.

 

  • genes: 고양이의 유전자를 표현하는 256bit integer로, 고양이의 외관을 결정하는 핵심 데이터
  • birthTime: 고양이가 탄생한 시간을 저장하는 block의 timestamp
  • cooldownEndBlock: 고양이가 다시 교배를 하기 위해 필요한 최소 시간
  • matronId & sireId: 고양이의 아빠, 엄마 ID
  • siringWithId: 고양이가 임신중일 경우 아빠의 ID로 설정되고, 그렇지 않을 경우 0
  • cooldownIndex: 이 고양이의 현재까지 cool down 지속 시간(고양이가 다시 새끼를 낳으려면 얼마나 더 기다려야 하는지)
  • generation: 고양이의 세대 번호. contract에 의해 처음 탄생한 고양이는 세대 번호가 0이고, 자식의 세대 번호는 부모보다 1이 크다.

Cryptokitties에서 고양이는 무성생식을 하고, 2마리의 고양이끼리 교배가 가능하므로 고양이의 성별이 없다는 것에 유의해야 한다.

 

KittyBase 컨트랙트는 이러한 Kitty 구조체의 배열을 가지고 있다.

Kitty[] kitties

이 배열은 존재하는 모든 Kitties의 데이터를 담고 있으며, master Kitty DB와 같다. 언제든지 새로운 고양이가 생성되면, 이 배열에 추가되게 되고, 고양이의 ID는 배열의 index와 관계를 갖는다. 다음과 같은 Genesis 고양이의 경우 1번 ID를 갖고 있는 것을 확인할 수 있다.

 

이 컨트랙트는 고양이의 ID를 소유자의 주소로 매핑해주는 mapping 변수를 가지고 있는데, 누가 이 고양이를 소유하고 있는지 추적하기 위함이다.

mapping(uint256 => address) public kittyIndexToOwner;

다른 mapping들 역시 정의되어 있지만, 모두 설명하면 내용이 너무 길어질 수 있으므로 생략하도록 하겠다.

 

만약 kitty의 소유권이 다른사람에게 넘어가면(transfer), kittyIndexToOwner가 새로운 owner로 갱신된다.

/// @dev Assigns ownership of a specific Kitty to an address.
function _transfer(address _from, address _to, uint256 _tokenId) internal {
    // Since the number of kittens is capped to 2^32 we can't overflow this
    ownershipTokenCount[_to]++;
    // transfer ownership
    kittyIndexToOwner[_tokenId] = _to;
    // When creating new kittens _from is 0x0, but we can't account that address.
    if (_from != address(0)) {
        ownershipTokenCount[_from]--;
        // once the kitten is transferred also clear sire allowances
        delete sireAllowedToAddress[_tokenId];
        // clear any previously approved ownership exchange
        delete kittyIndexToApproved[_tokenId];
    }
    // Emit the transfer event.
    Transfer(_from, _to, _tokenId);
}

 

새로운 kitty가 생성되는 함수는 아래와 같다.

function _createKitty(
    uint256 _matronId,
    uint256 _sireId,
    uint256 _generation,
    uint256 _genes,
    address _owner
)
    internal
    returns (uint)
{
    // These requires are not strictly necessary, our calling code should make
    // sure that these conditions are never broken. However! _createKitty() is already
    // an expensive call (for storage), and it doesn't hurt to be especially careful
    // to ensure our data structures are always valid.
    require(_matronId == uint256(uint32(_matronId)));
    require(_sireId == uint256(uint32(_sireId)));
    require(_generation == uint256(uint16(_generation)));

    // New kitty starts with the same cooldown as parent gen/2
    uint16 cooldownIndex = uint16(_generation / 2);
    if (cooldownIndex > 13) {
        cooldownIndex = 13;
    }

    Kitty memory _kitty = Kitty({
        genes: _genes,
        birthTime: uint64(now),
        cooldownEndBlock: 0,
        matronId: uint32(_matronId),
        sireId: uint32(_sireId),
        siringWithId: 0,
        cooldownIndex: cooldownIndex,
        generation: uint16(_generation)
    });
    uint256 newKittenId = kitties.push(_kitty) - 1;

    // It's probably never going to happen, 4 billion cats is A LOT, but
    // let's just be 100% sure we never let this happen.
    require(newKittenId == uint256(uint32(newKittenId)));

    // emit the birth event
    Birth(
        _owner,
        newKittenId,
        uint256(_kitty.matronId),
        uint256(_kitty.sireId),
        _kitty.genes
    );

    // This will assign ownership, and also emit the Transfer event as
    // per ERC721 draft
    _transfer(0, _owner, newKittenId);

    return newKittenId;
}

이 함수는 엄마와 아빠의 ID, 세대 번호, 유전자 코드, Owner의 주소를 매개변수로 받고 있다. 새로운 Kitty를 생성하고 나면 master kitty 배열에 push하고 있으며, 새로운 주인에게 할당하기 위해 _transfer() 함수를 호출하고 있다.

 

이제 우리는 CryptoKitties의 Kitty가 어떤 데이터로 이루어져 있고, 어떻게 블록체인에 저장되고, Kitty의 소유자를 어떻게 추적하는지 알게 되었다.

 

 

[ 3. KittyOwnership: Kitties as Tokens ]

 

 

KittyOwnership 컨트랙트는 ERC721 명세에 따라 non-fungible token(대체 불가능한 토큰)의 트랜잭션을 위한 함수들을 제공한다.

CryptoKitties는 non-fungible token(대체 불가능한 토큰)인 ERC721 token의 명세를 따르고 있는데, ERC721은 디지털 게임 카드나 MMORPG의 희귀 아이템과 같이 디지털 수짐품들의 소유권을 추적하는데 도움이 된다.

 

Fungibility(대체 가능성)란?: 내가 가진 5ether는 다른 사람의 5ether와 동일한 가치를 지니기 때문에 Ether는 fungible(대체 가능)하다. 하지만 서로 다른 유전자 번호를 갖는 CryptoKitties의 경우, 모든 고양이는 각각 다르며, 그에 따라 다른 가치를 지니기 때문에 non-fungible(대체 불가능)하다.

 

이러한 이유로, KittyOwnership 컨트랙트는 ERC721 컨트랙트를 상속받고 있음을 확인할 수 있다.

contract KittyOwnership is KittyBase, ERC721 {

 

그리고 모든 ERC721 토큰은 아래의 표준을 따르기 때문에 KittyOwnership 컨트랙트 역시 다음과 같은 함수들을 채운다.

/// @title Interface for contracts conforming to ERC-721: Non-Fungible Tokens
/// @author Dieter Shirley <dete@axiomzen.co> (https://github.com/dete)
contract ERC721 {
    // Required methods
    function totalSupply() public view returns (uint256 total);
    function balanceOf(address _owner) public view returns (uint256 balance);
    function ownerOf(uint256 _tokenId) external view returns (address owner);
    function approve(address _to, uint256 _tokenId) external;
    function transfer(address _to, uint256 _tokenId) external;
    function transferFrom(address _from, address _to, uint256 _tokenId) external;

    // Events
    event Transfer(address from, address to, uint256 tokenId);
    event Approval(address owner, address approved, uint256 tokenId);

    // Optional
    // function name() public view returns (string name);
    // function symbol() public view returns (string symbol);
    // function tokensOfOwner(address _owner) external view returns (uint256[] tokenIds);
    // function tokenMetadata(uint256 _tokenId, string _preferredTransport) public view returns (string infoUrl);

    // ERC-165 Compatibility (https://github.com/ethereum/EIPs/issues/165)
    function supportsInterface(bytes4 _interfaceID) external view returns (bool);
}

위의 메소드들은 public이기 때문에, 사용자가 다른 ERC721 토큰과 상호작용하는 것과 동일한 방법으로 CryptoKitties 토큰과 상호작용하는 표준을 제공한다. 웹을 거치지 않고 이더리움 블록체인상의 CryptoKitties 컨트랙트로 직접 상호작용 함으로써, 누군가에게 토큰을 직접 전달할 수 있으며, 이러한 관점에서 자신만의 Kitties를 소유했다고 볼 수 있다.

 

이번에도 역시 모든 메소드의 구현체를 직접 확인하지는 않을 것이므로 EthFiddle에서 “KittyOwnership”을 찾아 직접 확인해보기 바란다.

 

[ 4. KittyBreeding: Cats Get Down and Dirty ]

 

KittyBreeding 컨트랙트는 양육 제안 등을 포함하여, 고양이를 교배시키기 위해 필요한 메소드들을 가지고 있으며 external genetic combination 컨트랙트에 의존하고 있다.

The “external genetic combination contract” (geneScience)는 별도의 컨트랙트에 작성되어 있으며 오픈소스가 아니다. KittyBreeding 컨트랙트는 external 컨트랙트의 주소를 set하기 위한 CEO의 메소드를 가지고 있다.

/// @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;
}

위와 같이 구성되어 있기 때문에 게임이 너무 쉽진 않다. 만약 고양이의 유전자가 어떻게 결정되는지를 확인할 수 있다면, 멋진 고양이(가치가 높은 고양이)를 얻는 방법으로 게임이 너무 쉬워질 것이다.

 

이러한 external genScience 컨트랙트는 나중에 새로운 고양이의 DNA를 결정하기 위한 giveBirth() 함수에서 사용된다.

이제 두 고양이가 교배하면 어떤 일이 일어나는지 살펴보도록 하자.

/// @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);
}

위와 같은 교배 함수는 엄마와 아빠의 ID를 매개변수로 받고 있고, master Kitty Array쪽에서 참조하고 있다. 그리고 엄마의 siringWithId를 아빠의 ID로 set해주고 있다.(위에서 siringWihtId가 0이 아니면, 엄마가 임신중이라고 설명하였다.)

_breedWith함수는 또한 각 부모에게 triggerCooldown을 실행시켜 일정 시간 동안 다시 교배하지 못하도록 해주고 있다.

 

그 다음, 실제로 새로운 고양이를 생성하는 giveBirth() 함수를 살펴보면 아래와 같다.

/// @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;
}

인라인 주석으로 설명이 잘 되어 있어서 이해하기 수월할 것이다. 기본적으로 이 함수는 엄마가 출산할 준비가 되었는지를 검사한다. 그리고 geneScience.mixGenes() 함수를 사용하여 자식의 유전자를 결정하고 있으며, 엄마 고양이의 소유자에게 새로운 고양이의 소유권을 할당하고 KittyBase에서 살펴본 _createKitty() 함수를 호출하고 있다.

geneScience.mixGenes() 함수는 Black Box 함수로써, 공개되지 않은 컨트랙트이다. 그렇기 때문에 우리는 어떻게 자식의 유전자가 결정되는지 알 수 없지만 엄마의 유전자, 아빠의 유전자, 엄마의 cooldownEndBlock을 활용한다는 것은 알고 있다.

 

 

[ 5. KittyAuctions: Buying, Selling and Pimpin' of Cats ]

 

KittyAuctions 컨트랙트는 고양이를 경매 또는 입찰하는 public 메소드들을 가지고 있다. 실제 경매 기능은 2개의 sibling 컨트랙트에 의해 수행되는데, 판매용 1개와 교배용 1개로 구성되어 있다. 또한 경매의 작성과 입찰은 핵심 컨트랙트를 통해 중재된다. 

CryptoKitties의 개발자에 따르면 그들은 경매 기능을 2개의 “sibling” contracts로 분리하였는데, 그들은 "관련 로직이 복잡하고, 사소한 버그를 포함할 가능성이 있기 때문이다. 이렇게 컨트랙트를 분리함으로써, Kitty의 소유권을 추적하고 있는 Main Contract에 방해 없이 버그가 생겼을 경우 수정할 수 있다." 라고 설명하였다. 

KittyAuctions 컨트랙트는 2개의 분리된 컨트랙트(SaleAuction, Siring Auction)를 가지고 있기 때문에 CEO만 호출가능했던 setGeneScienceAddress() 함수처럼 setSaleAuctionAddress() 함수와 setSiringAuctionAddress() 함수를 가지고 있다. 2개의 함수는 각각의 컨트랙트를 set해주는 함수이다.

(여기서 Siring 경매란 다른 사용자가 올린 고양이를 ether로 입찰하면 자신의 고양이와 교배시킬 수 있는 경매를 의미한다.) 

 

이것은 CryptoKitties의 컨트랙트 자체는 수정불가능하지만 CEO가 auction 컨트랙트의 주소를 변경할 수 있어 경매의 규칙이 바뀔 수 있음을 의미한다. 즉, 개발자가 버그를 수정해야 하는 경우가 존재하기 때문에 이러한 것이 존재하는 것이며, 반드시 나쁜 것은 아니지만 그래도 알고 있어야 한다.

 

위에서와 마찬가지로 경매 및 입찰에 대한 내용을 쓰면 너무 길어질 수 있으므로 생략하도록 하겠다.

 

[ 6. KittyMinting: The Gen0 Cat Factory ]

 

KittyMinting 컨트랙트는 새로운 gen0 고양이들을 생성하기 위한 기능을 가지고 있다. 우리는 최대 5000개의 "promo" 고양이를 생성할 수 있으며(시장이 처음 생길 때 중요하다), 다른 모든 고양이(gen0 고양이)들은 알고리즘으로 정해진 시작 가격을 통해서만 경매에 올라갈 수 있다. 어떻게 만들어지는지에 관계없이 5만 마리라는 한계가 있다. 그 이후에 모든 것은 Community에 달려 있다.

 

생성가능한 promo 고양이와 gen0 고양이의 개수는 아래와 같이 상수로 정의되어 있다.

uint256 public constant PROMO_CREATION_LIMIT = 5000;
uint256 public constant GEN0_CREATION_LIMIT = 45000;

 

그리고 "COO"가 promo 고양이와 gen0 고양이를 생성할 수 있는 코드는 아래에 정의되어 있다.

/// @dev we can create promo kittens, up to a limit. Only callable by COO
/// @param _genes the encoded genes of the kitten to be created, any value is accepted
/// @param _owner the future owner of the created kittens. Default to contract COO
function createPromoKitty(uint256 _genes, address _owner) external onlyCOO {
    address kittyOwner = _owner;
    if (kittyOwner == address(0)) {
         kittyOwner = cooAddress;
    }
    require(promoCreatedCount < PROMO_CREATION_LIMIT);

    promoCreatedCount++;
    _createKitty(0, 0, 0, _genes, kittyOwner);
}

/// @dev Creates a new gen0 kitty with the given genes and
///  creates an auction for it.
function createGen0Auction(uint256 _genes) external onlyCOO {
    require(gen0CreatedCount < GEN0_CREATION_LIMIT);

    uint256 kittyId = _createKitty(0, 0, 0, _genes, address(this));
    _approve(kittyId, saleAuction);

    saleAuction.createAuction(
        kittyId,
        _computeNextGen0Price(),
        0,
        GEN0_AUCTION_DURATION,
        address(this)
    );

    gen0CreatedCount++;
}

 

createPromoKitty()를 보면 최대 5000마리까지 "COO"가 그가 원하는 유전자에 따라 새로운 고양이를 생성하고, 그가 원하는 누구에게나 보낼 수 있는 것으로 보인다. 나는 그들이 beta tester, 친구, 가족을 위해 promo 고양이를 생성했을 것이라고 추측한다. 이러한 결과로 당신은 개발자가 원하는 만큼 5000개의 고양이를 생성할 수 있기 때문에 자신의 고양이가 특별하지 않다고 생각할 수 있다.

createGen0Auction() 역시 "COO"가 새로운 고양이에게 genetic code를 제공하고 있다. 하지만 특정인의 주소를 할당하지 않고, 사용자들이 Ether를 입찰하여 살 수 있도록 경매에 올리고 있다.

 

 

[ 7. KittyCore: The Master Contract ]

KittyCore 컨트랙트는 CryptoKitties의 핵심 컨트랙트이며, 이더리움에서 구동되고 있는 컨트랙트이다. 이 컨트랙트는 모든 계약을 하나로 묶어서 가지고 있다.

상속 구조 때문에 KittyCore 컨트랙트는 위에서 설명한 모든 컨트랙트를 상속받고 있고, 고양이의 ID를 사용하여 모든 고양이의 정보를 얻기 위한 코드가 추가되어 있다.

/// @notice Returns all the relevant information about a specific kitty.
/// @param _id The ID of the kitty of interest.
function getKitty(uint256 _id)
    external
    view
    returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
) {
    Kitty storage kit = kitties[_id];

    // if this variable is 0 then it's not gestating
    isGestating = (kit.siringWithId != 0);
    isReady = (kit.cooldownEndBlock <= block.number);
    cooldownIndex = uint256(kit.cooldownIndex);
    nextActionAt = uint256(kit.cooldownEndBlock);
    siringWithId = uint256(kit.siringWithId);
    birthTime = uint256(kit.birthTime);
    matronId = uint256(kit.matronId);
    sireId = uint256(kit.sireId);
    generation = uint256(kit.generation);
    genes = kit.genes;
}

위의 함수는 public 함수로 블록체인에 존재하는 특정한 kitty의 모든 정보를 반환하고 있다. 이 쿼리가 Web에서 고양이를 보여주기 위해 사용될 것 이다. Solidty에는 고양이의 이미지나 설명을 저장하거나 256-bit의 유전자코드가 실제로 어떻게 표현되는지 보여주는 코드가 없다. 그러므로 genetic코드를 통해 고양이의 모습을 보여주는 코드는 CryptoKitty의 웹서버에 존재할 것이다.

 

 

CryptoKittes는 블록체인 기반의 게임을 아주 영리하게 구현하여 보여주고 있지만, 실제로 100% 블록체인 기반으로 작동하고 있지는 않다. 만약 CryptoKitty의 웹서버가 미래에 오프라인으로 전환되었고, 누군가는 이미지를 백업하지 않았다면 무의미한 256-bit 정수만을 갖고 있는 것이 되기 때문이다.

컨트랙트 코드에서 ERC721Metadata 라고 불리는 컨트랙트를 찾았지만, 그 계약서는 어떠한 용도로도 쓰이고 있지 않다. 그래서 추측컨데 원래 모든 것을 블록체인에 저장하려고 계획했지만 나중에 그것을 하지 않기로 결정하였으며, 결국 웹서버에 저장하기로 결정하였을 것이다.(아마 이더리움에 모든 데이터를 저장하려면 많은 비용이 발생하기 때문이 아닐가 생각한다.)

 

[ 결론 ]

우리는 지금까지 다음과 같은 항목들을 살펴보았다.

  • 고양이들이 어떻게 데이터로 표현되는지
  • 존재하는 모든 고양이들이 어떻게 하나의 스마크 컨트랙트에 저장되었고, 소유자를 어떻게 추적하는지
  • 어떻게 gen0 고양이들이 생성되었는지
  • 어떻게 고양이들이 교배하여 새로운 고양이가 탄생하는지?

 

이것은 단지 개요만을 살펴본 것일 뿐이고, 자세한 참고를 위해서는 EthFiddle에서 전체 코드를 살펴보아야 한다. 또한 이더리움 언어를 공부하기 위해서는 CryptoZombies.io를 진행해보기 바란다.

반응형
댓글
댓글쓰기 폼
반응형
공지사항
Total
3,025,796
Today
2,778
Yesterday
1,931
TAG
more
«   2022/10   »
            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          
글 보관함