Solidity Path: Beginner to Intermediate Smart Contracts

 

Chapter1.

좀비 꾸며주기

 

Chapter2.

솔리디티 코드는 컨트랙트 안에 싸여 있지. 컨트랙트는 이더리움 애플리케이션의 기본적인 구성 요소로, 모든 변수와 함수는 어느 한 컨트랙트에 속하게 마련이지. 컨트랙트는 자네의 모든 프로젝트의 시작 지점이라고 할 수 있지.

비어 있는 HelloWorld 컨트랙트는 다음과 같네:

contract HelloWorld {

}
Version Pragma
모든 솔리디티 소스 코드는 "version pragma"로 시작해야 하는데, 이는 해당 코드가 이용해야 하는 솔리디티 버전을 선언하는 것이지. 이를 통해 이후에 새로운 컴파일러 버전이 나와도 기존 코드가 깨지지 않도록 예방하는 거지.

선언은 다음과 같이 하면 되네: pragma solidity ^0.4.19; (이 코스 집필 시 최신 버전이 0.4.19임).

종합하자면 컨트랙트 초기 뼈대는 다음과 같네. 새로운 프로젝트를 시작할 때마다 이 뼈대를 제일 먼저 작성해야 하지:

pragma solidity ^0.4.19;

contract HelloWorld {

}

정답 작성
성공!

Chapter3.

챕터 3: 상태 변수 & 정수
잘했네! 이제 우리 컨트랙트를 위한 뼈대를 갖추게 되었네. 이제 솔리디티에서 변수를 다루는 방법을 배워 보도록 하지.

상태 변수는 컨트랙트 저장소에 영구적으로 저장되네. 즉, 이더리움 블록체인에 기록된다는 거지. 데이터베이스에 데이터를 쓰는 것과 동일하네.

예시:
contract Example {
  // 이 변수는 블록체인에 영구적으로 저장된다
  uint myUnsignedInteger = 100;
}
이 예시 컨트랙트에서는 myUnsignedInteger라는 uint를 생성하여 100이라는 값을 배정했네.

부호 없는 정수: uint
uint 자료형은 부호 없는 정수로, 값이 음수가 아니어야 한다는 의미네. 부호 있는 정수를 위한 int 자료형도 있네.

참고: 솔리디티에서 uint는 실제로 uint256, 즉 256비트 부호 없는 정수의 다른 표현이지. uint8, uint16, uint32 등과 같이 uint를 더 적은 비트로 선언할 수도 있네. 하지만 앞으로의 레슨에서 다루게 될 특수한 경우가 아니라면 일반적으로 단순히 uint를 사용하지.

정답 적기
성공!!

Chapter4.

솔리디티에서 수학은 꽤 간단하지. 다음 연산은 대부분의 프로그래밍 언어의 수학 연산과 동일하네:

덧셈: x + y
뺄셈: x - y,
곱셈: x * y
나눗셈: x / y
모듈로 / 나머지: x % y (이를테면, 13 % 5는 3이다. 왜냐면 13을 5로 나누면 나머지가 3이기 때문이다)
솔리디티는 지수 연산도 지원하지 (즉, "x의 y승", x^y이지):

uint x = 5 ** 2; // 즉, 5^2 = 25

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

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

정답 작성

Chapter 5.

챕터 5: 구조체
자네가 좀 더 복잡한 자료형을 필요로 할 때가 가끔 있을 거네. 이를 위해 솔리디티는 구조체를 제공하지:

struct Person {
  uint age;
  string name;
}

구조체를 통해 여러 특성을 가진, 보다 복잡한 자료형을 생성할 수 있지.

참고로 string이라는 새로운 자료형을 방금 소개했네. 스트링은 임의의 길이를 가진 UTF-8 데이터를 위해 활용되네. 이를테면 string greeting = "Hello world!" 이렇게 말이지.

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

Zombie라는 struct를 생성한다.

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

Chapter6.

챕터 6: 배열
어떤 것의 모음집이 필요할 때 _배열_을 사용할 수 있네. 솔리디티에는 _정적_ 배열과 _동적_ 배열이라는 두 종류의 배열이 있지:

// 2개의 원소를 담을 수 있는 고정 길이의 배열:
uint[2] fixedArray;
// 또다른 고정 배열으로 5개의 스트링을 담을 수 있다:
string[5] stringArray;
// 동적 배열은 고정된 크기가 없으며 계속 크기가 커질 수 있다:
uint[] dynamicArray;
구조체의 배열을 생성할 수도 있다. 이전 챕터의 Person 구조체를 이용하면:

Person[] people; // 이는 동적 배열로, 원소를 계속 추가할 수 있다.
상태 변수가 블록체인에 영구적으로 저장될 수 있다는 걸 기억하나? 그러니 이처럼 구조체의 동적 배열을 생성하면 마치 데이터베이스처럼 컨트랙트에 구조화된 데이터를 저장하는 데 유용하네.

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

Person[] public people;
그러면 다른 컨트랙트들이 이 배열을 읽을 수 있게 되지 (쓸 수는 없네). 이는 컨트랙트에 공개 데이터를 저장할 때 유용한 패턴이지.

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

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

Chapter7.

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

function eatHamburgers(string _name, uint _amount) {

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

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

이 함수를 다음과 같이 호출할 수 있지:

eatHamburgers("vitalik", 100);
직접 해보기
우리 앱에서 좀비들을 생성할 수 있을 필요가 있을 거네. 이를 위한 함수를 생성해 보세.

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

Chapter8.

챕터 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]과 같다.
직접 해보기
createZombie 함수가 무언가 할 수 있도록 만들어 보세!

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

_언더바때문에 한참 헤맸다...

Chapter9

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

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

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

uint[] numbers;

function _addToArray(uint _number) private {
  numbers.push(_number);
}
private는 컨트랙트 내의 다른 함수들만이 이 함수를 호출하여 numbers 배열로 무언가를 추가할 수 있다는 것을 의미하지.

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

직접 해보기
우리 컨트랙트의 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));
    }

}

Chapter10

챕터 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로 언제 표시할지 기억하기 어려울 수 있지. 운 좋게도 솔리디티 컴파일러는 어떤 제어자를 써야 하는지 경고 메시지를 통해 잘 알려주네.

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

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

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

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

Chapter11

챕터 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으로 형 변환하면 코드가 제대로 작동하고 컴파일러도 에러 메시지를 주지 않을 걸세.

직접 해보기
_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;
    }

}

Chapter12

챕터 12: 종합하기
랜덤 좀비 생성기를 거의 다 완성해 가는군! 이제 모든 내용을 종합하는 public 함수를 생성해 보세.

좀비의 이름을 입력값으로 받아 랜덤 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);
    }
}

Chapter 13

챕터 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) {
  // 결과와 관련된 행동을 취한다
})
직접 해보기
좀비가 생성될 때마다 우리 앱의 사용자 단에서 이에 대해 알고, 이를 표시하도록 하는 이벤트가 있으면 좋겠네.

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

}

Chapter 14

 

+ Recent posts