[Lesson1] ZombieFactory

Seokhun Yoon·2022년 2월 9일
0

[Solidity] CryptoZombie

목록 보기
1/5
post-thumbnail

Solidity란?

이더리움 디앱을 개발하려면 Solidty 언어에 대해서 무조건 알아야합니다.

솔리디티는 C++, Python 그리고 Javascript의 영향을 받아, EVM (Ethereum Virtual Machine)에서 구동되도록 설계된 언어입니다.

EVM은 이더리움에서 사용되는 가상머신으로, SolidityEVM bytecode로 작성된 스마트 컨트랙트를 실행하는 런타임 환경입니다.

더 자세한 내용이 필요하신 분은 솔리디티의 공식 문서를 참고해주세요!

Crypto Zombie로 솔리디티 배우기!

저는 크립토 좀비를 이용해보면서 솔리디티 문법을 익히려고 합니다.
저처럼 처음 하는 사람도 손쉽게 따라할 수 있고, 옆에 문법에 대한 설명도 자세히 나와있어서 입문자에게 좋은 것 같습니다.
Crypto Zombie로 들어가셔서 직접 해보실 수 있어요!
(한국어 버전도 있지만 영어 버전 설명이 더 자세하게 나와있어서, 웬만하면 영문으로 보시는걸 추천드려요!)

ZombieFactory 만들기

첫번째로 고유의 DNA를 가지는 좀비를 랜덤하게 생성하는 컨트랙트를 만들 것이다.
DNA는 16자리의 10진수로 이루어져있고 각 자리수에 따라 여러 특성을 부여한다.
예를 들어, DNA가 8356281049284737 이라면 맨 앞 두자리 83은 머리, 다음 두자리 56은 눈 특성을 결정한다.
이제 본격적으로 문법을 배워가면서 좀비 생성 스마트 컨트랙트를 만들어보자!

1. pragma

솔리디티로 작성된 코드들을 보면 항상 pragma solidity로 시작하는 것을 볼 수 있다.
솔리디티는 지속적으로 업데이트가 되고 있는데 그 버전에 따라 기능이 추가되거나 삭제되는 경우가 있다.

버전에 따른 오류를 막기 위해서 pragma를 사용해서 필요한 컴파일러의 버전을 반드시 명시해줘야 한다.

// ^ 로 특정 버전 명시
// 또는 부등호를 이용해서 여러 버전 명시 가능
pragma solidity ^0.4.19			// 0.4.19 버전 사용
pragma solidity >=0.5.0 <0.6.0	// 0.5.x 버전 사용

2. Contracts

contract란 이더리움 어플리케이션의 기본 구성 단위이며, 솔리디티에서는 contract 단위로 코드를 작성한다.
따라서 모든 변수와 함수는 contract안에 속해야 한다.
(자바스크립의 class와 비슷해보인다.)

contract ZombieFactory {
  
}

3. State Variables & Integer

State Vaiablescontract storage에 영원히 저장되는 변수이다. 즉, 이더리움 블록체인에 저장되는 것이다.
솔리디티에서 정수에 대한 타입은 int8~int256, uint8~uint256이 있으며, intuint는 각각 int256,uint256의 별칭이다.
변수에 값을 지정할 때는 타입 변수명 = 값 의 형태로 적어준다.

contract ZombieFactory {
  uint dnaDigits = 16;  // 문장 끝은 항상 세미콜론(;)
}

4. Math Operations

연산자설명
+더하기
-빼기
*곱하기
/나누기
%나머지
**제곱
contract ZombieFactory {
  uint dnaDigits = 16; 
  uint dnaModulus = 10 ** 16; // 10의 16제곱 (10^16) 
}

5. Structs

struct는 복잡한 자료형을 만들때 사용한다.

contract ZombieFactory {
  ...
  
  struct Zombie {
    string name;
    uint dna;
  }
}

6. Arrays

6-1) fixed & dynamic

솔리디티에서 배열은 두가지 타입이 있다.

  • fixed : 길이가 고정된 배열
  • dynamic : 길이의 제한이 없는 배열
uint[2] fixedArray; // 길이가 2인 uint 배열 
string[5] stringArray; // 길이가 5인 string 배열
uint[] dynamicArray; // 길이 제한이 없는 uint 배열

6-2) public

배열 선언할 때 public을 적어주면, 솔리디티가 자동으로 이 배열에 대한 getter method를 생성해준다.

contract ZombieFactory {
  ...
  
  struct Zombie {
    string name;
    uint dna;
  }
  
  Zombie[] public zombies;
}

7. Function Declarations

함수의 선언은 아래의 형태로 진행한다.

contract ZombieFactory {
  ...
  function createZombie (string memory _name, uint _dna) public {
  }
}

필수는 아니지만 전역변수와 구분하기 위해 함수의 인자를 명시할 때, 앞에 _ 를 붙여준다.

7-1) memory

함수의 파라미터를 적어줄 때 타입을 지정하는데, 이때 변수의 reference type의 변수에는 memory를 입력해야 한다.
이렇게 하면 해당 인자의 복사본을 memory에 임시 저장할 수 있고, 함수에서는 복사본을 사용하여 입력한 변수의 값을 변경하지 않고 보존할 수 있다.
따라서 reference type 변수를 함수의 인자로 사용할 때는 memory를 같이 적어주어야 한다.

  • reference type 변수들 : string, array, struct, mapping

call by value vs call by reference

  • call by value : 값을 복사하여 그 복사본을 사용, 복사본을 변경해도 기존 변수의 값은 변경 되지 않는다.
  • call by reference : 값을 직접 호출하여 사용, 변수 값을 변경하면 참조한 기존 변수의 값도 변경된다.

7-2) public & private

함수의 인자를 명시한 뒤에는 visibility를 정해주어야 한다.
(명시를 안하더라도 기본적으로 public 함수가 된다.)

  • public : 누구든 contract의 함수를 불러서 실행할 수 있다.
  • private : 자신 이외에 다른 사람들이 이 함수를 사용할 수 없다. private 함수명은 보통 _를 맨 앞에 붙여준다.

public을 사용하면 해당 contract가 공격에 노출되기 때문에, 항상 private으로 함수를 만드는 습관을 들이는 것이 좋다.
그리고 그 함수를 공유할 필요가 있을 때 public으로 바꾸면 된다!

위에서 만든 함수를 private으로 바꾸고 안에 내용을 추가 해보자. (함수명도 _ 추가!)

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

7-3) return values

솔리디티에서는 함수를 선언할 때, 함수의 returns (타입)을 이용해서 return 값의 타입도 명시한다.

string hello = "Hello!";
function sayHello() public returns (string) {
	return hello;
}

7-4) view &pure

  • view : state 변수를 사용하지만 변경하지 않는 함수
    방금 만든 sayHello() 함수는 state를 변경하지 않는다. 이런 경우는 view 함수로 선언하면 된다.
function sayHello() public view returns (string) {
  • pure : app에 있는 어떤 데이터도 사용하지 않고 변경하지도 않는 함수(오로지 사용하는 인자에 따라 반환값이 결정됨)
function _multiply(uint a, uint b) private pure returns (uint) {
	return a *b;
}

8. Keccak256 & Typecasting

8-1) Keccak256

이더리움에서는 SHA3의 한 버전인 keccak256라는 해쉬 함수가 내장되어있다.
해쉬 함수는 기본적으로 입력값을 랜덤에 가까운 256bit 16진수로 변환한다.
그리고 해시 함수는 입력값의 작은 변화에도 아예 다른 해쉬 값을 도출한다.
우리는 이를 용해서 유사 랜덤 숫자를 만들때 사용할 것이다. (보안 상으로는 좋은 방법이 아님)

keccack256 함수는 byte 타입의 값을 입력해줘야 한다.
이를 위해서 abi.encodedPacked()를 사용하여 입력값을 byte 타입으로 바꿔준다.

keccak256(abi.encodePacked("aaab"));

8-2) Typecasting

변수의 타입에 따라 연산이 안되는 경우가 생긴다.
그럴땐 타입 변환을 해주면 된다.

uint8 a = 5;
uint b = 6;
uint8 c = a * b; // 둘의 타입이 다르기 때문에 에러 발생
uint8 c = a * uint8(b); // 타입 변환으로 에러 해결

이제 위에서 배운 내용을 이용해서 좀비의 랜덤 DNA를 반환하는 함수를 만들어보자.
(여기서 사용한 ABI에 대해서 자세히 알고싶은 분은 여기를 참고해주세요.)

contract ZombieFactory {
  ...
  
  function _generateRandomDna (string memory _name) private view returns (uint) {
    uint rand = uint(keccak256(abi.encodePacked(_name)));
    return rand % dnaModulus;
  }
}

9. Events

event는 자바스크립트에서 eventListener와 비슷하다.
이를 이용해서 특정 동작이 발생하는지 기다렸다가, 그 동작이 발생하는 순간 원하는 이벤트를 발생시킬 수 있다.

  • event : 이벤트를 정의한다.
  • emit : 이벤트 발생 시점을 지정한다.

좀비가 발생했을 때 NewZombie라는 이벤트를 발생시켜보자.

contract ZombieFactory {

  event NewZombie(uint id, string name, uint dna);

  ...
  
  Zombie[] public zombies;

  function _createZombie (string memory _name, uint _dna) private {
    uint id = zombies.push(Zombie(_name, _dna)) - 1;
    emit NewZombie(id, _name, _dna);
  }
  
  ...
}

ZombieFactoy 전체 코드

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
  
  event NewZombie(uint id, string name, uint dna); // 새로운 좀비가 생성됐을 때의 이벤트 정의

  uint dnaDigits = 16;  // DNA는 16자리 수
  uint dnaModulus = 10 ** dnaDigits;  // 16자리 수보다 많은 경우, 16자리 수보다 큰 수는 제외할 때 사용

  // Zombie : name, dnan 값을 가짐
  struct Zombie {
    string name;
    uint dna;
  }

  // zombies : Zombie로 이루어진 배열
  Zombie[] public zombies;

  // zombie id => user's address : zombie를 소유한 사용자 매핑
  mapping (uint => address) public zombieToOwner;
  // user's address => number of owned zombies : 사용자가 소유한 좀비 수 매핑
  mapping (address => uint) ownerZombieCount;

  // 좀비의 name과 dna를 이용해서 좀비 생성하는 함수
  function _createZombie(string memory _name, uint _dna) private {
    uint id = zombies.push(Zombie(_name, _dna)); // 생성한 좀비는 zombis 배열에 추가, 배열의 인덱스 : 좀비의 id
    zombieToOwner[id] = msg.sender; // 생성된 좀비와 현재 사용자를 매핑함
    ownerZombieCount[msg.sender]++; // 현재 사용자의 좀비 보유 수를 1 증가 시킴
    emit NewZombie(id, _name, _dna); // 새로운 좀비가 생성됐다는 이벤트 발생
  }
  
  // 좀비의 이름으로 랜덤한 dna 발생하는 함수
  function _generateRandomDna(string memory _name) private view returns (uint) {
    uint randomDna = uint(keccak256(abi.encodePacked(_name))); // keccak256으로 유사 난수를 발생시켜 dna 값으로 사용
    return randomDna % dnaModulus;  // 랜덤 DNA가 16자리가 되도록 dnaModulus를 나눈 나머지를 반환
  }

  // 랜덤한 새로운 좀비 생성하는 함수
  function createRandomZombie(string memory _name) public {
    uint randDna = _generateRandomDna(_name);
    _createZombie(_name, randDna);
  } 
}
profile
블록체인 개발자를 꿈꾸다

0개의 댓글