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.solerc721.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 : 함수의 매개 변수와 반환값 설명