https://cryptozombies.io/ko/course
위 사이트의 내용을 요약하였습니다.
: 이더리움에서 토큰은 기본적으로 그저 몇몇 공통 규약을 따르는 스마트 컨트랙트이다. 즉 다른 모든 토큰 컨트랙트가 사용하는 표준 함수 집합을 구현하는 것이다.
: 아래와 같은 함수들을 가진다.transfer(address _to, uint256 _value) balanceOf(address _owner)
: 요약하자면, 토큰은 하나의 컨트랙트이고, 몇몇 함수로 전송 등이 가능하게 한다.
- 종류
- ERC20 토큰은 화폐로 적절.
- ERC721 토큰은 각각의 토큰이 유일하고 분할이 불가능. 주로 게임에 쓰임 Ex) 크립토키티
: ERC271의 표준은 아직 정확히 정의되지 않았다.
: 이에 참고를 위해 ERC20 표준을 가져와 보았다.
// Grabbed from: https://github.com/ethereum/EIPs/issues/20
contract ERC20 {
function totalSupply() constant returns (uint theTotalSupply);
function balanceOf(address _owner) constant returns (uint balance);
function transfer(address _to, uint _value) returns (bool success);
function transferFrom(address _from, address _to, uint _value) returns (bool success);
function approve(address _spender, uint _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint remaining);
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}
: 그 외에 상속의 경우, contract A is B, C등 두개가 상속이 가능하다.
: address를 받아 그 address가 가진 토큰을 반환
: 토큰 ID를 받아 소유하는 사람의 address를 반환
: 제어자와 함수의 이름은 겹칠 수 없어서, 변경해줘야 한다.
: 한 사람이 다른 사람에게 소유권을 넘기는 것을 구현하는데 2가지 방식이 있음
- 토큰의 소유자가 전송 상대의 address, 전송하고자 하는 _tokenId와 함께 transfer 함수를 호출하는 것이다.
- 토큰의 소유자가 먼저 전송 상대의 address, 전송하고자 하는 _tokenId와 함께 approve를 호출한다. 컨트랙트에 누가 해당 토큰을 가질 수 있도록 허가를 받았는지 저장한다(보통 mapping (uint256 => address을 사용함). 이후 누군가 takeOwnership을 호출하면, 해당 컨트랙트는 이 msg.sender가 소유자로부터 토큰을 받을 수 있게 허가를 받았는지 확인하고, 그리고 허가를 받았다면 해당 토큰을 전송한다.
이는 동일한 전송로직이지만, 순서만 반대인 것이다. 전자는 보내는 사람이 함수를 호출하고, 후자는 받는 사람이 호출한다.
*mapping이란? 제공된 key를 가지고 value를 얻어내는 것
: 여기서는 아래와 같이 두 개의 방식에서 모두 쓰일수 있는 _transfer함수를 사용하였습니다.
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to]++;
ownerZombieCount[_from]--;
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
: 앞서 봤듯이 approve / takeOwnership을 사용하는 전송은 2단계로 나뉜다.
먼저, 오버플로우와 언더플로우 개념은 다음과 같다.
1. 오버플로우 : 변수보다 큰 수를 저장할 경우
2. 언더플로우 : 변수보다 작은 수를 저장할 경우
이를 방지하기 위하여 SafeMath 라이브러리를 사용함(단위는 uint256).
using SafeMath for uint
: add, sub, mul, div 4가지가 있음.
uint test = 2; // 첫번째 인수는 자동으로 전달
test = test.mul(3); // test *= 3
test = test.add(5); // test += 5
: uint32와 uint16을 혼용할 경우, 오버플로우를 막아주지 못하는 문제점 텍스트(기본적인 연산의 단위가 uint256이기 때문)
: 이에 오버플로우/언더플로우를 막기 위해 라이브러리를 각각 새로 만들어야 함.
: 아래에는 uint32를 단위로 계산할 경우의 예시
library SafeMath32 {
function mul(uint32 a, uint32 b) internal pure returns (uint32) {
if (a == 0) {
return 0;
}
uint32 c = a * b;
assert(c / a == b);
return c;
}
function div(uint32 a, uint32 b) internal pure returns (uint32) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint32 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint32 a, uint32 b) internal pure returns (uint32) {
assert(b <= a);
return a - b;
}
function add(uint32 a, uint32 b) internal pure returns (uint32) {
uint32 c = a + b;
assert(c >= a);
return c;
}
}
natspec
: 솔리디티 커뮤니티에서 주석을 사용할때 표준으로 쓰이는 형식
: @notice는 사용자에게 컨트랙트/함수가 무엇을 하는지 설명
: @dev는 개발자에게 추가적인 상세 정보를 설명, 웬만하면 남기면 좋음
: @param과 @return은 함수의 매개 변수와 반환값
// , /**/ 한줄, 여러줄