솔리디티 스마트 계약에서 ERC-20 토큰 전송 및 승인
지난 튜토리얼에서는 이더리움 블록체인 상의 솔리디티 ERC-20 토큰의 구조에 대해 알아보았습니다. 이 문서에서는 솔리디티 언어를 이용하여 스마트 계약으로 토큰과 상호작용하는 방법을 알아보겠습니다.
이 스마트 계약에서는 사용자가 새로 배포한 ERC-20 토큰을 이더로 교환할 수 있는 더미 탈중앙화 거래소를 만들 것입니다.
이번 튜토리얼에서는 이전 튜토리얼에서 작성한 코드를 기반으로 사용하겠습니다. DEX는 생성자에서 컨트랙트의 인스턴스를 생성하고 다음 작업을 수행합니다.
- 토큰을 이더로 교환
- 이더를 토큰으로 교환
간단한 ERC20 코드베이스를 추가하여 탈중앙화 거래소 코드를 시작하겠습니다.
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 function transfer(address receiver, uint256 numTokens) public override returns (bool) {46 require(numTokens <= balances[msg.sender]);47 balances[msg.sender] = balances[msg.sender]-numTokens;48 balances[receiver] = balances[receiver]+numTokens;49 emit Transfer(msg.sender, receiver, numTokens);50 return true;51 }5253 function approve(address delegate, uint256 numTokens) public override returns (bool) {54 allowed[msg.sender][delegate] = numTokens;55 emit Approval(msg.sender, delegate, numTokens);56 return true;57 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 balances[owner] = balances[owner]-numTokens;68 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;69 balances[buyer] = balances[buyer]+numTokens;70 emit Transfer(owner, buyer, numTokens);71 return true;72 }73}7475모두 보기새로운 DEX 스마트 계약은 ERC-20을 배포하고 토큰의 전체 공급량을 보유하게 됩니다:
1contract DEX {23 IERC20 public token;45 event Bought(uint256 amount);6 event Sold(uint256 amount);78 constructor() {9 token = new ERC20Basic();10 }1112 function buy() payable public {13 // TODO14 }1516 function sell(uint256 amount) public {17 // TODO18 }1920}모두 보기이제 DEX가 준비되었고, 모든 토큰 예비분을 보유하고 있습니다. 이 계약에는 두 가지 기능이 있습니다.
buy: 사용자는 이더를 보내고 그 대가로 토큰을 받을 수 있습니다.sell: 사용자는 토큰을 보내고 이더를 돌려받을 수 있습니다.
buy 함수
buy 함수를 코딩해 봅시다. 먼저 메시지에 포함된 이더의 양을 확인하고, 계약이 충분한 토큰을 소유하고 있는지, 그리고 메시지에 이더가 일부 포함되어 있는지 확인해야 합니다. 계약이 충분한 토큰을 소유하고 있다면 사용자에게 해당 수량의 토큰을 보내고 Bought 이벤트를 발생시킵니다.
오류가 발생하여 require 함수를 호출하는 경우 전송된 이더는 즉시 되돌려져 사용자에게 반환됩니다.
간단하게 하기 위해 1 토큰을 1 Wei와 교환합니다.
1function buy() payable public {2 uint256 amountTobuy = msg.value;3 uint256 dexBalance = token.balanceOf(address(this));4 require(amountTobuy > 0, "이더를 보내야 합니다");5 require(amountTobuy <= dexBalance, "예비 자산에 토큰이 충분하지 않습니다");6 token.transfer(msg.sender, amountTobuy);7 emit Bought(amountTobuy);8}구매가 성공하면 트랜잭션에서 토큰 Transfer와 Bought라는 두 개의 이벤트를 볼 수 있습니다.
sell 함수
판매를 담당하는 함수는 먼저 사용자가 사전에 approve 함수를 호출하여 금액을 승인하도록 요구합니다. 전송을 승인하려면 사용자가 DEX에 의해 인스턴스화된 ERC20Basic 토큰을 호출해야 합니다. 이것은 먼저 DEX 계약의 token() 함수를 호출하여 DEX가 token이라는 ERC20Basic 계약을 배포한 주소를 검색하여 수행할 수 있습니다. 그런 다음 세션에서 해당 계약의 인스턴스를 만들고 approve 함수를 호출합니다. 그런 다음 DEX의 sell 함수를 호출하여 토큰을 다시 이더로 교환할 수 있습니다. 예를 들어, 대화형 브라우니 세션에서는 다음과 같이 보입니다.
1#### Python in interactive brownie console...23# DEX 배포4dex = DEX.deploy({'from':account1})56# buy 함수를 호출하여 이더를 토큰으로 교환7# 1e18은 wei 단위의 1 이더입니다8dex.buy({'from': account2, 1e18})910# ERC20 토큰의 배포 주소 가져오기11# DEX 계약 생성 중에 배포됨12# dex.token()은 토큰의 배포 주소를 반환합니다13token = ERC20Basic.at(dex.token())1415# 토큰의 approve 함수 호출16# dex 주소를 지출자로 승인17# 그리고 지출을 허용할 토큰의 양18token.approve(dex.address, 3e18, {'from':account2})19모두 보기그런 다음 sell 함수가 호출되면 호출자 주소에서 계약 주소로의 전송이 성공했는지 확인한 다음 호출자 주소로 이더를 다시 보냅니다.
1function sell(uint256 amount) public {2 require(amount > 0, "일부 토큰이라도 판매해야 합니다");3 uint256 allowance = token.allowance(msg.sender, address(this));4 require(allowance >= amount, "토큰 허용량을 확인하세요");5 token.transferFrom(msg.sender, address(this), amount);6 payable(msg.sender).transfer(amount);7 emit Sold(amount);8}모든 것이 제대로 작동하면 트랜잭션에서 Transfer 및 Sold라는 2개의 이벤트가 표시되고 토큰 잔액과 이더 잔액이 업데이트된 것을 볼 수 있습니다.
이 튜토리얼에서는 ERC-20 토큰의 잔액과 허용량을 확인하는 방법과 인터페이스를 사용하여 ERC20 스마트 계약의 Transfer 및 TransferFrom을 호출하는 방법을 살펴보았습니다.
트랜잭션을 생성한 경우, 컨트랙트에 대한 트랜잭션이 채굴될 때까지 기다리고 세부 정보를 얻는 방법 (opens in a new tab)에 대한 JavaScript 튜토리얼이 있으며, ABI가 있다면 토큰 전송 또는 기타 이벤트로 생성된 이벤트를 디코딩하는 방법 (opens in a new tab)에 대한 튜토리얼도 있습니다.
다음은 튜토리얼의 전체 코드입니다.
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 function transfer(address receiver, uint256 numTokens) public override returns (bool) {46 require(numTokens <= balances[msg.sender]);47 balances[msg.sender] = balances[msg.sender]-numTokens;48 balances[receiver] = balances[receiver]+numTokens;49 emit Transfer(msg.sender, receiver, numTokens);50 return true;51 }5253 function approve(address delegate, uint256 numTokens) public override returns (bool) {54 allowed[msg.sender][delegate] = numTokens;55 emit Approval(msg.sender, delegate, numTokens);56 return true;57 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 balances[owner] = balances[owner]-numTokens;68 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;69 balances[buyer] = balances[buyer]+numTokens;70 emit Transfer(owner, buyer, numTokens);71 return true;72 }73}747576contract DEX {7778 event Bought(uint256 amount);79 event Sold(uint256 amount);808182 IERC20 public token;8384 constructor() {85 token = new ERC20Basic();86 }8788 function buy() payable public {89 uint256 amountTobuy = msg.value;90 uint256 dexBalance = token.balanceOf(address(this));91 require(amountTobuy > 0, "이더를 보내야 합니다");92 require(amountTobuy <= dexBalance, "예비 자산에 토큰이 충분하지 않습니다");93 token.transfer(msg.sender, amountTobuy);94 emit Bought(amountTobuy);95 }9697 function sell(uint256 amount) public {98 require(amount > 0, "일부 토큰이라도 판매해야 합니다");99 uint256 allowance = token.allowance(msg.sender, address(this));100 require(allowance >= amount, "토큰 허용량을 확인하세요");101 token.transferFrom(msg.sender, address(this), amount);102 payable(msg.sender).transfer(amount);103 emit Sold(amount);104 }105106}모두 보기페이지 마지막 업데이트됨: 2025년 8월 21일

