diff --git a/Languages/ja/33_Airdrop_ja/Airdrop.sol b/Languages/ja/33_Airdrop_ja/Airdrop.sol new file mode 100644 index 000000000..13eccb92f --- /dev/null +++ b/Languages/ja/33_Airdrop_ja/Airdrop.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +// By 0xAA +pragma solidity ^0.8.21; + +import "./IERC20.sol"; //import IERC20 + +/// @notice 複数のアドレスに対してトークンをエアドロするコントラクト +contract Airdrop { + mapping(address => uint256) failTransferList; + + /// @notice 複数のアドレスに対してERC20トークンをエアドロする。使用前に承認が必要 + /// + /// @param _token エアドロップするERC20トークンのアドレス + /// @param _addresses エアドロップするアドレスの配列 + /// @param _amounts トークンの数量の配列(各アドレスにエアドロップするトークンの数量) + function multiTransferToken(address _token, address[] calldata _addresses, uint256[] calldata _amounts) external { + // チェック:_addressesと_amounts配列の長さが等しいこと + require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL"); + IERC20 token = IERC20(_token); // IERC20コントラクトインスタンスを宣言 + uint256 _amountSum = getSum(_amounts); // エアドロップするトークンの総量を計算 + // チェック:承認されたトークン数 > エアドロップするトークンの総量 + require(token.allowance(msg.sender, address(this)) > _amountSum, "Need Approve ERC20 token"); + + // forループを使用し、transferFrom関数でエアドロップを送信 + for (uint256 i; i < _addresses.length; i++) { + token.transferFrom(msg.sender, _addresses[i], _amounts[i]); + } + } + + /// 複数のアドレスにETHを送金する + function multiTransferETH(address payable[] calldata _addresses, uint256[] calldata _amounts) public payable { + // チェック:_addressesと_amounts配列の長さが等しいこと + require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL"); + uint256 _amountSum = getSum(_amounts); // エアドロップするETHの総量を計算 + // 送金されたETHがエアドロップの総量と等しいことをチェック + require(msg.value == _amountSum, "Transfer amount error"); + // forループを使用し、call関数でETHを送信 + for (uint256 i = 0; i < _addresses.length; i++) { + // コメントアウトされたコードにはDoS攻撃のリスクがあり、transferも推奨されない書き方です + // DoS攻撃については https://github.com/AmazingAng/WTF-Solidity/blob/main/S09_DoS/readme.md を参照してください + // _addresses[i].transfer(_amounts[i]); + (bool success,) = _addresses[i].call{value: _amounts[i]}(""); + if (!success) { + failTransferList[_addresses[i]] = _amounts[i]; + } + } + } + + // エアドロップ失敗に対して能動的な操作の機会を提供 + function withdrawFromFailList(address _to) public { + uint256 failAmount = failTransferList[msg.sender]; + require(failAmount > 0, "You are not in failed list"); + failTransferList[msg.sender] = 0; + (bool success,) = _to.call{value: failAmount}(""); + require(success, "Fail withdraw"); + } + + // 配列の合計を計算する関数 + function getSum(uint256[] calldata _arr) public pure returns (uint256 sum) { + for (uint256 i = 0; i < _arr.length; i++) { + sum = sum + _arr[i]; + } + } +} + +// ERC20トークンコントラクト +contract ERC20 is IERC20 { + mapping(address => uint256) public override balanceOf; + + mapping(address => mapping(address => uint256)) public override allowance; + + uint256 public override totalSupply; // トークンのトータルサプライ(総供給量) + + string public name; // 名前 + string public symbol; // シンボル + + uint8 public decimals = 18; // 小数点以下の桁数 + + constructor(string memory name_, string memory symbol_) { + name = name_; + symbol = symbol_; + } + + // @dev `transfer`関数を実装、トークン転送ロジック + function transfer(address recipient, uint256 amount) public override returns (bool) { + balanceOf[msg.sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(msg.sender, recipient, amount); + return true; + } + + // @dev `approve`関数を実装、トークン承認ロジック + function approve(address spender, uint256 amount) public override returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + // @dev `transferFrom`関数を実装、トークン承認転送ロジック + function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { + allowance[sender][msg.sender] -= amount; + balanceOf[sender] -= amount; + balanceOf[recipient] += amount; + emit Transfer(sender, recipient, amount); + return true; + } + + // @dev トークンをミントする。`0`アドレスから呼び出し元アドレスに転送 + function mint(uint256 amount) external { + balanceOf[msg.sender] += amount; + totalSupply += amount; + emit Transfer(address(0), msg.sender, amount); + } + + // @dev トークンをバーン。呼び出し元アドレスから`0`アドレスに転送 + function burn(uint256 amount) external { + balanceOf[msg.sender] -= amount; + totalSupply -= amount; + emit Transfer(msg.sender, address(0), amount); + } +} diff --git a/Languages/ja/33_Airdrop_ja/IERC20.sol b/Languages/ja/33_Airdrop_ja/IERC20.sol new file mode 100644 index 000000000..3d42fef92 --- /dev/null +++ b/Languages/ja/33_Airdrop_ja/IERC20.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +// WTF Solidity by 0xAA + +pragma solidity ^0.8.21; + +/** + * @dev ERC20 インターフェース契約. + */ +interface IERC20 { + /** + * @dev 発行条件:`value` 単位の通貨がアカウント (`from`) から別のアカウント (`to`) に転送されたとき. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev 発行条件:`value` 単位の通貨がアカウント (`owner`) から別のアカウント (`spender`) に承認されたとき. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev トークンの総供給量を返す. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev アカウント`account`が保有するトークン数を返す. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev 呼び出し元のアカウントから別のアカウント `to` に `amount` 単位のトークンを転送する. + * + * 成功した場合は `true` を返す. + * + * {Transfer} イベントを発行する. + */ + function transfer(address to, uint256 amount) external returns (bool); + + /** + * @dev `owner`アカウントが`spender`アカウントに承認した額を返す。デフォルトは0. + * + * {approve} または {transferFrom} が呼び出されると、`allowance`は変更される. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev 呼び出し元のアカウントが`spender`アカウントに `amount` 数量のトークンを承認する. + * + * 成功した場合は `true` を返す. + * + * {Approval} イベントを発行する. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev 承認メカニズムを通じて、`from`アカウントから`to`アカウントに`amount`数量のトークンを転送する。転送された部分は呼び出し元の`allowance`から差し引かれる. + * + * 成功した場合は `true` を返す. + * + * {Transfer} イベントを発行する. + */ + function transferFrom(address from, address to, uint256 amount) external returns (bool); +} diff --git a/Languages/ja/33_Airdrop_ja/img/33-1.png b/Languages/ja/33_Airdrop_ja/img/33-1.png new file mode 100644 index 000000000..b3398f788 Binary files /dev/null and b/Languages/ja/33_Airdrop_ja/img/33-1.png differ diff --git a/Languages/ja/33_Airdrop_ja/img/33-2.png b/Languages/ja/33_Airdrop_ja/img/33-2.png new file mode 100644 index 000000000..92bc63af9 Binary files /dev/null and b/Languages/ja/33_Airdrop_ja/img/33-2.png differ diff --git a/Languages/ja/33_Airdrop_ja/img/33-3.png b/Languages/ja/33_Airdrop_ja/img/33-3.png new file mode 100644 index 000000000..824182401 Binary files /dev/null and b/Languages/ja/33_Airdrop_ja/img/33-3.png differ diff --git a/Languages/ja/33_Airdrop_ja/img/33-4.png b/Languages/ja/33_Airdrop_ja/img/33-4.png new file mode 100644 index 000000000..afad6112b Binary files /dev/null and b/Languages/ja/33_Airdrop_ja/img/33-4.png differ diff --git a/Languages/ja/33_Airdrop_ja/img/33-5.png b/Languages/ja/33_Airdrop_ja/img/33-5.png new file mode 100644 index 000000000..2f012626e Binary files /dev/null and b/Languages/ja/33_Airdrop_ja/img/33-5.png differ diff --git a/Languages/ja/33_Airdrop_ja/img/33-6.png b/Languages/ja/33_Airdrop_ja/img/33-6.png new file mode 100644 index 000000000..d4b265d24 Binary files /dev/null and b/Languages/ja/33_Airdrop_ja/img/33-6.png differ diff --git a/Languages/ja/33_Airdrop_ja/readme.md b/Languages/ja/33_Airdrop_ja/readme.md new file mode 100644 index 000000000..17dd7563b --- /dev/null +++ b/Languages/ja/33_Airdrop_ja/readme.md @@ -0,0 +1,120 @@ +--- +title: 33. エアドロコントラクト +tags: + - solidity + - application + - wtfacademy + - ERC20 + - airdrop +--- + +# WTF Solidity 超シンプル入門: 33. エアドロコントラクト + +最近、Solidity の学習を再開し、詳細を確認しながら「Solidity 超シンプル入門」を作っています。これは初心者向けのガイドで、プログラミングの達人向けの教材ではありません。毎週 1〜3 レッスンのペースで更新していきます。 + +僕のツイッター:[@0xAA_Science](https://twitter.com/0xAA_Science)|[@WTFAcademy\_](https://twitter.com/WTFAcademy_) + +コミュニティ:[Discord](https://discord.gg/5akcruXrsk)|[Wechat](https://docs.google.com/forms/d/e/1FAIpQLSe4KGT8Sh6sJ7hedQRuIYirOoZK_85miz3dw7vA1-YjodgJ-A/viewform?usp=sf_link)|[公式サイト wtf.academy](https://wtf.academy) + +すべてのソースコードやレッスンは github にて公開: [github.com/AmazingAng/WTFSolidity](https://github.com/AmazingAng/WTFSolidity) + +--- + +仮想通貨の世界で最も嬉しいことの一つは、エアドロップを受け取ることです。タダでトークンを手に入れられるからです。このレッスンでは、スマートコントラクトを使用して`ERC20`トークンのエアドロップを送信する方法を学びます。 + +## エアドロップ Airdrop + +エアドロップは仮想通貨界隈でのマーケティング戦略の一つで、プロジェクトチームが特定のユーザーグループに無料でトークンを配布します。エアドロップの資格を得るために、ユーザーは通常、製品のテスト、ニュースの共有、友人の紹介など、いくつかの簡単なタスクを完了する必要があります。プロジェクトチームはエアドロップを通じてシードユーザーを獲得し、ユーザーは財産を得ることができるため、双方にとって利益があります。 + +エアドロップを受け取るユーザーは多いため、プロジェクトチームが一つ一つ取引を行うことは不可能です。スマートコントラクトを利用して`ERC20`トークンを一括で配布することで、エアドロップの効率を大幅に向上させることができます。 + +### エアドロップトークンコントラクト + +`Airdrop`エアドロップコントラクトのロジックは非常にシンプルです:ループを使用して、1 回の取引で複数のアドレスに`ERC20`トークンを送信します。コントラクトには 2 つの関数が含まれています: + +- `getSum()`関数:`uint`配列の合計を返します。 + + ```solidity + // 配列の合計を計算する関数 + function getSum(uint256[] calldata _arr) public pure returns(uint sum){ + for(uint i = 0; i < _arr.length; i++) + sum = sum + _arr[i]; + } + ``` + +- `multiTransferToken()`関数:`ERC20`トークンのエアドロップを送信します。3 つのパラメータがあります: + + - `_token`:トークンコントラクトアドレス(`address`型) + - `_addresses`:エアドロップを受け取るユーザーアドレスの配列(`address[]`型) + - `_amounts`:エアドロップ量の配列、`_addresses`の各アドレスに対応する量(`uint[]`型) + + この関数には 2 つのチェックがあります:最初の`require`は`_addresses`と`_amounts`の 2 つの配列の長さが等しいかをチェックします。2 つ目の`require`はエアドロップコントラクトの承認額がエアドロップするトークンの総量よりも大きいかをチェックします。 + + ```solidity + /// @notice 複数のアドレスにERC20トークンを転送します。使用前に承認が必要です + /// + /// @param _token 転送するERC20トークンのアドレス + /// @param _addresses エアドロップアドレスの配列 + /// @param _amounts トークン量の配列(各アドレスのエアドロップ量) + function multiTransferToken( + address _token, + address[] calldata _addresses, + uint256[] calldata _amounts + ) external { + // チェック:_addressesと_amountsの配列の長さが等しいこと + require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL"); + IERC20 token = IERC20(_token); // IERCコントラクト変数を宣言 + uint _amountSum = getSum(_amounts); // エアドロップするトークンの総量を計算 + // チェック:承認されたトークン量 >= エアドロップするトークンの総量 + require(token.allowance(msg.sender, address(this)) >= _amountSum, "Need Approve ERC20 token"); + + // forループを使用し、transferFrom関数でエアドロップを送信 + for (uint8 i; i < _addresses.length; i++) { + token.transferFrom(msg.sender, _addresses[i], _amounts[i]); + } + } + ``` + +- `multiTransferETH()`関数:`ETH`エアドロップを送信します。2 つのパラメータがあります: + + - `_addresses`:エアドロップを受け取るユーザーアドレスの配列(`address[]`型) + - `_amounts`:エアドロップ量の配列、`_addresses`の各アドレスに対応する量(`uint[]`型) + + ```solidity + /// 複数のアドレスにETHを転送 + function multiTransferETH( + address payable[] calldata _addresses, + uint256[] calldata _amounts + ) public payable { + // チェック:_addressesと_amountsの配列の長さが等しいこと + require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL"); + uint _amountSum = getSum(_amounts); // エアドロップするETHの総量を計算 + // 送金されたETHがエアドロップの総量と等しいことをチェック + require(msg.value == _amountSum, "Transfer amount error"); + // forループを使用し、call関数でETHを送信 + for (uint256 i = 0; i < _addresses.length; i++) { + // コメントアウトされたコードにはDoS攻撃のリスクがあり、transferも推奨されない書き方です + // DoS攻撃については https://github.com/AmazingAng/WTF-Solidity/blob/main/S09_DoS/readme.md を参照してください + // _addresses[i].transfer(_amounts[i]); + (bool success, ) = _addresses[i].call{value: _amounts[i]}(""); + if (!success) { + failTransferList[_addresses[i]] = _amounts[i]; + } + } + } + ``` + +### エアドロップの実践 + +1. `ERC20`トークンコントラクトをデプロイし、自分に 10000 単位のトークンをミントします。 + ![`ERC20`をデプロイ](./img/33-1.png) + ![ミント](./img/33-2.png) +2. `Airdrop`エアドロップコントラクトをデプロイします。 + ![`Airdrop`コントラクトをデプロイ](./img/33-3.png) +3. `ERC20`トークンコントラクトの`approve()`関数を使用して、`Airdrop`エアドロップコントラクトに 10000 単位のトークンを承認します。 + ![`Airdrop`コントラクトを承認](./img/33-4.png) +4. `Airdrop`コントラクトの`multiTransferToken()`関数を実行してエアドロップを行います。`_token`に`ERC20`トークンアドレスを入力し、`_addresses`と`_amounts`は以下のように入力します: + +## まとめ + +このレッスンでは、`Solidity`を使用して`ERC20`トークンのエアドロップを送信する方法を学びました。これにより、エアドロップの効率を大幅に向上させることができます。私が一番最初にもらったエアドロップップは`ENS`でした。あなたはどのプロジェクトのエアドロップをもらったことがありますか。