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

独自の ERC20 トークン支払いスプリッターを作成する方法を 1 つの記事で学びましょう

星球君的朋友们
Odaily资深作者
この記事は約11750文字で、全文を読むには約17分かかります
今日は、あらゆるプロジェクトに組み込むことができる独自の ERC20 トークン支払いスプリッターを作成する方法を学びます。
AI要約
今日は、あらゆるプロジェクトに組み込むことができる独自の ERC20 トークン支払いスプリッターを作成する方法を学びます。

支払いは、暗号通貨のほぼすべての分野で繰り返し話題になり、特に複数のステーカーへの支払いの提供がその傾向にあります。たとえば、DAO は複数のイニシアチブに資金を提供したい、DEX は特定の参加者に取引手数料を分配するためにマージしたい、またはチームは月給としてチーム メンバーにトークンを分配したいと考えています。

最初のレベルのタイトル

今日は、あらゆるプロジェクトに組み込むことができる独自の ERC20 トークン支払いスプリッターを作成する方法を学びます。

前提条件とセットアップ

副題

プロジェクト構造

2つの契約書を作成します。 1 つ目は ERC20 トークン支払い分割スマート コントラクトで、2 つ目はモック プール スマート コントラクトです。 ERC20 トークン支払いスプリッター スマート コントラクトは抽象的であり、送信者と支払い者、およびそれぞれの支払い部分を管理するためのロジックとデータを保持します。モック プールは ERC20 トークン支払いスプリッターを継承するため、支払いを複数のステーカーに自動的に分配できます。支払い機能を 2 つの契約に分割する理由は 2 つあります。

  • 実際のユースケースでのトークン支払い分割契約の使用をデモンストレーションする

  • トークン支払い分割契約が、誰もが選択して自分のプロジェクトに統合できるほど柔軟であることを確認する

副題

開発環境をセットアップする

このチュートリアルのツール:

  • ハードハット - スマートコントラクト開発環境

  • 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 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 は、受信者のアドレスと支払いトークンの量のマッピングです。

次に、3 つのパラメーターを受け入れるコンストラクターを配置します。最初のパラメータは、コントラクトの展開時に初期化する受取人の配列です。 2 番目のパラメーターは、各受取人の株式の配列です。 3 番目は、支払いに使用される 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 ステートメントが含まれており、各受取人に割り当てられるシェアが確保されます。契約が少なくとも 1 人の受取人で初期化されていることを確認するための別の 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 は、受取人の配列を設定するためにコンストラクターで呼び出す関数です。この関数は、受取人の口座とそれに関連付けられた株式数という 2 つのパラメータを取ります。次に、アカウントのアドレスがゼロかどうか、シェアがゼロより大きいかどうか、アカウントがすでに受取人として登録されているかどうかを確認します。すべてのチェックに合格した場合、データをそれぞれの変数に追加します。

次に、受取人へのトークンの配布をサポートする関数を追加しましょう。

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 を更新します。最後に、アカウントに支払われたトークンの金額が転送されます。

これで機能が整いました!しかし、この契約にはもう 1 つやるべきことがあります...イベント!

コントラクトに 2 つのイベントを追加します。イベントをコントラクトの先頭に追加することをお勧めします。

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

}

この契約では、3 つのものをインポートします。 1 つ目は OpenZeppelin の Ownable ユーティリティで、一部の関数で独自のonlyOwner 修飾子を使用します。 2 つ目は SafeERC20 で、契約に示されているように、ERC20 トークンの安全な転送を可能にします。 3 つ目は TokenPaymentSplitter コントラクトです。

MockPool コンストラクターでは、TokenPaymentSplitter から同じ 3 つのパラメーターが必要です。それらを継承したコントラクトに渡すだけです。

副題

テスト契約

スマート コントラクトのテストは、スマート コントラクトを作成するのと同じくらい重要です。これらの契約で扱われるアセットは通常、他の誰かに属しているため、それらのアセットが想定どおりに動作すること、およびテストがほぼすべてのエッジケースをカバーしていることを確認するのは開発者としての責任です。

ここで実行されるテストは、TokenPaymentSplitter スマート コントラクトが期待どおりに機能することを示すいくつかの例です。独自のプロジェクトに取り組むとき、独自のユースケースに合わせて特別に調整されたテストを作成したい場合があります。

テストをサポートするには、ERC20 トークンを含める必要があります。そのために、テストで使用する OpenZepplin ERC20 テンプレートをインポートする新しい Solididity ファイルを作成します。契約フォルダーに、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 関数を作成し、すぐにテストを含む空の description 関数を作成します。

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)

})

最初のレベルのタイトル

支払いトークンが複数の受取人に不均等に分配される

2 番目のテストでは、各受信者の取り分が不均等に分配されている場合でも、計算が機能することを確認したいと思います。

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 hardware test を使用してテストを開始します。すべてが正しい場合は、下の図に示すように、すべての緑色の四角形が表示されるはずです。

要約する

要約する

この記事は分散型金融コミュニティからのものであり、許可を得て転載しています。

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

この記事は分散型金融コミュニティからのものであり、許可を得て転載しています。

ETH
Odaily公式コミュニティへの参加を歓迎します
購読グループ:
https://t.me/Odaily_News
チャットグループ:
https://t.me/Odaily_GoldenApe
公式アカウント:
https://twitter.com/OdailyChina
記事目次
Odailyプラネットデイリーアプリをダウンロード
一部の人々にまずWeb3.0を理解させよう
IOS
Android