BTC
ETH
HTX
SOL
BNB
시장 동향 보기
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

하나의 기사에서 자신의 ERC20 토큰 지불 스플리터를 만드는 방법에 대해 알아보십시오.

星球君的朋友们
Odaily资深作者
이 기사는 약 11750자로, 전체를 읽는 데 약 17분이 소요됩니다
오늘은 모든 프로젝트에 통합할 수 있는 자체 ERC20 토큰 지불 스플리터를 만드는 방법을 배웁니다!
AI 요약
펼치기
오늘은 모든 프로젝트에 통합할 수 있는 자체 ERC20 토큰 지불 스플리터를 만드는 방법을 배웁니다!

결제는 거의 모든 암호화폐 영역에서 반복되는 주제이며, 특히 여러 스테이커에게 결제를 제공합니다. 예를 들어, DAO는 여러 이니셔티브에 자금을 지원하고 싶고, DEX는 합병하여 특정 참여자에게 거래 수수료를 분배하고 싶거나, 팀은 월급으로 팀 구성원에게 토큰을 분배하려고 합니다.

첫 번째 레벨 제목

오늘은 모든 프로젝트에 통합할 수 있는 자체 ERC20 토큰 지불 스플리터를 만드는 방법을 배웁니다!

전제 조건 및 설정

보조 제목

프로젝트 구조

우리는 두 개의 계약서를 작성할 것입니다. 첫 번째는 ERC20 토큰 지불 분할 스마트 계약이고 두 번째는 모의 풀 스마트 계약입니다. ERC20 토큰 지불 스플리터 스마트 계약은 추상적이며 발신인과 지불인 및 각각의 지불 부분을 관리하기 위한 논리와 데이터를 보유합니다. 모의 풀은 ERC20 토큰 지불 스플리터를 상속하여 여러 스테이커에게 지불을 자동으로 분배할 수 있습니다. 두 계약에서 결제 기능을 분할하는 데에는 두 가지 이유가 있습니다.

  • 실제 사용 사례에서 토큰 지불 분할 계약의 사용 시연

  • 누구나 자신의 프로젝트를 선택하고 통합할 수 있도록 토큰 지불 분할 계약이 충분히 유연한지 확인하십시오.

보조 제목

개발 환경 설정

이 튜토리얼의 도구:

  • Hardhat - 스마트 계약 개발 환경

  • OpenZeppelin - 감사된 스마트 계약 템플릿

이제 빈 디렉터리에서 NPM init -y를 사용하여 NPM 프로젝트를 시작합니다.

프로젝트를 설정한 후 다음 명령을 사용하여 Hardhat을 설치합니다.

Hardhat을 설치한 후 npx Hardhat을 입력하고 옵션을 선택하여 기본 예제 프로젝트를 생성합니다. 여기에는 자신의 계약을 쉽게 생성, 테스트 및 배포할 수 있는 편리한 파일 구조가 포함됩니다.

기본 예제 프로젝트를 생성하도록 선택

contract 폴더에서 Greeter.sol 파일을 삭제하고 test 폴더에서 sample-test.js 파일을 삭제할 수 있습니다.

Hardhat 플러그인인 Hardhat 플러그인 라이브러리도 설치합니다. 이를 통해 스마트 계약을 테스트하고 배포하기 위한 도구를 추가할 수 있습니다.

hardhat.config.js 파일 상단에 다음을 추가합니다.

스마트 계약을 테스트하려면 chai라는 패키지를 설치해야 합니다.

보조 제목

토큰 결제 스플리터 생성

이 토큰 지불 분할 스마트 계약은 수취인 목록 및 각 수취인의 공유와 관련된 데이터를 설정하고 저장하는 논리를 제공합니다. 각 수취인은 수령해야 하는 자금의 비율과 동일한 수의 주식을 보유하고 있습니다(예: 4명의 수취인이 각각 5주를 보유하고 있는 경우 각각 지불금의 25%를 받습니다).

이 계약을 시작하기 위해 계약 폴더에 새 파일을 만들고 이름을 TokenPaymentSplitter.sol로 지정합니다.

pragma 라인 및 계약 셸을 설정합니다.

pragma solidity ^0.8.0;
abstract contract TokenPaymentSplitter {

}

이것은 추상적 계약이므로 나중에 시뮬레이션된 풀 계약으로 가져올 것입니다. 또한 추상화하면 이 계약을 향후 다른 실제 프로젝트로 쉽게 가져올 수 있습니다.

이제 OpenZeppelin에서 유용한 도구를 가져오겠습니다.

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
abstract contract TokenPaymentSplitter {
   using SafeERC20 for IERC20;

}

SafeERC20.sol은 ERC20 스마트 계약에서 표준 기능을 호출하고 이러한 호출을 추가 기능으로 래핑하여 보다 안전한 토큰 전송 방법을 제공할 수 있는 ERC20 인터페이스를 제공합니다.

이제 계약 데이터를 저장할 변수를 생성합니다.

abstract contract TokenPaymentSplitter {
   using SafeERC20 for IERC20;
   
   address internal paymentToken;
   uint256 internal _totalShares;
   uint256 internal _totalTokenReleased;
   address[] internal _payees;
   mapping(address => uint256) internal _shares;
   mapping(address => uint256) internal _tokenReleased;
   

}

paymentToken은 결제에 사용하는 ERC20 토큰의 주소입니다.

_totalShares는 모든 수취인의 주식 합계를 제공합니다.

_totalTokenReleased는 모든 수취인에게 지불된 총 지불 토큰입니다.

_payees는 모든 현재 수취인 주소의 배열을 제공합니다.

_shares는 할당된 공유 수에 대한 수신자 주소의 매핑입니다.

_tokenReleased는 수신자 주소와 결제 토큰 금액의 매핑입니다.

이제 세 개의 매개변수를 허용하는 생성자를 배치합니다. 첫 번째 매개변수는 계약 배포 시 초기화하려는 수취인의 배열입니다. 두 번째 매개변수는 각 수취인의 주식 배열입니다. 세 번째는 결제에 사용될 ERC20 토큰의 주소입니다.

pragma solidity 0.8.0
constructor(
   address[] memory payees,
   uint256[] memory shares_,
   address _paymentToken
) {
   require(
       payees.length == shares_.length,
       "TokenPaymentSplitter: payees and shares length mismatch"
   );
   require(payees.length > 0, "TokenPaymentSplitter: no payees");
   for (uint256 i = 0; i < payees.length; i++) {
       _addPayee(payees[i], shares_[i]);
   }
   paymentToken = _paymentToken;

}

생성자에는 각 수취인이 할당된 몫을 갖도록 두 어레이의 길이가 동일한지 확인하는 require 문이 포함되어 있습니다. 적어도 한 명의 수취인과 계약이 초기화되도록 보장하는 또 다른 require 문이 있습니다.

위에서 만든 변수에 각 수취인과 그들의 몫을 할당하는 for 루프도 있습니다. 이것은 곧 생성할 _addPayee라는 함수로 수행됩니다.

생성자가 배치되면 계약 변수를 호출하고 가져오는 함수를 몇 개 더 추가합니다.

pragma solidity 0.8.0
function totalShares() public view returns (uint256) {
   return _totalShares;
}
function shares(address account) public view returns (uint256) {
   return _shares[account];
}
function payee(uint256 index) public view returns (address) {
   return _payees[index];

}

이제 수취인 추가 기능을 만들어 보겠습니다.

pragma solidity 0.8.0;
function _addPayee(address account, uint256 shares_) internal {
   require(
       account != address(0),
       "TokenPaymentSplitter: account is the zero address"
   );
   require(shares_ > 0, "TokenPaymentSplitter: shares are 0");
   require(
       _shares[account] == 0,
       "TokenPaymentSplitter: account already has shares"
   );
   _payees.push(account);
   _shares[account] = shares_;
   _totalShares = _totalShares + shares_;

}

_addPayee는 수취인 배열을 설정하기 위해 생성자에서 호출하는 함수입니다. 이 함수는 두 개의 매개변수, 수취인의 계정 및 이와 관련된 주식 수를 사용합니다. 그런 다음 계정에 주소가 0인지, 지분이 0보다 큰지, 계정이 이미 수취인으로 등록되어 있는지 확인합니다. 모든 검사가 통과되면 해당 변수에 데이터를 추가합니다.

이제 수취인에게 토큰 배포를 지원하는 기능을 추가해 보겠습니다.

pragma solidity 0.8.0;
function release(address account) public virtual {
   require(
       _shares[account] > 0, "TokenPaymentSplitter: account has no shares"
   );
   
   uint256 tokenTotalReceived = IERC20(paymentToken).balanceOf(address(this)) + _totalTokenReleased;
   
   uint256 payment = (tokenTotalReceived * _shares[account]) / _totalShares - _tokenReleased[account];
   
   require(payment != 0, "TokenPaymentSplitter: account is not due payment");
   _tokenReleased[account] = _tokenReleased[account] + payment;
   _totalTokenReleased = _totalTokenReleased + payment;
   IERC20(paymentToken).safeTransfer(account, payment);
}

해제는 기존 수취인 계정의 인수를 취하는 누구나 호출할 수 있는 기능입니다. 이 함수에서 무슨 일이 일어나는지 분석해 봅시다. 먼저 계정에 할당된 주식이 있는지 확인합니다. 그런 다음 이전에 릴리스된 총 토큰 수에 계약의 현재 토큰 잔액을 추가하는 tokenTotalReceived라는 변수를 생성합니다. 수령한 총 토큰 금액 중 계정에 지불해야 할 금액을 결정하는 지불이라는 또 다른 변수를 만든 다음 계정으로 릴리스된 금액을 뺍니다. 그런 다음 require 문은 현재 지불 금액이 0보다 큰지 확인합니다(즉, 현재 지불해야 할 토큰이 더 많습니다). 검사에 통과하면 계정의 tokenReleased를 업데이트하고 totalTokenReleased를 업데이트합니다. 마지막으로 계정에 지불된 토큰 금액이 이체됩니다.

이제 기능이 제자리에 있습니다! 하지만 이 계약에는 할 일이 하나 더 있습니다... 이벤트!

계약에 두 개의 이벤트를 추가할 것입니다. 계약 상단에 이벤트를 추가하는 것이 좋습니다.

pragma solidity 0.8.0;
event PayeeAdded(address account, uint256 shares);

event PaymentReleased(address to, uint256 amount);

이러한 이벤트가 계약에 포함되면 적절한 함수에서 이벤트를 내보냅니다.

pragma solidity 0.8.0;
function _addPayee(address account, uint256 shares_) internal {
   ///existingFunctionCode
   emit PayeeAdded(account, shares_);
}
function release(address account) public virtual {
   ///existingFunctionCode
   emit PaymentReleased(account, payment);

}

보조 제목

시뮬레이션 풀 계약 생성

이 계약은 토큰 지불 스플리터를 통합하는 방법을 보여주기를 원하기 때문에 그리 복잡하지 않을 것입니다. 이 계약은 수취인 목록에 배포하려는 특정 ERC20 토큰을 주기적으로 받습니다. 이 ERC20 토큰은 사용자 보증금 또는 다른 스마트 계약에서 리디렉션된 수수료와 같은 다양한 시나리오를 통해 도착할 수 있습니다. 실생활에서는 프로젝트에 따라 사용자의 사용 사례를 충족하기 위해 더 많은 기능을 포함하는 더 복잡한 계약이 있을 수 있습니다.

계약 폴더에서 MockPool.sol이라는 새 파일을 만듭니다. 그런 다음 다음 코드를 추가합니다.

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./TokenPaymentSplitter.sol";
contract MockPool is Ownable, TokenPaymentSplitter {
   using SafeERC20 for IERC20;
   constructor(
       address[] memory _payees,
       uint256[] memory _shares,
       address _paymentToken
   ) TokenPaymentSplitter(_payees, _shares, _paymentToken) {}
   
   function drainTo(address _transferTo, address _token) public onlyOwner {
       require(
       _token != paymentToken,
       "MockPool: Token to drain is PaymentToken"
       );
       uint256 balance = IERC20(_token).balanceOf(address(this));
       require(balance > 0, "MockPool: Token to drain balance is 0");
       IERC20(_token).safeTransfer(_transferTo, balance);
   }

}

이 계약에서 세 가지를 가져옵니다. 첫 번째는 일부 기능에서 고유한 onlyOwner 수정자를 사용하는 OpenZeppelin의 Ownable 유틸리티입니다. 두 번째는 계약에서 볼 수 있듯이 안전한 ERC20 토큰 전송을 허용하는 SafeERC20입니다. 세 번째는 TokenPaymentSplitter 계약입니다.

MockPool 생성자에서 TokenPaymentSplitter의 동일한 세 매개변수가 필요하며 상속된 계약에 전달하기만 하면 됩니다.

보조 제목

테스트 계약

스마트 계약을 테스트하는 것은 스마트 계약을 생성하는 것만큼 중요합니다. 이러한 계약에서 다루는 자산은 일반적으로 다른 사람의 소유이므로 해당 자산이 예상대로 작동하는지 확인하고 테스트가 거의 모든 극단적인 경우를 다루도록 하는 것은 개발자로서 우리의 책임입니다.

여기에서 수행될 테스트는 TokenPaymentSplitter 스마트 계약이 예상대로 작동하는지 보여주는 몇 가지 예입니다. 자신의 프로젝트에서 작업할 때 자신의 사용 사례에 맞게 특별히 조정된 테스트를 만들고 싶을 수 있습니다.

테스트를 지원하기 위해 ERC20 토큰을 포함하고자 합니다. 이를 위해 테스트에 사용할 OpenZepplin ERC20 템플릿을 가져오는 새로운 견고성 파일을 생성합니다. 계약 폴더에서 Imports.sol이라는 새 파일을 만들고 다음 코드를 포함합니다.

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol";

contract Imports {}

이제 test 폴더에 test.js라는 파일을 만듭니다. 이 파일의 맨 위에서 테스트를 지원하는 패키지를 가져옵니다.

const { expect } = require('chai')

const { ethers } = require('hardhat')

이제 테스트를 설정하기 위해 먼저 필요한 변수를 만들고 각 테스트 전에 호출되는 beforeEach 함수를 만들고 곧 테스트를 포함할 빈 설명 함수를 만듭니다.

describe('TokenPaymentSplitter Tests', () => {
let deployer
let account1
let account2
let account3
let account4
let testPaymentToken
let mockPool
beforeEach(async () => {
   [deployer, account1, account2, account3, account4] = await ethers.getSigners()
   const TestPaymentToken = await ethers.getContractFactory('ERC20PresetMinterPauser')
   testPaymentToken = await TestPaymentToken.deploy('TestPaymentToken', 'TPT')
   await testPaymentToken.deployed()
})
 
describe('Add payees with varying amounts and distribute payments', async () => {}

}

첫 번째 레벨 제목

지불 토큰은 여러 수취인에게 균등하게 분배됩니다.

첫 번째 테스트에서 균등하게 분배된 수취인 목록이 포함된 계약을 배포할 때 어떤 일이 발생하는지 확인하고 싶었습니다. 아래는 테스트 코드입니다.

it('payment token is distributed evenly to multiple payees', async () => {
   payeeAddressArray = [account1.address, account2.address, account3.address, account4.address]
   payeeShareArray = [10, 10, 10, 10]
   const MockPool = await ethers.getContractFactory('MockPool')
   mockPool = await MockPool.deploy(
       payeeAddressArray,
       payeeShareArray,
       testPaymentToken.address
   )
   await mockPool.deployed()
   await testPaymentToken.mint(mockPool.address, 100000)
   await mockPool
       .connect(account1)
       .release(account1.address)
   await mockPool
       .connect(account2)
       .release(account2.address)
   await mockPool
       .connect(account3)
       .release(account3.address)
   await mockPool
       .connect(account4)
       .release(account4.address)
   const account1TokenBalance = await testPaymentToken.balanceOf(account1.address)
   const account2TokenBalance = await testPaymentToken.balanceOf(account2.address)
   const account3TokenBalance = await testPaymentToken.balanceOf(account3.address)
   const account4TokenBalance = await testPaymentToken.balanceOf(account4.address)
   expect(account1TokenBalance).to.equal(25000)
   expect(account2TokenBalance).to.equal(25000)
   expect(account3TokenBalance).to.equal(25000)
   expect(account4TokenBalance).to.equal(25000)

})

첫 번째 레벨 제목

지불 토큰은 여러 수취인에게 고르지 않게 분배됩니다.

두 번째 테스트에서는 각 수신자의 몫이 고르지 않게 분배되더라도 수학이 작동하는지 확인하려고 합니다.

it('payment token is distributed unevenly to multiple payees', async () => {
   payeeAddressArray = [account1.address, account2.address, account3.address, account4.address]
   payeeShareArray = [10, 5, 11, 7]
   const MockPool = await ethers.getContractFactory('MockPool')
   mockPool = await MockPool.deploy(
       payeeAddressArray,
       payeeShareArray,
       testPaymentToken.address
   )
   await mockPool.deployed()
   await testPaymentToken.mint(mockPool.address, 100000)
   await mockPool
       .connect(account1)
       .release(account1.address)
   await mockPool
       .connect(account2)
       .release(account2.address)
   await mockPool
       .connect(account3)
       .release(account3.address)
   await mockPool
       .connect(account4)
       .release(account4.address)
   const mockPoolTestPaymentTokenBalance = await testPaymentToken.balanceOf(
       mockPool.address
   )
   const account1TokenBalance = await testPaymentToken.balanceOf(account1.address)
   const account2TokenBalance = await testPaymentToken.balanceOf(account2.address)
   const account3TokenBalance = await testPaymentToken.balanceOf(account3.address)
   const account4TokenBalance = await testPaymentToken.balanceOf(account4.address)
   expect(mockPoolTestPaymentTokenBalance).to.equal(1)
   expect(account1TokenBalance).to.equal(30303)
   expect(account2TokenBalance).to.equal(15151)
   expect(account3TokenBalance).to.equal(33333)
   expect(account4TokenBalance).to.equal(21212)

})

첫 번째 레벨 제목

지불 토큰은 여러 수취인에게 고르지 않게 분배되고 추가 지불 토큰은 풀로 전송됩니다.

이는 이전 테스트와 유사하지만 자금이 수취인에게 지급되기 전에 더 많은 지불 토큰이 풀로 전송됩니다. 이것은 지불 토큰이 시뮬레이션된 풀 계약으로 계속 유입됨에 따라 수취인이 올바른 금액을 받을 수 있도록 수학을 통해 여전히 확인할 수 있음을 보여줍니다.

it('payment token is distributed unevenly to multiple payees with additional payment token sent to pool', async () => {
   payeeAddressArray = [account1.address, account2.address, account3.address, account4.address]
   payeeShareArray = [10, 5, 11, 7]
   const MockPool = await ethers.getContractFactory('MockPool')
   mockPool = await MockPool.deploy(
       payeeAddressArray,
       payeeShareArray,
       testPaymentToken.address
   )
   await mockPool.deployed()
   await testPaymentToken.mint(mockPool.address, 100000)
   await mockPool
       .connect(account1)
       .release(account1.address)
   await mockPool
       .connect(account2)
       .release(account2.address)
   await testPaymentToken.mint(mockPool.address, 100000)
   await mockPool
       .connect(account3)
       .release(account3.address)
   await mockPool
       .connect(account4)
       .release(account4.address)
   await mockPool
       .connect(account1)
       .release(account1.address)
   await mockPool
       .connect(account2)
       .release(account2.address)
   const mockPoolTestPaymentTokenBalance = await testPaymentToken.balanceOf(
       mockPool.address
           )
   const account1TokenBalance = await testPaymentToken.balanceOf(account1.address)
   const account2TokenBalance = await testPaymentToken.balanceOf(account2.address)
   const account3TokenBalance = await testPaymentToken.balanceOf(account3.address)
   const account4TokenBalance = await testPaymentToken.balanceOf(account4.address)
   expect(mockPoolTestPaymentTokenBalance).to.equal(1)
   expect(account1TokenBalance).to.equal(60606)
   expect(account2TokenBalance).to.equal(30303)
   expect(account3TokenBalance).to.equal(66666)
   expect(account4TokenBalance).to.equal(42424)

})

이제 모든 테스트가 준비되었으므로 테스트를 실행하고 작동하는지 확인할 시간입니다 프로젝트 루트 폴더에서 npx hardhat test로 테스트를 시작합니다. 모든 것이 정확하면 아래 이미지와 같이 녹색 사각형이 모두 표시되어야 합니다.

요약하다

요약하다

이 기사는 분산형 금융 커뮤니티에서 제공되며 승인을 받아 복제됩니다.

Source:https://medium.com/coinmonks/create-an-erc20-token-payment-splitting-smart-contract-c79436470ccc

이 기사는 분산형 금융 커뮤니티에서 제공되며 승인을 받아 복제됩니다.

ETH
Odaily 공식 커뮤니티에 가입하세요