Solidity

CryptoZombies 2 (좀비가 희생물을 공격하다) 정리

dlwltn98 2023. 2. 7. 14:02

매핑과 주소

 - 새로운 자료형

 

Address

 - 이더리움 블록체인은 계정들로 이루어져 있음

 - 계정은 이더의 잔액을 가짐 (ETH : 이더리움 블록체인상의 통화)

 - 계정을 통해 다른 계정과 이더를 주고 받을 수 있음

 - 각 계정은 특정 계정을 가리키는 고유 식별자인 주소를 가짐

 - 주소 → 특정 유저 혹은 스마트 컨트랙트가 소유함

 

Mapping

 - 구조화된 데이터를 저장하는 방법

 - Key - Value 저장소

 - 데이터를 저장하고 검색하는데 이용

// 금융 앱용으로, 유저의 계좌 잔액을 보유하는 uint를 저장
// key -> address / value -> uint
mapping (address => uint) public accountBalance;

// 혹은 userID로 유저 이름을 저장/검색하는데 mapping 사용
// key -> uint / value -> string
mapping (uint => string) userIdToName;

 

Msg.sender

 - 현재 함수를 호출한 사람 혹은 스마트 컨트랙트의 주소를 가리킴

 - 모든 함수에서 이용 가능한 특정 전역 변수들 중 하나

 - 컨트랙트는 누군가가 컨트랙트 함수를 호출하기 전까진 블록체인 상에서 아무것도 안함

      → 항상 msg.sender 가 필요

// 구조화된 데이터를 저장하는 방법
mapping (address => uint) favoriteNumber;

function serMyNumber(uint _myNumber) public {
    // `msg.sender`에 대해 `_myNumber`가 저장되도록 `favoriteNumber` 매핑을 업데이트
    // 데이터를 저장하는 구문은 배열로 데이터를 저장할 떄와 동일
    // address(key) = msg.sender, uint(value) = _myNumber 
    favoriteNumber[msg.sender] = _myNumber;
}

function whatIsMyNumber() public view  returns (uint) {
    // sender의 주소에 저장된 값을 불러옴
    // sender가 `setMyNumber`을 아직 호출하지 않았다면 반환값은 `0`
    return favoriteNumber[msg.sender];
}

 

+ JS와 마찬가지로 솔리디티에서도 uint를 ++를 이용해 증가시킬 수 있음

uint number = 0;
number++;   // `number`는 이제 `1`이다

 

Require

 - 특정 조건이 참이 아닐때 함수가 에러 메시지를 발생하고 실행을 멈추게 됨

 - 함수를 실행하기 전에 참이어야하는 특정 조건을 확인하는데 유용

function sayHiVitalik(string _name) public returns (string) {
    // _name이 "Vitalik"인지 확인
    require( keccak256(_name) == kecceak256("Vitalik") );
    return "Hi!";  // 참이면 실행

 - 솔리디티는 고유의 string 비교 기능을 가지고 있지 않음

    → 스트링의 keccak256 해시값을 비교하여 스트링 값이 같은지 판단

 

상속

 긴 컨트랙트 하나를 만드는 것보다 코드를 잘 정리해서 여러 컨트랙트에 코드 로직을 나누는것이 합리적일때가 있음

    → 이를 관리하기 쉽게 해주는게 컨트렉트 상속 → is

contract Doge {
    function catchphrase() public returns (string) {
        return "So Wow CryptoDoge";
    }
}

// BabyDoge 컨트렉트는 Doge 컨트렉트를 상속
// catchphrase() 함수와 anotherCatchphrase()에 모두 접근 가능
contract BabyDoge is Doge {
    function anotherCatchphrase() public returns (string) {
        return "Suxh Moon BabyDoge";
    }
}

 

Import

 - 다수의 파일이 있고 어떤 파일을 다른 파일로 불러오고 싶을 때 사용

// ./ -> 현재 컨트랙트와 동일한 폴더에 있다는 의미
import "./someothercontract.sol";

 

Storage VS Memory

 - 변수를 저장할 수 있는 공간

 - 솔리디티가 알아서 처리해준다고 설명 되어 있는데 바뀐걸로 알고 있음

 

Storage

 - 블록체인 상에 영구적으로 저장되는 변수

 - 상태변수 (함수 외부에 선언된 함수)

 

Memory

 - 임시로 저장되는 변수

 - 함수 내에 선언된 변수 (호출이 종료되면 사라짐)

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

    Sandwich[] sandwiches;

    function eatSandwich(uint _index) public {
        // `storage` 키워드를 활용하여 다음과 같이 선언
        Sandwich storage mySandwich = sandwiches[_index];
        // ...이 경우, `mySandwich`는 저장된 `sandwiches[_index]`를 가리키는 포인터
        // 그리고 아래의 코드는 블록체인 상에서 `sandwiches[_index]`을 영구적으로 변경
        mySandwich.status = "Eaten!";
       
        // 단순히 복사를 하고자 한다면 `memory`를 이용 
        Sandwich memory anotherSandwich = sandwiches[_index + 1];
        // ...이 경우, `anotherSandwich`는 단순히 메모리에 데이터를 복사하는 것이 된다. 
        
        anotherSandwich.status = "Eaten!";
        // 그리고 위 코드는 임시 변수인 `anotherSandwich`를 변경하는 것으로 
        // `sandwiches[_index + 1]`에는 아무런 영향을 끼치지 않는다. 그러나 다음과 같이 코드를 작성할 수 있다 
        sandwiches[_index + 1] = anotherSandwich;
        // ...이는 임시 변경한 내용을 블록체인 저장소에 저장하고자 하는 경우이다.
    }
}

 

함수 접근 제어자

Private

 - 같은 컨트랙트에서 호출 가능

Internal

 - 함수가 정의된 컨트랙트를 상속한 컨트랙트에서도 호출 가능

 - 위 특징을 제외하면 Private과 동일

Public

 - 같은 컨트랙트, 외부 컨트랙트, 상속한 컨트랙트 내에서 호출 가능

External

 - 함수가 외부 컨트랙트에서만 호출될 수 있음

 - 같은 컨트랙트 내의 다른 함수에 의해 호출될 수 없음

 - 위 특징을 제외하면 Public과 동일

  내부 컨트랙트 외부 컨트랙트 상속 컨트랙트
Private O X X
Internal O X O
Public O O O
External X O X
contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string) {
    baconSandwichesEaten++;
    // eat 함수가 internal로 선언되었기 때문에 여기서 호출이 가능하다 
    eat();
  }
}

 

Interface

 - 다른 컨트랙트와 상호 작용

 - 소유하지 않은 컨트랙트와 소유한 컨트랙트가 상호 작용을 하려면 인터페이스를 정의해야 함

// 자신의 행운의 수를 저장할 수 있는 간단한 컨트랙트
contract LuckyNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}

// LuckyNumber 컨트랙트의 인터페이스 정의 - 컨트랙트를 정의하는 것과 유사
contract NumberInterface {
    // 다른 컨트랙트와 상호작용하고자 하는 함수만을 선언
    function getNum(address _myAddress) public view returns (uint);
}

 - 인터페이스 정의를 하는 것은 컨트랙트를 정의하는 것과 비슷함

 - 다른 컨트랙트와 상호작용하고자 하는 함수만 선언, 함수 몸체는 정의하지 않음

 

if 문

 - JS문과 동일

 

+ 솔리디티에서는 함수가 하나 이상의 값을 반환 할 수 있음

 

 

전체 코드

 - zombiefactory.sol

pragma solidity ^0.4.19;

contract ZombieFactory {

    // event 선언
    event NewZombie(uint zombieId , string name, uint dna);

    // dnaModulus라는 uint형 변수를 생성하고 10의 dnaDigits승을 배정
    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    // Zombie 라는 구조체 생성
    struct Zombie {
        string name;
        uint dna;
    }

    // Zombie 구조체의 public 배열 생성
    Zombie[] public zombies;

    // 매핑 생성
    // 좀비 id로 좀비를 저장하고 검색
    mapping (uint => address) public zombieToOwner;
    mapping (address => uint) ownerZombieCount;

    // 좀비 생성 private 함수 선언
    // 함수 접근 제어자를 private에서 internal로 변경
    function _createZombie(string _name, uint _dna) internal {

        // 새로운 좀비를 zombies 배열에 추가
        // 배열의 첫 원소가 0이라는 인덱스를 갖기 때문에, array.push() - 1은 막 추가된 좀비의 인덱스가 됨
        uint id = zombies.push(Zombie(_name, _dna)) - 1;

        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;

        // 새로운 좀비가 배열에 추가되면 이벤트 실행
        NewZombie(id, _name, _dna);
    }

    // uint를 반환하는 view 함수 선언
    function _generateRandomDna(string _str) private view returns (uint) {

        uint rand = uint(keccak256(_str));

        // 좀비 DNA가 16자리 숫자이기만을 원하므로 연산 후 값 반환
        return rand % dnaModulus;
    }

    // public 함수 선언
    function createRandomZombie(string _name) public {

        require(ownerZombieCount[msg.sender] == 0);
        // 좀비 dna 생성
        uint randDna = _generateRandomDna(_name);
        // 새로운 좀비 배열에 추가 
        _createZombie(_name, randDna);
    }

}

 - zombiefeeding.sol

pragma solidity ^0.4.19;

// zombiefactory.sol 불러오기
import "./zombiefactory.sol";

// KittyInterface라는 인터페이스를 정의
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
    );
}

// ZombieFactory 상속
contract ZombieFeeding is ZombieFactory {

    // 크립토키티 스마트 컨트랙트에서 데이터를 읽어 오도록 컨트랙트를 설정
    address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
    // `ckAddress`를 이용하여 kittyContract를 초기화
    KittyInterface kittyContract  = KittyInterface(ckAddress);

    // 좀비 dna가 생명체의 dna와 혼합되어 새로운 좀비 생성
    function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
        
        // 주인만 좀비에게 먹이를 줄 수 있음
        require(msg.sender == zombieToOwner[_zombieId]);

        // 먹이를 먹는 좀비의 dna 얻음
        Zombie storage myZombie = zombies[_zombieId];
        _targetDna = _targetDna % dnaModulus; // 16자리보다 크면 안됨
        uint newDna = (myZombie + _targetDna) / 2;  // 새로운 dna 

        // 키티 유전자를 가진 좀비는 dna 마지막 2자리를 99로 변경
        if(keccak256(_species) == keccak256("kitty")) {
            newDna = newDna - newDna % 100 + 99;
        }

       // 좀비 생성 함수 호출
       // 인자로 좀비의 이름과 dna 값을 받음
        _createZombie("NoName", newDna);
    }

    // 크립토키티 컨트랙트와 상호작용
    // 크립토키티 컨트랙트에서 고양이 유전자를 얻어내는 함수를 생성
    function feedOnKitty (uint _zombieId, uint _kittyId) public {

        uint kittyDna;
        (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);

        // dna가 혼합된 새로운 좀비를 생성하는 함수
        feedAndMultiply(_zombieId, kittyDna, "kitty");
    }

}

 

github : https://github.com/dlwltn98/studyCryptoZombies

cryptoZombies: https://cryptozombies.io/ko/course