https://cryptozombies.io/ko/course
#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies
CryptoZombies is The Most Popular, Interactive Solidity Tutorial That Will Help You Learn Blockchain Programming on Ethereum by Building Your Own Fun Game with Zombies — Master Blockchain Development with Web3, Infura, Metamask & Ethereum Smart Contracts
cryptozombies.io
Lesson 01. 좀비 공장 만들기
챕터 1: 레슨 개요
좀비 군대를 건설하기 위한 "좀비 공장" 만들기
- 우리 공장은 군대 내 모든 좀비의 데이터베이스를 유지한다
- 우리 공장은 새로운 좀비를 생성하는 함수를 가진다
- 각 좀비는 랜덤하고 독특한 외모를 가진다
좀비 DNA가 활용되는 방법
좀비의 외모는 DNA에 따라 달라진다.
DNA는 다음과 같이 간단하게 16자리 정수이다.
8356281049284737
실제 DNA처럼 이 숫자의 각 부분은 좀비가 가진 개별 특성과 매핑된다.
ex. 첫 2자리 숫자 83은 (83 % 7 + 1 = 7) 연산을 하여 7번째 좀비의 머리 타입을 가진다.
챕터 2: 컨트랙트
솔리디티 코드는 contract 안에 싸여 있다.
컨트랙트는 이더리움 애플리케이션의 기본적인 구성 요소로, 모든 변수와 함수는 어느 한 컨트랙트에 속한다.
모든 프로젝트의 시작 지점이라 할 수 있다.
contract HelloWorld {
// 비어 있는 HelloWorld 컨트랙트
}
Version Pragma
모든 솔리디티 소스코드는 version pragma로 시작해야 한다.
해당 코드가 이용해야 하는 솔리시티 버전을 선언하는 것이다. 이를 통해 이후에 새로운 컴파일러 버전이 나와도 기존 코드가 깨지지 않도록 예방한다.
pragma solidity ^0.4.19; //1. 여기에 솔리디티 버전 적기
//2. 여기에 컨트랙트 생성
contract ZombieFactory {
}
챕터 3: 상태 변수 & 정수
상태 변수는 컨트랙트 저장소에서 영구적으로 저장된다. 즉, 이더리움 블록체인에 기록된다.
( 데이터베이스에 데이터를 쓰는 것과 동일하다. )
contract Example {
uint myUnsignedInteger = 100;
}
myUnsignedInteger 라는 uint를 생성하여 100이라는 값을 배정한다.
부호 없는 정수: uint
uint 자료형은 부호 없는 정수로, 값이 음수가 아니어야 한다는 의미이다. 부호 있는 정수를 위한 int 자료형도 있다.
솔리디티에서 uint는 실제로 uint256, 즉 256비트 부호 없는 정수의 다른 표현이다. uint8, unit16, uint32 등과 같이 더 적은 비트로도 선언할 수 있다. 일반적으로 단순히 uint를 사용한다.
pragma solidity ^0.4.19;
contract ZombieFactory {
uint dnaDigits = 16;
}
챕터 4: 수학 연산
솔리디티에서의 수학 연산은 대부분의 프로그래밍 언어와 동일하다
+, -, *, /, %
지수 연산도 지원한다.
uint x = 5 ** 2; // 5^2 = 25
16자리 dna 연산이 저장될 수 있도록 지수 연산으로 변수를 선언하자.
pragma solidity ^0.4.19;
contract ZombieFactory {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
}
챕터 5: 구조체
솔리디티는 구조체를 제공한다. 구조체를 통해 여러 특성을 가진, 보다 복잡한 자료형을 생성할 수 있다.
struct Person {
uint age;
string name;
}
string 은 임의의 길이를 가진 UTF-8 데이터를 위해 활용된다.
pragma solidity ^0.4.19;
contract ZombieFactory {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
}
챕터 6: 배열
솔리디티에는 정적 배열( 고정 길이의 배열)과 동적 배열(고정 길이가 없는 배열)이 있다.
// 2개의 원소를 담을 수 있는 고정 길이의 배열:
uint[2] fixedArray;
// 또다른 고정 배열으로 5개의 스트링을 담을 수 있다:
string[5] stringArray;
// 동적 배열은 고정된 크기가 없으며 계속 크기가 커질 수 있다:
uint[] dynamicArray;
구조체의 배열을 생성할 수도 있다.
Person[] people; // 이는 동적 배열로, 원소를 계속 추가할 수 있다.
구조체의 동적 배열은 컨트랙트에 구조화된 데이터를 저장하는데 유용하다.
public 배열
public으로 배열을 선언할 수 있다. 이러한 배열을 위해 getter 메소드를 자동적으로 생성한다.
Person[] public people;
다른 컨트랙트들은 이 배열을 읽을 수 있게 된다. 컨트랙트에 공개 데이터를 저장할 때 유용한 패턴이다.
( 읽은 순 있지만 쓸 순 없다. )
우리 앱에는 좀비 군대를 저장하여 다른 앱에 저장하고 싶다. 그러니 좀비 군대 저장소를 public으로 해야 한다.
pragma solidity ^0.4.19;
contract ZombieFactory {
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
struct Zombie {
string name;
uint dna;
}
Zombie[] public zombies;
}
챕터 7: 함수 선언
솔리디티에서는 함수를 선언할 수 있다.
function eatHamburgers(string _name, uint _amount) {
}
함수 인자명을 언더스코어(_)로 시작해서 전역 변수와 구별하는 것이 관례이다.
해당 함수는 다음과 같이 호출할 수 있다.
eatHamburgers("vitalik", 100);
우리 앱에서 좀비들을 생성하기 위한 함수를 생성해보자.
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: 구조체와 배열 활용하기
구조체 배열에 구조체를 추가해보자.
struct Person {
uint age;
string name;
}
Person[] public 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]과 같다.
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으로 선언하고, 공개할 함수만 public으로 선언하는 것이 좋다.
int[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
private는 컨트랙트 내의 다른 함수들만이 이 함수를 호출하여 numbers 배열로 무언가를 추가할 수 있다.
private 키워드는 함수명 다음에 적는다. 함수 인자명과 마찬가지로 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;
}
솔리디티에서 함수 선언은 반환값 종류를 포함한다.
함수 제어자
위에서 살펴본 sayHello()는 솔리디티에서 상태를 변화시키지 않는다. 즉, 어떤 값을 변경하거나 무언가를 쓰지 않는다.
이러한 경우에는 view 함수로 선언하여 함수가 데이터를 보기만 하고 변경하지 않는다는 뜻을 갖는다.
function sayHello() public view returns (string) {
솔리디티는 pure 함수도 가지고 있는데, 이는 함수가 앱에서 어떤 데이터도 접근하지 않는 것을 의미한다.
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
이 함수는 앱에서 읽는 것도 하지 않고, 다만 반환값이 함수에 전달된 인자값에 따라 달라진다.
함수를 pure나 view로 언제 표시할지 기억하기 어려울 수 있다. 운 좋게도 솔리디티 컴파일러는 어떤 제어자를 써야 하는지 경고 메시지를 통해 알려준다!
스트링으로부터 랜덤 DNA 숫자를 생성하는 도우미 함수를 작성해보자.
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과 형 변환
_generateRandomDna 함수의 반환값이 랜덤이 되기 원한다면 이더리움은 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);
_str을 keccak256 해시값으로 받아와 uint형 rand 변수에 결과값을 저장한다.
우리는 좀비의 DNA가 16자리 숫자이기만을 원하므로 danModulus로 연산한 값을 반환해야 한다.
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: 종합하기
이제 모든 내용을 종합하는 public 함수를 생성해보자!
좀비의 이름을 입력값으로 받아 랜덤 DNA를 가진 좀비를 만드는 public 함수를 생성하자.
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: 이벤트
이벤트는 컨트랙트가 블록체인 상에서 앱의 사용자 단에서 무언가 액션이 발생했을 때 의사소통하는 방법이다. 컨트랙트는 특정 이벤트가 일어나는지 "귀를 기울이고" 그 이벤트가 발생하면 행동을 취한다.
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에서 생성된 값을 받아 웹 브라우저 기반 자바스크립트의 기능을 활용하여 이미지를 변경하고 CSS 필터를 적용한다.
https://share.cryptozombies.io/ko/lesson/1/share/유정민?id=Y3p8MTk4MDMy
제 크립토좀비 OGURL을/를 확인해 보세요!
제가 방금 전에 크립토좀비 레슨 1을 마쳤어요. 이더리움 게임을 만드는 기본 내용을 배웠어요. 제가 처음 만든 좀비를 한번 보세요!
share.cryptozombies.io
해당 링크로 확인할 수 있다.
'Dev > 개발일기장' 카테고리의 다른 글
[mysql] ERROR 1824 (HY000): Failed to open the referenced table ' ' 참조 관계 설정 오류 (0) | 2022.04.08 |
---|---|
[Notion] 개발자를 꿈꾸는 대학생의 포트폴리오 만들기 (0) | 2022.01.22 |
Apple Developer Academy 211214 온라인 설명회 정리 (0) | 2021.12.16 |
우당탕탕 백준 자바 언어로 제출하는 법! (0) | 2021.08.18 |
IPAD로 코딩하기 (0) | 2021.08.06 |