티스토리 뷰

 

총 여섯개의 코스가 있는데, 이제 네 번째다. 절반 왔다.

 

소개글을 보니 좀비들을 "싸우도록!" 하는걸 배울 차례인데, 이번 레슨에서는 이전에 배운거도 써먹고, payable 함수에 대해서도 배우고, 사용자로부터 돈을 받을 수 있는 DApp을 만드는 방법도 익힌다고 한다.

 

돈을 주고받아? 당장 배워.

 

Chapter 1: Payable

이제까지 몇 가지 modifiers를 다뤘었다. 

 

1. visibility modifier: private, internal external, public

  • private은 사적인거다. 오직 contract 내부에서만 호출할 수 있다. 
  • internal은 private이랑 비슷한데, 상속된 다른 컨트랙트에서 호출할 수 있어서 조금 더 범위가 넓다.
  • external은 컨트랙트 외부에서만 호출이 된다.
  • public은 어디서든 호출이 된다. 

 

2. state modifier: view, pure

  • view같은 경우는 함수 실행시에 어떠한 데이터도 저장되거나 변경되지 않는다.
  • pure은 view 처럼 어떤 데이터도 저장되거나 변경되지 않는것 뿐만 아니라, 블록체인의 어떤 데이터도 읽지 않는다는거다. 

 

3. custom modifiers: onlyOwner 이런식으로 만들었던 것. stack 해서 쓸 수도 있다. 다음과 같이. 

function test() external view onlyOwner anotherModifier { /* ... */ }

 

이번에 배울 payable도 위 친구들이랑 같은 카테고리에 속하는 function modifier라고 한다. 

payable은 솔리디티랑 이더리움을 굉장히 쿨하게 만들어주는,,, 그런거다. 왜냐면 이더(Ether)를 받을 수 있는 함수니까. 

 

web2 시스템의 웹서버에서, 어느 누구도 함수 호출하면서 달러를 보내거나 비트코인을 받거나 할 수 없다. 하지만 이더리움에서는 돈(Ether)랑 data(transaction palyload) 그리고 컨트랙트 코드 모두 이더리움 생태계 안에 있다. 따라서 사용자는 함수를 호출하면서 동시에! 해당 컨트랙트에 송금을 할 수도 있는거다. 

 

이 덕분에 특정 함수 실행을 위해선 이더리움 지불이 필요하다던가 하는 굉장히 흥미로운 로직이 가능해진다. 

 

contract OnlineStore {
  function buySomething() external payable {
    // Check to make sure 0.001 ether was sent to the function call:
    require(msg.value == 0.001 ether);
    // If so, some logic to transfer the digital item to the caller of the function:
    transferThing(msg.sender);
  }
}

 

여기보면 payable 이 modifier로 붙어있는걸 볼 수 있다. 이거 없이 이더를 보내려고 한다? 안된다. 

 

Chapter 2: Withdraws

인출하는거 배우나보다. 챕터 제목이 그렇다. 

그리고 이전에 돈 보내는거 배웠으니 이제 돈 꺼내는것도 배워야지. 그래야 경제가 살고~ 돈이 돌고~ 그런거 아니겠어요? 호호..

그런데 영어 단어는 한국어로 인출로 번역되지만, 내용은 돈 받는 내용이다.

앞서 돈 보내는걸 배웠으니 이제 돈 받는걸 배울 차례인것이지. 그렇게 경제가 활성화되고,,,

 

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    address payable _owner = address(uint160(owner()));
    _owner.transfer(address(this).balance);
  }
}

 

위 예제 코드에서는 Ownable 컨트랙트를 import 해와서 지금 사용중이다. 여기서 꼭 체크할 부분은 address payable 타입인 주소에 대해서만 송금이 가능하다는 점이다. 그런데 _owner 변수의 타입은 uint160이므로, 송금을 하기 위해서 이 _owner 변수를 address payable로 형변환 시켜줘야 한다. 

 

이렇게 한 뒤에 transfer 함수를 이용해서 송금을 할 수 있고, address(this).balance는 컨트랙트에 저장된 총 balance를 반환한다. 

 

Chapter 3: Zombie Battles

 

Zombie Attack 이라는 컨트랙트를 새로 만들었다. ZombieHelper로부터 상속받았음. 

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {

}

 

Chapter 4: Random Numbers

 

난수 생성은 tricky한 문제다. keccak256 해쉬 함수를 이용해서 난수를 만드는걸 생각해 볼 수 있다. 

// Generate a random number between 1 and 100:
uint randNonce = 0;
uint random = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;
randNonce++;
uint random2 = uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % 100;

 

이런식으로. 그런데 블록체인의 특성상 모든 내용이 모든 참여자에게 공개가 되고, 위에서 난수 생성을 때려맞힐 금전적인 유인이 크다면, 저 연산은 꽤 예측 가능한 범위로 떨어지게된다. now라는 시점도 미래의 시점으로 집어넣어볼 수 있고 randNonce 값도 이리저리 때려넣어서 맞추면 장땡인것! 

 

그리고 dishonest 노드가, 저 연산의 결과가 자기에게 유리한것만 기록증명으로 남기고, 아닌것은 제외해버릴 수도 있고 뭐 그렇다고 한다. 

 

그래서 나중에는 oracle, 예를 들어 난수 생성 전문 서비스를 컨트랙트로 가져와서 사용하는 것도 언급한다. 근데 이건 나중의 일이고, 지금은 간단한 데모 게임이고, 금전적 보상도 없는 그런 상태이므로, 간단하게 난수 생성을 연습해 봅니다. 

 

pragma solidity >=0.5.0 <0.6.0;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;

  function randMod(uint _modulus) internal returns(uint) {
    randNonce++;
    return uint(keccak256(abi.encodePacked(now, msg.sender, randNonce))) % _modulus;
  }

}

 

이렇게 익숙한 keccak256 해쉬함수랑 나머지 연산 진행해서 난수를 만들었다. 

 

Chapter 5: Zombie Fightin'

여기서는 좀비 싸우는 확률 변수 정하고, attack 함수 뼈대 만들었다. 

 

Chapter 6: Refactoring Common Logic

여기서는 msg.sender == zombieToOwner[_zombieId] 이거를 require로 체크하는 modifier를 만들어서 이렇게 소유권 확인이 필요한 부분에 modifier로 활용햘 수 있게 추가해줬다.

 

Chapter 7: More Refactoring

여기서도 같은 업무를 했다. 이미 위에서 만든 ownerOf (위에서 만든 modifier 이름) 함수를 다른 함수에도 추가했다.

 

Chapter 8: Back to Attack!

여기서 공격 함수를 만들었다. 

앞서 만든 modifier인 ownerOf를 십분 활용한다. 

  function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
  }

 

Chapter 9: Zombie WIns and Losses

좀비끼리 싸움을 시켰으면 승자와 패자가 생긴다. 이런 레코드 그냥 둘 수 없다. 리더보드 만들기 위해 준비를 해야해.

그러므로 winCount와 lossCount를 기록해 둘테다. 이걸 기록하는 방법은 다양하고 각각의 장단점이 있을거다. 새로 리더보드 struct를 만들수도 있을거고, 개별 Mapping을 할 수도 있겠지만, 여기서는 간단하게 이미 만들어둔 Zombie 구조체에 winCount와 lossCount 항목을 추가할 예정이다.

 

storage에 기록될 좀비 구조체, 그 용량을 신경쓰지 않을 수 없다. 가스비와 직결될테니까. 그래서 타입은 uint16으로 각각 한다. uint8이 가장 작은 규모이지만, 이걸 타입으로 정하면, 좀비 싸움 얼마 못 시키고 오버플로우가 발생한다. uint8이면 2^8해서 256인데, 하루에 한 번씩만 싸움 하게 해 둬도 일 년을 못간다. 반면 uint16으로 타입을 해 두면 65536 값이 되고, 한 179년 동안 매일 한 번씩 싸움 붙여도 걱정이 없다. 65536을 365로 나누면 179.5~~~~ 뭐 이렇게 나오기 때문이다. 

    struct Zombie {
      string name;
      uint dna;
      uint32 level;
      uint32 readyTime;
      // 1. Add new properties here
      uint16 winCount;
      uint16 lossCount;
    }

 

보면 마지막 두 줄이 새로 추가한 항목임을 알 수 있다. 

 

Chapter 10: Zombie Victory

여기서는 좀비 승률이랑 랜덤값 비교해서, 내 좀비 승률이 높으면, 내가 이긴거니까 내 좀비의 WinCount 올려주고, level도 올려주고, 상대방 좀비의 lossCount를 높여주는 일을 했다. 간단했다. 

 

Chapter 11: Zombie Loss

위에서 이기면 어떤일이 생길지 코드로 작성해두었으니, 이제 지면 어떤 일을 발생시킬지 코드로 작성할 차례다. 

 

 

레쓴 4도 끝~

 

https://share.cryptozombies.io/en/lesson/4/share/mar?id=WyJjenw2NTcxNDUiLDEsMTRd

 

My CryptoZombie defeated the evil IOTA zombie!

I just completed CryptoZombies Lesson 4, and defeated the evil IOTA zombie! Come relive the battle. Thanks @CryptoZombiesHQ.

share.cryptozombies.io

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함