[블록체인] 이더리움 solidity 공부 크립토 좀비 level 2 해설
본문 바로가기
Block Chain

[블록체인] 이더리움 solidity 공부 크립토 좀비 level 2 해설

by 쏠수있어ㅤ 2022. 2. 16.
반응형

 

crypto zombie 크립토 좀비 게임(?)으로 이더리움의 대표 언어 solidity 배우기 lesson2 

lesson1 보다 더 어렵고 시간도 꽤 걸렸다. 그리고 다 통과를 했어도 lesson1보다 이해가 완전히 되지는 않았다. 

다시 재도전해보면서 최종 코드 해석해보자 아좌아좌 

 

이번 레슨에서는 기존 좀비의 DNA와 먹이의 DNA를 합쳐 새로운 좀비 DNA를 만들어 낸다. 

새로운 개념인 mapping 과 address라는 새로운 자료형을 배웠다. 

 

 

Key Points 

1) address

* address는 은행 계좌와 같은 이더리움의 계정 주소를 가리키는 고유 식별자이다. 

 

ex) address 형식

0x0cE446255506E92DF41614C46F1d6df9Cc969183

쉽게 말하면 이더리움 지갑 주소이다. 실제로 위의 계좌에 이더리움을 이체 시킬 수 있다 !

 

 

2) mapping

* mapping 은 솔리디티에서 구조화 된 데이터를 (key: value) 형식으로 저장하는 방법이다. 

mapping (address => unit) public accountBalance; 

key - address형식 : value - unit 형식 

JavaScript의 객체 { key: value } 와 비슷한듯 하다! 

 

3) msg.sender

* msg.sender = 솔리디티 모든 함수에서 이용 가능한 특정 전역 변수 중 하나. 현재 함수를 호출한 사람 (혹은 스마트 컨트랙트) 의 주소를 가리킨다. msg.sender를 활용하면 이더리움 블록체인의 보안성을 이용할 수 있다. 

 

 

4) require

* require 는 JavaScript의 if 문과 비슷하다.

솔리디티 언어의 if문은 자바스크립트와 동일하게 사용된다. 

 

 

5) storage vs memory 

* storage : 블록체인 상 영구적으로 저장되는 변수 (솔리디티 변수 선언 시 기초값) 

ex) 컴퓨터의 하드 디스크

 

* memory  : 임시적으로 저장되는 변수 - 컨트랙트 함수에 대한 외부 호출들이 일어나는 사이에 지워진다. 

ex) 컴퓨터의 RAM

 

=> 솔리디티는 자동으로 디폴트 값 storage를 적용시키지만 개발자가 직접 위의 storage, memory를 적어줘야하는 때가 있다. => ** 함수 내 구조체와 배열을 처리할 때 ** 

ex) 출처 : https://cryptozombies.io/ko/lesson/2/chapter/7

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ 꽤 간단해 보이나, 솔리디티는 여기서 
    // `storage`나 `memory`를 명시적으로 선언해야 한다는 경고 메시지를 발생한다. 
    // 그러므로 `storage` 키워드를 활용하여 다음과 같이 선언해야 한다:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터이다.
    // 그리고 
    mySandwich.status = "Eaten!";
    // ...이 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경한다. 

    // 단순히 복사를 하고자 한다면 `memory`를 이용하면 된다: 
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. 
    // 그리고 
    anotherSandwich.status = "Eaten!";
    // ...이 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 
    // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다: 
    sandwiches[_index + 1] = anotherSandwich;
    // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
  }
}

 

 

6) Internal vs External

* Internal 함수 : private과 동일하지만 다른 점은 정의된 컨트랙트를 상속하는 컨트랙트 안에서 접근이 가능하다. 

* External 함수 : public과 동일하지만 다른 점은 컨트랙트 바깥에서만 호출될 수 있고 컨트랙트 안에서 다른 함수에 의해 호출될 수 없다.

 

=> 지금까지 배운 함수의 "함수 접근 제어자" 로는 private, public, internal, external 이 있다. 

 

 


 

 

lesson2를 마무리하고 나온 최종 solidity 코드는 아래와 같다. 

파일은 두 개로 나눠진다.

 

zombiefactory.sol

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;

	// key - uint : value - address인 mapping 형식의 zombieToOwner 선언 
    mapping (uint => address) public zombieToOwner;
    // key - address : value - uint인 mapping 형식의 ownerZombieCount 선언 
    mapping (address => uint) ownerZombieCount;

	// * _createZombie 함수가 lesson2에서 private -> interal로 접근 제어자가 변경되었다.
    // private처럼 비공개이지만 상속 컨트랙트에서는 해당 함수를 사용할 수 있다. 
    function _createZombie(string _name, uint _dna) internal {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        
        // zombieToOwner mapping에 key - id : value - msg.sender(주소) 담기
        zombieToOwner[id] = msg.sender;
        
        // 해당 key(주소)의 value 값 1씩 증가시키기
        ownerZombieCount[msg.sender]++;
        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 {
    	// zombie가 생성될때마다 함수 호출한 사람의 (msg.sender) 주소에 매핑된 nint 값이 ++ 가 되는 
        // 점을 이용하여 무한 반복을 예방할 수 있다. 
        // ownerZombieCount[msg.sender]가 0 일경우 == 좀비 생성이 처음인 경우에만 아래 명령어들이 실행된다.
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        randDna = randDna - randDna % 100;
        _createZombie(_name, randDna);
    }

}

 

 

 

 

zombiefeeding.sol

√pragma solidity ^0.4.19;

import "./zombiefactory.sol"; // 같은 위치에 있는 zombiefactory.sol 파일을 불러온다. 

 // 외부 컨트랙트에 있는 함수를 가져올 때 아래처럼 인터페이스를 선언한다.
 // 인터페이스 선언은 컨트랙트처럼 contract ~ 으로 시작하고 안의 함수는 (); 까지 끝낸다.
contract KittyInterface {
  function getKitty(uint256 _id) external view returns (
    bool isGestating,
    bool isReady,
    uint256 cooldownIndex,
    uint256 nextActionAt,
    uint256 siringWithId,
    uint256 birthTime,
    uint256 matronId,
    uint256 sireId,
    uint256 generation,
    uint256 genes
  );
}

contract ZombieFeeding is ZombieFactory { // ZombieFeeding은 ZombieFactory 를 상속 받았다.

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  // 위에서 만든 인터페이스 KittyInterface형인 kittyContract라는 변수를 선언한다.
  // 안에는 ckAddress인자값으로 넣은 외부 인터페이스가 된다. 
  // -> 이제 kittyContract.getKitty() 이렇게 해당 인터페이스 안의 함수를 쓸 수 있다. 
  KittyInterface kittyContract = KittyInterface(ckAddress);

  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
    
    // msg.sender가 좀비 주인과 동일하도록 require(if)를 추가한다.
    require(msg.sender == zombieToOwner[_zombieId]);
    
    // Zombie형 myZombie명의 변수를 storage로 선언하고 zombies 배열의 [_zombieId]번째의 값을 부여한다.
    Zombie storage myZombie = zombies[_zombieId];
    
    // _targetDna를 16자리수로 맞추기
    _targetDna = _targetDna % dnaModulus;
    
    // 새로운 newDna를 myZombie dna와 target(먹이)dna 값을 합한 평균으로 한다.(임의대로! 나중에 복잡하게 바꿀 수 있음)
    uint newDna = (myZombie.dna + _targetDna) / 2;
    
    // 만약 해당 함수 인자값의 3번째 _species가 "kitty"와 동일하다면 newDna의 끝 2자리를 99로 맞춰준다.
    if (keccak256(_species) == keccak256("kitty")) {
      newDna = newDna - newDna % 100 + 99; // 100으로 나눈 나머지로 newDna를 빼면 ~2341100이 된다. 여기에 99를 더하기
    }
    
    // 좀비를 create하는 함수를 불러 인자값을 넣어준다. 이름은 일단은 없으니 "NoName"으로 한다.
    _createZombie("NoName", newDna);
  }


	// 위에서 가져온 kitty 인터페이스
  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    // kittyDna 빈 변수 선언
    uint kittyDna;
    
    // kittyContract 안의 getKitty 함수를 불러와 인자값으로 _kittyId를 넣는다. 
    // return되는 10가지 중 genes는 10 번째 자리에 있다. 10번째에 kittyDna 변수를 놓고 값을 담는다.
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

}

 

 

 

레벨 2를 하고 나서 솔리디티 언어의 느낌 (?) 문법은 이해가 조금 되었다. 블록체인 책을 이번에 e book으로 무료 다운해서 읽기 시작했는데 더 공부하다보면 전반적인 이해도가 높아질 것 같다! 

 

e-book 어플 (pc, mobile) - RIDI 리디북스 / 알라딘 e-book 

읽기 시작한 책 : 코어 이더리움 프로그래밍 (저자 : 박재현, 오재훈, 박혜영)

책을 다 읽게되면 책 후기도 써보면 좋을 것 같다.

 

반응형

댓글