Quote
크립토 좀비
에서
Solidity
를 학습해보자.
[ch01] Making the Zombie Factory
챕터1을 통과하게 되면 나만의 좀비
를 가질 수 있다. :)

솔리디티 코드는 Contract
안에 싸여져 있다. 컨트랙트는 이더리움 app의 기본 구성 요소로, 모든 변수/함수는 하나의 컨트랙트안에 속해 있어야 한다.
즉 컨트랙트는 모든 프로젝트의 시작 지점이라고 할 수 있다.
1
2
|
contract ZombieFactory {
}
|
모든 솔리디티 코드는 솔리디티의 버전을 선언 해주어야 한다. 이를 통해 새로운 컴파일러 버전이 나오더라도 코드가 깨지지 않도록 한다.
1
2
3
4
|
pragma solidity ^0.4.19;
contract ZombieFactory {
}
|
솔리디티에서 state variable
는 컨트랙트 저장소에 영구적으로 저장된다. 즉 이더리움 블록체인에 기록이 된다.
uint
는 unsigned 즉 부호가 없는 정수로, 음이 아닌 정수이다. (<-> int
)
uint
는 uint256
를 의미하며, 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
}
|