transfer(address _to, uint256 _value)
,balanceOf(address _owner)
,mapping(address => uint256) balances
❓ 이러한 ERC20
과 같은 토큰을 사용해야 하는 이유?
ERC20 토큰들이 똑같은 이름의 동일한 함수 집합을 공유하기 때문에, 토큰들 간의 서로 상호작용이 가능하다.
즉, 하나의 ERC20 토큰과 상호작용하는 애플리케이션이 있다면, 또다른 ERC20 토큰과도 상호작용할 수 있다.
따라서, 커스텀 코드를 추가하지 않고도 나의 앱에 여러 토큰을 쉽게 추가할 수 있다.
ERC20
: 화폐처럼 사용되는 토큰에 적절
ERC721
: 크립토 수짐품에 적절한 토큰 표준
이러한 ERC721 표준을 사용하면, 컨트랙트에서 사용자들이 쉽품을 거래/판매할 수 있도록하는 로직을 직접 구현하지 않아도 된다.
🔽 ERC721 표준
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.sol
erc721.sol
을 나의 컨트랙트 파일로 import 해온 뒤, 상속하는 컨트랙트를 생성한다. function balanceOf(address _owner) public view returns (uint256 _balance);
function ownerOf(uint256 _tokenId) public view returns (address _owner);
토큰 ID를 받아, 소유자의 address를 반환
기존 컨트랙트에서 위의 것들을 mapping
으로 미리 정의했다면, 구현은 return문 하나만으로 가능함
ex)
기존 : mapping (uint => address) public zombieToOwner;
구현 : return zombieToOwner[_tokenId];
function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
approve
를 호출하여, 토큰 수신 허가자를 지정한다.mapping
으로 저장한다. function takeOwnership(uint256 _tokenId) public;
takeOwnership
을 호출하면, 컨트랙트는 msg.sender
가 토큰을 받기로 허가받은 주소인지 확인한 후, 토큰을 전송한다. 즉,
transfer
는 토큰 송신자가 호출,takeOwnership
은 토큰 수신자가 호출하는 함수.
두 함수의 로직은 결국 동일하기 때문에, 구체화시킬 공통 함수를 private으로 따로 정의하는 것이 좋다.
토큰의 소유자를 바꿀 두번 째 방식은
1. 소유자가 approve
호출로 토큰 소유 허가권을 주는 것,
2. 수신자가 takeOwnership
으로 소유권을 가져오는 것
이렇게 두번의 함수 호출로 발생 되기 때문에,
그 사이에 허가 여부를 저장할 데이터 구조가 필요하다.
-> mapping (uint => address)
를 사용하여 토큰id
의 허가된 주소 address
매핑을 관리한다.
takeOwnership
의 로직은
1. msg.sender
가 토큰을 소유할 허가권이 있는지 확인하고,
2. 있다면 _transfer
를 호출한다.
여기서 _transfer
는, 토큰 전송의 두가지 공통 로직을 별도로 구현해놓은 private 함수이다.
function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerZombieCount[_to]++;
ownerZombieCount[_from]--;
zombieToOwner[_tokenId] = _to;
Transfer(_from, _to, _tokenId);
}
스마트 컨트랙트를 작성할 때 인지할 주요한 보안 기능
❓ 오버플로우란?
uint8 number = 255;
일 때,
number++;
을 해버리면,
number는 256이 아닌 0이 되어버린다.
-> number 변수가 8bit
짜리 변수라서, 0~255까지만 담을 수 있기 때문이다.
이런 경우, overflow
가 발생한 것이다.
❓ 언더플로우란?
uint8 number = 0;
일 때,
number--;
을 해버리면,
number는 -1이 아닌 255가 되어버린다.
-> uint
는 부호가 없기 때문에, 음수가 될 수 없다.
이런 경우, underflow
가 발생한 것이다.
솔리디티의 라이브러리는 특별한 컨트랙트 역할을 한다!
🔊기본(native) 데이터 타입에 함수를 붙일 수 있음
사용법 : using SafeMath for uint256
구문을 추가한다.
SafeMath 라이브러리의 4개 함수
using SafeMath for uint256;
uint256 a = 5;
uint256 b = a.add(3); // 5 + 3 = 8
uint256 c = a.mul(2); // 5 * 2 = 10
🤍 라이브러리를 사용하는 법
1. 라이브러리 코드를 복붙한 .sol 파일을 생성한다.
2. 라이브러리를 사용할 파일에 해당 파일을 import 한다.
3. using SafeMath for uint256;
을 선언한다.
contract
가 아닌 library
키워드로 시작된다. using
키워드를 통해 라이브러리의 메소드를 데이터 타입에 적용할 수 있음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 SafeMath for uint;
// 우리는 이제 이 메소드들을 아무 uint에서나 쓸 수 있네.
uint test = 2;
test = test.mul(3); // test는 이제 6이 되네
test = test.add(5); // test는 이제 11이 되네
❓ 그렇다면 얘네가 어떻게 오버플로우를 막아주는가?
add
함수를 보면 assert(c >= a)
가 있는데, 얘는 합한 값이 a보다 커야한다는 조건이다. 즉, 오버플로우가 발생하여 합을 했음에도 결과가 작아지는 것을 막는 방법이다.
require
: 함수 실행이 실패하면 남은 가스를 유저에게 돌려줌.assert
: 유저에게 안돌려줌. -> 코드가 잘못 실행되는 경우에 사용함. (like overflow)즉! SafeMath의 네 종류의 함수는 사칙연산 시, 오버플로우/언더플로우가 발생하면 오류를 발생시켜준다.
(새로 배운 내용 없음)
(새로 배운 내용 없음)
//
/* */
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;
}
}
@title
, @author
: 말그대로.@notice
: 컨트랙트/함수가 하는 일 설명. @dev
: 개발자에게 상세 정보 설명@param
, @return
: 함수의 매개 변수와 반환값 설명