Development/Blockchain

[블록체인] 크립토좀비 - 1. 좀비 공장 만들기 / 2. 좀비가 희생물을 공격하다

안다희 2019. 4. 3. 02:01
728x90

챕터 6: 배열

어떤 것의 모음집이 필요할 때 _배열_을 사용할 수 있네.

솔리디티에는 _정적_ 배열과 _동적_ 배열이라는 두 종류의 배열이 있지:

// 2개의 원소를 담을 수 있는 고정 길이의 배열: 
uint[2] fixedArray; 

// 또다른 고정 배열으로 5개의 스트링을 담을 수 있다: 
string[5] stringArray; 

// 동적 배열은 고정된 크기가 없으며 계속 크기가 커질 수 있다: 
uint[] dynamicArray;

구조체의 배열을 생성할 수도 있다. 이전 챕터의 Person 구조체를 이용하면:

Person[] people; // 이는 동적 배열로, 원소를 계속 추가할 수 있다.

상태 변수가 블록체인에 영구적으로 저장될 수 있다는 걸 기억하나?

그러니 이처럼 구조체의 동적 배열을 생성하면 마치 데이터베이스처럼 컨트랙트에 구조화된 데이터를 저장하는 데 유용하네.

Public 배열

public으로 배열을 선언할 수 있지. 솔리디티는 이런 배열을 위해 getter 메소드를 자동적으로 생성하지. 구문은 다음과 같네:

Person[] public people;

 

챕터 7: 함수 선언

솔리디티에서 함수 선언은 다음과 같이 하네:

function eatHamburgers(string _name, uint _amount) { }

이 함수는 eatHamburgers라는 함수로, string과 uint 2개의 인자를 전달받고 있군. 현재로선 함수의 내용이 비어 있네.

참고: 함수 인자명을 언더스코어(_)로 시작해서 전역 변수와 구별하는 것이 관례이네 (의무는 아님). 본 튜토리얼 전체에서 이 관례를 따를 것이네.

 

챕터 8: 구조체와 배열 활용하기

새로운 구조체 생성하기

지난 예시의 Person 구조체를 기억하나?

struct Person {
	uint age;
    string name;
}

Person[] public people;

이제 새로운 Person를 생성하고 people 배열에 추가하는 방법을 살펴보도록 하지.

// 새로운 사람을 생성한다: 
Person satoshi = Person(172, "Satoshi"); 
// 이 사람을 배열에 추가한다: 
people.push(satoshi);

이 두 코드를 조합하여 깔끔하게 한 줄로 표현할 수 있네:

people.push(Person(16, "Vitalik"));

참고로 array.push()는 무언가를 배열의 에 추가해서 모든 원소가 순서를 유지하도록 하네. 다음 예시를 살펴보도록 하지:

uint[] numbers; 
numbers.push(5); 
numbers.push(10); 
numbers.push(15); 
// numbers 배열은 [5, 10, 15]과 같다.

구조체에 생성자(?)를 따로 안만들어도 그냥 Person(_age, _name) 하면 되는구나

 

챕터 9: Private / Public 함수

솔리디티에서 함수는 기본적으로 public으로 선언되네. 즉, 누구나 (혹은 다른 어느 컨트랙트가) 자네 컨트랙트의 함수를 호출하고 코드를 실행할 수 있다는 의미지.

확실히 이는 항상 바람직한 건 아닐 뿐더러, 자네 컨트랙트를 공격에 취약하게 만들 수 있지. 그러니 기본적으로 함수를 private으로 선언하고, 공개할 함수만 public으로 선언하는 것이 좋지.

private 함수를 선언하는 방법을 살펴보도록 하겠네:

uint[] numbers; 
function _addToArray(uint _number) private { 
	numbers.push(_number); 
}

private컨트랙트 내의 다른 함수들만이 이 함수를 호출하여 numbers 배열로 무언가를 추가할 수 있다는 것을 의미하지.

위의 예시에서 볼 수 있듯이 private 키워드는 함수명 다음에 적네. 함수 인자명과 마찬가지로 private 함수명도 언더바(_)로 시작하는 것이 관례라네.

 

챕터 10: 함수 더 알아보기

이번 챕터에서는 함수의 반환값과 함수 제어자에 대해서 알아보겠네.

반환값

함수에서 어떤 값을 반환 받으려면 다음과 같이 선언해야 하네:

string greeting = "What's up dog"; 
function sayHello() public returns (string) { 
	return greeting; 
}

솔리디티에서 함수 선언은 반환값 종류를 포함하지 (이 경우에는 string이네).

함수 제어자

위에서 살펴 본 함수 sayHello()는 솔리디티에서 상태를 변화시키지 않는다네. 즉, 어떤 값을 변경하거나 무언가를 쓰지 않지.

이 경우에는 함수를 view 함수로 선언한다네. 이는 함수가 데이터를 보기만 하고 변경하지 않는다는 뜻이지:

function sayHello() public view returns (string) {

솔리디티는 pure 함수도 가지고 있는데, 이는 함수가 앱에서 어떤 데이터도 접근하지 않는 것을 의미하지. 다음을 살펴보게:

function _multiply(uint a, uint b) private pure returns (uint) { return a * b; }

이 함수는 앱에서 읽는 것도 하지 않고, 다만 반환값이 함수에 전달된 인자값에 따라서 달라지지. 그러니 이 경우에 함수를 pure로 선언하지.

참고: 함수를 pure나 view로 언제 표시할지 기억하기 어려울 수 있지. 운 좋게도 솔리디티 컴파일러는 어떤 제어자를 써야 하는지 경고 메시지를 통해 잘 알려주네.

 

챕터 11: Keccak256과 형 변환

우리가 _generateRandomDna 함수의 반환값이 (반) 랜덤인 uint가 되기를 원하면, 어떻게 하면 되겠는가?

이더리움은 SHA3의 한 버전인 keccak256내장 해시 함수로 가지고 있지. 해시 함수는 기본적으로 입력 스트링을 랜덤 256비트 16진수로 매핑하네. 스트링에 약간의 변화라도 있으면 해시 값은 크게 달라지네.

해시 함수는 이더리움에서 여러 용도로 활용되지만, 여기서는 의사 난수 발생기(pseudo-random number generator)로 이용하도록 하지.

예시:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 
keccak256("aaaab"); 
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 
keccak256("aaaac");

이 예시를 보면 입력값의 한 글자가 달라졌음에도 불구하고 반환값은 완전히 달라짐을 알 수 있지.

참고: 블록체인에서 안전한 의사 난수 발생기는 매우 어려운 문제네. 여기서 우리가 활용한 방법은 안전하지는 않지만, 좀비 DNA에 있어서 보안은 최우선순위가 아니니 우리의 목적에는 충분히 적합할 것이네.

형 변환

가끔씩 자네가 자료형 간에 변환을 할 필요가 있지. 다음 예시를 살펴보세:

uint8 a = 5; 
uint b = 6; 
// a * b가 uint8이 아닌 uint를 반환하기 때문에 에러 메시지가 난다: 
uint8 c = a * b; 
// b를 uint8으로 형 변환해서 코드가 제대로 작동하도록 해야 한다: 
uint8 c = a * uint8(b);

위의 예시에서 a * b는 uint를 반환하지. 하지만 우리는 이 반환값을 uint8에 저장하려고 하니 잠재적으로 문제를 야기할 수 있네. 반환값을 uint8으로 형 변환하면 코드가 제대로 작동하고 컴파일러도 에러 메시지를 주지 않을 걸세.

 

챕터 13: 이벤트

우리의 컨트랙트가 거의 완성되어 가는군! 이제 이벤트를 추가해 보세.

이벤트는 자네의 컨트랙트가 블록체인 상에서 자네 앱의 사용자 단에서 무언가 액션이 발생했을 때 의사소통하는 방법이지. 컨트랙트는 특정 이벤트가 일어나는지 "귀를 기울이고" 그 이벤트가 발생하면 행동을 취하지.

 

event 안에 uint indexed x 를 하면 필터링하여 검색 가능.

이벤트가 발생하면 단순히 어떤 값을 가지는지만 알고 싶다면 indexed를 붙이지 않아도 되지만, 

필터링 검색을 하려면 indexed 키워드를 붙여주어야 한다.

 

예시:

// 이벤트를 선언한다 
event IntegersAdded(uint x, uint y, uint result); 
function add(uint _x, uint _y) public { 
	uint result = _x + _y; // 이벤트를 실행하여 앱에게 add 함수가 실행되었음을 알린다: 
	IntegersAdded(_x, _y, result); 
    return result; 
}

그러면 자네 앱의 사용자 단은 해당 이벤트가 일어나는지 귀를 기울이지. 자바스크립트로 이를 구현하면 다음과 같네:

YourContract.IntegersAdded(function(error, result) { // 결과와 관련된 행동을 취한다 }
pragma solidity ^0.4.19;

contract ZombieFactory {
  event NewZombie(uint zombieId, string name, uint dna);

  uint dnaDigits = 16;
  uint dnaModulus = 10 ** dnaDigits;

  struct Zombie {
    string name;
    uint dna;
  }

  Zombie[] public zombies;

  function _createZombie(string _name, uint _dna) private {
      uint id = zombies.push(Zombie(_name, _dna)) - 1;
      NewZombie(id, _name, _dna);
  }

  function _generateRandomDna(string _str) private view returns (uint) {
    uint rand = uint(keccak256(_str));
    return rand % dnaModulus;
  }

  function createRandomZombie(string _name) public {
    uint randDna = _generateRandomDna(_name);
    _createZombie(_name, randDna);
  }
}

우리의 솔리디티 컨트랙트가 완성되었네! 이제 이 컨트랙트와 상호작용하는 사용자 단의 자바스크립트 코드를 작성해야 하네.

챕터 14: Web3.js

이더리움은 Web3.js라고 하는 자바스크립트 라이브러리를 가지고 있네.

이후 레슨에서 컨트랙트를 구축하고 Web3.js를 셋업하는 방법을 자세히 살펴볼 걸세. 하지만 지금으로선 Web3.js가 구축된 컨트랙트와 어떤 방식으로 상호작용하는지에 대한 샘플 코드를 살펴보도록 하지.

아직 코드를 잘 이해하지 못 하더라도 걱정하지 말게.

// 여기에 우리가 만든 컨트랙트에 접근하는 방법을 제시한다:
var abi = /* abi generated by the compiler */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory`는 우리 컨트랙트의 public 함수와 이벤트에 접근할 수 있다.

// 일종의 이벤트 리스너가 텍스트 입력값을 취한다:
$("#ourButton").click(function(e) {
  var name = $("#nameInput").val()
  // 우리 컨트랙트의 `createRandomZombie`함수를 호출한다:
  ZombieFactory.createRandomZombie(name)
})

// `NewZombie` 이벤트가 발생하면 사용자 인터페이스를 업데이트한다
var event = ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  generateZombie(result.zombieId, result.name, result.dna)
})

// 좀비 DNA 값을 받아서 이미지를 업데이트한다
function generateZombie(id, name, dna) {
  let dnaStr = String(dna)
  // DNA 값이 16자리 수보다 작은 경우 앞 자리를 0으로 채운다
  while (dnaStr.length < 16)
    dnaStr = "0" + dnaStr

  let zombieDetails = {
    // 첫 2자리는 머리의 타입을 결정한다. 머리 타입에는 7가지가 있다. 그래서 모듈로(%) 7 연산을 하여
    // 0에서 6 중 하나의 값을 얻고 여기에 1을 더해서 1에서 7까지의 숫자를 만든다. 
    // 이를 기초로 "head1.png"에서 "head7.png" 중 하나의 이미지를 불러온다:
    headChoice: dnaStr.substring(0, 2) % 7 + 1,
    // 두번째 2자리는 눈 모양을 결정한다. 눈 모양에는 11가지가 있다:
    eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
    // 셔츠 타입에는 6가지가 있다:
    shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
    // 마지막 6자리는 색깔을 결정하며, 360도(degree)까지 지원하는 CSS의 "filter: hue-rotate"를 이용하여 아래와 같이 업데이트된다:
    skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),
    eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),
    clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),
    zombieName: name,
    zombieDescription: "A Level 1 CryptoZombie",
  }
  return zombieDetails
}

그런 다음, 우리의 자바스크립트 코드가 위의 zombieDetails에서 생성된 값을 받아 웹 브라우저 기반 자바스크립트의 마법과 같은 기능(우리는 Vue.js를 이용함)을 활용하여 이미지를 변경하고 CSS 필터를 적용하지. 이를 위한 코드를 이후의 레슨에서 살펴볼 것이네.


챕터 2: 매핑과 주소

데이터베이스에 저장된 좀비들에게 주인을 설정하여 우리 게임을 멀티 플레이어 게임으로 만들어 보세.

이걸 하려면 mapping과 address라는 2가지 새로운 자료형이 필요할 거네.

주소

이더리움 블록체인은 은행 계좌와 같은 계정들로 이루어져 있지. 계정은 이더리움 블록체인상의 통화인 _이더_의 잔액을 가지지. 자네의 은행 계좌에서 다른 계좌로 돈을 송금할 수 있듯이, 계정을 통해 다른 계정과 이더를 주고 받을 수 있지.

각 계정은행 계좌 번호와 같은 주소를 가지고 있네. 주소는 특정 계정을 가리키는 고유 식별자로, 다음과 같이 표현되지:

0x0cE446255506E92DF41614C46F1d6df9Cc969183

(이 주소는 크립토좀비 팀의 주소지. 자네가 크립토좀비를 즐기고 있다면 우리에게 이더 몇 개를 보내줄 수 있겠지! 😉)

이후 레슨에서 주소에 관한 핵심 내용을 알아 볼 것일세. 지금은 자네가 "주소는 특정 유저(혹은 스마트 컨트랙트)가 소유한다"라는 점만 이해하면 되네.

그러니까 주소를 우리 좀비들에 대한 소유권을 나타내는 고유 ID로 활용할 수 있네. 유저가 우리 앱을 통해 새로운 좀비를 생성하면 좀비를 생성하는 함수를 호출한 이더리움 주소에 그 좀비에 대한 소유권을 부여하지.

매핑

레슨 1에서 구조체 _배열_을 살펴 봤네. _매핑_은 솔리디티에서 구조화된 데이터를 저장하는 또다른 방법이지.

다음과 같이 매핑을 정의하지:

// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장한다: 
mapping (address => uint) public accountBalance; 
// 혹은 userID로 유저 이름을 저장/검색하는 데 매핑을 쓸 수도 있다
mapping (uint => string) userIdToName;

매핑은 기본적으로 키-값 (key-value) 저장소로, 데이터를 저장하고 검색하는 데 이용된다.

첫번째 예시에서 키는 address이고 값은 uint이다. 두번째 예시에서 키는 uint이고 값은 string이다.

직접 해보기

좀비 소유권을 저장하기 위해 2가지 매핑을 이용하고자 하네: 하나는 좀비 소유자의 주소를 추적하기 위한 것이고, 다른 하나는 소유한 좀비의 숫자를 추적하기 위한 것이네.

  1. zombieToOwner라는 매핑을 생성한다. 키는 uint이고 (좀비 ID로 좀비를 저장하고 검색할 것이다), 값은 address이다. 이 매핑을 public으로 설정하자.

  2. ownerZombieCount라는 매핑을 생성한다. 키는 address이고 값은 uint이다.

챕터 3: Msg.sender

좀비 소유자를 추적하는 매핑을 가지고 있으니 _createZombie 메소드를 업데이트해서 이 매핑을 이용하도록 하고 싶네.

이를 위해, msg.sender라는 것을 이용할 필요가 있네.

msg.sender

솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수들이 있지. 그 중의 하나가 현재 함수를 호출한 사람 (혹은 스마트 컨트랙트)의 주소를 가리키는 msg.sender이지.

참고: 솔리디티에서 함수 실행항상 외부 호출자가 시작하네. 컨트랙트는 누군가가 컨트랙트의 함수를 호출할 때까지 블록체인 상에서 아무 것도 안 하고 있을 것이네. 그러니 항상 msg.sender가 있어야 하네.

msg.sender를 이용하고 mapping을 업데이트하는 예시가 여기에 있네:

mapping (address => uint) favoriteNumber;

function setMyNumber(uint _myNumber) public {
  // `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트한다 `
  favoriteNumber[msg.sender] = _myNumber;
  // ^ 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일하다 
}

function whatIsMyNumber() public view returns (uint) {
  // sender의 주소에 저장된 값을 불러온다 
  // sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`이 될 것이다
  return favoriteNumber[msg.sender];
}

이 간단한 예시에서 누구나 setMyNumber을 호출하여 본인의 주소와 연결된 우리 컨트랙트 내에 uint를 저장할 수 있지.

msg.sender를 활용하면 자네는 이더리움 블록체인의 보안성을 이용할 수 있게 되지. 즉, 누군가 다른 사람의 데이터를 변경하려면 해당 이더리움 주소와 관련된 개인키를 훔치는 것 밖에는 다른 방법이 없다는 것이네.

직접 해보기

레슨 1에서 다뤘던 _createZombie 메소드를 업데이트하여 이 함수를 호출하는 누구나 좀비 소유권을 부여하도록 해 보세.

  1. 먼저, 새로운 좀비의 id가 반환된 후에 zombieToOwner 매핑을 업데이트하여 id에 대하여 msg.sender가 저장되도록 해보자.

  2. 그 다음, 저장된 msg.sender을 고려하여 ownerZombieCount를 증가시키자.

자바스크립트와 마찬가지로 솔리디티에서도 uint를 ++로 증가시킬 수 있다.

uint number = 0;
number++;
// `number`는 이제 `1`이다

자네의 최종 답안은 코드 2줄로 표현되어야 하네.

 

챕터 4: Require

레슨 1에서 유저가 createRandomZombie를 호출하여 좀비 이름을 입력하면 새로운 좀비를 생성할 수 있도록 했네. 하지만, 만일 유저가 이 함수를 계속 호출해서 무제한으로 좀비를 생성한다면 게임이 매우 재미있지는 않을 걸세.

각 플레이어가 이 함수를 한 번만 호출할 수 있도록 만들어 보세. 이로써 새로운 플레이어들이 게임을 처음 시작할 때 좀비 군대를 구성할 첫 좀비를 생성하기 위해 createRandomZombie함수를 호출하게 될 것이네.

어떻게 하면 이 함수가 각 플레이어마다 한 번씩만 호출되도록 할 수 있을까?

이를 위해 require를 활용할 것이네. require를 활용하면 특정 조건이 참이 아닐 때 함수가 에러 메시지를 발생하고 실행을 멈추게 되지:

function sayHiToVitalik(string _name) public returns (string) {
  // _name이 "Vitalik"인지 비교한다. 참이 아닐 경우 에러 메시지를 발생하고 함수를 벗어난다
  // (참고: 솔리디티는 고유의 스트링 비교 기능을 가지고 있지 않기 때문에 
  // 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단한다)
  require(keccak256(_name) == keccak256("Vitalik"));
  // 참이면 함수 실행을 진행한다:
  return "Hi!";
}

sayHiToVitalik("Vitalik")로 이 함수를 실행하면 "Hi!"가 반환될 것이네. "Vitalik"이 아닌 다른 값으로 이 함수를 호출할 경우, 에러 메시지가 뜨고 함수가 실행되지 않을 걸세.

그러므로 require는 함수를 실행하기 전에 참이어야 하는 특정 조건을 확인하는 데 있어서 꽤 유용하지.

직접 해보기

우리의 좀비 게임에서 유저가 createRandomZombie 함수를 반복적으로 호출해서 자신의 군대에 좀비를 무제한으로 생성하는 것을 원하지 않네. 그렇게 되면 게임이 재미없게 될 걸세.

require를 활용하여 유저들이 첫 좀비를 만들 때 이 함수가 유저 당 한 번만 호출되도록 해 보세.

  1. require 키워드를 createRandomZombie 앞부분에 입력한다. require 함수가 ownerZombieCount[msg.sender]이 0과 같은지 확인하도록 하고, 0이 아닌 경우 에러 메시지를 출력하도록 한다.

참고: 솔리디티에서 값을 비교할 때 어떤 항이 먼저 오느냐는 중요하지 않네. 어떤 순서든 동일하지. 하지만, 우리가 작성한 확인 기능은 매우 기본적이라서 한 가지 답만을 참이라고 하네. 그러니 ownerZombieCount[msg.sender]을 가장 먼저 작성 해주게.  

 

 

챕터 5: 상속

우리의 게임 코드가 꽤 길어지고 있군. 엄청나게 긴 컨트랙트 하나를 만들기 보다는 코드를 잘 정리해서 여러 컨트랙트에 코드 로직을 나누는 것이 합리적일 때가 있지.

이를 보다 관리하기 쉽도록 하는 솔리디티 기능이 바로 컨트랙트 _상속_이지:

contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

BabyDoge 컨트랙트는 Doge 컨트랙트를 상속하네. 즉, 자네가 BabyDoge 컨트랙트를 컴파일해서 구축할 때, BabyDoge 컨트랙트가 catchphrase() 함수와 anotherCatchphrase() 함수에 모두 접근할 수 있다는 뜻이지. (Doge 컨트랙트에 정의되는 다른 어떤 public 함수가 정의되어도 접근이 가능하네)

상속 개념은 "고양이는 동물이다"의 경우처럼 부분집합 클래스가 있을 때 논리적 상속을 위해 활용할 수 있지. 하지만 동일한 로직을 다수의 클래스로 분할해서 단순히 코드를 정리할 때도 활용할 수 있지.

직접 해보기

다음 챕터에서 우리 좀비들이 먹이를 먹고 번식하도록 하는 기능을 구현할 것일세. 그 기능의 로직을 ZombieFactory의 모든 메소드를 상속하는 클래스에 넣어 보도록 하세.

  1. ZombieFactory 아래에 ZombieFeeding 컨트랙트를 생성한다. 이 컨트랙트는 ZombieFactory를 상속해야 한다.

 

 

챕터 6: Import

와우! 우리가 방금 코드를 오른편으로 정리했다는 걸 알 수 있을 걸세. 이제 에디터의 상단부에 탭들이 있네. 탭들을 클릭해서 살펴보도록 하게.

우리 코드가 꽤 길어지고 있으니, 여러 파일로 나누어 정리하면 관리하기 더 편하겠지. 보통 이런 방식으로 솔리디티 프로젝트의 긴 코드를 처리할 것이네.

다수의 파일이 있고 어떤 파일을 다른 파일로 불러오고 싶을 때, 솔리디티는 import라는 키워드를 이용하지:

import "./someothercontract.sol"; contract newContract is SomeOtherContract { }

import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}

이 컨트랙트와 동일한 폴더에 (이게 ./가 의미하는 바임) someothercontract.sol이라는 파일이 있을 때, 이 파일을 컴파일러가 불러오게 되지.

직접 해보기

다수의 파일이 있는 구조를 갖추었으니 import를 활용하여 다른 파일의 내용을 읽어올 필요가 있네.

  1. 새로운 파일 zombiefeeding.sol에 zombiefactory.sol를 불러 온다(import).

 

 

 

챕터 7: Storage vs Memory

솔리디티에는 변수를 저장할 수 있는 공간으로 storage와 memory 두 가지가 있지.

Storage블록체인 상에 영구적으로 저장되는 변수를 의미하지. Memory임시적으로 저장되는 변수로, 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이에 지워지지. 두 변수는 각각 컴퓨터 하드 디스크와 RAM과 같지.

대부분의 경우에 자네는 이런 키워드들을 이용할 필요가 없네. 왜냐면 솔리디티가 알아서 처리해 주기 때문이지. 상태 변수(함수 외부에 선언된 변수)는 초기 설정상 storage로 선언되어 블록체인에 영구적으로 저장되는 반면, 함수 내에 선언된 변수 memory로 자동 선언되어서 함수 호출이 종료되면 사라지지.

하지만 이 키워드들을 사용해야 하는 때가 있지. 바로 함수 내의 구조체와 _배열_을 처리할 때지:

 

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ 꽤 간단해 보이나, 솔리디티는 여기서 
    // `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다. 
    // 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다.
    // 그리고 
    mySandwich.status = "Eaten!";
    // ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. 

    // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: 
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. 
    // 그리고 
    anotherSandwich.status = "Eaten!";
    // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 
    // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다: 
    sandwiches[_index + 1] = anotherSandwich;
    // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
  }
}

 

어떤 키워드를 이용해야 하는지 정확하게 이해하지 못한다고 해도 걱정 말게. 이 튜토리얼을 진행하는 동안 언제 storage 혹은 memory를 사용해야 하는지 알려 주겠네. 솔리디티 컴파일러도 경고 메시지를 통해 어떤 키워드를 사용해야 하는지 알려 줄 것이네.

지금으로선 명시적으로 storage나 memory를 선언할 필요가 있는 경우가 있다는 걸 이해하는 것만으로 충분하네!

직접 해보기

먹이를 먹고 번식하는 능력을 우리 좀비들에게 부여할 시간이네!

좀비가 어떤 다른 생명체를 잡아 먹을 때, 좀비 DNA가 생명체의 DNA와 혼합되어 새로운 좀비가 생성될 것이네.

  1. feedAndMultiply라는 함수를 생성한다. 이 함수는 uint형인 _zombieId  _targetDna을 전달받는다. 이 함수는 public으로 선언되어야 한다.

  2. 다른 누군가가 우리 좀비에게 먹이를 주는 것을 원치 않는다. 그러므로 주인만이 좀비에게 먹이를 줄 수 있도록 한다. require 구문을 추가하여 msg.sender가 좀비 주인과 동일하도록 한다. (이는 createRandomZombie 함수에서 쓰인 방법과 동일하다)

    참고: 다시 말하지만, 우리가 작성한 확인 기능은 기초적이기 때문에 컴파일러는 msg.sender가 먼저 나올 것을 기대하고, 항의 순서를 바꾸면 잘못된 값이 입력되었다고 할 걸세. 하지만 보통 코드를 작성할 때 항의 순서는 자네가 원하는 대로 정하면 되네. 어떤 경우든 참이 되거든.

  3. 먹이를 먹는 좀비 DNA를 얻을 필요가 있으므로, 그 다음으로 myZombie라는 Zombie형 변수를 선언한다 (이는 storage 포인터가 될 것이다). 이 변수에 zombies 배열의 _zombieId 인덱스가 가진 값에 부여한다.

자네 코드는 마지막 }를 포함해서 4줄이어야 하네.

다음 챕터에서 이 함수의 내용을 계속해서 작성할 걸세!

 

챕터 8: 좀비 DNA

feedAndMultiply 함수 작성을 마무리해 보세!

새로운 좀비의 DNA를 계산하는 공식은 간단하네: 먹이를 먹는 좀비의 DNA와 먹이의 DNA의 평균을 내는 거지.

예시:

function testDnaSplicing() public {
  uint zombieDna = 2222222222222222;
  uint targetDna = 4444444444444444;
  uint newZombieDna = (zombieDna + targetDna) / 2;
  // ^ 3333333333333333이 될 것이다
}

자네가 원한다면 나중에 공식을 좀더 복잡하게 할 수도 있을 거네. 하지만 지금으로선 공식을 간단하게 하도록 하지. 나중에 언제든지 변경할 수 있으니까.

직접 해보기

  1. 먼저, _targetDna가 16자리보다 크지 않도록 해야 한다. 이를 위해, _targetDna를 _targetDna % dnaModulus와 같도록 해서 마지막 16자리 수만 취하도록 한다.

  2. 그 다음, 함수가 newDna라는 uint를 선언하고 myZombie의 DNA와 _targetDna의 평균 값을 부여해야 한다. (위의 예시 참고)

    참고: myZombie.name와 myZombie.dna를 이용하여 myZombie 구조체의 변수에 접근할 수 있지.

  3. 새로운 DNA 값을 얻게 되면 _createZombie 함수를 호출한다. 이 함수를 호출하는 데 필요한 인자 값을 zombiefactory.sol 탭에서 확인할 수 있다. 참고로, 이 함수는 좀비의 이름을 인자 값으로 필요로 한다. 그러니 새로운 좀비의 이름을 현재로서는 "NoName"으로 하도록 하자. 나중에 좀비 이름을 변경하는 함수를 작성할 수 있을 것이다.

참고: 솔리디티가 자네를 위해 열심히 일해서 자네가 코드의 문제점을 알아 차렸을 수도 있겠군. 걱정 말게. 다음 챕터에서 문제를 해결할 걸세 ;)

 

 

챕터 9: 함수 접근 제어자 더 알아보기

지난 레슨의 코드에 실수가 있네!

자네가 코드를 컴파일하려고 하면 컴파일러가 에러 메시지를 출력할 거네.

문제는 ZombieFeeding 컨트랙트 내에서 _createZombie 함수를 호출하려고 했다는 거지. 그런데 _createZombie 함수는 ZombieFactory 컨트랙트 내의 private 함수이지. 즉, ZombieFactory 컨트랙트를 상속하는 어떤 컨트랙트도 이 함수에 접근할 수 없다는 뜻이지.

Internal과 External

public과 private 이외에도 솔리디티에는 internal과 external이라는 함수 접근 제어자가 있지.

internal함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근이 가능하다 점을 제외하면 private과 동일하지. **(우리한테 필요한 게 바로 internal인 것 같군!

external은 함수가 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 내의 다른 함수에 의해 호출될 수 없다는 점을 제외하면 public과 동일하지. 나중에 external과 public이 각각 왜 필요한지 살펴 볼 것이네.

interal이나 external 함수를 선언하는 건 private과 public 함수를 선언하는 구문과 동일하네:

contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string) {
    baconSandwichesEaten++;
    // eat 함수가 internal로 선언되었기 때문에 여기서 호출이 가능하다 
    eat();
  }
}

직접 해보기

  1. _createZombie() 함수를 private에서 internal로 바꾸어 선언하여 이 함수가 정의된 컨트랙트를 상속하는 컨트랙트에서도 접근 가능하도록 한다.

    이미 zombiefactory.sol 탭이 활성화되어 있다.

 

 

챕터 10: 좀비가 무엇을 먹나요?

이제 좀비들에게 먹이를 줄 시간이군! 좀비가 가장 좋아하는 먹이가 뭘까?

크립토좀비가 가장 좋아하는 먹이는...

크립토키티! 😱😱😱

(그래, 정말이라네 😆 )

좀비에게 크립토키티를 먹이로 주려면 크립토키티 스마트 컨트랙트에서 키티 DNA를 읽어와야 할 것이네. 이게 가능한 이유는 크립토키티 데이터가 블록체인 상에 공개적으로 저장되어 있기 때문이지. 블록체인이 환상적이지 않나?!

걱정 말게 - 우리 게임이 어느 누구의 크립토키티에게도 실제 해를 끼치지 않을 것이니 말일세. 우린 단지 크립토키티 데이터를 읽어 올 뿐이지. 실제로 이 데이터를 지울 수는 없다네. 😉

다른 컨트랙트와 상호작용하기

블록체인 상에 있으면서 우리가 소유하지 않은 컨트랙트와 우리 컨트랙트가 상호작용을 하려면 우선 인터페이스를 정의해야 하네.

간단한 예시를 살펴 보도록 하지. 다음과 같은 블록체인 컨트랙트가 있다고 해 보세:

contract LuckyNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}

이 컨트랙트는 아무나 자신의 행운의 수를 저장할 수 있는 간단한 컨트랙트이고, 각자의 이더리움 주소와 연관이 있을 것이네. 이 주소를 이용해서 누구나 그 사람의 행운의 수를 찾아 볼 수 있지.

이제 getNum 함수를 이용하여 이 컨트랙트에 있는 데이터를 읽고자 하는 external 함수가 있다고 해 보세.

먼저, LuckyNumber 컨트랙트의 인터페이스를 정의할 필요가 있네:

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

약간 다르지만, 인터페이스를 정의하는 것이 컨트랙트를 정의하는 것과 유사하다는 걸 참고하게. 먼저, 다른 컨트랙트와 상호작용하고자 하는 함수만을 선언할 뿐(이 경우, getNum이 바로 그러한 함수이지) 다른 함수나 상태 변수를 언급하지 않네.

다음으로, 함수 몸체를 정의하지 않지. 중괄호 {, }를 쓰지 않고 함수 선언을 세미콜론(;)으로 간단하게 끝내지.

그러니 인터페이스는 컨트랙트 뼈대처럼 보인다고 할 수 있지. 컴파일러도 그렇게 인터페이스를 인식하지.

우리의 dapp 코드에 이런 인터페이스를 포함하면 컨트랙트는 다른 컨트랙트에 정의된 함수의 특성, 호출 방법, 예상되는 응답 내용에 대해 알 수 있게 되지.

다음 레슨에서 다른 컨트랙트의 함수를 실제로 호출할 것일세. 지금은 크립토키티 컨트랙트를 위한 인터페이스를 선언해 보세.

직접 해보기

자네를 위해 크립토키티 소스 코드를 찾아 봤네. 거기서, (우리 좀비 게임에서 새로운 좀비를 생성하는 데 필요한!) "유전자"를 포함한 모든 키티 데이터를 반환하는 getKitty라는 함수를 발견했네.

getKitty 함수는 다음과 같네:

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

이 함수는 우리에게 익숙한 함수들과는 달라 보이지. 함수가 다양한 값들을 반환하고 있지... 자네가 자바스크립트 같은 프로그래밍 언어를 이용해 본 적이 있다면 이 점이 다르다는 걸 알 수 있을 거네. 솔리디티에서는 함수가 하나 이상의 값을 반환할 수 있지.

getKitty 함수가 어떤 함수인지 알아 보았으니, 이를 이용하여 인터페이스를 만들어 볼 수 있을 걸세:

  1. KittyInterface라는 인터페이스를 정의한다. 인터페이스 정의가 contract 키워드를 이용하여 새로운 컨트랙트를 생성하는 것과 같다는 점을 기억할 것.

  2. 인터페이스 내에 getKitty 함수를 선언한다 (위의 함수에서 중괄호 안의 모든 내용은 제외하고 return 키워드 및 반환 값 종류까지만 복사/붙여넣기 하고 그 다음에 세미콜론을 넣어야 한다).

 

 

챕터 11: 인터페이스 활용하기

이전 챕터의 예시였던 NumberInterface를 활용하여 설명을 이어 나가겠네. 아래와 같이 인터페이스가 정의되면:

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

다음과 같이 컨트랙트에서 인터페이스를 이용할 수 있지:

contract MyContract {
  address NumberInterfaceAddress = 0xab38...
  // ^ 이더리움상의 FavoriteNumber 컨트랙트 주소이다
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress)
  // 이제 `numberContract`는 다른 컨트랙트를 가리키고 있다.

  function someFunction() public {
    // 이제 `numberContract`가 가리키고 있는 컨트랙트에서 `getNum` 함수를 호출할 수 있다:
    uint num = numberContract.getNum(msg.sender);
    // ...그리고 여기서 `num`으로 무언가를 할 수 있다
  }
}

이런 식으로 자네의 컨트랙트가 이더리움 블록체인상의 다른 어떤 컨트랙트와도 상호작용할 수 있네. 물론 상호작용하는 함수가 public이나 external로 선언되어 있어야 하지.

직접 해보기

크립토키티 스마트 컨트랙트에서 데이터를 읽어 오도록 우리 컨트랙트를 설정해 보세!

  1. 코드를 보면 ckAddress라는 변수에 크립토키티 컨트랙트 주소가 입력되어 있다. 다음 줄에 kittyContract라는 KittyInterface를 생성하고, 위의 numberContract 선언 시와 동일하게 ckAddress를 이용하여 초기화한다.

zombiefeeding.sol

pragma solidity ^0.4.19;

import "./zombiefactory.sol";

contract KittyInterface {
  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
  );
}

contract ZombieFeeding is ZombieFactory {

  // 크립토키티 컨트랙트 주소
  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  // `ckAddress`를 이용하여 여기에 kittyContract를 초기화한다
  KittyInterface kittyContract = KittyInterface(ckAddress);

  function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

}

 

챕터 12: 다수의 반환값 처리하기

getKitty 함수는 우리가 살펴 본 예시 중 유일하게 다수의 반환값을 갖는 함수이지. 본 챕터에서는 어떻게 다수의 반환값을 처리하는지 살펴 보세:

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 다음과 같이 다수 값을 할당한다:
  (a, b, c) = multipleReturns();
}

// 혹은 단 하나의 값에만 관심이 있을 경우: 
function getLastReturnValue() external {
  uint c;
  // 다른 필드는 빈칸으로 놓기만 하면 된다: 
  (,,c) = multipleReturns();
}

직접 해보기

이제 크립토키티 컨트랙트와 상호작용할 시간이네!

크립토키티 컨트랙트에서 고양이 유전자를 얻어내는 함수를 생성해 보세:

  1. feedOnKitty라는 함수를 생성한다. 이 함수는 _zombieId와 _kittyId라는 uint 인자 값 2개를 전달받고, public 함수로 선언되어야 한다.

  2. 이 함수는 kittyDna라는 uint를 먼저 선언해야 한다.

    참고: KittyInterface 인터페이스에서 genes은 uint256형이지만, 레슨 1에서 배웠던 내용을 되새겨 보면 uint는 uint256의 다른 표현으로, 서로 동일하지.

  3. 그 다음, 이 함수는 _kittyID를 전달하여 kittyContract.getKitty 함수를 호출하고 genes을 kittyDna에 저장해야 한다.getKitty가 다수의 변수를 반환한다는 사실을 기억할 것 (정확히 말하자면 10개의 변수를 반환한다). 하지만 우리가 관심 있는 변수는 마지막 변수인 genes이다. 쉼표 수를 유심히 세어 보기 바란다!

  4. 마지막으로 이 함수는 feedAndMultiply를 호출하고 이 때 _zombieId와 kittyDna를 전달해야 한다.

 

챕터 13: 보너스: 키티 유전자

우리의 함수 로직이 이제 완료되었군... 하지만 한 가지를 보너스로 추가해 보도록 하세.

고양이 유전자와 조합되어 생성된 좀비가 몇 가지 독특한 특성을 가져서 고양이 좀비로 보이도록 해 보세.

이를 위해 좀비 DNA에 몇 가지 특별한 키티 코드를 추가할 수 있네.

레슨 1에서 배운 내용을 떠올려 보면, 좀비의 외모를 결정하는 데 있어서 16자리 DNA 중에서 처음 12자리만 이용되지. 그러니 마지막에서 2자리 숫자를 활용하여 "특별한" 특성을 만들어 보세.

고양이 좀비는 DNA 마지막 2자리로 99를 갖는다고 해 보세 (고양이는 9개의 목숨을 가졌다고 할 만큼 생명력이 강하므로). 그러면 우리 코드에서는 만약(if) 좀비가 고양이에서 생성되면 좀비 DNA의 마지막 2자리를 99로 설정한다.

If 문

솔리디티에서 if 문은 자바스크립트의 if 문과 동일하다:

function eatBLT(string sandwich) public {
  // 스트링 간의 동일 여부를 판단하기 위해 keccak256 해시 함수를 이용해야 한다는 것을 기억하자 
  if (keccak256(sandwich) == keccak256("BLT")) {
    eat();
  }
}

직접 해보기

우리의 좀비 코드에 고양이 유전자에 대한 내용을 구현해 보세.

  1. 먼저, feedAndMultiply 함수 정의를 변경하여 _species라는 string을 세번째 인자 값으로 전달받도록 한다.

  2. 그 다음, 새로운 좀비 DNA를 계산한 후에 if 문을 추가하여 _species와 "kitty" 스트링 각각의 keccak256 해시값을 비교하도록 한다.

  3. if 문 내에서 DNA 마지막 2자리를 99로 대체하고자 한다. 한가지 방법은 newDna = newDna - newDna % 100 + 99; 로직을 이용하는 것이다.

    설명: newDna가 334455라고 하면 newDna % 100는 55이고, 따라서 newDna - newDna % 100는 334400이다. 마지막으로 여기에 99를 더하면 334499를 얻게 된다.

  4. 마지막으로, feedOnKitty 함수 내에서 이뤄지는 함수 호출을 변경해야 한다. feedAndMultiply가 호출될 때, "kitty"를 마지막 인자값으로 전달한다.

챕터 14: 마무리하기 Wrapping It Up

그게 전부네. 레슨 2를 완료했네!

코드가 작동하는지 오른편에서 데모를 확인해 볼 수 있네. 자네가 얼른 이 페이지 하단으로 가고 싶어한다는 걸 아네 😉. 고양이를 클릭해서 공격하고 나서 새로운 고양이 좀비가 어떤지 보게!

자바스크립트를 활용한 구현

우리 컨트랙트를 이더리움에 구축할 준비가 되면 ZombieFeeding 컨트랙트만 컴파일해서 구축하면 될 것일세. 왜냐면 이 컨트랙트가 ZombieFactory를 상속하는 우리의 마지막 컨트랙트이고 두 컨트랙트에 있는 public 메소드를 모두 접근할 수 있기 때문이지.

자바스크립트와 web3.js를 활용하여 우리의 컨트랙트와 상호작용하는 예시를 살펴 보도록 하지:

var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)

// 우리 좀비의 ID와 타겟 고양이 ID를 가지고 있다고 가정하면 
let zombieId = 1;
let kittyId = 1;

// 크립토키티의 이미지를 얻기 위해 웹 API에 쿼리를 할 필요가 있다. 
// 이 정보는 블록체인이 아닌 크립토키티 웹 서버에 저장되어 있다.
// 모든 것이 블록체인에 저장되어 있으면 서버가 다운되거나 크립토키티 API가 바뀌는 것이나 
// 크립토키티 회사가 크립토좀비를 싫어해서 고양이 이미지를 로딩하는 걸 막는 등을 걱정할 필요가 없다 ;) 
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
  let imgUrl = data.image_url
  // 이미지를 제시하기 위해 무언가를 한다 
})

// 유저가 고양이를 클릭할 때:
$(".kittyImage").click(function(e) {
  // 우리 컨트랙트의 `feedOnKitty` 메소드를 호출한다 
  ZombieFeeding.feedOnKitty(zombieId, kittyId)
})

// 우리의 컨트랙트에서 발생 가능한 NewZombie 이벤트에 귀를 기울여서 이벤트 발생 시 이벤트를 제시할 수 있도록 한다: 
ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  // 이 함수는 레슨 1에서와 같이 좀비를 제시한다: 
  generateZombie(result.zombieId, result.name, result.dna)
})

직접 해보기!

좀비에게 먹일 고양이를 선택해 보게. 자네 좀비의 DNA와 고양이의 DNA가 조합되어 새로운 좀비를 얻을 수 있을 거네!

자네의 새로운 좀비가 귀여운 고양이 다리를 가진 게 보이나? 저게 바로 DNA의 마지막 2자리 99가 작동하고 있다는 증거지. 😉

원하면 다시 시작해서 좀비에게 고양이를 먹여 보게. 자네 마음에 드는 고양이좀비를 얻으면 (한 마리만 가질 수 있네) 다음 챕터로 이동해서 레슨 2를 완료하게!

 

 

전체코드

 

zombiefactory.sol

 

pragma solidity ^0.4.19; 

contract ZombieFactory { 

    event NewZombie(uint zombieId, string name, uint dna); 

    uint dnaDigits = 16; 
    uint dnaModulus = 10 ** dnaDigits; 

    struct Zombie { 
        string name; 
        uint dna; 
    } 

    Zombie[] public zombies; 

    mapping (uint => address) public zombieToOwner; 
    mapping (address => uint) ownerZombieCount; 

    function _createZombie(string _name, uint _dna) internal { 
        uint id = zombies.push(Zombie(_name, _dna)) - 1; 
        zombieToOwner[id] = msg.sender; 
        ownerZombieCount[msg.sender]++; 
        NewZombie(id, _name, _dna); 
    } 

    function _generateRandomDna(string _str) private view returns (uint) { 
        uint rand = uint(keccak256(_str)); 
        return rand % dnaModulus; 
    } 

    function createRandomZombie(string _name) public { 
        require(ownerZombieCount[msg.sender] == 0); 
        uint randDna = _generateRandomDna(_name); 
        randDna = randDna - randDna % 100; 
        _createZombie(_name, randDna); 
    } 

}

zombiefeeding.sol

 

pragma solidity ^0.4.19; 

import "./zombiefactory.sol"; 

contract KittyInterface { 
  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 
  ); 
} 

contract ZombieFeeding is ZombieFactory { 

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d; 
  KittyInterface kittyContract = KittyInterface(ckAddress); 

  // 여기에 있는 함수 정의를 변경: 
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public { 
    require(msg.sender == zombieToOwner[_zombieId]); 
    Zombie storage myZombie = zombies[_zombieId]; 
    _targetDna = _targetDna % dnaModulus; 
    uint newDna = (myZombie.dna + _targetDna) / 2; 
    // 여기에 if 문 추가 
    if (keccak256(_species) == keccak256("kitty")) { 
      newDna = newDna - newDna % 100 + 99; 
    } 
    _createZombie("NoName", newDna); 
  } 

  function feedOnKitty(uint _zombieId, uint _kittyId) public { 
    uint kittyDna; 
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); 
    // 여기에 있는 함수 호출을 변경:  
    feedAndMultiply(_zombieId, kittyDna, "kitty"); 
  } 

}
출처: https://mingos-habitat.tistory.com/34 [밍고의서식지:티스토리]