Basic - ERC721 & 크립토 수집품

EllaDev·2022년 4월 2일
0

CrytoZombie

목록 보기
5/6

해당 글은 CrytoZombie를 공부하면서 정리한 내용입니다.

CrytoZombie

Ch1. 이더리움 상의 토큰

이더리움에서 "토큰"은 기본적으로 그저 몇몇 공통 규약을 따르는 스마트 컨트랙트이다. 즉 다른 모든 토큰 컨트랙트가 사용하는 표준 함수 집합을 구현하는 것이다. 예를 들면 transfer(address _to, uint256 _value)나 balanceOf(address _owner) 같은 함수들이 있다.

내부적으로 스마트 컨트랙트는 보통 mapping(address => uint256) balances와 같은 매핑을 가지고 있다. 각각의 주소에 잔액이 얼마나 있는지 기록하는 것이다.

즉 기본적으로 토큰은 그저 하나의 컨트랙트인데 그 안에서 누가 얼마나 많은 토큰을 가지고 있는지 기록하고, 몇몇 함수를 가지고 사용자들이 그들의 토큰을 다른 주소로 전송할 수 있게 해주는 것이다.

이렇게 하는 이유?

모든 ERC20 토큰들이 똑같은 이름의 동일한 함수 집합을 공유하기 때문에, 이 토큰들에 똑같은 방식으로 상호작용이 가능하다.

즉 하나의 ERC20 토큰과 상호작용할 수 있는 애플리케이션 하나를 만들면, 이 앱이 다른 어떤 ERC20 토큰과도 상호작용이 가능한 것이다. 이런 방식으로 커스텀 코드를 추가하지 않고도 앱에 더 많은 토큰들을 추가할 수 있다. 그저 새로운 토큰의 컨트랙트 주소만 끼워넣으면 된다. 그러고 나면 앱에서 사용할 수 있는 또 다른 토큰이 생긴다.

이러한 것의 한 예로는 거래소가 있다. 한 거래소에서 새로운 ERC20 토큰을 상장할 때, 실제로는 이 거래소에서 통신이 가능한 또 하나의 스마트 컨트랙트를 추가하는 것이다. 사용자들은 이 컨트랙트에 거래소의 지갑 주소에 토큰을 보내라고 할 수 있고, 거래소에서는 이 컨트랙트에 사용자들이 출금을 신청하면 토큰을 다시 돌려보내라고 할 수 있게 만드는 것이다.

거래소에서는 이 전송 로직을 한 번만 구현하면 된다. 그리고서 새로운 ERC20 토큰을 추가하고 싶으면, 데이터베이스에 단순히 새 컨트랙트 주소를 추가하기만 하면 된다.

다른 토큰 표준

ERC20 토큰은 화폐처럼 사용되는 토큰으로는 정말 적절하지만 좀비 게임에서 좀비를 표현할 때에는 그다지 쓸모 없다.
첫째로, 좀비는 화폐처럼 분할할 수가 없다. 난 상대에게 0.237ETH를 보낼 수 있지만, 상대에게 0.237개의 좀비를 보내는 것은 말이 안된다.
둘째로, 모든 좀비가 똑같지는 않다.

여기에 크립토좀비와 같은 크립토 수집품을 위해 더 적절한 토큰 표준이 있다, 바로 ERC721 토큰이다.

ERC721 토큰은 교체가 불가하다. 각각의 토큰이 유일하고 분할이 불가하기 때문이다. 이 토큰을 하나의 전체 단위로만 거래할 수 있고, 각각의 토큰은 유일한 ID를 가지고 있다. 그러니 이게 우리의 좀비를 거래할 수 있게 하기에는 아주 적절하다.

ERC721과 같은 표준을 사용하면 우리의 컨트랙트에서 사용자들이 우리의 좀비를 거래/판매할 수 있도록 하는 경매나 중계 로직을 우리가 직접 구현하지 않아도 된다는 이점이 있다. 우리가 스펙에 맞추기만 하면, 누군가 ERC721 자산을 거래할 수 있도록 하는 거래소 플랫폼을 만들면 우리의 ERC721 좀비들을 그 플랫폼에서 쓸 수 있게 될 것이다. 그러니 거래 로직을 만드느라 고생하는 것보다 토큰 표준을 사용하는 것이 명확한 이점이 있는 것이다.


Ch2. ERC721 표준, 다중 상속

// ERC
contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

  function balanceOf(address _owner) public view returns (uint256 _balance);
  function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256 _tokenId) public;
  function approve(address _to, uint256 _tokenId) public;
  function takeOwnership(uint256 _tokenId) public;
}

*참고: ERC721 표준은 현재 초안인 상태이고, 아직 공식으로 채택된 구현 버전은 없다. 이 튜토리얼에서 우리는 OpenZeppelin 라이브러리에서 쓰이는 현재 버전을 사용할 것이지만, 공식 릴리즈 이전에 언젠가 바뀔 가능성도 있다. 그러니 하나의 구현 가능한 버전 정도로만 생각하고, ERC721 토큰의 정식 표준으로 생각하지는 말아야한다.

토큰 컨트랙트 구현

토큰 컨트랙트를 구현할 때, 처음 해야 할 일은 바로 인터페이스를 솔리디티 파일로 따로 복사하여 저장하고 import "./erc721.sol";을 써서 임포트를 하는 것이다. 그리고 해당 컨트랙트를 상속하는 우리의 컨트랙트를 만들고, 각각의 함수를 오버라이딩하여 정의하여야 한다.

솔리디티에서 컨트랙트는 다음과 같이 다수의 컨트랙트를 상속할 수 있다.

contract SatoshiNakamoto is NickSzabo, HalFinney {	
}

다중 상속을 쓸 때는 상속하고자 하는 다수의 컨트랙트를 쉼표(,)로 구분하면 되네. 위의 경우에, 컨트랙트는 NickSzabo와 HalFinney를 상속하고 있다.


Ch3. balanceOf & ownerOf

balanceOf

function balanceOf(address _owner) public view returns (uint256 _balance);

이 함수는 단순히 address를 받아, 해당 address가 토큰을 얼마나 가지고 있는지 반환한다.
이 경우, 우리의 "토큰"은 좀비들이다. 우리의 DApp에서 어떤 소유자가 얼마나 많은 좀비를 가지는지 저장해놓은 곳을 기억하는가?

ownerOf

function ownerOf(uint256 _tokenId) public view returns (address _owner);

이 함수에서는 토큰 ID(우리의 경우에는 좀비 ID)를 받아, 이를 소유하고 있는 사람의 address를 반환한다.
우리가 이 정보를 저장하는 mapping을 우리 DApp에 이미 가지고 있기 떄문에 이 함수들은 단 한 줄로 구현할 수 있다.

*참고: uint256은 uint와 동일하다는 것을 기억하고 우리 코드에서 지금까지 uint를 사용해왔지만, 여기서는 우리가 스펙을 복사/붙여넣기 했으니 uint256을 쓸 것이다.

  function balanceOf(address _owner) public view returns (uint256 _balance) {
    return ownerZombieCount[_owner];
  }

  function ownerOf(uint256 _tokenId) public view returns (address _owner) {
    return zombieToOwner[_tokenId];
  }

Ch4. ERC721: 전송 로직

ERC721에서 한 사람이 다른 사람에게 소유권을 넘기는 것을 구현한다.

= ERC721 스펙에서는 토큰을 전송하는 2개의 방식

function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;

첫 번째 방법은 토큰의 소유자가 전송 상대의 address, 전송하고자 하는 _tokenId와 함께 transfer 함수를 호출하는 것이다.

두 번째 방법은 토큰의 소유자가 먼저 위에서 본 정보들을 가지고 approve를 호출하는 것이다. 그리고서 컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장한다. 보통 mapping (uint256 => address)를 쓴다. 이후 누군가 takeOwnership을 호출하면, 해당 컨트랙트는 이 msg.sender가 소유자로부터 토큰을 받을 수 있게 허가를 받았는지 확인하고 허가를 받았다면 해당 토큰을 그에게 전송한다.

transfer와 takeOwnership 모두 동일한 전송 로직을 가지고 있고 순서만 반대이다.(전자는 토큰을 보내는 사람이 함수를 호출하고 후자는 토큰을 받는 사람이 호출하는 것이다)

그러니 이 로직만의 프라이빗 함수, _transfer를 만들어 추상화하는 것이 좋다. 두 함수에서 모두 쓸 수 있도록 하면 똑같은 코드를 두 번씩 쓰지 않아도 된다.

  function _transfer(address _from, address _to, uint256 _tokenId) private {
    ownerZombieCount[_to]++;
    ownerZombieCount[_from]--;
    zombieToOwner[_tokenId] = _to;
    Transfer(_from, _to, _tokenId);
  }

  function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    _transfer(msg.sender, _to, _tokenId);
  }

Ch5. ERC721: Approve

approve / takeOwnership을 사용하는 전송은 2단계로 나뉜다
1. 기존 소유자가 새로운 소유자의 address와 새로운 소유자에게 보내고 싶은 _tokenId를 사용하여 approve를 호출한다.
2. 새로운 소유자가 _tokenId를 사용하여 takeOwnership 함수를 호출하면, 컨트랙트는 그가 승인된 자인지 확인하고 그에게 토큰을 전송한다.

이처럼 2번의 함수 호출이 발생하기 때문에, 우리는 함수 호출 사이에 누가 무엇에 대해 승인이 되었는지 저장할 데이터 구조가 필요하다.

  mapping (uint => address) zombieApprovals;
  
  function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
    zombieApprovals[_tokenId] = _to;
    Approval(msg.sender, _to, _tokenId);
  }

Ch6. ERC721: takeOwnership

마지막 함수인 takeOwnership에서는 msg.sender가 이 토큰/좀비를 가질 수 있도록 승인되었는지 확인하고, 승인이 되었다면 _transfer를 호출해야 한다.

  function takeOwnership(uint256 _tokenId) public {
    require(zombieApprovals[_tokenId] == msg.sender);
    address owner = ownerOf(_tokenId);
    _transfer(owner, msg.sender, _tokenId);
  }

Ch7. Overflow 막기

Overflow와 Underflow?

8비트 데이터를 저장할 수 있는 uint8 하나를 가지고 있다. 이 말인즉 우리가 저장할 수 있는 가장 큰 수는 이진수로 11111111(또는 십진수로 2^8 - 1 = 255)가 된다.

uint8 number = 255;
number++;

마지막에 number의 값은 무엇이 되겠나?
이 예시에서, 이 변수에 오버플로우를 만들었다. 즉 number가 증가를 시켰음에도 직관과는 다르게 0이 된다. 이진수 11111111에 1을 더하면, 이 수는 00000000으로 돌아간다. 시계가 23:59에서 00:00으로 가듯이 말이다.

언더플로우는 이와 유사하게 0 값을 가진 uint8에서 1을 빼면, 255와 같아지는 것을 말한다.(uint에 부호가 없어, 음수가 될 수 없기 때문이다)

여기서 uint8을 쓰지 않고, 1씩 증가시킨다고 uint256에 오버플로우가 발생하지는 않을 것 같지만(2^256은 정말 큰 숫자이네), 미래에 우리의 DApp에 예상치 못한 문제가 발생하지 않도록 여전히 우리의 컨트랙트에 보호 장치를 두는 것이 좋다.

SafeMath

이를 막기 위해, OpenZeppelin에서 기본적으로 이런 문제를 막아주는 SafeMath라고 하는 라이브러리를 만들었다.

  • 라이브러리(Library) : 솔리디티에서 특별한 종류의 컨트랙트로 유용하게 사용되는 경우 중 하나는 기본(native) 데이터 타입에 함수를 붙일 때이다.

ex> SafeMath 라이브러리를 쓸 때는 using SafeMath for uint256이라는 구문을 사용한다. SafeMath 라이브러리는 4개의 함수(add, sub, mul, div)를 가지고 있다. 그리고 이제 우리는 uint256에서 다음과 같이 이 함수들에 접근할 수 있다.

using SafeMath for uint256;

uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10

Ch8. SafeMath

= SafeMath 내부의 코드

library SafeMath {

  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
  • library 키워드 : contract와 비슷하지만 조금 다르다. 우리가 using 키워드를 사용할 수 있게 해준다. 이를 통해 라이브러리의 메소드들을 다른 데이터 타입에 적용할 수 있다.

기본 예시

= 예시1

using SafeMath for uint;
// 우리는 이제 이 메소드들을 아무 uint에서나 쓸 수 있네.
uint test = 2;
test = test.mul(3); // test는 이제 6이 되네
test = test.add(5); // test는 이제 11이 되네
  • mul과 add 함수는 각각 2개의 인수를 필요로 한다.
  • 우리가 using SafeMath for uint를 선언할 때, 우리가 함수를 적용하는 uint(test)는 첫 번째 인수로 자동으로 전달된다.

= 예시2 : add 함수

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}
  • 기본적으로 add는 그저 2개의 uint를 +처럼 더하지만, 그 안에 assert 구문을 써서 그 합이 a보다 크도록 보장한다. 이것이 오버플로우를 막아준다.
  • assert: 조건을 만족하지 않으면 에러를 발생시킨다는 점에서 require와 비슷하다.
  • assert와 require의 차이점: require는 함수 실행이 실패하면 남은 가스를 사용자에게 되돌려 주지만, assert는 그렇지 않다. assert는 일반적으로 코드가 심각하게 잘못 실행될 때 사용한다.(uint 오버플로우)

간단히 말해, SafeMath의 add, sub, mul, 그리고 div는 4개의 기본 수학 연산을 하는 함수이지만, 오버플로우나 언더플로우가 발생하면 에러를 발생시킨다.

SafeMath 이슈 1

winCount와 lossCount는 uint16이고, level은 uint32이다. 즉 이런 인수들을 써서 SafeMath의 add 메소드를 사용하면, 이 타입들을 uint256으로 바꿀 것이기 때문에 실제로 오버플로우를 막아주지 못한다.

function add(uint256 a, uint256 b) internal pure returns (uint256) {
  uint256 c = a + b;
  assert(c >= a);
  return c;
}
// 만약 `uint8`에 `.add`를 호출한다면, 타입이 `uint256`로 변환되네.
// 그러니 2^8에서 오버플로우가 발생하지 않을 것이네. 256은 `uint256`에서 유효한 숫자이기 때문이지.

이는 즉 uint16과 uint32에서 오버플로우/언더플로우를 막기 위해 2개의 라이브러리를 더 만들어야 한다는 것을 의미한다.


Ch9. 주석

컨트랙트의 모든 함수에서 예상되는 행동값을 자네의 코드에 주석으로 설명하는 것이 좋다. 그렇게 하면 다른 개발자들이 코드 자체를 다 읽어보지 않고 훑어보더라도 큰 맥락에서 그 코드를 이해할 수 있다.

  • // 주석, /* 주석 */ 이러게 2가지 방법이 있다.

솔리디티 커뮤니티에서 표준으로 쓰이는 형식은 natspec이다.

/// @title 기본적인 산수를 위한 컨트랙트
/// @author H4XF13LD MORRIS 💯💯😎💯💯
/// @notice 지금은 곱하기 함수만 추가한다.
contract Math {
  /// @notice 2개의 숫자를 곱한다.
  /// @param x 첫 번쨰 uint.
  /// @param y 두 번째 uint.
  /// @return z (x * y) 곱의 값
  /// @dev 이 함수는 현재 오버플로우를 확인하지 않는다.
  function multiply(uint x, uint y) returns (uint z) {
    // 이것은 일반적인 주석으로, natspec에 포함되지 않는다.
    z = x * y;
  }
}
  • @notice : 사용자에게 컨트랙트/함수가 무엇을 하는지 설명
  • @dev : 개발자에게 추가적인 상세 정보를 설명
  • @param과 @return : 함수에서 어떤 매개 변수와 반환값을 가지는지 설명


= zombieownership.sol ```js pragma solidity ^0.4.19;

import "./zombieattack.sol";
import "./erc721.sol";
import "./safemath.sol";

/// TODO: natspec에 맞도록 이 부분을 바꾸게.
contract ZombieOwnership is ZombieAttack, ERC721 {

using SafeMath for uint256;

mapping (uint => address) zombieApprovals;

function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerZombieCount[_owner];
}

function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return zombieToOwner[_tokenId];
}

function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].sub(1);
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}

function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}

function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
zombieApprovals[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}

function takeOwnership(uint256 _tokenId) public {
require(zombieApprovals[_tokenId] == msg.sender);
address owner = ownerOf(_tokenId);
_transfer(owner, msg.sender, _tokenId);
}
}


= zombieattack.sol
```js
pragma solidity ^0.4.19;

import "./zombiehelper.sol";

contract ZombieAttack is ZombieHelper {
  uint randNonce = 0;
  uint attackVictoryProbability = 70;

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

  function attack(uint _zombieId, uint _targetId) external onlyOwnerOf(_zombieId) {
    Zombie storage myZombie = zombies[_zombieId];
    Zombie storage enemyZombie = zombies[_targetId];
    uint rand = randMod(100);
    if (rand <= attackVictoryProbability) {
      myZombie.winCount++;
      myZombie.level++;
      enemyZombie.lossCount++;
      feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
    } else {
      myZombie.lossCount++;
      enemyZombie.winCount++;
      _triggerCooldown(myZombie);
    }
  }
}

= erc721.sol

contract ERC721 {
  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);

  function balanceOf(address _owner) public view returns (uint256 _balance);
  function ownerOf(uint256 _tokenId) public view returns (address _owner);
  function transfer(address _to, uint256 _tokenId) public;
  function approve(address _to, uint256 _tokenId) public;
  function takeOwnership(uint256 _tokenId) public;
}

= safemath.sol

pragma solidity ^0.4.18<;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}

= zombiehelper.sol

pragma solidity ^0.4.19;

import "./zombiefeeding.sol";

contract ZombieHelper is ZombieFeeding {

  uint levelUpFee = 0.001 ether;

  modifier aboveLevel(uint _level, uint _zombieId) {
    require(zombies[_zombieId].level >= _level);
    _;
  }

  function withdraw() external onlyOwner {
    owner.transfer(this.balance);
  }

  function setLevelUpFee(uint _fee) external onlyOwner {
    levelUpFee = _fee;
  }

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

  function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) onlyOwnerOf(_zombieId) {
    zombies[_zombieId].name = _newName;
  }

  function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) onlyOwnerOf(_zombieId) {
    zombies[_zombieId].dna = _newDna;
  }

  function getZombiesByOwner(address _owner) external view returns(uint[]) {
    uint[] memory result = new uint[](ownerZombieCount[_owner]);
    uint counter = 0;
    for (uint i = 0; i < zombies.length; i++) {
      if (zombieToOwner[i] == _owner) {
        result[counter] = i;
        counter++;
      }
    }
    return result;
  }

}

= zombiefeeding.sol

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 {

  KittyInterface kittyContract;

  modifier onlyOwnerOf(uint _zombieId) {
    require(msg.sender == zombieToOwner[_zombieId]);
    _;
  }

  function setKittyContractAddress(address _address) external onlyOwner {
    kittyContract = KittyInterface(_address);
  }

  function _triggerCooldown(Zombie storage _zombie) internal {
    _zombie.readyTime = uint32(now + cooldownTime);
  }

  function _isReady(Zombie storage _zombie) internal view returns (bool) {
      return (_zombie.readyTime <= now);
  }

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

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

= zombiefactory.sol

pragma solidity ^0.4.19;

import "./ownable.sol";
import "./safemath.sol";

contract ZombieFactory is Ownable {

  using SafeMath for uint256;

  event NewZombie(uint zombieId, string name, uint dna);

  uint dnaDigits = 16;
  uint dnaModulus = 10 ** dnaDigits;
  uint cooldownTime = 1 days;

  struct Zombie {
    string name;
    uint dna;
    uint32 level;
    uint32 readyTime;
    uint16 winCount;
    uint16 lossCount;
  }

  Zombie[] public zombies;

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

  function _createZombie(string _name, uint _dna) internal {
    uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
    zombieToOwner[id] = msg.sender;
    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 {
    require(ownerZombieCount[msg.sender] == 0);
    uint randDna = _generateRandomDna(_name);
    randDna = randDna - randDna % 100;
    _createZombie(_name, randDna);
  }

}

= ownable.sol

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable {
  address public owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public {
    owner = msg.sender;
  }


  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }


  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    OwnershipTransferred(owner, newOwner);
    owner = newOwner;
  }

}
profile
Frontend Developer

0개의 댓글