오늘은 솔리디티툴인 REMIX와 OPEZEPELIN을 이용하여 ERC20 표준을 따르는 토큰을 만들어 보자!
아주 쉽고도 간단하면 누구나 10초만에 코드를 만들 수 있다. 토큰을 만드는 것은 그렇게 어려운 행위가 아니며 이더리움과 여러 체인들에 많은 토큰들이 생겨 나고 있다. ERC20은 그중 EIP20에서 올라온 이더리움 개선 제안을 통해 현재는 STANDARD 즉 토큰 표준으로 사용되고 있다. 아래의 코드를 보고 간단하게 토큰을 개발하며 어떤 의미가 있는지 이해해 보자.
총 공급량은 10억 개로 설정하고, constructor 생성자에 토큰의 이름인 Snowmen과 티커명인 SNOW를 써주고 민트를 사용하여 msg.sender 즉 민트주체가 총공급량을 소유하게 한다. 이때 민트는 토큰을 주조한다, 만든다 정도로 이해하면 좋다. 아래글에 NFT 민트에 대한 실사용 사례도 있다.
이렇게 쉽게 이미 만들어진 라이브러리들과 토큰 표준을 이용하여 블록체인 네트워크 상에서 동작할 수 있는 스마트 컨트랙트 즉, 스마트한 계약을 작성함으로 우리가 만든 토큰들을 블록체인 네트워크 상에서 사용할 수 있다. 아래는 실제 ERC20 토큰표준의 EIP 개선제안으로 EIP 읽어주는 남자 세션에서 설명하겠다.
목차. 1. 동등연산자 (==, ===)로 문자열 비교 2. 문자열 크기 비교 (>. < 연산자) 3. 문자열에 어떤 문자열이 포함 되어 있는지 확인 : String.indexOf() 4. 문자열에 어떤 문자열이 포함 되어 있는지 확인 : String.includes() 5. 문자열이 어떤 문자열로 시작하거나 끝나는지 확인 : String.startsWith(), String.endsWith()
1. 동등 연산자 (==, ===) 문자열 비교
1. == Equality 비교, === Identity 비교 2. == 비교하는 객체의 타입이 달라도, 형변환하여 값이 같은면 true를 리턴 3. === 는 객체 타입이 같곡 값이 같을 때 true 리턴
2. 문자열 크기 비교 (>, < 연산자 )
1. > , < 연산자에서 문자열의 크기 비교는 ASCII 코드로 결정. 2. 문자열이 길어도 같은 위치 INDEX의 문자의 알파벳 순서가 작다면 문자열의 크기가 작다고 계산 3. 문자열 길이가 짧아서 INDEX에 문자가 없으면 더 작은 문자열 계산
3. 문자열에 어떤 문자열이 포함되어 있는지 확인 : String.indeOf()
1. str.indexOf(문자열) ! = -1 : 인자로 전달된 문자열이 안에 존재한다면 그 문자열이 위치한 Index를 리턴 2. 존재하지 않는다면, -1 리턴. -1이 리턴되는지 여부로 문자열이 포함되었는지 판단.
4. 문자열에 어떤 문자열이 포함되었는지 확인 : String.includes()
1. String.includes()를 이용하여 문자열에 어떤 문자열이 포함되었는지 확인 가능 2. 인자로 전달된 문자열이 안에 존재한다면 true가 리턴, 존재하지 않는다면 false 리턴
5. 문자열이 어떤 문자열로 시작하거나 끝나는지 확인: String.startsWith(), Sting.endWith()
1. startWith()는 문자열이 인자로 전달된 문자열로 시작할 때 true 리턴 endWith()는 그 반대.
블록체인에서 MultiCall이란 한 번의 트랜잭션으로 여러 스마트 컨트랙트 함수를 호출하여 블록체인 네트워크에 효율적으로 처리할 수 있는 방법을 제공하는 것이다. 이와 같이 블록체인 지갑등에서 자신의 잔고를 확인하거나 체크하는 용도로 많이 쓰이고 있다. 아래 멀티콜 예제를 보면 하나하나 어떤 내용이 있는지 확인해보자.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
}
interface Multicall {
function aggregate(
call[] calldata calls
) external returns (uint256 blockNumber, bytes[] memory returnData);
}
struct call {
address target;
bytes callData;
}
contract UniswapMulticallExample {
address public uniswapRouter; // Uniswap Router contract address
address[] public tokens; // ERC-20 token addresses
constructor(address _uniswapRouter, address[] memory _tokens) {
uniswapRouter = _uniswapRouter;
tokens = _tokens;
}
function getBalances(address user) external view returns (uint256[] memory balances) {
balances = new uint256[](tokens.length);
for (uint256 i = 0; i < tokens.length; i++) {
// Encode the balanceOf function call for the ERC-20 token
bytes memory data = abi.encodeWithSelector(IERC20(tokens[i]).balanceOf.selector, user);
// Add the call to the Multicall data
Multicall.call memory tokenBalanceCall = Multicall.call({
target: tokens[i],
callData: data
});
// Aggregate the results in a single call to Multicall
(, bytes[] memory results) = Multicall(uniswapRouter).aggregate([tokenBalanceCall]);
// Decode the result and store the balance
balances[i] = abi.decode(results[0], (uint256));
}
return balances;
}
}
코드 해석
SPDX-License-Identifier: 컨트랙트의 라이선스를 지정하는 부분
pragma solidity ^0.8.0;: Solidity 컴파일러 버전을 지정
IERC20 인터페이스: ERC-20 토큰 표준을 따르는 balanceOf 함수를 정의한 인터페이스
Multicall 인터페이스: aggregate 함수를 정의한 인터페이스로, 여러 호출을 집계하여 실행
function aggregate가 여러 call들을 집계하여 배열형태로 한 번에 처
call 구조체: 호출할 대상 주소와 호출할 데이터를 담는 구조체
UniswapMulticallExample 컨트랙트: ERC-20 토큰의 잔액을 가져오는 기능을 제공하는 컨트랙트
uniswapRouter 및 tokens 변수: Uniswap 라우터 주소와 ERC-20 토큰 주소 배열을 저장
constructor: 컨트랙트를 배포할 때 Uniswap 라우터 주소와 ERC-20 토큰 주소 배열을 초기화
getBalances 함수: 사용자의 주소를 받아와 각 ERC-20 토큰의 잔액을 조회하는 함수
for 루프: tokens 배열의 각 토큰에 대해 잔액을 조회
abi.encodeWithSelector: balanceOf 함수를 호출하기 위해 함수 시그니처와 사용자 주소를 인코딩
**Multicall**을 사용하여 여러 호출을 집계하고 결과를 얻음
abi.decode: 결과를 해석하여 해당 토큰의 잔액을 가져옴
return balances;: 각 토큰의 잔액을 반환
부록
view 또는 pure 키워드를 사용하면 함수가 스마트 계약의 상태를 변경하지 않고 데이터를 읽기만 하는 데 사용됨을 나타냄. 이러한 함수는 외부로부터 호출되어도 블록체인의 상태를 변경하지 않으므로 가스 비용이 들지 않는다. 이러한 함수의 실행은 로컬 노드에서 처리되며, 블록체인 트랜잭션을 생성하지 않으므로 가스비용이 발생하지 않는다.
view 및 pure 함수는 블록체인의 무언가를 기록하거나 변경하지 않고 단순히 데이터를 반환하는 데 사용된다. 이 경우 getBalances 함수가 external view returns를 사용하기 때문에 블록체인 상태를 변경하지 않고 사용자의 잔액을 읽기만 하므로 호출 시 가스비용이 발생하지 않는다.
위와 같이 코드들의 함수를 살펴보면서 각각의 함수들이 어떠한 기능들을 하는지 알아보았다. 멀티콜의 경우 스왑을 하거나 나의 잔고를 모두 가져올 때 많이 사용될 것 같다는 생각이 들었다. 멀티콜 기능을 통해 다음에는 유니스왑 v2를 구현해 보도록 하겠다.