BTC
ETH
HTX
SOL
BNB
查看行情
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt

一文學習如何創建自己的ERC20代幣支付拆分器

星球君的朋友们
Odaily资深作者
本文約11750字,閱讀全文需要約17分鐘
今天,我們將學習如何創建自己的ERC20代幣支付拆分器,它可以合併到任何項目中!
AI總結
展開
今天,我們將學習如何創建自己的ERC20代幣支付拆分器,它可以合併到任何項目中!

一級標題

一級標題

今天,我們將學習如何創建自己的ERC20代幣支付拆分器,它可以合併到任何項目中!

二級標題

二級標題

項目架構

我們將創建兩個合約。第一個將是ERC20代幣支付拆分智能合約,第二個將是模擬池智能合約。 ERC20代幣支付拆分器智能合約將是抽象的,並持有用於管理收付方及其各自支付部分的邏輯和數據。模擬池將繼承ERC20代幣支付拆分器,以便我們可以自動將支付分發給多個質押者。在兩個合約中拆分支付功能的原因有兩個:

  • 展示在真實世界的用例中代幣支付拆分合約的使用

  • 二級標題

二級標題

本教程中的工具:

本教程中的工具:

  • 安全帽——智能合約開發環境

  • OpenZeppelin -經過審計的智能合約模板

現在在一個空目錄中使用NPM init -y啟動一個NPM項目

設置項目後,使用以下命令安裝Hardhat:

在安裝了Hardhat之後,輸入npx Hardhat並選擇創建基本示例項目的選項。這將包括一個方便的文件結構,可以輕鬆地創建、測試和部署您自己的合約。

選擇創建基本示例項目

可以刪除contract 文件夾中的Greeter.sol文件,並從test文件夾中刪除sample-test.js文件。

我們還將安裝安全帽插件庫,它們是Hardhat插件。它們允許我們添加用於測試和部署智能合約的工具。

在hardhat.config.js文件的頂部,添加

二級標題

二級標題

創建代幣支付拆分器

這個代幣支付拆分智能合約將提供邏輯來設置和存儲涉及收款人列表和每個收款人份額的數據。每個收款人持有的份額數等於他們應該獲得的資金比例(例如,如果有4個收款人,每個人持有5份額,那麼他們每個人將獲得任何支出的25%)。

要開始這個合約,我們將在我們的合約文件夾中創建一個新文件,並將其命名為TokenPaymentSplitter.sol。

設置pragma line和contract shell。

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是我們在構造函數中調用的用於設置收款人數組的函數。這個函數有兩個參數,收款人的帳戶和與其相關的份額數量。然後它會檢查賬戶是否為零地址,份額是否大於零,以及該賬戶是否已經註冊為收款人。如果所有檢查都通過,那麼我們將數據添加到各自的變量中。

現在讓我們添加一個函數來支持將代幣分發給收款人。

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);
}

Release是一個任何人都可以調用的函數,它接受一個現有收款人帳戶的參數。來分析一下這個函數中發生了什麼。首先,它檢查帳戶是否有分配給它的份額。然後,它創建一個名為tokenTotalReceived的變量,該變量將合約的當前代幣餘額與之前釋放的代幣總數相加。創建另一個稱為payment的變量,該變量確定收到的代幣總額中有多少是欠賬戶的,然後減去多少已經釋放到賬戶。然後,一個require語句檢查當前支付金額是否大於零(即,當前是否欠下了更多代幣)。如果該檢查通過,則更新賬戶的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);
   }

}

在這份合約中,導入三樣東西。首先是OpenZeppelin的Ownable實用程序,它在某些函數上使用唯一的onlyOwner 修飾符。第二個是SafeERC20,它允許安全的ERC20代幣轉賬,正如將在合約中看到。第三個是我們的TokenPaymentSplitter合約。

二級標題

二級標題

測試合約

測試智能合約與創建它們同樣重要。這些合約處理的資產通常是屬於其他人的,所以作為開發人員,我們有責任確保這些資產按照他們應該的方式工作,並且我們的測試可以覆蓋幾乎所有的邊緣情況。

將在這裡進行的測試是一些示例,以顯示TokenPaymentSplitter智能合約按照我們的預期工作。在處理自己的項目時,可能希望創建專門適合自己的用例的測試。

為了支持我們的測試,我們希望包含一個ERC20代幣,為此,我們將創建一個新的solididity文件,該文件導入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')

一級標題

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)

})

本文來自去中心化金融社區,經授權轉載。

總結

總結

本文來自去中心化金融社區,經授權轉載。

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

本文來自去中心化金融社區,經授權轉載。

ETH
歡迎加入Odaily官方社群