[Solidity] 크립토 좀비 레슨 2 학습 리뷰

드림보이즈·2023년 2월 18일
0

크립토좀비

목록 보기
2/6

이 포스팅의 목표

  • 솔리디티에 익숙해지고 실력을 늘린다.
  • 내가 이더리움 DAPP 개발자라고 생각하고 능동적으로 사고하는 법을 배운다.

레슨 2 목표

  • 멀티 플레이어 게임 만들기
  • 좀비가 인간을 물면 DNA가 섞여서 좀비가 되기

챕터 2 : 매핑과 주소

주소

like 은행 계좌 번호

주소를 좀비에 대한 소유권을 나타내는 ID로 활용하자.

매핑

배열, 구조체 말고 구조화된 데이터를 저장하는 방법

KEY-VALUE로, 데이터를 저장하고 검색하는데 이용

Q. 이 사람의 좀비 몇 개 있는지, 이 좀비의 주인은 누구인지 구현해라

mapping (address => uint) ownerZombieCount;
mapping (uint => address) public zombieToOwner;

챕터 3: Msg.sender

솔리디티에는 모든 함수에서 이용 가능한 특정 전역 변수가 존재

현재 함수를 호출한 사람(스마트 컨트랙트를 호출하던가)의 주소가 msg.sender

솔리디티에서 함수 실행은 무조건 외부 호출자가 시작하기에, 무조건 있음

이더리움 보안성을 이용할 수 있다.

Q. _createZombie에서, msg.sender를 이용해 zombieToOwner, ownerZombieCount 업데이트 하라

    function _createZombie(string _name, uint _dna) private {
        uint id = zombies.push(Zombie(_name, _dna)) - 1;
        // 여기서 시작
        zombieToOwner[id] = msg.sender;
        ownerZombieCount[msg.sender]++;
        // 요 사이가 답
        NewZombie(id, _name, _dna);
    }

챕터 4 : Require

require를 활용하면 특정 조건이 참이 아닐 때 함수가 에러 메시지를 발생하고 실행을 멈춘다.

Q. 좀비 무한 생성을 막아라, 계정당 딱 1개만 가능

    function createRandomZombie(string _name) public {
    	// 요렇게 세서 파악 가능
        require(ownerZombieCount[msg.sender] == 0);
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name, randDna);
    }

챕터 5 : 상속

긴 코드를 잘 정리해서 관리하게 하는 기능이 상속이지

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

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

BabyDoge 컨트랙트는 catchphrase(), anotherCatchphrase() 둘 다 접근 가능

Q. 코드 정리를 위해 ZombieFeeding 컨트랙트 만들고, 상속 받아라

contract ZombieFeeding is ZombieFactory {
}

챕터 6 : import

파일을 나누어 관리하고, import로 불러오면 좋다.

파일만 import하면, 그 안의 컨트랙트, 함수는 그냥 쓰면 된데이


챕터 7 : Storage vs Memory

변수를 저장하는 공간이 2개여

storage는 블록체인 상에 영구적으로 저장 (하드)

memory는 임시저장, 컨트랙트 함수 외부 호출이 일어나는 사이 지워짐 (램)

대부분 솔리디티가 알아서 해줌. 함수 외부면 storage, 함수 내부 선언 변수는 memory로 해줌

그런데, 함수 내의 구조체와 배열 처리 시 써줘야 돼

Q. 좀비가 어떤 생명체를 먹을 때, 새로운 좀비를 만드는 함수를 만들라

  function feedAndMultiply (uint _zombieId, uint _targetDna) public {
      require(msg.sender == zombieToOwner[id]);
      Zombie storage myZombie = zombies[_zombieId];
  }

챕터 8 : 좀비 DNA

새로운 좀비의 DNA = (좀비 + 생명체) / 2

Q.feedAndMultyply 함수를 완성하자

인자로 받은 _targetDna를 16자리 이하로 만들고

좀비에서 DNA를 받아서 평균을 내서 새로운 dna를 만들고

좀비 생성 함수를 실행시키자 (이름, dna)

    function feedAndMultiply(uint _zombieId, uint _targetDna) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    _createZombie("NoName", newDna);
  }

챕터 9 : 함수 접근 제어자 더 알아보기

internal : 상속 컨트랙트에서도 접근 가능 (private은 안됨)

external : 컨트랙트 밖에서만 호출 가능 / 안에서 안돼


챕터 10 : 좀비가 무엇을 먹는가? (먹이)

좀비의 먹이는 '크립토키티'!

즉 크립토키티의 스마트 컨트랙트에서 DNA를 읽어와야겠지

다른 컨트랙트와 상호작용하기

블록체인에 있는 남의 컨트랙트와 우리 컨트랙트가 상호작용하려면 인터페이스를 정의해야!!!

인터페이스는 컨트랙트 뼈대의 역할, 함수 몸체 정의 x

contract NumberInterface {
	function getNum(address _myAddress) public view returns (uint);
    }

이렇게 해놓으면 다른 컨트랙트에 대한 정보를 알게 되겠지

Q. 크립토 키티에서 유전자를 포함한 데이터를 반환하는 함수를 찾았다. 인터페이스 만들어

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

반환값 타입만 아니라 변수들도 받아오는구나!


챕터 11 : 인터페이스 활용하기

Q. 크립토 키티 컨트랙트 주소 받아서, 인터페이스로 초기화 하자

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  // `ckAddress`를 이용하여 여기에 kittyContract를 초기화한다
  KittyInterface kittyContract = KittyInterface(ckAddress)

챕터 12 : 다수의 반환값 처리

다수의 반환값을 갖는 함수 처리

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 다음과 같이 다수 값을 할당한다:
  (a, b, c) = multipleReturns();
}

// 혹은 단 하나의 값에만 관심이 있을 경우: 
function getLastReturnValue() external {
  uint c;
  // 다른 필드는 빈칸으로 놓기만 하면 된다: 
  (,,c) = multipleReturns();
}

Q. 크립토키티 유전자 얻어내는 함수를 만들라

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);
  
 

function feedOnKitty(uint _zombieId, uint _kittyId) public {
	uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna);

챕터 13 : 키티 유전자

고양이 좀비는 독특한 외모를 가지게 해보자

DNA 16자리 중 12자리만 사용하니, 고양이 좀비는 마지막 2자리를 99로 설정하자

dna = dna - dna % 100 + 99;
//이렇게 하면 십의자리까지 날라가고 99가 들어간다
//수학 섹시하네

Q. feedAndMultiply 입력값에 종을 추가하고, kitty라면 유전자 99로 바꿔

// 여기에 있는 함수 정의를 변경:
  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    // 여기에 if 문 추가
    if(keccak256(_species) == keccak256("kitty") {
        newDna = newDna - newDna % 100 + 99;
    })
    _createZombie("NoName", newDna);
  }
  
function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    // 여기에 있는 함수 호출을 변경: 
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

  

챕터 14: 마무리

var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)

// 우리 좀비의 ID와 타겟 고양이 ID를 가지고 있다고 가정하면 
let zombieId = 1;
let kittyId = 1;

// 크립토키티의 이미지를 얻기 위해 웹 API에 쿼리를 할 필요가 있다. 
// 이 정보는 블록체인이 아닌 크립토키티 웹 서버에 저장되어 있다.
// 모든 것이 블록체인에 저장되어 있으면 서버가 다운되거나 크립토키티 API가 바뀌는 것이나 
// 크립토키티 회사가 크립토좀비를 싫어해서 고양이 이미지를 로딩하는 걸 막는 등을 걱정할 필요가 없다 ;) 
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
  let imgUrl = data.image_url
  // 이미지를 제시하기 위해 무언가를 한다 
})

// 유저가 고양이를 클릭할 때:
$(".kittyImage").click(function(e) {
  // 우리 컨트랙트의 `feedOnKitty` 메소드를 호출한다 
  ZombieFeeding.feedOnKitty(zombieId, kittyId)
})

// 우리의 컨트랙트에서 발생 가능한 NewZombie 이벤트에 귀를 기울여서 이벤트 발생 시 이벤트를 제시할 수 있도록 한다: 
ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  // 이 함수는 레슨 1에서와 같이 좀비를 제시한다: 
  generateZombie(result.zombieId, result.name, result.dna)
})

전체 코드를 보며 플로우를 느끼자

  • 먹이를 먹으면 새로운 좀비가 탄생한다. (기존 거 말고)
  • 크립토 키티를 먹으면 특이한 좀비가 된다
pragma solidity ^0.4.19;

import "./zombiefactory.sol";

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 {

  address ckAddress = 0x06012c8cf97BEaD5deAe237070F9587f8E7A266d;
  KittyInterface kittyContract = KittyInterface(ckAddress);


  function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) public {
    require(msg.sender == zombieToOwner[_zombieId]);
    Zombie storage myZombie = zombies[_zombieId];
    _targetDna = _targetDna % dnaModulus;
    uint newDna = (myZombie.dna + _targetDna) / 2;
    if(keccak256(_species) == keccak256("kitty") {
        newDna = newDna - newDna % 100 + 99;
    })
    _createZombie("NoName", newDna);
  }

  function feedOnKitty(uint _zombieId, uint _kittyId) public {
    uint kittyDna;
    (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId);
    feedAndMultiply(_zombieId, kittyDna, "kitty");
  }

}

느낀점

import , interface 사용법, 수학 로직 사용법을 배웠다.

백지주고 하라 그러면 절대 못할 거 같다. 쉽지 않다.

포스팅하는데 ㅈㄴ 오래걸린다. 최소 2시간이다. 갈수록 더 어려워질텐데 하 ㅋㅋ

profile
10년 후 세계 최고 블록체인 개발자

0개의 댓글