CryptoZombies, 1-좀비 공장 만들기

챕터 1: 레슨 개요
좀비 DNA

  • 16자리 정수로 식별
  • 크립토 지갑 주소와 같이 유일한 값의 개념을 설명하기 위함으로 보임

==========

챕터 2: 컨트랙트
컨트랙트 알아보기

  • 솔리디티 코드는 컨트랙트 내에 작성됨
  • 스마트 컨트랙트는 이더리움 블록체인에 저장됨
  • 모든 변수와 함수는 특정 한 컨트랙트에 저장됨
  • Version Pragma, 소스 코드는 version pragma로 시작하고 이는 솔리디티 컴파일러 버전을 지정함

연습하기]

우리의 좀비 군대 생성을 시작하기 위해 ZombieFactory라는 기본 컨트랙트를 생성해 보세!

우측 박스에 우리 컨트랙트가 솔리디티 버전 0.4.19를 쓸 수 있도록 설정한다.

ZombieFactory라는 빈 컨트랙트를 생성한다.
pragma solidity ^0.4.19; //1. 여기에 솔리디티 버전 적기

contract ZombieFactory {
    // 여기서 시작
}

==========

챕터 3: 상태 변수 & 저장
변수를 다루는 방법

  • 상태 변수는 컨트랙트 저장소에 영구적으로 저장됨. = 이더리움 블록체인에 기록됨
  • 부호 없는 정수 : uint, 부호 없는 정수

연습하기]

우리의 좀비 DNA는 16자리 숫자로 결정될 걸세.

dnaDigits라는 uint를 선언하고 16이라는 값을 배정해 보게.
pragma solidity ^0.4.19;

contract ZombieFactory {

    // 여기서 시작
    uint dnaDigits = 16;

}

==========

챕터 4: 수학 연산
연산 방법

  • 대부분의 프로그래밍 언어의 수학 연산과 동일 (+ - * / %)

연습하기]

우리의 좀비 DNA가 16자리 숫자가 되도록 하기 위해 또다른 unit형 변수를 생성하고 10^16 값을 배정하세. 이로써 이 값을 이후 모듈로 연산자 %와 함께 이용하여 16자리보다 큰 수를 16자리 숫자로 줄일 수 있네.

dnaModulus라는 uint형 변수를 생성하고 10의 dnaDigits승을 배정한다.
pragma solidity ^0.4.19;

contract ZombieFactory {

    uint dnaDigits = 16;
    // 여기서 시작
    uint dnaModulus = 10**dnaDigits;
}

==========

챕터 5: 구조체
구조체 변수

  • 더 복잡한 자료형을 위해 구조체를 사용 가능

연습하기]

우리 앱에서 좀비 몇 마리를 생성하기를 원할 것이네! 좀비들이 다양한 특성을 가질 것이니 구조체를 활용하기에 안성맞춤이군.

Zombie라는 struct를 생성한다.

우리의 Zombie 구조체는 name (string형)과 dna (uint형)이라는 2가지 특성을 가진다.
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    // 여기서 시작
    struct Zombie {
        string name;
        uint dna;
    }
}

==========

챕터 6: 배열
배열(동적, 정적 배열)

  • 정적 배열 [#], 원소를 담는 길이가 고정된 배열
  • 동적 배열 [], 배열의 크기가 고정되지 않은 배열
    Public 배열
  • public으로 선언한 대상에 대해 솔리디티는 getter(Read) 메소드를 자동적으로 생성함

연습하기]

우리 앱에 좀비 군대를 저장하고 싶네. 그리고 우리 좀비들을 다른 앱에 자랑하고 싶네. 그러니 좀비 군대 저장소를 public으로 해야 하네.

Zombie 구조체의 public 배열을 생성하고 이름을 zombies로 한다.
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    // 여기서 시작
    Zombie[] public zombies;
}

==========

챕터 7: 함수 선언
함수 형태

  • eatHamburgers라는 함수로, string과 uint 타입의 2개의 인자를 사용
function eatHamburgers(string _name, uint _amount) {}

함수 호출

  • "vitalik", 100 인자로 호출
eatHamburgers("vitalik", 100);

연습하기]

우리 앱에서 좀비들을 생성할 수 있을 필요가 있을 거네. 이를 위한 함수를 생성해 보세.

createZombie라는 함수를 생성한다. 이 함수는 다음 2개의 인자를 전달받아야 한다: _name (string형)과 _dna (uint형).
함수의 내용은 지금으로선 비어 두면 되네. 나중에 채울 것이니까.
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    // 여기서 시작
    function createZombie (string _name, uint _dna) {
    }
}

==========

챕터 8: 구조체와 배열 활용하기
새로운 구조체 생성

  • 배열.push(요소) or 구조체.push(요소), 배열이나 구조체에 요소를 추가하는 방법

연습하기]

createZombie 함수가 무언가 할 수 있도록 만들어 보세!

함수에 코드를 넣어 새로운 Zombie를 생성하여 zombies 배열에 추가하도록 한다. 새로운 좀비를 위한 name과 dna는 createZombie함수의 인자값이어야 한다.
코드를 한 줄로 간결하게 작성해 보자.
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function createZombie(string _name, uint _dna) {
        // 여기서 시작
        zombies.push(Zombie(_name, _dna));
    }
}

==========

챕터 9: Private/Public 함수
public 함수

  • 누구나 컨트랙트 함수를 호출하고 코드를 실행할 수 있음
    private 함수
  • 컨트랙트 내 다른 함수들만이 이 함수를 호출하여 numbers 배열로 무언가를 추가할 수 있다는 의미
    private 함수 선언 방법
  • 함수와 인자명에 앞에 언더바(_)로 시작하는 것이 관례
uint[] numbers;

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

연습하기]

우리 컨트랙트의 createZombie 함수는 현재 기본적으로 public으로 선언되어 있네. 즉, 누구나 이 함수를 호출해서 새로운 좀비를 컨트랙트에서 만들 수 있다는 뜻이지! 이 함수를 private로 선언해 보세.

createZombie 함수를 변경하여 private 함수로 만든다. 함수명에 대한 관례를 잊지 말 것!
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }
}

==========

챕터 10: 함수 더 알아보기
함수의 반환값

  • 함수에서 어떤 값을 반환 받는 방법
string greeting = "What's up dog";

function sayHello() public returns (string) {
	return greeting;
} // 반환값의 타입이 string

함수 제어자

  • view, 함수가 데이터를 보기만 하고 변경하지 않음(상태변수 접근 가능, 수정 불가)
function sayHello() public view returns (string) {}
  • pure, 함수가 앱에서 어떤 데이터도 접근하지 않음(상태변수 접근 불가)
function _multiply(uint a, uint b) private pure returns (uint) {
	return a * b;
}

연습하기]

스트링으로부터 랜덤 DNA 숫자를 생성하는 도우미 함수가 필요할 걸세.

_generateRandomDna라는 private 함수를 만드시게. 이 함수는 _str (string형)을 인자로 전달받고, uint을 반환해야 하네.

이 함수는 컨트랙트 변수를 보지만 변경하지는 않을 것이므로 view로 선언하게.

이 함수의 내용은 현재로서는 비어 있는 상태로 냅두시게. 나중에 채울 것이네.
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }

    // 여기서 시작
    function _generateRandomDna(string _str) private view returns (uint) {}
}

==========

챕터 11: Keccak256과 형 변환
내장 해시 함수 keccak256

  • 이더리움은 SHA3의 한 버전인 keccak256를 내장 해시 함수로 가지고 있음
  • 해시 함수는 기본적으로 입력 스트링을 16진수의 랜덤한 256비트로 매핑함
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");

형 변환

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

연습하기]

_generateRandomDna 함수의 내용을 채워 보세! 여기에 함수가 무엇을 해야 하는지 나와 있네:

코드 첫 줄에서는 _str을 이용한 keccak256 해시값을 받아서 의사 난수 16진수를 생성하고 이를 uint로 형 변환한 다음, rand라는 uint에 결과값을 저장해야 한다.

우리는 좀비의 DNA가 16자리 숫자이기만을 원하므로(dnaModulus를 기억하나?) 코드의 두번째 줄에서는 위의 결과 값을 모듈로(%) dnaModulus로 연산한 값을 반환해야 한다. (형식: return 위의 결과 값 % dnaModulus).
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    } 

    function _generateRandomDna(string _str) private view returns (uint) {
        // 여기서 시작
        uint rand = uint(keccak256(_str));

        return rand % dnaModulus;
    }
}

==========

챕터 12: 종합하기
좀비 이름을 입력값으로 받아 랜덤 DNA를 가진 좀비를 만드는 public 함수를 생성

연습하기]

createRandomZombie라는 public함수를 생성한다. 이 함수는 _name이라는 string형 인자를 하나 전달받는다. (참고: 함수를 private로 선언한 것과 마찬가지로 함수를 public로 생성할 것)

이 함수의 첫 줄에서는 _name을 전달받은 _generateRandomDna 함수를 호출하고, 이 함수의 반환값을 randDna라는 uint형 변수에 저장해야 한다.

두번째 줄에서는 _createZombie 함수를 호출하고 이 함수에 _name와 randDna를 전달해야 한다.

함수의 내용을 닫는 }를 포함해서 코드가 4줄이어야 한다.
pragma solidity ^0.4.19;

contract ZombieFactory {

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

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string _name, uint _dna) private {
        zombies.push(Zombie(_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);
    }
}

==========

챕터 13: 이벤트
이벤트 추가하기!!

  • 이벤트는 컨트랙트가 블록체인 상에서 FrontEnd-Service에 무언가 액션(transaction)을 발생했을 때 소통하는 방법
  • 컨트랙트는 특정 이벤트가 발생하는지 대기하다가 해당 이벤트가 발생하면 동작
// 이벤트를 선언한다
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;
}
  • FrontEnd-Service은 무언가 액션이 발생하면, 자바스크립트로 이를 구현
YourContract.IntegersAdded(function(error, result) {
  // 결과와 관련된 행동을 취한다
})

연습하기]

좀비가 생성될 때마다 우리 앱의 사용자 단에서 이에 대해 알고, 이를 표시하도록 하는 이벤트가 있으면 좋겠네.

NewZombie라는 event를 선언한다. zombieId (uint형), name (string형), dna (uint형)을 인자로 전달받아야 한다.

_createZombie 함수를 변경하여 새로운 좀비가 zombies 배열에 추가된 후에 NewZombie 이벤트를 실행하도록 한다.

이벤트를 위해 좀비의 id가 필요할 것이다. array.push()는 배열의 새로운 길이를 uint형으로 반환한다. 배열의 첫 원소가 0이라는 인덱스를 갖기 때문에, array.push() - 1은 막 추가된 좀비의 인덱스가 될 것이다. zombies.push() - 1의 결과값을 uint형인 id로 저장하고 이를 다음 줄에서 NewZombie 이벤트를 위해 활용한다.
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
FrontEnd 앱과 상호작용하는 방법

  • 작성한 컨트랙트와 상호작용하기 위해 사용자 단의 자바스크립트 코드가 필요
  • 이더리움은 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 값을 Front에서 받아서 이미지를 변경하고 CSS 필터를 적용

==========

챕터 15: 레슨 1 완료!
https://share.cryptozombies.io/ko/lesson/1/share/JohnnyJI?id=Y3p8MjAwNDQ3

좋은 웹페이지 즐겨찾기