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

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

크립토좀비

목록 보기
4/6

챕터 1 : payable

함수 제어자 :
private(내부의 다른 함수들에 의해서만 호출)
internal(private + 상속하는 컨트랙트에서도 호출)
external(외부에서만 호출 가능)
public(어디서든)

상태 제어자 :
view
pure

사용자 정의 제어자
onlyOwner
aboveLevel
...

payable 제어자

이더를 받을 수 있는 특별한 함수 유형

일반 웹 서버에서 API 함수를 실행할 때 달러를 보낼 수 있냐? NO

이더리움은 돈(이더), 데이터, 코드 모두 이더리움 위에 존재하기 때문에, 함수 실행과 동시에

지불이 가능

contract OnlineStore {
  function buySomething() external payable {
    // 함수 실행에 0.001이더가 보내졌는지 확실히 하기 위해 확인:
    require(msg.value == 0.001 ether);
    // 보내졌다면, 함수를 호출한 자에게 디지털 아이템을 전달하기 위한 내용 구성:
    transferThing(msg.sender);
  }

그리고 프론트엔드에서 이 함수를 실행한다면?

OnlineStore.buySomething({from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001)})

value: web3.utils.toWei(0.001) 이게 msg.value

(payable이 없는데 이더 보내려고 하면 거부됨)

Q. 이용자가 돈 주면 레벨업 시켜주는 함수를 만들어라

uint levelUpFee = 0.001 ether;

function levelUp(uint _zombieId) external payable {
	require(msg.value == levelUpFee);
    zombies[_zombieId].level++;
}

챕터 2 : 출금

누가 컨트랙트로 이더를 보내면, 해당 컨트랙트 이더리움 계좌에 이더가 저장되고 갇힘

이더를 인출하는 함수

contract GetPaid is Ownable {
  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }
}

transfer 함수를 사용해 특정 주소로 이더를 전달

this.balance는 컨트랙트 내 전체 잔액

초과지불을 했다면, 돌려주는 함수도 만들 수 있지

uint itemFee = 0.001 ether;
msg.sender.transfer(msg.value - itemFee);

만약 구매자 판매자를 이어주는 컨트랙트라면 판매자 주소를 저장해놨다가,
구매자가 사면 그 돈을 판매자에게 전달할 수도

seller.transfer(msg.value).

Q. 돈 다 빼는 함수를 만들라

Q. 이더 시세가 들쭉날쭉이니 _levelUpFee를 설정하는 함수도 만들라

function withdraw external onlyOwner {
	owner.transfer(this.balance);
    };
    
function setLevelUpFee(uint _fee) external onlyOwner {
	levelUpFee = _fee;
    }

챕터 3 : 좀비 전투

좀비 전투를 위한 기능을 추가하기 때문에 따로 파일을 파자

pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieBattle is ZombieHelper {
    
}

챕터 4 : 난수

모든 게임은 일정 수준의 무작위성을 필요로 하는데, 솔리디티에서는 난수를 어떻게 발생시킬까?

적어도 안전하지는 못한다.

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

이렇게 현재 시간, 증가하는 랜덤 논스를 사용해 꽤나 안전하고 랜덤해 보이는데...

이 방법은 정직하지 않은 노드의 공격에 취약

만약 동전 던지기로 앞-뒤 맞추는 함수가 있다면, 그리고 내가 노드라면,

못 맞추면 트랜잭션을 공유를 안하고, 맞추면 공유하는 거지

그래서 어쩌라고???

오라클을 사용해 블록체인 외부의 난수 함수에 접근할 수 있긴 하다

오라클 : 블록체인 외부에서 데이터를 받아오는 안전한 방법 중 하나

우린 공부하니까 너무 쿠사리 주진 말자

Q. 난수 함수를 만들어봐라

uint randNonce = 0;

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

챕터 5 : 좀비 싸움

좀비 전투 방식

내가 내 좀비 중 하나 고르고, 상대방 좀비를 공격 대상으로 선정

공격하는 쪽이 이길 확률 70%

모든 좀비들은 전투 결과에 따라 증가하는 winCount, lossCount 가짐

공격하는 놈이 이기면 레벨 오르고 새로운 좀비 생김

지면 lossCount만 올라가고 아무일 없음

공격한 놈 재사용 대기시간 활성화

Q. 확률 70으로 세팅하고 공격 함수를 만들라

uint attackVictoryProbability = 70;

function attack(uint _zombieId, uint _targetId) external {
}

챕터 6 : 공통 로직 구조 개선 Refactoring

공격을 할 때, 남의 좀비를 쓰면 안되니 주인인지 확인해야 겟지

맨날 쓰잖아 require(msg.sender == zombieToOwner[_zombieId];

계속 쓰는 거면 아싸리 modifier로 만들자

Q. ownerOf modifier만들고 적용시켜

modifier ownerOf(uint _zombieId) {
	require(msg.sender == zombieToOwner[_zombieId]);
    _;
    };
    
    //...
    function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) {
    

챕터 7 : 구조 더 개선하기

Q. changeName(), changeDna()에서도 써야지?

  // 1. 이 함수를 `ownerOf`를 사용하도록 변경하게:
  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  // 2. 이 함수에도 똑같이 적용하게:
  function changeDna(uint _zombieId, uint _newDna) external  aboveLevel(20, _zombieId)  ownerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

챕터 8 : 공격으로 돌아가자

Q. 내 좀비, 상대 좀비 가져오자, 설마 인자로 받은 ID를 쓰는 건 아니지? 0~99 사이 난수를 사용해야 하니 인수를 100으로

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

챕터 9 : 좀비 승리와 패배

좀비 순위표도 만들거여

struct 수정 해야것지?

그리고 좀비 생성할 때도 수정해줘야 것지?

struct Zombie {
	string name;
    uint dna;
    uint32 level;
    uint32 readyTime;
    uint16 winCount;
    uint16 lossCount;
    }
    
function _createZombie(string _name, uint _dna) internal {
        // 2. 여기서 새로운 좀비의 생성을 수정하게:
        uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime),0,0)) - 1;    
    
    

챕터 10 : 좀비 승리

난수가 0~99이고 공격자 이길 확률을 70으로 세팅해뒀다. 어찌해야 내가 이길 확률이 70%가 될 것이며, 이기면 무엇을 해야 할까?

if(rand <= attackVictoryProbability) {
	myZombie.winCount++;
    myZombie.level++;
    enemyZombie.lossCount++;
    feedAndMultiply(_zombieId, _targetId, "zombie");
    }

챕터 11 : 좀비 패배

내가 지면 카운팅을 반대로 해주면 될 것이고,

이기고 지던 간에 쿨타임을 돌려야지

else {
      myZombie.lossCount++;
      enemyZombie.winCount++;

    }
    _triggerCooldown(myZombie);
  }
profile
10년 후 세계 최고 블록체인 개발자

0개의 댓글