슬슬 느껴져, 나도 솔리디티 전문가??
이더리움에서 토큰은 그저 공통 규약을 따르는 스마트 컨트랙트
모든 컨트랙트가 사용한느 표준 함수 집합을 구현하는 것. (transfer, balanceOf)
즉 토큰 = 컨트랙트, 그 안에서 누가 얼마나 가지는지, 전송하게 해주는 것 뿐
모든 ERC20 토큰이 똑같은 이름의 동일한 함수들을 공유하기에, 서로 상호작용 가능
하나의 토큰과 상호작용하는 어플을 만들면, 다른 모든 ERC20토큰을 추가할 수 있음 (거래소처럼)
ERC20은 화폐처럼 사용하는 토큰에 제격, 그러나 좀비는 쪼갤 수 없고, 각자 고유한 특성이 있지
ERC721은 교체불가, 각각이 유일하고 분할 불가.
표준을 사용하면 거래/판매 같은 로직을 구현 안해도, 누군가 ERC721 자산을 거래할 수 있는 플랫폼 만들면 알아서 쓰일 거여
pragma solidity ^0.4.19;
import "./zombieattack.sol";
contract ZombieOwnership is ZombieAttack {}
contract ZombieOwnership is ZombieAttack, ERC721 {
}
function balanceOf(address _owner) public view returns (uint256 _balance);
그냥 address가 토큰 얼마나 가지고 있는지
function ownerOf(uint256 _tokenId) public view returns (address _owner);
토큰 ID의 주인이 누구인지
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];
우린 이미 ownerOf 라는 modifier를 만들고 쓰고 있었으니 충돌이 날거다.
뭘 바꿔줘야 할까 ? 당연히 표준은 냅둬야지, 다른 컨트랙트는 다 저거 쓰는 줄 알텐데.
modifier를 onlyOwnerof로 고치고, 이거 쓴 거 다 찾아서 이름 바꿔줘라.
ERC721에는 토큰을 전송할 때 2가지 방식이 있음
누가 호출하냐 차이이지, 전송 로직은 같음
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) {
// 2. 여기서 함수를 정의하게.
_transfer(msg.sender, _to, _tokenId);
}
approve / takeOwnership 전송은 2 step
mapping (uint => address) zombieApprovals;
// 2. 여기에 함수 제어자를 추가하게.
function approve(address _to, uint256 _tokenId) public onlyOwnerof( _tokenId) {
// 3. 여기서 함수를 정의하게.
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);
}
uint256은 2^256 -1 까지 가겠지만, 만약 게임을 진행하다 저것보다 커진다면 ? 0이 될것이다(마치 23:59분에서 00:00처럼)
이런 문제를 막기위해 OpenZeppelin에서 라이브러리를 만들어줌
Library는 솔리디티에서 특별한 종류의 컨트랙트.
기본(native) 데이터 타입에 함수를 붙일 때 유용하게 사용됨
add / sub / mul / div
using SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3) // 5 + 3 = 8
요런식으로 쓸 수 있음.
import "./safeMath.sol";
contraact !@$#$%{
using SafeMath for uint256;
}
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;
}
}
라이브러리는 컨트랙트와 비슷하지만, using 키워드를 써서 다른 데이터 타입에 메서드를 사용할 수 있게 함
using SafeMath for uint;
// 우리는 이제 이 메소드들을 아무 uint에서나 쓸 수 있네.
uint test = 2;
test = test.mul(3); // test는 이제 6이 되네
test = test.add(5); // test는 이제 11이 되네
본래 mul, add의 인자는 2개지만, 메서드 형식으로 사용하면 test가 인자 1에 자동으로 들어감
assert를 사용하면서 에러 안나게 보장을 해주는 역할이지
assert는 require과 비슷하지만, require는 함수 실행이 실패하면 가스를 돌려주고, assert는 안 돌려줌
assert는 일반적으로 코드가 심각하게 잘못됬을 때 사용
function _transfer(address _from, address _to, uint256 _tokenId) private {
// 1. SafeMath의 `add`로 교체하게.
ownerZombieCount[_to] = ownerZombieCount[_to].add(1);
// 2. SafeMath의 `sub`로 교체하게.
ownerZombieCount[_from] = ownerZombieCount[_from].div(1);
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
일반적으로 수학 연산을 ++ 이런거 쓰지말고 앵간하면 safeMath를 쓰는게 좋다.
근데 만약 uint16,uint32에 적용하면???
메서드를 사용하면 uint256으로 바꿀 것이고, 그럼 우리가 세팅한 범위가 아무 쓸모 없어지겠지
그러니 얘네들 용으로 라이브러리를 따로 만들어야겠지 (다 똑같은데 타입만 다르게)
using SafeMath for uint256;
using SafeMath32 for uint32;
using SafeMath16 for uint16;
// 1. using SafeMath32 for uint32를 선언하게.
// 2. using SafeMath16 for uint16를 선언하게.
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(1);
컨트랙트의 모든 함수에서 예상되는 행동값을 주석으로 설명하는 것이 필요
다른 개발자들이 코드를 안 훑고 주석만 읽더라도 큰 맥락으로 이해를 할 수 있기 때문
솔리디티 커뮤니티의 표준은 natspec이라 불림
/// @title : 좀비 소유권 전송을 관리하는 컨트랙트
/// @author : Young Joo
/// @dev : OpenZeppelin의 ERC721 표준 초안 구현을 따른다
@notice는 사용자에게 컨트랙트, 함수가 무엇을 하는지
@dev는 개발자에게 추가적인 상세 정보를 설명
@param과 @return은 함수에서 어떤 매개 변수와 반환값을 가지는지
모든 함수에 대해 꼭 이 모든 태그들을 항상 써야만 하는 것은 아니다
모든 태그는 필수가 아니지만,
최소한 각각의 함수가 어떤 것을 하는지 설명하도록 @dev는 써라.