Contents

[Cryptozombies01] Making the Zombie Factory

Quote
크립토 좀비 에서 Solidity를 학습해보자.

[ch01] Making the Zombie Factory

챕터1을 통과하게 되면 나만의 좀비 를 가질 수 있다. :)

/images/zombie.png

솔리디티 코드는 Contract안에 싸여져 있다. 컨트랙트는 이더리움 app의 기본 구성 요소로, 모든 변수/함수는 하나의 컨트랙트안에 속해 있어야 한다. 즉 컨트랙트는 모든 프로젝트의 시작 지점이라고 할 수 있다.

1
2
contract ZombieFactory {
}

모든 솔리디티 코드는 솔리디티의 버전을 선언 해주어야 한다. 이를 통해 새로운 컴파일러 버전이 나오더라도 코드가 깨지지 않도록 한다.

1
2
3
4
pragma solidity ^0.4.19;

contract ZombieFactory {
}

솔리디티에서 state variable는 컨트랙트 저장소에 영구적으로 저장된다. 즉 이더리움 블록체인에 기록이 된다. uint는 unsigned 즉 부호가 없는 정수로, 음이 아닌 정수이다. (<-> int) uintuint256를 의미하며, 256비트 정수를 표현한다.

1
2
3
4
5
6
pragma solidity ^0.4.19;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
}

솔리디티는 c++과 마찬가지롤 struct를 제공한다.

1
2
3
4
5
6
pragma solidity ^0.4.19;

contract ZombieFactory {
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;
}

array안에 크기를 주지 않으면 동적할당 가능하다.

1
2
3
4
5
6
// 2개의 원소를 담을 수 있는 고정 길이의 배열:
uint[2] fixedArray;
// 또다른 고정 배열으로 5개의 스트링을 담을 수 있다:
string[5] stringArray;
// 동적 배열은 고정된 크기가 없으며 계속 크기가 커질 수 있다:
uint[] dynamicArray;

public으로 배열을 선언할 수 있으며 솔리디티는 이런 배열을 위해 getter 메소드를 자동적으로 생성해준다.

1
Person[] public people;

함수의 경우 param명언더스코어(_)로 시작해서 전역 변수와 구별하는 것이 관례이다. 마찬가지로 private 함수명 또한 _를 쓰는 것이 convention이다.

1
2
3
4
5
    function _createZombie(string _name, uint _dna) private {
    }

    function createRandomZombie(string _name) public {
    }

참고로 솔리디티에서 함수는 기본적으로 public이다.

  • 누구나 또는 다른 컨트랙트가 나의 컨트랙트 함수를 호출하고 코드를 실행할 수 있다.

솔라디티는 함수가 데이터를 read만 하고 state change를 하지 않을 때 view 함수를 사용한다.

1
2
function sayHello() public view returns (string) {
}

또한 pure 함수도 가지고 있는데, 이는 함수가 앱에서 어떤 데이터도 접근하지 않는(read, write둘다 하지 x) 것을 의미한다.

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

이더리움은 SHA3의 버전 중 하나인 keccak256를 내장 해시 함수로 가지고 있다. 해시 함수는 기본적으로 입력 스트링을 랜덤 256bit 16진수로 매핑한다.

Question
블록체인에서 안전한 의사 난수 발생기는 어려운 문제라고 한다고 하는데 hash collision과 관련해서 어렵다고 하는 걸까? 아니면 안전하다고 하는게 Oracle?과 관련이 있는 걸까?
1
2
3
4
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");

event는 생성한 컨트랙트가 블록체인 상에서 앱에 접근하는 사용자 layer에서 액션이 발생했을 때, 통신하는 방법이다. 컨트랙트는 특정 이벤트가 일어나는지 “listen"하며, 그 이벤트가 발생하면 action을 취한다.

1
2
3
4
5
6
7
8
9
// 이벤트를 선언한다
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;
}

Summary

지금까지의 과정을 정리하면 아래와 같은 코드가 최종적으로 만들어진다.

 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
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; // push returns length
        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);
    }

}
 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
// 여기에 우리가 만든 컨트랙트에 접근하는 방법을 제시한다:
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
}