2022. 3. 31. 15:56ㆍ개발 잡부/블록체인
1. 이더리움 상의 토큰
이 챕터에서는 토큰에 대해 알아본다.
토큰
토큰이란 기본적으로 공통 규약을 따르는 스마트 컨트랙트다.
사실 이 부분은 이해가 어려워서 따로 검색을 해봤다.
토큰은 이더리움 블록체인 최상위에 빌드된 디지털 자산이다.
다양한 공급 곡선을 가질 수 있다.
이더리움은 앱들을 판매하는 플랫폼이다.
사실 보니까 더 헷갈리는데,
일종의 '교환권'이라고 난 이해했다.
예를 들어서 회사를 하나 세워서 상장을 하면
주식을 쪼개서 팔게 된다.
이렇게 쪼개진 주식들을 하나의 토큰이라고 볼 수 있는거다.
이 토큰으로 주주로서의 권리를 주장하거나, 판매할수도 있는거다.
단, 그냥 엿바꿔 먹으라고 주는 '교환권'과는 얘기가 다르다.
토큰은 일종의 인터페이스와 같다.
어떤 속성을 지니는지, 어떻게 처리가 되는지에 대한 명세다.
이 부분에 대한 정의는 나중에 따로 해봐야겠다.
ERC20 & ERC721 토큰
ERC20 토큰은 우리가 사용하는 비트코인과 같은 화폐다.
교체 가능하고(내가 가진 1개나 너가 가진 1개나 같다),
분할이 가능하다. (0.01개만큼 거래 가능)
하지만 ERC721 토큰은 교체가 불가능하다.
각각의 토큰이 고유한 내용을 갖고있기 때문이다.
우리가 흔히 말하는 NFT다.
정답
// 여기서 시작하게
pragma solidity ^0.4.19;
import "./zombieattack.sol";
contract ZombieOwnership is ZombieAttack {
}
2. ERC721 표준, 다중 상속
이번 챕터에서는 ERC721의 표준과 다중 상속에 대해 배워본다.
우리가 만드려는 것은 좀비를 거래할 수 있는 토큰이기 때문에,
ERC721을 상속받아서 그에 맞게 형식을 구현해야 한다.
(ERC721은 인터페이스다)
다중 상속
여러 컨트랙트를 상속받고 싶다면,
','로 구분해서 여러 컨트랙트를 추가하면 된다.
contract SatoshiNakamoto is NickSzabo, HalFinney {
정답
// 여기서 import 하게.
import "./erc721.sol";
// 여기서 ERC721 상속을 선언하게.
contract ZombieOwnership is ZombieAttack, ERC721 {
3. balanceOf & ownerOf
이 챕터에서는 ERC721 토큰의 형식을 상속받아서
자산을 확인하고, 전송하고, 소유권을 확인하는 등의 처리를 할 것이다.
정답
...
function balanceOf(address _owner) public view returns (uint256 _balance) {
// 1. 여기서 `_owner`가 가진 좀비의 수를 반환하게.
return ownerZombieCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address _owner) {
// 2. 여기서 `_tokenId`의 소유자를 반환하게.
return zombieToOwner[_tokenId];
}
...
4. 리팩토링
우리가 기존에 만들어놨던 ownerOf라는 제어자는
ERC721의 ownerOf와 이름이 겹친다!
그래서 우리는 기존 함수의 이름을 바꾸기로 했다.
(이런 일을 예방하려면, 토큰들의 함수명들을 기억했다가
네이밍을 피해서 하는게 좋을 것 같다.)
정답
...
// 1. 제어자의 이름을 `onlyOwnerOf`로 바꾸게.
modifier onlyOwnerOf(uint _zombieId) {
...
// 2. 여기서도 제어자의 이름을 바꾸게.
function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal onlyOwnerOf(_zombieId) {
...
5. ERC721: 전송 로직
이번 챕터에서는 소유권을 넘기는 transfer를 정의할 것이다.
정답
// 여기에 _transfer()를 정의하게.
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to]++;
ownerZombieCount[_from]--;
zombieToOwner[_tokenId] = _to;
// 이 녀석은 이벤트다.
Transfer(_from, _to, _tokenId);
}
6. ERC721: 전송 (이어서)
정답
...
// 1. 여기에 제어자를 추가하게.
function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
// 2. 여기서 함수를 정의하게.
_transfer(msg.sender, _to, _tokenId);
}
...
7. ERC721: Approve
approve 함수는 이름 그대로
새 소유자에게 토큰을 가져갈 수 있도록 허락해주는 것이다.
정답
...
// 1. 여기에 mapping을 정의하게.
mapping (uint => address) zombieApprovals;
// 2. 여기에 함수 제어자를 추가하게.
function approve(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
// 3. 여기서 함수를 정의하게.
zombieApprovals[_tokenId] = to;
Approval(msg.sender, _to, _tokenId);
}
...
8. ERC721: takeOwnership
이 함수에서는 승인이 되었는지(approve) 확인하고,
토큰/좀비의 소유권을 전달(_transfer)하면 된다.
정답
...
// 여기서 시작하게.
require(zombieApprovals[_tokenId] == msg.sender);
address owner = ownerOf(_tokenId);
_transfer(owner, msg.sender, _tokenId);
...
9. 오버플로우 막기
오버플로우란?
변수에 담긴 값이 최대치를 넘는 경우.
예를 들어 8비트라면 11111111에서 1을 더하면 00000000이 된다.
SafeMath
이런 오버플로우, 언더플로우를 막기 위해 안전한 숫자 처리가 필요하다.
그래서 우리는 SafeMath 라이브러리르 사용할 것이다.
라이브러리
라이브러리는 특별한 종류의 컨트랙트다.
네이티브 타입에 함수를 붙일 수 있다.
예를들어 기존의 uint에 a라는 함수가 없는데,
특정 라이브러리를 붙일 경우 uint변수가 a함수를 쓸 수 있게 되는 것이다.
라이브러리를 가져오는 방법은 컨트랙트 내에서
'using' 키워드를 사용하는 것이다.
using SafeMath for uint256;
정답
...
// 1. 여기서 import 하게.
import "./safemath.sol";
// 2. 여기에 using safemath를 선언하게.
using SafeMath for uint256;
...
10. SafeMath 파트 2
이 챕터에서는 라이브러리 키워드와 safemath의 사용법을 알아본다.
library 키워드
safemath.sol 코드를 보면,
contract 위치에 library 키워드가 들어간 것이 보인다.
이런 library는 다른 컨트랙트에서 using할 수 있게 해준다.
library SafeMath {
SafeMath 사용하기
기존의 사칙연산을 다음의 방식으로 치환할 수 있다.
uint a = 2;
uint b;
b = a + 3; => b = a.sum(3);
b = a - 3; => b = a.sub(3);
b = a * 3; => b = a.mul(3);
b = a / 3; => b = a.div(3);
정답
...
// 1. SafeMath의 `add`로 교체하게.
ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
// 2. SafeMath의 `sub`로 교체하게.
ownerZombieCount[_from] = ownerZombieCount[_from].sub(1);
...
11. SafeMath 파트 3
uint16과 uint32에 대해서도 이러한 처리를 하고 싶은데,
현재 safemath.sol에는 uint256기준으로만 있다.
그래서 uint16과 uint32에 대해서도 같은 처리를 했다 (복붙)
너무 비인간적인 것 같은데
네이티브 타입 자체가 적어서 괜찮은것 같기도 하고 모르겠다.
정답
...
// 1. using SafeMath32 for uint32를 선언하게.
using SafeMath32 for uint32;
// 2. using SafeMath16 for uint16를 선언하게.
using SafeMath16 for uint16;
// 3. 여기에 SafeMath의 `add`를 사용하게:
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);
NewZombie(id, _name, _dna);
...
12. SafeMath 파트 4
정답
...
function randMod(uint _modulus) internal returns(uint) {
// 여기 하나 있네!
...
if (rand <= attackVictoryProbability) {
// 여기 세 개 더 있군!
myZombie.winCount = myZombie.winCount.add(1);
myZombie.level = myZombie.level.add(1);
enemyZombie.lossCount = enemyZombie.lossCount.add(1);
feedAndMultiply(_zombieId, enemyZombie.dna, "zombie");
} else {
// ...그리고 2개 더!
myZombie.lossCount = myZombie.lossCount.add(1);
enemyZombie.winCount = enemyZombie.winCount.add(1);
_triggerCooldown(myZombie);
}
...
13. 주석 (Comment)
주석에는 3종류가 있다.
한줄 주석
// 이건 주석
여러줄 주석.
/*
안녕하세요.
이건 여러줄 입니다.
*/
natspec
커뮤니티에서 사용하는 컨트랙트 설명 주석
/// @title 제목. 설명
/// @author 작성자 이름
/// @notice 사용자에게 이 컨트랙트/함수가 무엇을 하는지 알려준다.
contract Math {
// 여기부터는 함수 natspec
/// @notice 함수에_대한_설명
/// @param 인자명 인자에_대한_설명
/// @return 반환변수명 반환내용에 대한 설명
/// @dev 개발자가 읽으라고 써놓는 것
function multiply(uint x, uint y) returns (uint z) {
// 이것은 일반적인 주석으로, natspec에 포함되지 않는다.
z = x * y;
}
}
정답
...
/// TODO: natspec에 맞도록 이 부분을 바꾸게.
/// @title 좀비 소유권 전송을 관리하는 컨트랙트
/// @auther Songinho
/// @dev ERC721 표준 초안 구현을 따른다.
...
후기
사실 토큰 구현하고 safemath쓰니까 금방 끝났다.
생각보다 엄청 빨리 끝나서 어안이 벙벙하다.
이대로라면 일찍 끝나겠는걸
'개발 잡부 > 블록체인' 카테고리의 다른 글
SPDX-License-Identifier 이슈 (0) | 2022.04.01 |
---|---|
크립토 좀비 1-6. 앱 프론트엔드 & Web3.js (0) | 2022.03.31 |
크립토 좀비 1-4. 좀비 전투 시스템 (0) | 2022.03.31 |
크립토 좀비 1-3. 고급 솔리디티 개념 (0) | 2022.03.30 |
크립토 좀비 1-2. 좀비가 희생물을 공격하다. (0) | 2022.03.30 |