diff --git a/22_Call/Call.sol b/22_Call/Call.sol index af1c98eb6..45167a67c 100644 --- a/22_Call/Call.sol +++ b/22_Call/Call.sol @@ -2,61 +2,56 @@ pragma solidity ^0.8.21; contract OtherContract { - uint256 private _x = 0; // 状态变量x - // 收到eth事件,记录amount和gas - event Log(uint amount, uint gas); + uint256 private _x = 0; // 状態変数_x + // ethを受け取るイベント、amountとgasを記録 - fallback() external payable{} + event Log(uint256 amount, uint256 gas); - // 返回合约ETH余额 - function getBalance() view public returns(uint) { + fallback() external payable {} + + // コントラクトのETH残高を返す関数 + function getBalance() public view returns (uint256) { return address(this).balance; } - // 可以调整状态变量_x的函数,并且可以往合约转ETH (payable) - function setX(uint256 x) external payable{ + // _xの値を設定できる関数。同時にコントラクトへETHを送信することもできる(payable) + function setX(uint256 x) external payable { _x = x; - // 如果转入ETH,则释放Log事件 - if(msg.value > 0){ + // もしETHの送信がある場合のみLogイベントを放出 + if (msg.value > 0) { emit Log(msg.value, gasleft()); } } - // 读取x - function getX() external view returns(uint x){ + // xの値を取得する関数 + function getX() external view returns (uint256 x) { x = _x; } } -contract Call{ - // 定义Response事件,输出call返回的结果success和data +contract Call { + // Response イベントは`call`の結果`success`と`data`を出力します event Response(bool success, bytes data); function callSetX(address payable _addr, uint256 x) public payable { - // call setX(),同时可以发送ETH - (bool success, bytes memory data) = _addr.call{value: msg.value}( - abi.encodeWithSignature("setX(uint256)", x) - ); + // setX()をcallし、ETHを送信 + (bool success, bytes memory data) = _addr.call{value: msg.value}(abi.encodeWithSignature("setX(uint256)", x)); - emit Response(success, data); //释放事件 + emit Response(success, data); // イベントを放出 } - function callGetX(address _addr) external returns(uint256){ + function callGetX(address _addr) external returns (uint256) { // call getX() - (bool success, bytes memory data) = _addr.call( - abi.encodeWithSignature("getX()") - ); + (bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("getX()")); - emit Response(success, data); //释放事件 + emit Response(success, data); // イベントを放出 return abi.decode(data, (uint256)); } - function callNonExist(address _addr) external{ - // call 不存在的函数 - (bool success, bytes memory data) = _addr.call( - abi.encodeWithSignature("foo(uint256)") - ); + function callNonExist(address _addr) external { + // 存在しない関数を呼び出す + (bool success, bytes memory data) = _addr.call(abi.encodeWithSignature("foo(uint256)")); - emit Response(success, data); //释放事件 + emit Response(success, data); // イベントを放出 } } diff --git a/Languages/en/23_Delegatecall_en/img/23-8.png b/Languages/en/23_Delegatecall_en/img/23-8.png deleted file mode 100644 index 2993223f4..000000000 Binary files a/Languages/en/23_Delegatecall_en/img/23-8.png and /dev/null differ diff --git a/Languages/ja/17_Library_ja/readme.md b/Languages/ja/17_Library_ja/readme.md index fa7ac146e..b8d9d859b 100644 --- a/Languages/ja/17_Library_ja/readme.md +++ b/Languages/ja/17_Library_ja/readme.md @@ -109,7 +109,6 @@ library Strings { 1. `using for`を使うケース - 指令`using A for B;`可用于附加库合约(从库 A)到任何类型(B)。添加完指令后,库`A`中的函数会自动添加为`B`类型变量的成员,可以直接调用。注意:在调用的时候,这个变量会被当作第一个参数传递给函数: コマンドの`using A for B;`は、ライブラリ`A`を任意の型`B`に追加するために使用できます。コマンドを追加すると、ライブラリ`A`の関数は自動的に`B`型変数のメンバーとして追加され、直接呼び出すことができます。注意:呼び出す際、この変数は関数に第 1 引数として渡されます。 ```solidity diff --git a/Languages/ja/19_Fallback_ja/img/19-3.jpg b/Languages/ja/19_Fallback_ja/img/19-3.jpg index bda5a5f85..dc2be7db0 100644 Binary files a/Languages/ja/19_Fallback_ja/img/19-3.jpg and b/Languages/ja/19_Fallback_ja/img/19-3.jpg differ diff --git a/Languages/ja/19_Fallback_ja/readme.md b/Languages/ja/19_Fallback_ja/readme.md index d7754cb9e..8b38710e3 100644 --- a/Languages/ja/19_Fallback_ja/readme.md +++ b/Languages/ja/19_Fallback_ja/readme.md @@ -97,9 +97,9 @@ receive()あるか? fallback() receive() fallback() ``` -简单来说,合约接收`ETH`时,`msg.data`为空且存在`receive()`时,会触发`receive()`;`msg.data`不为空或不存在`receive()`时,会触发`fallback()`,此时`fallback()`必须为`payable`。 +簡単にいうと、コントラクトが`ETH`を受け取るとき、`msg.data`が空で`receive()`が存在する場合は`receive()`がトリガーされます。`msg.data`が空で`receive()`が存在しない場合は、`fallback()`がトリガーされます。この場合、`fallback()`は`payable`である必要があります。 -`receive()`和`payable fallback()`均不存在的时候,向合约**直接**发送`ETH`将会报错(你仍可以通过带有`payable`的函数向合约发送`ETH`)。 +`receive()`と`payable fallback()`が存在しない場合、コントラクトに直接`ETH`を送信するとエラーが発生します(`payable`関数を使ってコントラクトに`ETH`を送信することはできます)。 ## Remix 演示 diff --git a/Languages/ja/20_SendETH_ja/img/20-1.png b/Languages/ja/20_SendETH_ja/img/20-1.png index d1e6c264c..7a226b61c 100644 Binary files a/Languages/ja/20_SendETH_ja/img/20-1.png and b/Languages/ja/20_SendETH_ja/img/20-1.png differ diff --git a/Languages/ja/20_SendETH_ja/img/20-2.png b/Languages/ja/20_SendETH_ja/img/20-2.png index 7c67e2ace..e68219cfc 100644 Binary files a/Languages/ja/20_SendETH_ja/img/20-2.png and b/Languages/ja/20_SendETH_ja/img/20-2.png differ diff --git a/Languages/ja/20_SendETH_ja/img/20-3.png b/Languages/ja/20_SendETH_ja/img/20-3.png index 0ea34f62d..223e5c1eb 100644 Binary files a/Languages/ja/20_SendETH_ja/img/20-3.png and b/Languages/ja/20_SendETH_ja/img/20-3.png differ diff --git a/Languages/ja/20_SendETH_ja/img/20-5.png b/Languages/ja/20_SendETH_ja/img/20-5.png index b5675c062..5c9c5c180 100644 Binary files a/Languages/ja/20_SendETH_ja/img/20-5.png and b/Languages/ja/20_SendETH_ja/img/20-5.png differ diff --git a/Languages/ja/20_SendETH_ja/img/20-7.png b/Languages/ja/20_SendETH_ja/img/20-7.png index 8b6e4dff0..759b13898 100644 Binary files a/Languages/ja/20_SendETH_ja/img/20-7.png and b/Languages/ja/20_SendETH_ja/img/20-7.png differ diff --git a/Languages/ja/20_SendETH_ja/img/20-9.png b/Languages/ja/20_SendETH_ja/img/20-9.png new file mode 100644 index 000000000..4db6848c6 Binary files /dev/null and b/Languages/ja/20_SendETH_ja/img/20-9.png differ diff --git a/Languages/ja/20_SendETH_ja/readme.md b/Languages/ja/20_SendETH_ja/readme.md index 4bde7f40a..cb85ae41f 100644 --- a/Languages/ja/20_SendETH_ja/readme.md +++ b/Languages/ja/20_SendETH_ja/readme.md @@ -19,11 +19,10 @@ tags: --- -`Solidity`有三种方法向其他合约发送`ETH`,他们是:`transfer()`,`send()`和`call()`,其中`call()`是被鼓励的用法。 +`Solidity`には他のコントラクトに ETH を送る3つの方法がある。それらは`transfer()`, `send()`, `call()`。現在、おすすめされている方法は`call()`。 ## ETH を受け取るコントラクト -我们先部署一个接收`ETH`合约`ReceiveETH`。`ReceiveETH`合约里有一个事件`Log`,记录收到的`ETH`数量和`gas`剩余。还有两个函数,一个是`receive()`函数,收到`ETH`被触发,并发送`Log`事件;另一个是查询合约`ETH`余额的`getBalance()`函数。 まず、私たちは ETH を受け取る用のコントラクトをデプロイします。 このコントラクトは以下となっています。 @@ -55,8 +54,7 @@ contract ReceiveETH { ## ETH を送金するコントラクト -我们将实现三种方法向`ReceiveETH`合约发送`ETH`。首先,先在发送 ETH 合约`SendETH`中实现`payable`的`构造函数`和`receive()`,让我们能够在部署时和部署后向合约转账。 -私たちは3つの方法を使って`ReceiveETH`コントラクトに ETH を送ります。まず、`SendETH`コントラクトをデプロイします。 +私たちは3つの方法を使って`ReceiveETH`コントラクトに ETH を送ります。まず、`SendETH`コントラクトの中で`payable`の`constructor`関数と`receive()`関数を実装し、デプロイ時とデプロイ後に コントラクトに対して ETH を送金できるようにします。 ```solidity contract SendETH { @@ -74,7 +72,7 @@ contract SendETH { - `transfer()`の`gas`の制限は`2300`で、送金には十分ですが、相手のコントラクトの`fallback()`や`receive()`関数には複雑なロジックを実装できません。 - もし`transfer()`が失敗すると、自動的に`revert`(ロールバック)します。 -サンプルコードです。`_to`には`ReceiveETH`コントラクトのアドレスを入力し、`amount`には送金する`ETH`の量を入力します。 +以下はサンプルコードです。`_to`には`ReceiveETH`コントラクトのアドレスを入力し、`amount`には送金する`ETH`の量を入力します。 ```solidity // transfer関数を使ってETHを送る @@ -100,7 +98,6 @@ function transferETH(address payable _to, uint256 amount) external payable{ - 使い方は`受取アドレス.send(送るETHの量)`。 - `send()`の`gas`の制限は`2300`で、送金には十分ですが、相手のコントラクトの`fallback()`や`receive()`関数には複雑なロジックを実装できません。 - `send()`がもし失敗したら、`revert`されることはない。 -- `send()`的返回值是`bool`,代表着转账成功或失败,需要额外代码处理一下。 - `send()`の返り値は`bool`で、送金が成功したあるいは失敗したを表します。送金が失敗した場合、処理するコードの追加が必要です。 サンプルコード: @@ -130,7 +127,6 @@ function sendETH(address payable _to, uint256 amount) external payable{ - 使い方は`受取アドレス.call{value: 送るETHの量}("")`。 - ` call()`は`gas`の制限がなく、相手のコントラクトの`fallback()`や`receive()`関数に複雑なロジックを実装できます。 -- `call()`如果转账失败,不会`revert`。 - `call()`がもし失敗したら、`revert`されることはない。 - `call()`の返り値は`(bool, bytes)`で、送金が成功したあるいは失敗したを表します。送金が失敗した場合、処理するコードの追加が必要です。 @@ -157,7 +153,6 @@ function callETH(address payable _to, uint256 amount) external payable{ ![20-8](./img/20-8.png) -运行三种方法,可以看到,他们都可以成功地向`ReceiveETH`合约发送`ETH`。 3つの方法を使って`ReceiveETH`コントラクトに ETH を送ります。 すべての方法で送金ができることがわかりましたね。 @@ -167,6 +162,5 @@ function callETH(address payable _to, uint256 amount) external payable{ 今回は、`Solidity`の3つの方法で`ETH`を送る方法を紹介しました:`transfer`、`send`、`call`。 - `call`は`gas`の制限がなく、最も柔軟であり、一番推奨される方法です。 -- `transfer`有`2300 gas`限制,但是发送失败会自动`revert`交易,是次优选择。 - `transfer`は`2300 gas`の制限があり、送金が失敗した場合、自動的に`revert`されるため、`call`に次ぐ選択肢です。 - `send`は`2300 gas`の制限があり、送金が失敗した場合、自動的に`revert`されないため、ほとんど使用されません。 diff --git a/Languages/ja/21_CallContract_ja/CallContract.sol b/Languages/ja/21_CallContract_ja/CallContract.sol new file mode 100644 index 000000000..b081d6c30 --- /dev/null +++ b/Languages/ja/21_CallContract_ja/CallContract.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract OtherContract { + uint256 private _x = 0; // 状態変数x + // ethを受け取るイベント、amountやgasを記録 + + event Log(uint256 amount, uint256 gas); + + // contractの残高を示す + function getBalance() public view returns (uint256) { + return address(this).balance; + } + + // 状態変数の_xを設定できる。ETHも送金できる(payable) + function setX(uint256 x) external payable { + _x = x; + // もしETHを送金したら、Logイベントを発行 + if (msg.value > 0) { + emit Log(msg.value, gasleft()); + } + } + + // xの値を取得 + function getX() external view returns (uint256 x) { + x = _x; + } +} + +contract CallContract { + function callSetX(address _Address, uint256 x) external { + OtherContract(_Address).setX(x); + } + + function callGetX(OtherContract _Address) external view returns (uint256 x) { + x = _Address.getX(); + } + + function callGetX2(address _Address) external view returns (uint256 x) { + OtherContract oc = OtherContract(_Address); + x = oc.getX(); + } + + function setXTransferETH(address otherContract, uint256 x) external payable { + OtherContract(otherContract).setX{value: msg.value}(x); + } +} diff --git a/Languages/ja/21_CallContract_ja/img/21-1.png b/Languages/ja/21_CallContract_ja/img/21-1.png new file mode 100644 index 000000000..356608c83 Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-1.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-2.png b/Languages/ja/21_CallContract_ja/img/21-2.png new file mode 100644 index 000000000..263747ebe Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-2.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-3.png b/Languages/ja/21_CallContract_ja/img/21-3.png new file mode 100644 index 000000000..95c84ae8b Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-3.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-4.png b/Languages/ja/21_CallContract_ja/img/21-4.png new file mode 100644 index 000000000..913bbdb98 Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-4.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-5.png b/Languages/ja/21_CallContract_ja/img/21-5.png new file mode 100644 index 000000000..021fb2985 Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-5.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-6.png b/Languages/ja/21_CallContract_ja/img/21-6.png new file mode 100644 index 000000000..2da52e328 Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-6.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-7.png b/Languages/ja/21_CallContract_ja/img/21-7.png new file mode 100644 index 000000000..66baf9f0c Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-7.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-8.png b/Languages/ja/21_CallContract_ja/img/21-8.png new file mode 100644 index 000000000..ac72ea7a3 Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-8.png differ diff --git a/Languages/ja/21_CallContract_ja/img/21-9.png b/Languages/ja/21_CallContract_ja/img/21-9.png new file mode 100644 index 000000000..03cfc0e68 Binary files /dev/null and b/Languages/ja/21_CallContract_ja/img/21-9.png differ diff --git a/Languages/ja/21_CallContract_ja/readme.md b/Languages/ja/21_CallContract_ja/readme.md new file mode 100644 index 000000000..b2f9d0ca6 --- /dev/null +++ b/Languages/ja/21_CallContract_ja/readme.md @@ -0,0 +1,150 @@ +--- +title: 21. 调用其他合约 +tags: + - solidity + - advanced + - wtfacademy + - call contract +--- + +# WTF Solidity 超シンプル入門: 21. コントラクトの呼び出し + +最近、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) + +--- + +## デプロイ済みのコントラクトの呼び出し + +`Solidity`において、コントラクトは他のコントラクトの関数を呼び出すことができます。これは複雑な DApps を構築する際に非常に便利です。このチュートリアルでは、既知のコントラクトコード(またはインターフェース)とアドレスがわかっている場合に、デプロイ済みのコントラクトを呼び出す方法について説明します。 + +## ターゲットコントラクト + +まず、私たちは他のコントラクトを呼び出すためのコントラクト`OtherContract`を作成します。 + +```solidity +contract OtherContract { + uint256 private _x = 0; // 状態変数_x + // LogイベントはETHの受け取りを記録します + event Log(uint amount, uint gas); + + // ETHの残高を示す関数 + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // 状態変数を設定できる関数で、ETHを送信する場合はLogイベントを放出します + function setX(uint256 x) external payable{ + _x = x; + // ETHが0より大きい場合はLogイベントを放出します + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // _xの値を取得する関数 + function getX() external view returns(uint x){ + x = _x; + } +} +``` + +このコントラクトは、状態変数`_x`、`Log`イベント、3 つの関数を含んでいます。 + +- `getBalance()`: コントラクトの ETH 残高を返します。 +- `setX()`: `external payable`関数で、`_x`の値を設定し、ETH を送信します。 +- `getX()`: `_x`の値を取得します。 + +## `OtherContract`コントラクトの呼び出し + +私たちはコントラクトのアドレスとコントラクトコード(またはインターフェース)を使用して、コントラクトの参照を作成できます。即ち、`_Name(_Address)`です。 + +ここで、`_Name`はコントラクト名であり、コントラクトコード(またはインターフェース)で指定されたコントラクト名と一致している必要があります。`_Address`はコントラクトのアドレスです。その後、コントラクトの参照を使用して、その関数を呼び出します:`_Name(_Address).f()`。ここで、`f()`は呼び出す関数です。 + +以下の 4 つのコントラクト呼び出しの例を紹介します。remix でコントラクトをコンパイルした後、`OtherContract`と`CallContract`をそれぞれデプロイします。 + +![deploy contract0 in remix](./img/21-1.png) + +![deploy contract1 in remix](./img/21-2.png) + +![deploy contract2 in remix](./img/21-3.png) + +### 1. コントラクトのアドレスを渡す + +私たちは、関数内でターゲットコントラクトのアドレスを渡すことができ、ターゲットコントラクトの参照を生成し、その関数を呼び出すことができます。例として、`OtherContract`コントラクトの`setX`関数を呼び出すために、新しいコントラクトに`callSetX`関数を記述します。すでにデプロイ済みの`OtherContract`コントラクトのアドレス`_Address`と`setX`のパラメータ`x`を渡します。 + +```solidity +function callSetX(address _Address, uint256 x) external{ + OtherContract(_Address).setX(x); +} +``` + +`otherContract`のアドレスをコピーし、`callSetX`関数のパラメータに入力し、成功した後、`OtherContract`の`getX`を呼び出して`x`が 123 になっていることを確認します。 + +![call contract1 in remix](./img/21-4.png) + +![call contract2 in remix](./img/21-5.png) + +### 2. コントラクト変数を渡す + +私たちは、関数内でターゲットコントラクトの参照を渡すことができます。上記の例の引数を`address`から目標コントラクト名にすればよいのです。 + +以下の例では、`OtherContract`コントラクトの`getX()`関数を呼び出します。 + +**注意**: この関数のパラメータ`OtherContract _Address`の基本型は`address`であり、生成された`ABI`では、`callGetX`を呼び出す際に渡されるパラメータはすべて`address`型です。 + +```solidity +function callGetX(OtherContract _Address) external view returns(uint x){ + x = _Address.getX(); +} +``` + +`otherContract`コントラクトのアドレスをコピーし、`callGetX`関数のパラメータとして入力し、成功した後、`OtherContract`の`getX`を呼び出して`x`が 123 になっていることを確認できるでしょう。 + +![call contract3 in remix](./img/21-6.png) + +### 3. コントラクト変数を作成 + +私たちは、コントラクト変数を作成し、それを使用してターゲット関数を呼び出すことができます。以下の例では、変数`oc`に`OtherContract`コントラクトの参照を保存し、その後、`getX()`関数を呼び出します。 + +```solidity +function callGetX2(address _Address) external view returns(uint x){ + OtherContract oc = OtherContract(_Address); + x = oc.getX(); +} +``` + +`OtherContract`のコントラクトアドレスをコピーし、`callGetX2`関数のパラメータとして入力し、`x`の値の取得に成功します。 + +![call contract4 in remix](./img/21-7.png) + +### 4. コントラクトを呼び出して ETH を送金 + +もしターゲットコントラクトは`payable`だったら、それを使ってコントラクトへ ETH を送金することができます。その中で`_Name`はコントラクト名であり、`_Address`はコントラクトアドレスです。`f`はターゲット関数名であり、`_Value`は送金する ETH の量(`wei`単位)です。 + +`OtherContract`コントラクトの`setX`関数は`payable`であるため、次の例では`setX`を呼び出して目標コントラクトに ETH を送金します。 + +```solidity +function setXTransferETH(address otherContract, uint256 x) payable external{ + OtherContract(otherContract).setX{value: msg.value}(x); +} +``` + +`OtherContract`のコントラクトアドレスをコピーし、`setXTransferETH`関数のパラメータとして入力し、10ETH を送金します。 + +![call contract5 in remix](./img/21-8.png) + +--- + +送金後、`Log`イベントと`getBalance()`関数を使用して、ターゲットコントラクトの`ETH`残高の変化を確認できます。 + +![call contract6 in remix](./img/21-9.png) + +## まとめ + +今回は、ターゲットコントラクトのコード(またはインターフェース)とアドレスを使用して、コントラクトの参照オブジェクトを作成し、ターゲットコントラクトの関数を呼び出す方法について説明しました。 diff --git a/Languages/ja/22_Call_ja/Call.sol b/Languages/ja/22_Call_ja/Call.sol new file mode 100644 index 000000000..af1c98eb6 --- /dev/null +++ b/Languages/ja/22_Call_ja/Call.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract OtherContract { + uint256 private _x = 0; // 状态变量x + // 收到eth事件,记录amount和gas + event Log(uint amount, uint gas); + + fallback() external payable{} + + // 返回合约ETH余额 + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // 可以调整状态变量_x的函数,并且可以往合约转ETH (payable) + function setX(uint256 x) external payable{ + _x = x; + // 如果转入ETH,则释放Log事件 + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // 读取x + function getX() external view returns(uint x){ + x = _x; + } +} + +contract Call{ + // 定义Response事件,输出call返回的结果success和data + event Response(bool success, bytes data); + + function callSetX(address payable _addr, uint256 x) public payable { + // call setX(),同时可以发送ETH + (bool success, bytes memory data) = _addr.call{value: msg.value}( + abi.encodeWithSignature("setX(uint256)", x) + ); + + emit Response(success, data); //释放事件 + } + + function callGetX(address _addr) external returns(uint256){ + // call getX() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("getX()") + ); + + emit Response(success, data); //释放事件 + return abi.decode(data, (uint256)); + } + + function callNonExist(address _addr) external{ + // call 不存在的函数 + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("foo(uint256)") + ); + + emit Response(success, data); //释放事件 + } +} diff --git a/Languages/ja/22_Call_ja/img/22-1.png b/Languages/ja/22_Call_ja/img/22-1.png new file mode 100644 index 000000000..8264a2c89 Binary files /dev/null and b/Languages/ja/22_Call_ja/img/22-1.png differ diff --git a/Languages/ja/22_Call_ja/img/22-2.png b/Languages/ja/22_Call_ja/img/22-2.png new file mode 100644 index 000000000..fc6cf96c8 Binary files /dev/null and b/Languages/ja/22_Call_ja/img/22-2.png differ diff --git a/Languages/ja/22_Call_ja/readme.md b/Languages/ja/22_Call_ja/readme.md new file mode 100644 index 000000000..004eaa002 --- /dev/null +++ b/Languages/ja/22_Call_ja/readme.md @@ -0,0 +1,172 @@ +--- +title: 22. Call +tags: + - solidity + - advanced + - wtfacademy + - call contract + - call +--- + +# WTF Solidity 超シンプル入門: 22. Call + +最近、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) + +--- + +以前、私たちは[第 20 回:ETH の送金](https://github.com/AmazingAng/WTFSolidity/tree/main/20_SendETH)にて`call`を使って ETH を送金する方法を紹介しました。今回は、`call`を使って他のコントラクトを呼び出す方法について説明します。 + +## Call + +`call`は`address`型の低レベルのメンバー関数です。他のコントラクトとのやり取りに使用されます。返り値は`(bool, bytes memory)`で、それぞれ`call`が成功したかどうかと、対象関数の返り値を表します。 + +- `call`は`fallback`関数または`receive`関数をトリガーして`ETH`を送信するための`Solidity`の推奨方法です。 + +- `call`を使って他のコントラクトを呼び出すのをおすすめしません。なぜなら、セキュアでないコントラクトを呼び出すとき、コントロール権を渡すことになるからです。他のコントラクトを呼び出す場合は、コントラクト変数を宣言して関数を呼び出すことがおすすめです。[第 21 回:コントラクトの呼び出し](https://github.com/AmazingAng/WTFSolidity/tree/main/21_CallContract)を参照してほしいです。 +- 他のコントラクトのソースコードや`ABI`がわからない場合、コントラクト変数を作成するには難があるので、`call`を使って他のコントラクトの関数を呼び出すことができます。 + +### `call`を使うときのルール + +`call`を使うときのルールは以下: + +```text +ターゲットコントラクトのアドレス.call(バイトコード); +``` + +その中で`バイトコード`は`abi.encodeWithSignature("関数名(パラメータの型)", パラメータ)`を使って取得可能です。 + +```text +abi.encodeWithSignature("関数シグネチャ", 半角カンマ区切りの具体的な引数) +``` + +`関数シグネチャ`は`"関数名(コンマで繋いだ引数)"`です。例えば`abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 + +それに`call`はコントラクトを呼び出すとき、送信する`ETH`量と`gas`量を指定することができます。`value`と`gas`を指定する場合、`call`の構文は以下のようになります: + +```text +ターゲットのコントラクトアドレス.call{value:送金額, gas:ガス量}(バイトコード); +``` + +複雑そうかもしれません。次に、理解を深めるため、`call`を使ったコードサンプルを見ましょう。 + +### ターゲットコントラクト + +まずは、シンプルな`OtherContract`コントラクトを作成・デプロイしましょう。コードは第 21 回とほぼ同じです。唯一の違いは`fallback`関数が追加されています。 + +```solidity +contract OtherContract { + uint256 private _x = 0; // 状態変数_x + // ethを受け取るイベント、amountとgasを記録 + event Log(uint amount, uint gas); + + fallback() external payable{} + + // コントラクトのETH残高を返す関数 + function getBalance() view public returns(uint) { + return address(this).balance; + } + + // _xの値を設定できる関数。同時にコントラクトへETHを送信することもできる(payable) + function setX(uint256 x) external payable{ + _x = x; + // もしETHの送信がある場合のみLogイベントを放出 + if(msg.value > 0){ + emit Log(msg.value, gasleft()); + } + } + + // xの値を取得する関数 + function getX() external view returns(uint x){ + x = _x; + } +} +``` + +このコントラクトは一つの状態変数`x`、`Log`イベント、3 つの関数を含んでいます。 + +- `getBalance()`: コントラクトの`ETH`残高を返す +- `setX()`: `external payable`関数で、`x`の値を設定し、コントラクトへ`ETH`も送金できる +- `getX()`: `x`の値を取得する + +### `call`を使って他のコントラクトを呼び出す + +#### 1. Response イベント + +私たちは`Call`のコントラクトを作成し、ターゲットコントラクトの関数を呼び出します。まずは Response ベントを定義しました。このイベントは呼び出しに際しての結果 success と data を出力して、便利に debug できるように準備しました。 + +```solidity +// Response イベントは`call`の結果`success`と`data`を出力します +event Response(bool success, bytes data); +``` + +#### 2. setX 関数を呼び出す + +私たちは`callSetX`関数を作成してターゲットコントラクトの`setX()`を呼び出します。同時に`msg.value`の`ETH`を送信し、`Response`イベントを放出して`success`と`data`を出力します。 + +```solidity +function callSetX(address payable _addr, uint256 x) public payable { + // setX()をcallし、ETHを送信 + (bool success, bytes memory data) = _addr.call{value: msg.value}( + abi.encodeWithSignature("setX(uint256)", x) + ); + + emit Response(success, data); // イベントを放出 +} +``` + +次に私たちは`callSetX`を呼び出して`_x`を`5`に変更します。パラメータに`OtherContract`のアドレスと`5`を入力します。目標関数`setX()`は返り値がないため、`Response`イベントの`data`は`0x`となります。 + +![22-1](./img/22-1.png) + +#### 3. getX 関数を呼び出す + +続いて`getX()`関すを呼び出しましょう。これによって`_x`の値を返されます。型は`uint256`、`abi.decode`を使って`data`を解読し、数値を取得します。 + +```solidity +function callGetX(address _addr) external returns(uint256){ + // call getX() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("getX()") + ); + + emit Response(success, data); //释放事件 + return abi.decode(data, (uint256)); +} +``` + +`Response`イベントの出力から、`data`が`0xとなり、`abi.decode`を通じて最終的な返り値は`5`になっていることがわかります。 + +![22-2](./img/22-2.png) + +#### 4. 存在しない関数を呼び出す + +もし`call`に存在しない関数を入力した場合、ターゲットコントラクトの`fallback`関数がトリガーされます。 + +```solidity +function callNonExist(address _addr) external{ + // 存在しない関数を呼び出す + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("foo(uint256)") + ); + + emit Response(success, data); // イベントを放出 +} +``` + +上記のサンプルでは、私たちは存在しない`foo`関数を呼び出し、`call`は成功して`success`を返しますが、実際にはターゲットコントラクトの`fallback`関数がトリガーされます。 + +![22-3](./img/22-3.png) + +## まとめ + +今回は、私たちは`call`を使って他のコントラクトを呼び出す方法について説明しました。 + +`call`は他のコントラクトを呼び出すのに使われますが、セキュリティ上のリスクがあるため、他のコントラクトを呼び出す際はコントラクト変数を宣言して関数を呼び出すことが推奨されます。 + +しかし、この`call`の呼び出し方はコントラクトのソースコードや`ABI`がわからない場合にも使えるので、非常に有用な方法です。次回は、`delegatecall`について説明します。 diff --git a/Languages/ja/23_Delegatecall_ja/Delegatecall.sol b/Languages/ja/23_Delegatecall_ja/Delegatecall.sol new file mode 100644 index 000000000..2e3c1e4c2 --- /dev/null +++ b/Languages/ja/23_Delegatecall_ja/Delegatecall.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// delegatecallはcallと似ており、低レベル関数である +// callの場合、BがCをcallすると、コンテキストはCとなる。(msg.sender = B, Cの中の状態変数は影響を受ける) +// delegatecallの場合、BがCをdelegatecallすると、コンテキストはBとなる。(msg.sender = A, Bの中の状態変数が影響を受ける) +// 注意B和C的数据存储布局必须相同!变量类型、声明的前后顺序要相同,不然会搞砸合约。 +// 注意したいのは、B, Cにあるストレージのストラクチャが同じでなければならないこと。そうでないとごちゃまぜになってしまいます。 + +// 呼び出されるコントラクトC +contract C { + uint public num; + address public sender; + + function setVars(uint _num) public payable { + num = _num; + sender = msg.sender; + } +} + +// delegatecallをする側のコントラクトB +contract B { + uint public num; + address public sender; + + // callを通じてCコントラクトを呼び出すと、Cのストレージが影響を受ける + function callSetVars(address _addr, uint _num) external payable{ + // call setVars() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } + // delegatecallを通じてCのsetVars()関数を呼び出し、コントラクトBの状態変数が変更される + function delegatecallSetVars(address _addr, uint _num) external payable{ + // delegatecall setVars() + (bool success, bytes memory data) = _addr.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); + } +} diff --git a/Languages/ja/23_Delegatecall_ja/img/23-1.png b/Languages/ja/23_Delegatecall_ja/img/23-1.png new file mode 100644 index 000000000..1d5b315c1 Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-1.png differ diff --git a/Languages/ja/23_Delegatecall_ja/img/23-2.png b/Languages/ja/23_Delegatecall_ja/img/23-2.png new file mode 100644 index 000000000..fca4e5ee5 Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-2.png differ diff --git a/Languages/ja/23_Delegatecall_ja/img/23-3.png b/Languages/ja/23_Delegatecall_ja/img/23-3.png new file mode 100644 index 000000000..d8de6ac8c Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-3.png differ diff --git a/Languages/ja/23_Delegatecall_ja/img/23-4.png b/Languages/ja/23_Delegatecall_ja/img/23-4.png new file mode 100644 index 000000000..e4446a58d Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-4.png differ diff --git a/Languages/ja/23_Delegatecall_ja/img/23-5.png b/Languages/ja/23_Delegatecall_ja/img/23-5.png new file mode 100644 index 000000000..1a84c9220 Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-5.png differ diff --git a/Languages/ja/23_Delegatecall_ja/img/23-6.png b/Languages/ja/23_Delegatecall_ja/img/23-6.png new file mode 100644 index 000000000..6198187cb Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-6.png differ diff --git a/Languages/ja/23_Delegatecall_ja/img/23-7.png b/Languages/ja/23_Delegatecall_ja/img/23-7.png new file mode 100644 index 000000000..a76cdead6 Binary files /dev/null and b/Languages/ja/23_Delegatecall_ja/img/23-7.png differ diff --git a/Languages/ja/23_Delegatecall_ja/readme.md b/Languages/ja/23_Delegatecall_ja/readme.md new file mode 100644 index 000000000..6ce0781a1 --- /dev/null +++ b/Languages/ja/23_Delegatecall_ja/readme.md @@ -0,0 +1,165 @@ +--- +title: 23. Delegatecall +tags: + - solidity + - advanced + - wtfacademy + - call contract + - delegatecall +--- + +# WTF Solidity 超シンプル入門: 23. Delegate + +最近、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) + +--- + +## `Delegatecall` + +`delegatecall`は`call`と似ており、`Solidity`におけるアドレス型の低いレベルのメンバー関数です。`delegate`は「委任・プロキシ」を意味していますが、一体`delegatecall`は何を委任しているのでしょうか。 + +### `call`の場合 + +ユーザー`A`がコントラクト`B`を通してコントラクト`C`を`call`すると、コントラクト`C`の関数が実行され、`Context`(コンテキスト:状態変数のストレージ、msg.sender など)もコントラクト`C`になります。この場合、`msg.sender`は`B`のアドレスであり、関数がいくつかの状態変数を変更する場合、その効果はコントラクト`C`の変数に影響を与えます。 + +![call的上下文](./img/23-1.png) + +### `delegatecall`の場合 + +ユーザー`A`がコントラクト`B`を通してコントラクト`C`を`delegatecall`すると、コントラクト`C`の関数が実行されますが、`Context`(コンテキスト:状態変数のストレージ、msg.sender など)はコントラクト`B`になります。この場合、`msg.sender`は`A`のアドレスであり、関数がいくつかの状態変数を変更する場合、その効果はコントラクト`B`の変数に影響を与えます。 + +![delegatecallのコンテキスト](./img/23-2.png) + +皆さんはこのように理解するとよいです。投資家(ユーザー`A`)は彼自身の資産(`B`コントラクトの`状態変数`)を資産管理会社(`C`コントラクト)に管理させてます。実行されるのは資産管理会社の関数(`C`)ですが、変更されるのは資産(`B`)の状態です。 + +`delegatecall`の文法は`call`と似ています。 + +このようになります: + +```solidity +ターゲットコントラクト.delegatecall(バイトコード); +``` + +その中で、`バイトコード`は`abi.encodeWithSignature`を使って取得されます。 + +```solidity +abi.encodeWithSignature("関数シグネチャ", カンマ区切りの引数); +``` + +`関数シグネチャ`は`"関数名(カンマ区切りの引数型)"`です。例えば`abi.encodeWithSignature("f(uint256,address)", _x, _addr)`。 + +`call`との違いとしては、`delegatecall`は`gas`を指定できますが、`ETH`の量を指定できません。 + +> **_注意_**: `delegatecall`を使うにはセキュリティリスクがあります。ときにはターゲットコントラクトがコントラクトとのストレージストラクチャが同じであることが求められます。そうでない場合、資産がなくされる可能性があります。 + +## どのような場合に`delegatecall`を使用するのか? + +現在、主に 2 つの`delegatecall`を使用する場面があります。 + +1. プロキシコントラクトのケース:スマートコントラクトのストレージコントラクトとロジックコントラクトを分離する。プロキシコントラクトはすべての関連する変数を保存し、ロジックコントラクトのアドレスを保存する。すべての関数はロジックコントラクト(`Logic Contract`)に存在し、`delegatecall`を使用して実行します。アップグレード時には、プロキシコントラクトを新しいロジックコントラクトに向けるだけです。 + +2. EIP-2535 Diamonds(ダイヤモンド):ダイヤモンドは、生産環境で拡張可能なモジュラーなスマートコントラクトシステムを構築するための標準です。ダイヤモンドは複数の実装コントラクトを持つプロキシコントラクトです。詳細はこちら:[ダイヤモンドスタンダードの紹介](https://eip2535diamonds.substack.com/p/introduction-to-the-diamond-standard)。 + +## `delegatecall`のサンプルコード + +呼び出し方:あなた(`A`)はコントラクト`B`を介してターゲットコントラクト`C`を呼び出します。 + +### 呼び出されるコントラクト`C` + +私たちはまず簡単なターゲットコントラクト`C`を作成します。`num`と`sender`の 2 つの`public`変数があります。それぞれ`uint256`と`address`の型です。`setVars`関数があり、`num`を渡された`_num`に設定し、`sender`を`msg.sender`に設定します。 + +```solidity +// 呼び出されるコントラクトC +contract C { + uint public num; + address public sender; + + function setVars(uint _num) public payable { + num = _num; + sender = msg.sender; + } +} +``` + +### 呼び出しをする側のコントラクト B + +まず、コントラクト`B`は必ずターゲットコントラクト`C`と同じストレージストラクチャでなければならず、2つの状態変数は`num`、`sender`です。 + +```solidity +contract B { + uint public num; + address public sender; +} +``` + +次に、それらの違いを理解するために、私たちはそれぞれ`call`や`delegatecall`を使ってコントラクト`C`の`setVars`関数を呼び出します。 + +`callSetVars`関数は`call`を通じて`setVars`を呼び出します。2つの引数`_addr`、`_num`があり、コントラクト`C`のアドレスや`setVars`の引数を意味しています。 + +以下はコントラクト B の一部です。 + +```solidity +// callを使ってCのsetVars()関数を呼び出す。これはCコントラクトの状態変数を変更する +function callSetVars(address _addr, uint _num) external payable{ + // call setVars() + (bool success, bytes memory data) = _addr.call( + abi.encodeWithSignature("setVars(uint256)", _num) + ); +} +``` + +一方で、`delegatecallSetVars`関数は`delegatecall`を通じて`setVars`関数を呼び出します。上の`callSetVars`関数と同じように、。2つの引数`_addr`、`_num`があり、コントラクト`C`のアドレスや`setVars`の引数を意味しています。 + +```solidity + // delegatecallを通じてCのsetVars()関数を呼び出し、コントラクトBの状態変数が変更される +function delegatecallSetVars(address _addr, uint _num) external payable{ + // delegatecall setVars() + (bool success, bytes memory data) = _addr.delegatecall( + abi.encodeWithSignature("setVars(uint256)", _num) + ); +} +``` + +### remix にて検証をする + +1. まず、私たちはコントラクト`B`、`C`コントラクトをデプロイする。 + + ![deploy.png](./img/23-1.png) + +2. デプロイ後、`C`コントラクトの状態変数の初期値を確認し、`B`コントラクトの状態変数と同じとなってることがわかる。 + + ![initialstate.png](./img/23-2.png) + +3. この時点で、コントラクト`B`にある`callSetVars`を呼び出し、いれる引数は`C`のアドレスと数字`10`。 + + ![call.png](./img/23-3.png) + +4. 関数を呼んだ後、コントラクト`C`にある状態変数が変更される:`num`は`10`になり、`sender`はコントラクト`B`のアドレスとなる。 + + ![resultcall.png](./img/23-4.png) + +5. 続いてコントラクト`B`にある`delegatecallSetVars`関数を呼び出し、いれる引数はコントラクト`C`と数字の`100`。 + + ![delegatecall.png](./img/23-5.png) + +6. `delegatecall`なので、コンテキストはコントラクト`B`である。呼んだ後、コントラクト`B`の状態変数はこのように変更された。 + + 1. `num`は`100` + 2. sender はあなたのウォレットアドレス + 3. コントラクト`C`の状態変数は不変 + + ![resultdelegatecall.png](./img/23-6.png) + +## まとめ + +今回、私たちは`Solidity`のもう一個の低レベル関数`delegatecall`を紹介しました。 + +`call`と同様、他のコントラクトを呼び出すことができますが、実行されるコンテキスト(context)が異なります。`B call C`の場合、コンテキストは`C`です。`B delegatecall C`の場合、コンテキストは`B`です。 + +現在、`delegatecall`の最大の応用はプロキシコントラクトと`EIP-2535 Diamonds`(ダイヤモンド)です。 diff --git a/Languages/ja/24_Create_ja/Create.sol b/Languages/ja/24_Create_ja/Create.sol new file mode 100644 index 000000000..fabddadc1 --- /dev/null +++ b/Languages/ja/24_Create_ja/Create.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Pair { + address public factory; // ファクトリコントラクト + address public token0; // トークン1 + address public token1; // トークン2 + + constructor() payable { + factory = msg.sender; + } + + // デプロイ時に一度呼ばれる初期化関数 + function initialize(address _token0, address _token1) external { + require(msg.sender == factory, "UniswapV2: FORBIDDEN"); // sufficient check + token0 = _token0; + token1 = _token1; + } +} + +contract PairFactory { + mapping(address => mapping(address => address)) public getPair; // トークン1, 2によってpairのアドレスを調べれるようにするマップ変数 + address[] public allPairs; // すべてのpairのアドレスを格納する配列 + + function createPair(address tokenA, address tokenB) external returns (address pairAddr) { + // 新しいPairコントラクトをデプロイする + Pair pair = new Pair(); + // Pairコントラクトの初期化関数を呼び出す + pair.initialize(tokenA, tokenB); + // マップ変数を更新する + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} diff --git a/Languages/ja/24_Create_ja/img/24-1.png b/Languages/ja/24_Create_ja/img/24-1.png new file mode 100644 index 000000000..abcf063d0 Binary files /dev/null and b/Languages/ja/24_Create_ja/img/24-1.png differ diff --git a/Languages/ja/24_Create_ja/img/24-2.png b/Languages/ja/24_Create_ja/img/24-2.png new file mode 100644 index 000000000..2c7489455 Binary files /dev/null and b/Languages/ja/24_Create_ja/img/24-2.png differ diff --git a/Languages/ja/24_Create_ja/img/24-3.png b/Languages/ja/24_Create_ja/img/24-3.png new file mode 100644 index 000000000..2bcc7dbd0 Binary files /dev/null and b/Languages/ja/24_Create_ja/img/24-3.png differ diff --git a/Languages/ja/24_Create_ja/readme.md b/Languages/ja/24_Create_ja/readme.md new file mode 100644 index 000000000..efe31240a --- /dev/null +++ b/Languages/ja/24_Create_ja/readme.md @@ -0,0 +1,131 @@ +--- +title: 24. スマートコントラクトを使ってスマートコントラクトを作成する方法 +tags: + - solidity + - advanced + - wtfacademy + - create contract +--- + +# WTF Solidity 超シンプル入門: 24. スマートコントラクトを使ってスマートコントラクトを作成する方法 + +最近、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) + +--- + +イーサリアムのブロックチェーン上では、ユーザー(外部アカウント、`EOA`)がスマートコントラクトを作成できます。 + +実は、スマートコントラクトを使っても新しいスマートコントラクトを作成できます。 + +分散型取引所`uniswap`は、ファクトリコントラクト(`PairFactory`)を使用して無数のペアコントラクト(`Pair`)を作成しています。今回のレッスンでは、簡易版の`uniswap`を使って、スマートコントラクトを使ってスマートコントラクトを作成する方法を説明します。 + +## `create` + +スマートコントラクトを使ってスマートコントラクトを作成する方法は 2 種類あり、`create`と`create2`があります。今回は`create`を説明し、次回は`create2`を説明します。 + +`create`の使い方は非常に簡単です。それは新しいコントラクトを`new`するとともに、新しいコントラクトのコンストラクタに必要な引数を渡します。 + +```solidity +Contract x = new Contract{value: _value}(params) +``` + +今のコードの中で、`Contract`は作りたいコントラクト名です。`x`はコントラクトのアドレスです。もしコントラクトのコンストラクタが`payable`なら、作成時に`_value`量の`ETH`を送ることができます。`params`は新しいコントラクトのコンストラクタの引数です。 + +## 簡易版 Uniswap + +`Uniswap V2`[コアコントラクト](https://github.com/Uniswap/v2-core/tree/master/contracts)に 2 つのコントラクトが含まれています。 + +1. UniswapV2Pair: トークンペアのコントラクト → 流動性、トークンペアコントラクト、売買を管理しています。 +2. UniswapV2Factory: ファクトリコントラクト → 新しいトークンペアコントラクトの作成、トークンペアのアドレスを管理しています。 + +以下では`create`メソッドを使って、簡易版の`Uniswap`を実装します。`Pair`はペアアドレスを管理し、`PairFactory`は新しいペアを作成し、ペアアドレスを管理します。 + +### `Pair`コントラクト + +```solidity +contract Pair { + address public factory; // ファクトリコントラクト + address public token0; // トークン1 + address public token1; // トークン2 + + constructor() payable { + factory = msg.sender; + } + + // デプロイ時に一度呼ばれる初期化関数 + function initialize(address _token0, address _token1) external { + require(msg.sender == factory, "UniswapV2: FORBIDDEN"); // sufficient check + token0 = _token0; + token1 = _token1; + } +} +``` + +`Pair`コントラクトは非常にシンプルです。3 つの状態変数があります:`factory`、`token0`、`token1`。 + +コンストラクト関数`constructor`はデプロイ時に`factory`をファクトリコントラクトのアドレスに設定します。`initialize`関数はファクトリコントラクトがデプロイ直後に呼び出され、トークンアドレスを初期化し、`token0`と`token1`を更新します。 + +> **質問**:なぜ`uniswap`はコンストラクタの中で、`token0`と`token1`のアドレスを更新しないのですか? + +> **答え**:それは`uniswap`が`create2`を使ってコントラクトを作成するため、生成されたコントラクトアドレスを予測できるためです。詳細は[第 25 回](https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md)を参照してください。 + +### `PairFactory` + +```solidity +contract PairFactory { + mapping(address => mapping(address => address)) public getPair; // トークン1, 2によってpairのアドレスを調べれるようにするマップ変数 + address[] public allPairs; // すべてのpairのアドレスを格納する配列 + + function createPair(address tokenA, address tokenB) external returns (address pairAddr) { + // 新しいPairコントラクトをデプロイする + Pair pair = new Pair(); + // Pairコントラクトの初期化関数を呼び出す + pair.initialize(tokenA, tokenB); + // マップ変数を更新する + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} +``` + +ファクトリコントラクト(`PairFactory`)は2つの状態変数があります。`getPair`は2つのトークンアドレスからペアアドレスを取得するためのマップ変数です。`allPairs`はすべてのペアアドレスを格納する配列です。 + +`PairFactory`コントラクトは一つの`createPair`関数しかありません。入力された2つのトークンアドレス`tokenA`と`tokenB`に基づいて新しい`Pair`コントラクトを作成します。 +(実際のコントラクトはトークンペアの作成がすでにあるかチェックするコードがあるが、ここでは省いていると思われます) + +```solidity +Pair pair = new Pair(); +``` + +これはコントラクトを作成するコードです。非常に簡単です。`PairFactory`コントラクトをデプロイし、以下の2つのアドレスを引数として`createPair`を呼び出し、作成されたペアのアドレスを確認してみてください。 + +```text +WBNBアドレス: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 +BSCにあるPEOPLEアドレス: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c +``` + +### remix で検証する + +1. `WBNB`、`PEOPLE`のアドレスを引数として`createPair`を呼び出し、`Pair`コントラクトのアドレスを取得すると、`0xD3e2008b4Da2cD6DEAF73471590fF30C86778A48`となります。 + + ![24-1](./img/24-1.png) + +2. `Pair`コントラクトのアドレスを確認する + + ![24-2](./img/24-2.png) + +3. Debug して、`create`オペコードを見る + + ![24-3](./img/24-3.png) + +## まとめ + +今回、私たちは簡易版の`Uniswap`の例を使って、`create`メソッドを使ってコントラクトを作成する方法を説明しました。次回は別の方法の`create2`メソッドを使って簡易版の`Uniswap`を実装します。 diff --git a/Languages/ja/25_Create2_ja/Create2.sol b/Languages/ja/25_Create2_ja/Create2.sol new file mode 100644 index 000000000..7d81b996f --- /dev/null +++ b/Languages/ja/25_Create2_ja/Create2.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Pair { + address public factory; // ファクトリコントラクト + address public token0; // トークン1 + address public token1; // トークン2 + + constructor() payable { + factory = msg.sender; + } + + // デプロイ時に一度呼び出される + function initialize(address _token0, address _token1) external { + require(msg.sender == factory, "UniswapV2: FORBIDDEN"); // sufficient check + token0 = _token0; + token1 = _token1; + } +} + +contract PairFactory2 { + mapping(address => mapping(address => address)) public getPair; // トークンのアドレスからペアのアドレスを参照する用 + address[] public allPairs; // すべてのペアアドレスを保存する + + function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { + require(tokenA != tokenB, "IDENTICAL_ADDRESSES"); // token Aとtoken Bが同じアドレスでないことを確認 + + // token Aとtoken Bをソートして小さい方をtoken0に、大きい方をtoken1にする + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + // token0、token1を使ってkeccak256でsaltを計算 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // create2をつかってコントラクトをデプロイする + Pair pair = new Pair{salt: salt}(); + // 新しいコントラクトのinitialize関数を呼び出す + pair.initialize(tokenA, tokenB); + // アドレスのマップallPairsを更新 + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } + + // ペアのアドレスをあらかじめ計算する関数 + function calculateAddr(address tokenA, address tokenB) public view returns (address predictedAddress) { + require(tokenA != tokenB, "IDENTICAL_ADDRESSES"); // token Aとtoken Bが同じアドレスでないことを確認 + + // token Aとtoken Bをソートして小さい方をtoken0に、大きい方をtoken1にする + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + // token0、token1を使ってkeccak256でsaltを計算 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // hash()を使ってコントラクトアドレスを計算する + predictedAddress = address( + uint160( + uint256( + keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(type(Pair).creationCode))) + ) + ) + ); + } +} diff --git a/Languages/ja/25_Create2_ja/create2test.js b/Languages/ja/25_Create2_ja/create2test.js new file mode 100644 index 000000000..11b629e8c --- /dev/null +++ b/Languages/ja/25_Create2_ja/create2test.js @@ -0,0 +1,34 @@ +const { expect } = require("chai"); +const { ethers } = require("hardhat"); + +describe("create2 test", function () { + it("Should return the new create2test once it's changed", async function () { + console.log("1.==> deploy pair"); + const PairFactory = await ethers.getContractFactory("Pair"); + const Pair = await PairFactory.deploy(); + await Pair.waitForDeployment(); + console.log("pair address =>",Pair.target); + + console.log(); + console.log("2.==> deploy PairFactory2"); + const PairFactory2Factory = await ethers.getContractFactory("PairFactory2"); + const PairFactory2 = await PairFactory2Factory.deploy(); + await PairFactory2.waitForDeployment(); + console.log("PairFactory2 address =>",PairFactory2.target); + + console.log("3.==> calculateAddr for wbnb people"); + const WBNBAddress = "0x2c44b726ADF1963cA47Af88B284C06f30380fC78"; + const PEOPLEAddress = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"; + + let predictedAddress = await PairFactory2.calculateAddr(WBNBAddress, PEOPLEAddress); + console.log("predictedAddress address =>",predictedAddress); + + console.log("4.==> createPair2 for wbnb people"); + await PairFactory2.createPair2(WBNBAddress, PEOPLEAddress); + let createPair2Address = await PairFactory2.allPairs(0); + console.log("createPair2Address address =>",createPair2Address); + + expect(createPair2Address).to.equal(predictedAddress); + + }); +}); diff --git a/Languages/ja/25_Create2_ja/img/25-1.jpg b/Languages/ja/25_Create2_ja/img/25-1.jpg new file mode 100644 index 000000000..97e7c1499 Binary files /dev/null and b/Languages/ja/25_Create2_ja/img/25-1.jpg differ diff --git a/Languages/ja/25_Create2_ja/readme.md b/Languages/ja/25_Create2_ja/readme.md new file mode 100644 index 000000000..6510b3c1c --- /dev/null +++ b/Languages/ja/25_Create2_ja/readme.md @@ -0,0 +1,209 @@ +--- +title: 25. Create2 +tags: + - solidity + - advanced + - wtfacademy + - create contract + - create2 +--- + +# WTF Solidity 超シンプル入門: 25. Create2 + +最近、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) + +--- + +## CREATE2 + +CREATE2 オペコードを使用すると、スマートコントラクトがイーサリアムネットワークにデプロイされる前に、そのアドレスを予測できます。`Uniswap`は`Pair`コントラクトを作成する際に`CREATE2`を使用しています。このレッスンでは、`CREATE2`の使い方について説明します。 + +### CREATE はどのようにアドレスを決めているのか + +スマートコントラクトは、他のスマートコントラクトや一般アカウントによって`CREATE`オペコードを使用して作成されることがある。 これらの 2 つの場合、新しいスマートコントラクトのアドレスは同じ方法で計算されます。 + +- 作成者のアドレス(通常はデプロイされたウォレットアドレスまたはコントラクトアドレス)と`nonce`(そのアドレスが送信したトランザクションの合計数、コントラクトアカウントの場合は作成されたコントラクトの合計数、コントラクトを作成するたびに nonce+1)のハッシュ値。 + +```text +新しいアドレス = hash(作成者アドレス, nonce) +``` + +作成者のアドレスは不変なのに対し、`nonce`は時間とともに変化する可能性があるため、`CREATE`で作成されたコントラクトのアドレスは予測しにくいという特徴を持っています。 + +### CREATE2 はどのようにアドレスを決めているのか + +`CREATE2`の目的は将来のイベントと分離するためにある。将来何が起ころうとも、事前に計算されたアドレスにコントラクトをデプロイできます。`CREATE2`で作成されたコントラクトのアドレスは 4 つの要素で決定されます: + +- `0xFF`:定数。`CREATE`との衝突を避けるためにある +- `CreatorAddress`: `CREATE2`を呼び出す当該コントラクトのアドレス +- `salt`(塩):作成者が指定する`bytes32`型の値で、新しいコントラクトのアドレスに一味別の影響を与えるためのもの(塩のような存在) +- `initcode`:新しいコントラクトの初期バイトコード(コントラクトの creation code とコンストラクタの引数)。 + +```text +新しいアドレス = hash("0xFF",作成者アドレス, salt, initcode) +``` + +`CREATE2`は、作成者が指定した`salt`を使用して、`initcode`をデプロイすると、そのコントラクトが`新しいアドレス`に格納されることを保証することができました。 + +## `CREATE2`はどう使うか + +`CREATE2`の使い方は`CREATE`と似ており、新しいコントラクトを`new`するとともに、新しいコントラクトのコンストラクタに必要な引数を渡します。ただし、余計に`salt`を指定する必要があります。 + +```solidity +Contract x = new Contract{salt: _salt, value: _value}(params) +``` + +その中で`Contract`は作りたいコントラクトの名前です。`x`はコントラクトのアドレスで、`_salt`は指定した任意の値。もしコントラクトのコンストラクタが`payable`なら、作成時に`_value`量の`ETH`を送ることができます。`params`は新しいコントラクトのコンストラクタの引数です。 + +## 簡易版 Uniswap2 + +[前回](https://mirror.xyz/wtfacademy.eth/kojopp2CgDK3ehHxXc_2fkZe87uM0O5OmsEU6y83eJs)と同じように、今回私たちは`CREATE2`を使って簡易版の`Uniswap`を実装します。 + +### `Pair` + +```solidity +contract Pair{ + address public factory; // ファクトリコントラクト + address public token0; // トークン1 + address public token1; // トークン2 + + constructor() payable { + factory = msg.sender; + } + + // デプロイ時に一度呼び出される + function initialize(address _token0, address _token1) external { + require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check + token0 = _token0; + token1 = _token1; + } +} +``` + +`Pair`コントラクトは非常にシンプルです。3 つの状態変数があります:`factory`、`token0`、`token1`。 + +`constructor`関数はデプロイ時に、`factory`をファクトリーのコントラクトアドレスにします。`initialize`関数は`Pair`コントラクトの作成時に一度だけ呼び出され、`token0`と`token1`を更新します。 + +### `PairFactory2` + +```solidity +contract PairFactory2{ + mapping(address => mapping(address => address)) public getPair; // トークンのアドレスからペアのアドレスを参照する用 + address[] public allPairs; // すべてのペアアドレスを保存する + + function createPair2(address tokenA, address tokenB) external returns (address pairAddr) { + require(tokenA != tokenB, "IDENTICAL_ADDRESSES"); // token Aとtoken Bが同じアドレスでないことを確認 + + // token Aとtoken Bをソートして小さい方をtoken0に、大きい方をtoken1にする + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + // token0、token1を使ってkeccak256でsaltを計算 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // create2をつかってコントラクトをデプロイする + Pair pair = new Pair{salt: salt}(); + // 新しいコントラクトのinitialize関数を呼び出す + pair.initialize(tokenA, tokenB); + // アドレスのマップallPairsを更新 + pairAddr = address(pair); + allPairs.push(pairAddr); + getPair[tokenA][tokenB] = pairAddr; + getPair[tokenB][tokenA] = pairAddr; + } +} +``` + +ファクトリーコントラクトの`PairFactory2`は2つの状態変数を持っており、`getPair`は2つのトークンアドレスからペアアドレスを取得するためのマップ変数です。`allPairs`はすべてのペアアドレスを格納する配列です。 + +`PairFactory2`コントラクトは一つの`createPair2`関数しかありません。入力された2つのトークンアドレス`tokenA`と`tokenB`に基づいて新しい`Pair`コントラクトを作成します。 + +その中で、 + +```solidity +Pair pair = new Pair{salt: salt}(); +``` + +は`CREATE2`を使ってコントラクトを作成しています。非常にシンプルですね。`salt`は`token1`と`token2`の`hash`値を利用しています。 + +```solidity +bytes32 salt = keccak256(abi.encodePacked(token0, token1)); +``` + +### 事前に`Pair`のアドレスを計算 + +```solidity +// ペアのアドレスをあらかじめ計算する関数 +function calculateAddr(address tokenA, address tokenB) public view returns (address predictedAddress) { + require(tokenA != tokenB, "IDENTICAL_ADDRESSES"); // token Aとtoken Bが同じアドレスでないことを確認 + + // token Aとtoken Bをソートして小さい方をtoken0に、大きい方をtoken1にする + (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + // token0、token1を使ってkeccak256でsaltを計算 + bytes32 salt = keccak256(abi.encodePacked(token0, token1)); + // hash()を使ってコントラクトアドレスを計算する + predictedAddress = address( + uint160( + uint256( + keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(type(Pair).creationCode))) + ) + ) + ); +} +``` + +私たちは`calculateAddr`関数を作り、事前に`tokenA`と`tokenB`によって生成される`Pair`のアドレスを計算しました。これを使って、計算したアドレスと実際のアドレスが一致するかどうかを確認できます。 + +皆さんはデプロイ済みの`PairFactory2`コントラクトを使って、以下のアドレスをパラメーターとして`createPair2`を呼び出し、作成されたペアのアドレスがどうなるか、事前に計算したアドレスと一致するかどうかを確認できます。 + +```text +WBNBアドレス: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 +BSCオンチェーンのPEOPLEアドレス: 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c +``` + +#### もし`constructor`関数に引数がある場合 + +例えば、`create2`を使ってコントラクトを作成する場合: + +> Pair pair = new Pair{salt: salt}(address(this)); + +計算時に、引数と`initcode`を一緒にパッケージにする必要があります。 + +> ~~keccak256(type(Pair).creationCode)~~ + +↓ + +> keccak256(abi.encodePacked(type(Pair).creationCode, abi.encode(address(this)))) + +```solidity +predictedAddress = address(uint160(uint(keccak256(abi.encodePacked( + bytes1(0xff), + address(this), + salt, + keccak256(abi.encodePacked(type(Pair).creationCode, abi.encode(address(this)))) + ))))); +``` + +### remix にて検証する + +1. まず、`WBNB`と`PEOPLE`のアドレスハッシュを`salt`として使って`Pair`コントラクトのアドレスを計算 +2. `PairFactory2.createPair2`を呼び出し、`WBNB`と`PEOPLE`のアドレスを引数として渡します。`pair`コントラクトのアドレスを取得 +3. コントラクトアドレスを比較 + + ![create2_remix_test.png](./img/25-1.jpg) + +## create2 実際の活用シーン + +3. 取引所が新規ユーザーのためのウォレットコントラクトアドレスを予約する。 +4. `CREATE2`を使って`factory`コントラクトを作成し、`Uniswap V2`の中でペアを作成するのは`Factory`で行います。これをするメリットとしては、確定した`pair`アドレスを得ることができ、`Router`で`(tokenA, tokenB)`を使って`pair`アドレスを計算できるようになります。もう一度`Factory.getPair(tokenA, tokenB)`というコントラクトをまたぐ呼び出しを実行する必要がなくなります。 + +## まとめ + +今回、私たちは`CREATE2`のオペコードの原理と使い方を紹介し、簡易版の`Uniswap`を作成しました。 + +事前にペアのコントラクトアドレスを計算しました。`CREATE2`を使うことで、コントラクトをデプロイする前にそのアドレスを予測できるようになります。 + +これはいくつかの`layer2`プロジェクトのベースの知識にもなります。 diff --git a/Languages/ja/26_DeleteContract_ja/DeleteContract.sol b/Languages/ja/26_DeleteContract_ja/DeleteContract.sol new file mode 100644 index 000000000..77a9c5bb5 --- /dev/null +++ b/Languages/ja/26_DeleteContract_ja/DeleteContract.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// selfdestruct: コントラクトを削除し、残りのETHを指定したアドレスに送金 + +contract DeleteContract { + uint256 public value = 10; + + constructor() payable {} + + receive() external payable {} + + function deleteContract() external { + // コントラクトを削除し、コントラクトの残りETHをmsg.senderに送金 + selfdestruct(payable(msg.sender)); + } + + function getBalance() external view returns (uint256 balance) { + balance = address(this).balance; + } +} diff --git a/Languages/ja/26_DeleteContract_ja/DeployContract.sol b/Languages/ja/26_DeleteContract_ja/DeployContract.sol new file mode 100644 index 000000000..0ac94eb1d --- /dev/null +++ b/Languages/ja/26_DeleteContract_ja/DeployContract.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "./DeleteContract.sol"; + +contract DeployContract { + + struct DemoResult { + address addr; + uint balance; + uint value; + } + + constructor() payable {} + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } + + function demo() public payable returns (DemoResult memory){ + DeleteContract del = new DeleteContract{value:msg.value}(); + DemoResult memory res = DemoResult({ + addr: address(del), + balance: del.getBalance(), + value: del.value() + }); + del.deleteContract(); + return res; + } +} \ No newline at end of file diff --git a/Languages/ja/26_DeleteContract_ja/img/26-1.png b/Languages/ja/26_DeleteContract_ja/img/26-1.png new file mode 100644 index 000000000..3ddfa21d1 Binary files /dev/null and b/Languages/ja/26_DeleteContract_ja/img/26-1.png differ diff --git a/Languages/ja/26_DeleteContract_ja/img/26-2.png b/Languages/ja/26_DeleteContract_ja/img/26-2.png new file mode 100644 index 000000000..6876864f2 Binary files /dev/null and b/Languages/ja/26_DeleteContract_ja/img/26-2.png differ diff --git a/Languages/ja/26_DeleteContract_ja/img/26-3.png b/Languages/ja/26_DeleteContract_ja/img/26-3.png new file mode 100644 index 000000000..3b5916a0e Binary files /dev/null and b/Languages/ja/26_DeleteContract_ja/img/26-3.png differ diff --git a/Languages/ja/26_DeleteContract_ja/img/26-4.png b/Languages/ja/26_DeleteContract_ja/img/26-4.png new file mode 100644 index 000000000..3d8c964ff Binary files /dev/null and b/Languages/ja/26_DeleteContract_ja/img/26-4.png differ diff --git a/Languages/ja/26_DeleteContract_ja/img/26-5.png b/Languages/ja/26_DeleteContract_ja/img/26-5.png new file mode 100644 index 000000000..5fee4215b Binary files /dev/null and b/Languages/ja/26_DeleteContract_ja/img/26-5.png differ diff --git a/Languages/ja/26_DeleteContract_ja/img/26-6.png b/Languages/ja/26_DeleteContract_ja/img/26-6.png new file mode 100644 index 000000000..25859fd37 Binary files /dev/null and b/Languages/ja/26_DeleteContract_ja/img/26-6.png differ diff --git a/Languages/ja/26_DeleteContract_ja/readme.md b/Languages/ja/26_DeleteContract_ja/readme.md new file mode 100644 index 000000000..3cb288b30 --- /dev/null +++ b/Languages/ja/26_DeleteContract_ja/readme.md @@ -0,0 +1,168 @@ +--- +title: 26. コントラクトの削除 + +tags: + - solidity + - advanced + - wtfacademy + - selfdestruct + - delete contract +--- + +# WTF Solidity 超シンプル入門: 26. コントラクトの削除 + +最近、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) + +--- + +## `selfdestruct` + +`selfdestruct`コマンドはコントラクトを削除するのに使います。同時にコントラクトに残った ETH を指定したアドレスへ送金することができます。 + +`selfdestruct`はコントラクトに間違いなどがおきた時のために設計されたもので、最初の頃には suicide と命名されていました。 + +しかし、このワードがあまりよろしくなく、ただでさえ鬱っぽいプログラマたちのために、`selfdestruct`と改名されました。[v0.8.18](https://blog.soliditylang.org/2023/02/01/solidity-0.8.18-release-announcement/) において、`selfdestruct` は「使用をおすすめしない」と決められました。 + +時には予期しない挙動をすることがあります。現在は代替手段がないため、開発者に対してコンパイル時の警告が出されるだけです。詳細は[EIP-6049](https://eips.ethereum.org/EIPS/eip-6049)を参照してください。 + +それに、イーサリアムの Cancun アップグレードにおいては、[EIP-6780](https://eips.ethereum.org/EIPS/eip-6780)はアップグレードに含まれていて、`Verkle Tree`へのサポートのために実施されました。 + +EIP-6780 は`SELFDESTRUCT`のオペコードの機能を減らしました。EIP の内容によると、現在`SELFDESTRUCT`は ETH を指定のアドレスへ送金することだけに対応しています。 + +従前の機能としてあったコントラクトを削除することはコントラクト作成及び削除が同じトランザクションにあった時にだけ機能できるとなりました。 + +現在の機能としては、 + +1. すでにデプロイ済みのコントラクトはもう`SELFDESTRUCT`して削除することができません。 +2. もし`SELFDESTRUCT`機能を使いたいのなら、必ず同じトランザクションにおいて使う必要があります。 + +### `selfdestruct`の使い方 + +`selfdestruct`の使用は非常にシンプルです: + +```solidity +selfdestruct(_addr); +``` + +その中で`_addr`はコントラクトに残りの ETH を受け取るためのアドレスで、`_addr`には`receive()`や`fallback()`のような関数を持っていなくても受け取れる様になっています。 + +### 例 - ETH の送金 + +以下は Cancun アップグレードより前に、`selfdestruct`を使ってコントラクトを削除することができる例です。 + +Cancun アップグレード後は、コントラクトの削除は内部の ETH の残高の移動のみが可能です。 + +```solidity +contract DeleteContract { + + uint public value = 10; + + constructor() payable {} + + receive() external payable {} + + function deleteContract() external { + // コントラクトを削除し、コントラクトの残りETHをmsg.senderに送金 + selfdestruct(payable(msg.sender)); + } + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } +} +``` + +`DeleteContract`コントラクトには`public`な`value`変数、`getBalance()`関数と`deleteContract()`関数があります。`getBalance()`関数はコントラクトの ETH 残高を返し、`deleteContract()`関数はコントラクトを削除し、コントラクトの ETH を呼び出し元に送金します。 + +コントラクトをデプロイした後、`DeleteContract`コントラクトに 1 ETH を送金します。`getBalance()`関数は 1 ETH を返し、`value`変数は 10 になります。 + +その後、`deleteContract()`関数を呼び出します。コントラクトは削除され、1 ETH は呼び出し元に送金されます。 +**Cancun アップグレード後、コントラクトは削除されますが、コントラクトの ETH は指定のアドレスに移動され、コントラクトは引き続き呼び出し可能です。** + +#### remix での検証 + +##### Before Cancun アップグレード + +1. コントラクトをデプロイし、1ETH を入金し、コントラクトのステートを確認 + + ![deployContract.png](./img/26-1.png) + +2. コントラクトを削除し、コントラクトのステートを確認 + + ![deleteContract.png](./img/26-2.png) + + テスト中にコントラクトのステートを観察すると、コントラクトが保持していた ETH がゼロになり(指定のアドレスに返却された)、コントラクトの関数を呼び出すことができないことがわかります。 + +##### After Cancun アップグレード + +1. コントラクトをデプロイし、1ETH を入金し、コントラクトのステートを確認 + + ![deployContract2.png](./img/26-3.png) + +2. コントラクトを削除し、コントラクトのステートを確認 + + ![deleteContract2.png](./img/26-4.png) + + テスト中、コントラクトのステートを観察すると、コントラクトが保持していた ETH がゼロになり(指定のアドレスに返却された)、コントラクトの関数を呼び出すことができることがわかります。 + +### 例 - 同じトランザクション内での`selfdestruct` + +EIP によると、以前機能していた削除機能は、コントラクトの作成と削除が同じトランザクションにあった時にだけ機能できるとなりました。そのため、別のコントラクトを使って制御する必要があります。 + +```solidity +contract DeployContract { + + struct DemoResult { + address addr; + uint balance; + uint value; + } + + constructor() payable {} + + function getBalance() external view returns(uint balance){ + balance = address(this).balance; + } + + function demo() public payable returns (DemoResult memory){ + DeleteContract del = new DeleteContract{value:msg.value}(); + DemoResult memory res = DemoResult({ + addr: address(del), + balance: del.getBalance(), + value: del.value() + }); + del.deleteContract(); + return res; + } +} +``` + +#### remix での検証 + +1. `DeployContract`コントラクトをデプロイし、1ETH を入金し、`demo()`関数を呼び出し、コントラクトのステートを確認します。`DeleteContract`が正しくデプロイされ、`selfdestruct`後に ETH が`DeployContract`に移動されていることがわかります。 + + ![deployContract3.png](./img/26-5.png) + +2️. `DeleteContract`のアドレスを選択してインポートし、`DeleteContract`には ETH がないことがわかり、コントラクト関数の呼び出しも失敗します。 + +![deleteContract3.png](./img/26-6.png) + +### 注意点 + +1. 外部に対してコントラクトの削除のインターフェースを提供する場合、コントラクトの所有者だけが呼び出せるようにすることが最善です。関数宣言時に`onlyOwner`修飾子を使用することができます。 + +2. コントラクトに`selfdestruct`機能があると、攻撃者に攻撃ベクトルを開くことになります(例えば、`selfdestruct`を使ってコントラクトに頻繁にトークンを送信する攻撃をすることで、GAS の費用を大幅に節約できます。ほとんどの人はそうしないでしょうけど)。また、この機能はコントラクトへのユーザーの信頼を低下させる可能性があります。 + +## まとめ + +`selfdestruct`はスマートコントラクトの緊急停止ボタンのようなものです。コントラクトを削除し、残りの ETH を指定のアドレスに送金します。 + +当初、あの有名な`The DAO`事件が発生した際、イーサリアムの創設者たちは`selfdestruct`をコントラクトに追加してなくて、ハッカーの攻撃を停止することができなかったのを後悔しているかもしれません。 + +Cancun アップグレード後、`selfdestruct`の機能も変わりました。現在の業界では、何がなんでも変わることがあるので、勉強を継続していくことの大切さが思い知らされました。 diff --git a/Languages/ja/27_ABIEncode_ja/ABIEncode.sol b/Languages/ja/27_ABIEncode_ja/ABIEncode.sol new file mode 100644 index 000000000..ae92b3429 --- /dev/null +++ b/Languages/ja/27_ABIEncode_ja/ABIEncode.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract ABIEncode{ + uint x = 10; + address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; + string name = "0xAA"; + uint[2] array = [5, 6]; + + function encode() public view returns(bytes memory result) { + result = abi.encode(x, addr, name, array); + } + + function encodePacked() public view returns(bytes memory result) { + result = abi.encodePacked(x, addr, name, array); + } + + function encodeWithSignature() public view returns(bytes memory result) { + result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); + } + + function encodeWithSelector() public view returns(bytes memory result) { + result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); + } + function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { + (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); + } +} diff --git a/Languages/ja/27_ABIEncode_ja/img/27-1.png b/Languages/ja/27_ABIEncode_ja/img/27-1.png new file mode 100644 index 000000000..447f25f79 Binary files /dev/null and b/Languages/ja/27_ABIEncode_ja/img/27-1.png differ diff --git a/Languages/ja/27_ABIEncode_ja/img/27-2.png b/Languages/ja/27_ABIEncode_ja/img/27-2.png new file mode 100644 index 000000000..ec060728e Binary files /dev/null and b/Languages/ja/27_ABIEncode_ja/img/27-2.png differ diff --git a/Languages/ja/27_ABIEncode_ja/img/27-3.png b/Languages/ja/27_ABIEncode_ja/img/27-3.png new file mode 100644 index 000000000..5edb4c7fe Binary files /dev/null and b/Languages/ja/27_ABIEncode_ja/img/27-3.png differ diff --git a/Languages/ja/27_ABIEncode_ja/img/27-4.png b/Languages/ja/27_ABIEncode_ja/img/27-4.png new file mode 100644 index 000000000..d878b9059 Binary files /dev/null and b/Languages/ja/27_ABIEncode_ja/img/27-4.png differ diff --git a/Languages/ja/27_ABIEncode_ja/img/27-5.png b/Languages/ja/27_ABIEncode_ja/img/27-5.png new file mode 100644 index 000000000..57f842134 Binary files /dev/null and b/Languages/ja/27_ABIEncode_ja/img/27-5.png differ diff --git a/Languages/ja/27_ABIEncode_ja/img/27-6.png b/Languages/ja/27_ABIEncode_ja/img/27-6.png new file mode 100644 index 000000000..5b9a95015 Binary files /dev/null and b/Languages/ja/27_ABIEncode_ja/img/27-6.png differ diff --git a/Languages/ja/27_ABIEncode_ja/readme.md b/Languages/ja/27_ABIEncode_ja/readme.md new file mode 100644 index 000000000..d75bfe409 --- /dev/null +++ b/Languages/ja/27_ABIEncode_ja/readme.md @@ -0,0 +1,176 @@ +--- +title: 27. ABIのエンコード、デコード +tags: + - solidity + - advanced + - wtfacademy + - abi encoding + - abi decoding +--- + +# WTF Solidity 超シンプル入門: 27. ABI のエンコード、デコード + +最近、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) + +--- + +`ABI`(Application Binary Interface,アプリケーションの二進数のインターフェース)は、イーサリアムスマートコントラクトとのインタラクションをするためのスタンダードです。 + +データはその型に基づいてエンコードされ、エンコードされたデータには型情報が含まれていないため、デコード時には型を指定する必要があります。 + +`Solidity`には、`ABIエンコード`に関連する 4 つの関数があります:`abi.encode`、`abi.encodePacked`、`abi.encodeWithSignature`、`abi.encodeWithSelector`。また、`ABIデコード`には 1 つの関数があります:`abi.decode`。このレッスンでは、これらの関数の使い方を学びます。 + +## ABI エンコード + +今回は 4 つの変数をエンコードします。それぞれの型は`uint256`(別名 uint), `address`, `string`, `uint256[2]`です: + +```solidity +uint x = 10; +address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71; +string name = "0xAA"; +uint[2] array = [5, 6]; +``` + +### `abi.encode` + +与えられたパラメータを[ABI ルール](https://learnblockchain.cn/docs/solidity/abi-spec.html)に従ってエンコードします。`ABI`はスマートコントラクトとのインタラクションに使用され、各パラメータを 32 バイトのデータで埋めて連結します。スマートコントラクトとのインタラクションを行う場合は、`abi.encode`を使用します。 + +```solidity +function encode() public view returns(bytes memory result) { + result = abi.encode(x, addr, name, array); +} +``` + +エンコードの結果は`0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`, + +`abi.encode`はデータごとに 32 バイトにするため、`0`が間にたくさん入っていることがわかります。 + +### `abi.encodePacked` + +`abi.encodePacked`は、最小のスペースコーディングに従って与えられたパラメータをエンコードします。`abi.encode`と似ていますが、埋め込まれた`0`が省略されます。たとえば、`uint8`型をエンコードする場合、1 バイトだけ使用します。スペースを節約したい場合、スマートコントラクトとのインタラクションがない場合は、`abi.encodePacked`を使用できます。 + +```solidity +function encodePacked() public view returns(bytes memory result) { + result = abi.encodePacked(x, addr, name, array); +} +``` + +エンコードの結果は`0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006`。`abi.encodePacked`で作成したコードが圧縮されるため,長さが`abi.encode`で作ったものより短くなります。 + +### `abi.encodeWithSignature` + +`abi.encodeWithSignature`は`abi.encode`と同様ですが、最初のパラメータが`関数シグネチャ`です。たとえば以下の例では`"foo(uint256,address,string,uint256[2])"`があります。 + +これは他のコントラクトを呼び出す場合に使用します。 + +```solidity +function encodeWithSignature() public view returns(bytes memory result) { + result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array); +} +``` + +エンコードの結果は`0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`。 + +`abi.encode`を下に 4 バイトの`関数セレクタ`[^说明]を追加したものとなります。 + +[^説明]:関数セレクタは関数名とパラメータを署名処理(Keccak–Sha3)して関数を識別するため、異なるコントラクト間での関数呼び出しに使用できます。 + +### `abi.encodeWithSelector` + +`abi.encodeWithSelector`は`abi.encodeWithSignature`と同様ですが、最初のパラメータが`関数セレクタ`です。これは`関数署名`Keccak ハッシュの最初の 4 バイトです。 + +```solidity +function encodeWithSelector() public view returns(bytes memory result) { + result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array); +} +``` + +エンコードの結果は`0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000`で、`abi.encodeWithSignature`の結果と同じです。 + +## ABI デコード + +### `abi.decode` + +`abi.decode`は`abi.encode`で生成されたバイナリエンコードをデコードし、元のパラメータに戻します。 + +```solidity +function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) { + (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2])); +} +``` + +`abi.encode`のバイナリエンコードを`decode`に入力すると、元のパラメータがデコードされてわかるようになります。 + +![27-3](https://images.mirror-media.xyz/publication-images/jboRaaq0U57qVYjmsOgbv.png?height=408&width=624) + +## remix で確認 + +- コントラクトをデプロイし、`encode`関数を呼び出す + + ![27-1](./img/27-1.png) + +- 4 種類のエンコード方法の違いを比較 + + ![27-2](./img/27-2.png) + +- abi.decode メソッドの結果を確認 + +![27-3](./img/27-3.png) + +## ABI のユースケース + +1. コントラクトの開発において、ABI は call と組み合わせて、コントラクトの低レベル呼び出しを実現します。 + +```solidity +bytes4 selector = contract.getValue.selector; + +bytes memory data = abi.encodeWithSelector(selector, _x); +(bool success, bytes memory returnedData) = address(contract).staticcall(data); +require(success); + +return abi.decode(returnedData, (uint256)); +``` + +2. ethers.js では、ABI はコントラクトのインスタンスを作成する際に使用されます。 + +```solidity +const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer); +/* + * Call the getAllWaves method from your Smart Contract + */ +const waves = await wavePortalContract.getAllWaves(); +``` + +3. ソースコードが非公開なコントラクトの場合、関数のシグネチャがわからないので、ABI を使用して関数を呼び出すことができます。 + + - 0x533ba33a() はデコンパイル後に表示される関数で、関数の署名だけがあり、関数のシグネチャがわからない + + ![27-4](./img/27-4.png) + ![27-5](./img/27-5.png) + + - このような場合、interface または contract を構築して呼び出すことはできません + + ![27-6](./img/27-6.png) + + この状況では、ABI を使用して関数を呼び出すことができます。 + + ```solidity + bytes memory data = abi.encodeWithSelector(bytes4(0x533ba33a)); + + (bool success, bytes memory returnedData) = address(contract).staticcall(data); + require(success); + + return abi.decode(returnedData, (uint256)); + ``` + +## まとめ + +イーサリアムにおいては、スマートコントラクトとのインタラクションが可能にするには、データをバイトコードにエンコードされなければなりません。 + +このレッスンでは、4 つの`ABIエンコード`メソッドと 1 つの`ABIデコード`メソッドを紹介しました。 diff --git a/Languages/ja/28_Hash_ja/Hash.sol b/Languages/ja/28_Hash_ja/Hash.sol new file mode 100644 index 000000000..8aa8e36bd --- /dev/null +++ b/Languages/ja/28_Hash_ja/Hash.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Hash { + bytes32 _msg = keccak256(abi.encodePacked("0xAA")); + + // ユニークな識別子 + function hash(uint256 _num, string memory _string, address _addr) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_num, _string, _addr)); + } + + // 弱い抗衝突性 + + function weak(string memory string1) public view returns (bool) { + return keccak256(abi.encodePacked(string1)) == _msg; + } + + // 強い抗衝突性 + function strong(string memory string1, string memory string2) public pure returns (bool) { + return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2)); + } +} diff --git a/Languages/ja/28_Hash_ja/img/28-1.png b/Languages/ja/28_Hash_ja/img/28-1.png new file mode 100644 index 000000000..9c78ad16d Binary files /dev/null and b/Languages/ja/28_Hash_ja/img/28-1.png differ diff --git a/Languages/ja/28_Hash_ja/img/28-2.png b/Languages/ja/28_Hash_ja/img/28-2.png new file mode 100644 index 000000000..ccee0113f Binary files /dev/null and b/Languages/ja/28_Hash_ja/img/28-2.png differ diff --git a/Languages/ja/28_Hash_ja/readme.md b/Languages/ja/28_Hash_ja/readme.md new file mode 100644 index 000000000..df22af738 --- /dev/null +++ b/Languages/ja/28_Hash_ja/readme.md @@ -0,0 +1,125 @@ +--- +title: 28. Hash +tags: + - solidity + - advanced + - wtfacademy + - hash +--- + +# WTF Solidity 超シンプル入門: 28. Hash + +最近、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) + +--- + +ハッシュ関数(hash function)は暗号学の概念で、任意の長さのメッセージを固定長の値に変換することができます。この値はハッシュ値(hash)とも呼ばれます。 + +このレッスンでは、ハッシュ関数と`Solidity`での応用について簡単に紹介します。 + +## Hash の特徴 + +一般的によいとされるハッシュ関数は以下の特徴があります: + +- 単方向性:入力からハッシュ値への計算は簡単かつ明確で、逆の計算は非常に困難であり、基本的にブルートフォース攻撃(暴力的な試行錯誤)に依存します。 + +- 感応性:入力が少し変わるだけで、生成されるハッシュ値は大きく変わります。 + +- 効率性:入力からハッシュ値への計算が高速です。 + +- 均一性:各ハッシュ値が取られる確率はほぼ等しくなければなりません。 + +- 衝突抵抗性: + + - 弱い衝突抵抗性:特定のメッセージ x に対して、同じハッシュ値を持つ異なるメッセージ x'を見つけることが困難です。 + - 強い衝突抵抗性:任意の x および x'を見つけて、hash(x) = hash(x')にすることが困難です。 + +## Hash の応用 + +- データのユニークな識別子を生成 +- 署名 +- セキュリティ暗号 + +## Keccak256 + +`Keccak256`函数是`Solidity`中最常用的哈希函数,用法非常简单: + +```solidity +ハッシュ = keccak256(何らかのデータ); +``` + +### Keccak256 や sha3 + +これは非常に面白いことです: + +1. sha3 が keccak の標準化から由来しています。多くの場合、Keccak と SHA3 は同義ですが、2015 年 8 月に SHA3 が最終的に標準化されると、NIST はパディングアルゴリズムを調整しました。**そのため、SHA3 は keccak で計算すると異なる結果を得られるようになった**、開発するときには注意してください。 +2. イーサリアムの開発時、sha3 がまだ標準化されている最中でして、keccak を採用しました。そのため、Ethereum と Solidity スマートコントラクトコードの SHA3 は Keccak256 を指します。NIST-SHA3 ではありません。混乱を避けるため、コントラクトコードに直接 Keccak256 と書くのが最も明確です。 + +### データのユニークな識別子を生成する + +私たちは`keccak256`を用いて、いくつかのデータのユニークな識別子を生成することができます。たとえば、異なる型のデータがいくつかある場合、`uint`、`string`、`address`、まず`abi.encodePacked`メソッドを使ってそれらをパックし、エンコードし、それから`keccak256`を使ってユニークな識別子を生成します: + +```solidity +function hash( + uint _num, + string memory _string, + address _addr + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_num, _string, _addr)); +} +``` + +### 弱い衝突抵抗性 + +私たちは`keccak256`を使って、先ほど説明した弱い衝突抵抗性を示す例を見てみましょう。つまり、メッセージ`x`が与えられたとき、異なるメッセージ`x'`を見つけて、`hash(x) = hash(x')`が困難であることを確認します。 + +私たちはメッセージ`0xAA`を与えられ、もう一つのメッセージを見つけて、それらのハッシュ値が等しくなるかどうかを確認します: + +```solidity +// 弱い衝突抵抗性 +function weak( + string memory string1 + )public view returns (bool){ + return keccak256(abi.encodePacked(string1)) == _msg; +} +``` + +皆さんは 10 回試して、たまたま衝突するかどうかを確認してください。 + +### 強い衝突抵抗性 + +私たちは`keccak256`を使って、先ほど説明した強い衝突抵抗性を示す例を見てみましょう。つまり、任意の`x`と`x'`を見つけて、`hash(x) = hash(x')`が困難であることを確認します。 + +私たちは、2 つの異なる`string`パラメータ`string1`と`string2`を受け取り、それらのハッシュが同じかどうかを確認する関数`strong`を作成します: + +```solidity +// 強い衝突抵抗性 +function strong( + string memory string1, + string memory string2 + )public pure returns (bool){ + return keccak256(abi.encodePacked(string1)) == keccak256(abi.encodePacked(string2)); +} +``` + +皆さんは 10 回試して、たまたま衝突するかどうかを確認してください。 + +## remix での検証 + +- コントラクトをデプロイし、ハッシュ関数を使って唯一の識別子を生成する結果を確認 + + ![28-1](./img/28-1.png) + +- ハッシュ関数の感度を確認し、強い、弱い抗衝突性を確認 + + ![28-2](./img/28-2.png) + +## まとめ + +今回、私たちはハッシュ関数とは何か、そして`Solidity`で最も一般的に使用されるハッシュ関数`keccak256`について紹介しました。 diff --git a/Languages/ja/29_Selector_ja/Selector.sol b/Languages/ja/29_Selector_ja/Selector.sol new file mode 100644 index 000000000..b02e7d763 --- /dev/null +++ b/Languages/ja/29_Selector_ja/Selector.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract DemoContract { +// empty contract +} + +contract Selector { + // eventがmsg.dataを返す + event Log(bytes data); + event SelectorEvent(bytes4); + + // Struct User + struct User { + uint256 uid; + bytes name; + } + + // Enum School + enum School { + SCHOOL1, + SCHOOL2, + SCHOOL3 + } + + // 入力パラメーター to: 0x2c44b726ADF1963cA47Af88B284C06f30380fC78 + function mint(address /*to*/ ) external { + emit Log(msg.data); + } + + // 出力selector + // "mint(address)": 0x6a627842 + function mintSelector() external pure returns (bytes4 mSelector) { + return bytes4(keccak256("mint(address)")); + } + + // パラメーターなしselector + // 入力: 無し + // nonParamSelector() : 0x03817936 + function nonParamSelector() external returns (bytes4 selectorWithNonParam) { + emit SelectorEvent(this.nonParamSelector.selector); + return bytes4(keccak256("nonParamSelector()")); + } + + // elementary(基础型)タイプのパラメーターselector + // 入力:param1: 1,param2: 0 + // elementaryParamSelector(uint256,bool) : 0x3ec37834 + function elementaryParamSelector(uint256 param1, bool param2) + external + returns (bytes4 selectorWithElementaryParam) + { + emit SelectorEvent(this.elementaryParamSelector.selector); + return bytes4(keccak256("elementaryParamSelector(uint256,bool)")); + } + + // fixed size(固定長型)タイプのパラメーターselector + // 入力: param1: [1,2,3] + // fixedSizeParamSelector(uint256[3]) : 0xead6b8bd + function fixedSizeParamSelector(uint256[3] memory param1) external returns (bytes4 selectorWithFixedSizeParam) { + emit SelectorEvent(this.fixedSizeParamSelector.selector); + return bytes4(keccak256("fixedSizeParamSelector(uint256[3])")); + } + + // non-fixed size(可変長型)タイプのパラメーターselector + // 入力: param1: [1,2,3], param2: "abc" + // nonFixedSizeParamSelector(uint256[],string) : 0xf0ca01de + function nonFixedSizeParamSelector(uint256[] memory param1, string memory param2) + external + returns (bytes4 selectorWithNonFixedSizeParam) + { + emit SelectorEvent(this.nonFixedSizeParamSelector.selector); + return bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)")); + } + + // mapping(マッピング)タイプのパラメーターselector + // 入力:demo: 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user: [1, "0xa0b1"], count: [1,2,3], mySchool: 1 + // mappingParamSelector(address,(uint256,bytes),uint256[],uint8) : 0xe355b0ce + function mappingParamSelector(DemoContract demo, User memory user, uint256[] memory count, School mySchool) + external + returns (bytes4 selectorWithMappingParam) + { + emit SelectorEvent(this.mappingParamSelector.selector); + return bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)")); + } + + // selectorを使って関数を呼び出す + function callWithSignature() external { + // uint256型の配列の初期化 + uint256[] memory param1 = new uint256[](3); + param1[0] = 1; + param1[1] = 2; + param1[2] = 3; + + // structの初期化 + User memory user; + user.uid = 1; + user.name = "0xa0b1"; + + // abi.encodeWithSelectorを使って関数のselectorとパラメーターをパックエンコードする + // nonParamSelector関数を呼び出す + (bool success0, bytes memory data0) = address(this).call(abi.encodeWithSelector(0x03817936)); + // elementaryParamSelector関数を呼び出す + (bool success1, bytes memory data1) = address(this).call(abi.encodeWithSelector(0x3ec37834, 1, 0)); + // fixedSizeParamSelector関数を呼び出す + (bool success2, bytes memory data2) = address(this).call(abi.encodeWithSelector(0xead6b8bd, [1, 2, 3])); + // nonFixedSizeParamSelector関数を呼び出す + (bool success3, bytes memory data3) = address(this).call(abi.encodeWithSelector(0xf0ca01de, param1, "abc")); + // mappingParamSelector関数を呼び出す + (bool success4, bytes memory data4) = address(this).call( + abi.encodeWithSelector(0xe355b0ce, 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user, param1, 1) + ); + require(success0 && success1 && success2 && success3 && success4); + } +} diff --git a/Languages/ja/29_Selector_ja/img/29-1.png b/Languages/ja/29_Selector_ja/img/29-1.png new file mode 100644 index 000000000..c9e552eb8 Binary files /dev/null and b/Languages/ja/29_Selector_ja/img/29-1.png differ diff --git a/Languages/ja/29_Selector_ja/img/29-2.png b/Languages/ja/29_Selector_ja/img/29-2.png new file mode 100644 index 000000000..5fbad0c3b Binary files /dev/null and b/Languages/ja/29_Selector_ja/img/29-2.png differ diff --git a/Languages/ja/29_Selector_ja/img/29-3.png b/Languages/ja/29_Selector_ja/img/29-3.png new file mode 100644 index 000000000..2b7155e76 Binary files /dev/null and b/Languages/ja/29_Selector_ja/img/29-3.png differ diff --git a/Languages/ja/29_Selector_ja/readme.md b/Languages/ja/29_Selector_ja/readme.md new file mode 100644 index 000000000..06db81233 --- /dev/null +++ b/Languages/ja/29_Selector_ja/readme.md @@ -0,0 +1,182 @@ +--- +title: 29. セレクタ +tags: + - solidity + - advanced + - wtfacademy + - selector +--- + +# WTF Solidity 超シンプル入門: 29. 関数のセレクタ + +最近、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) + +--- + +## 関数セレクタ + +私たちがスマートコントラクトをコールするとき、本質的にはターゲットコントラクトにただ`calldata`を送信しています。remix でトランザクションを送信すると、詳細情報の`input`に`calldata`が表示されます。 + +![tx input in remix](./img/29-1.png) + +送った`calldata`の最初の 4 バイトは`selector`(関数セレクタ)です。このレッスンでは、`selector`とは何か、どのように使用するかを説明します。 + +### msg.data + +`msg.data`は`Solidity`のグローバル変数であり、完全な`calldata`(関数呼び出し時に渡されるデータ)を表します。 + +以下のコードでは、`mint`関数の`calldata`を出力するために、`Log`イベントを使用しています: + +```solidity +// eventがmsg.dataを返す +event Log(bytes data); + +function mint(address to) external{ + emit Log(msg.data); +} +``` + +もし引数が`0x2c44b726ADF1963cA47Af88B284C06f30380fC78`の場合、出力される`calldata`は: + +```text +0x6a6278420000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 +``` + +この乱雑なバイトコードは、2 つの部分に分かれます: + +```text +一番前の 4 バイトは関数のセレクタ(`selector`): +0x6a627842 + +その後ろの部分は32バイトのパラメータ: +0x0000000000000000000000002c44b726adf1963ca47af88b284c06f30380fc78 +``` + +実をいうと、`calldata`はスマートコントラクトに、どの関数を呼び出すか、どのような引数を渡すかを教えているだけです。 + +### method id、selector と関数のシグネチャ + +`method id`の定義は`関数のシグネチャ`の`Keccak`ハッシュの最初の 4 バイトであり、`selector`と`method id`が一致すると、その関数が呼び出されることを示します。では、`関数のシグネチャ`とは何でしょうか? + +実はレッスン 21 では、私たちは簡単に関数のシグネチャについて紹介しました。これは`"関数名(コンマ区切りの引数型)"`です。例えば、上記のコードの`mint`関数のシグネチャは`"mint(address)"`です。同じスマートコントラクト内で、異なる関数は異なる関数シグネチャを持っており、関数シグネチャを使用してどの関数を呼び出すかを決定できます。 + +**注意**,関数のシグネチャにおいては、`uint`と`int`は`uint256`と`int256`として記述する必要があります。 + +私たちは関数を作成し、`mint`関数の`method id`が`0x6a627842`であることを検証します。以下の関数を実行して、結果を確認してください。 + +```solidity +function mintSelector() external pure returns(bytes4 mSelector){ + return bytes4(keccak256("mint(address)")); +} +``` + +その結果はまさに`0x6a627842`: + +![method id in remix](./img/29-2.png) + +`method id`を計算するときに、関数名とパラメータの型を使います。`Solidity`では、関数のパラメータ型は主に基本型、固定長型、可変長型、マッピング型の 4 つに分類されます。 + +##### 基本型 + +`solidity`では、基本型のパラメーターは`uint256`(`uint8`, ..., `uint256`)、`bool`、`address`などです。`method id`を計算する際は、`bytes4(keccak256("関数名(パラメータ型1,パラメータ型2,...)"))`を使用します。例えば、以下の関数`elementaryParamSelector`のパラメータは`uint256`と`bool`です。したがって、この関数の`method id`を計算するには、`bytes4(keccak256("elementaryParamSelector(uint256,bool)"))`を使用します。 + +```solidity +    // elementary(基础型)タイプのパラメーターselector +    // 入力:param1: 1,param2: 0 +    // elementaryParamSelector(uint256,bool) : 0x3ec37834 +    function elementaryParamSelector(uint256 param1, bool param2) external returns(bytes4 selectorWithElementaryParam){ +        emit SelectorEvent(this.elementaryParamSelector.selector); +        return bytes4(keccak256("elementaryParamSelector(uint256,bool)")); +    } +``` + +##### 固定長型 + +固定長型パラメーターのタイプは通常固定長の配列です。例:`uint256[5]`等。 + +例えば、以下の関数`fixedSizeParamSelector`のパラメータは`uint256[3]`です。したがって、この関数の`method id`を計算するには、`bytes4(keccak256("fixedSizeParamSelector(uint256[3])"))`を使用します。 + +```solidity +    // fixed size(固定長型)タイプのパラメーターselector +    // 入力: param1: [1,2,3] +    // fixedSizeParamSelector(uint256[3]) : 0xead6b8bd +    function fixedSizeParamSelector(uint256[3] memory param1) external returns(bytes4 selectorWithFixedSizeParam){ +        emit SelectorEvent(this.fixedSizeParamSelector.selector); +        return bytes4(keccak256("fixedSizeParamSelector(uint256[3])")); +    } +``` + +##### 可变长度类型参数 + +可変長型のパラメータータイプは通常可変長の配列です。例:`address[]`、`uint8[]`、`string`等。 + +例えば、以下の関数`nonFixedSizeParamSelector`のパラメータは`uint256[]`と`string`です。したがって、この関数の`method id`を計算するには、`bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)"))`を使用します。 + +```solidity +    // non-fixed size(可変長型)タイプのパラメーターselector +    // 入力: param1: [1,2,3], param2: "abc" +    // nonFixedSizeParamSelector(uint256[],string) : 0xf0ca01de +    function nonFixedSizeParamSelector(uint256[] memory param1,string memory param2) external returns(bytes4 selectorWithNonFixedSizeParam){ +        emit SelectorEvent(this.nonFixedSizeParamSelector.selector); +        return bytes4(keccak256("nonFixedSizeParamSelector(uint256[],string)")); +    } +``` + +##### マッピング型のパラメータ + +マッピング型のパラメーターは通常`contract`、`enum`、`struct`などです。`method id`を計算する際には、その型を`ABI`型に変換する必要があります。 + +例えば、以下の関数`mappingParamSelector`のパラメータは`DemoContract`、`User`、`uint256[]`、`School`です。したがって、この関数の`method id`を計算するには、`bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)"))`を使用します。 + +```solidity +contract DemoContract { +    // empty contract +} + +contract Selector{ +    // Struct User +    struct User { +        uint256 uid; +        bytes name; +    } +    // Enum School +    enum School { SCHOOL1, SCHOOL2, SCHOOL3 } +    ... +    // mapping(マッピング)タイプのパラメーターselector +    // 输入:demo: 0x9D7f74d0C41E726EC95884E0e97Fa6129e3b5E99, user: [1, "0xa0b1"], count: [1,2,3], mySchool: 1 +    // mappingParamSelector(address,(uint256,bytes),uint256[],uint8) : 0xe355b0ce +    function mappingParamSelector(DemoContract demo, User memory user, uint256[] memory count, School mySchool) external returns(bytes4 selectorWithMappingParam){ +        emit SelectorEvent(this.mappingParamSelector.selector); +        return bytes4(keccak256("mappingParamSelector(address,(uint256,bytes),uint256[],uint8)")); +    } +    ... +} +``` + +### selector を使う + +私たちは`selector`を使用してターゲット関数を呼び出すことができます。例えば、`elementaryParamSelector`関数を呼び出す場合、`elementaryParamSelector`関数の`method id`を`selector`として、引数をパックしてエンコードし、`call`関数に渡すだけです: + +```solidity + // selectorを使って関数を呼び出す + function callWithSignature() external{ + ... + // elementaryParamSelector関数を呼び出す + (bool success1, bytes memory data1) = address(this).call(abi.encodeWithSelector(0x3ec37834, 1, 0)); + ... + } +``` + +ログでは、私たちは`elementaryParamSelector`関数が呼び出され、`Log`イベントが出力されていることがわかります。 + +![logs in remix](./img/29-3.png) + +## まとめ + +今回は関数セレクトとはなにか、それが`msg.data`や関数のシグネチャとの関係、そしてそれを使って関数を呼び出す方法について説明しました。 diff --git a/Languages/ja/30_TryCatch_ja/TryCatch.sol b/Languages/ja/30_TryCatch_ja/TryCatch.sol new file mode 100644 index 000000000..2dc298b23 --- /dev/null +++ b/Languages/ja/30_TryCatch_ja/TryCatch.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract OnlyEven { + constructor(uint256 a) { + require(a != 0, "invalid number"); + assert(a != 1); + } + + function onlyEven(uint256 b) external pure returns (bool success) { + // 奇数の場合はrevertする + require(b % 2 == 0, "Ups! Reverting"); + success = true; + } +} + +contract TryCatch { + // 成功イベント + event SuccessEvent(); + // 失敗イベント + event CatchEvent(string message); + event CatchByte(bytes data); + + // OnlyEvenのコントラクト変数を宣言 + OnlyEven even; + + constructor() { + even = new OnlyEven(2); + } + + // external callの中でtry-catchを使用する + // execute(0)の場合、成功してイベント`SuccessEvent`を放出 + // execute(1)の場合、失敗してイベント`CatchEvent`を放出 + function execute(uint256 amount) external returns (bool success) { + try even.onlyEven(amount) returns (bool _success) { + // callが成功した場合 + emit SuccessEvent(); + return _success; + } catch Error(string memory reason) { + // callが失敗した場合 + emit CatchEvent(reason); + } + } + + // コントラクト作成時にtry-catchを使用する(コントラクトの作成はexternal callとみなされる) + // executeNew(0)の場合、失敗してイベント`CatchEvent`を放出 + // executeNew(1)の場合、失敗してイベント`CatchEvent`を放出 + // executeNew(2)の場合、成功してイベント`SuccessEvent`を放出 + function executeNew(uint256 a) external returns (bool success) { + try new OnlyEven(a) returns (OnlyEven _even) { + // callが成功した場合 + emit SuccessEvent(); + success = _even.onlyEven(a); + } catch Error(string memory reason) { + // revert("reasonString")やrequire(false, "reasonString")をキャッチする + emit CatchEvent(reason); + } catch (bytes memory reason) { + // assert()のエラーをcatchでキャッチする + // assert()の場合、エラーのタイプは`Panic(uint256)`なので、`Error(stiing)`とは異なる。だから、このcatchに入る + emit CatchByte(reason); + } + } +} diff --git a/Languages/ja/30_TryCatch_ja/img/30-1.png b/Languages/ja/30_TryCatch_ja/img/30-1.png new file mode 100644 index 000000000..cf6c34556 Binary files /dev/null and b/Languages/ja/30_TryCatch_ja/img/30-1.png differ diff --git a/Languages/ja/30_TryCatch_ja/img/30-2.png b/Languages/ja/30_TryCatch_ja/img/30-2.png new file mode 100644 index 000000000..bcc7f39d4 Binary files /dev/null and b/Languages/ja/30_TryCatch_ja/img/30-2.png differ diff --git a/Languages/ja/30_TryCatch_ja/img/30-3.png b/Languages/ja/30_TryCatch_ja/img/30-3.png new file mode 100644 index 000000000..aa30f66ee Binary files /dev/null and b/Languages/ja/30_TryCatch_ja/img/30-3.png differ diff --git a/Languages/ja/30_TryCatch_ja/img/30-4.png b/Languages/ja/30_TryCatch_ja/img/30-4.png new file mode 100644 index 000000000..27adecb44 Binary files /dev/null and b/Languages/ja/30_TryCatch_ja/img/30-4.png differ diff --git a/Languages/ja/30_TryCatch_ja/img/30-5.png b/Languages/ja/30_TryCatch_ja/img/30-5.png new file mode 100644 index 000000000..06b43b9de Binary files /dev/null and b/Languages/ja/30_TryCatch_ja/img/30-5.png differ diff --git a/Languages/ja/30_TryCatch_ja/readme.md b/Languages/ja/30_TryCatch_ja/readme.md new file mode 100644 index 000000000..b1e8772a6 --- /dev/null +++ b/Languages/ja/30_TryCatch_ja/readme.md @@ -0,0 +1,184 @@ +--- +title: 30. Try Catch +tags: + - solidity + - advanced + - wtfacademy + - try catch +--- + +# WTF Solidity 超シンプル入門: 30. Try Catch + +最近、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) + +--- + +`try-catch`は、現代のプログラミング言語でほぼ標準的な例外処理方法です。`Solidity`の 0.6 バージョンでも追加されました。このレッスンでは、`try-catch`を使用してスマートコントラクト内の例外を処理する方法について説明します。 + +## `try-catch` + +`Solidity`では、`try-catch`は`external`関数またはコントラクトの`constructor`(`external`関数として扱われる)の呼び出しにのみ使用できます。基本構文は次のとおりです: + +```solidity +try externalContract.f() { + // callが成功した場合、このブロック内のコードを実行 + +} catch { + // callが失敗した場合、このブロック内のコードを実行 +} +``` + +その中で、`externalContract.f()`は外部コントラクトの関数呼び出しを指します。`try`ブロックは呼び出しが成功した場合に実行され、`catch`ブロックは呼び出しが失敗した場合に実行されます。 + +同様に、`this.f()`は`externalContract.f()`の代わりに使用できます。`this.f()`も外部呼び出しとして扱われますが、コンストラクタ内では使用できません。なぜなら、その時点でコントラクトがまだ作成されていないからです。 + +もし呼び出し関数が戻り値を持っている場合、`try`の後に`returns(returnType val)`を宣言する必要があります。そして、`try`モジュール内で戻り値を使用できます。もしコントラクトを作成する場合、戻り値は新しいコントラクト変数です。 + +```solidity +try externalContract.f() returns(returnType val){ + // callが成功した場合、このブロック内のコードを実行 +} catch { + // callが失敗した場合、このブロック内のコードを実行 +} +``` + +それに、`catch`モジュールは特定の例外原因をキャッチできます: + +```solidity +try externalContract.f() returns(returnType){ + // callが成功したら、コードを実行 +} catch Error(string memory /*reason*/) { + // revert("reasonString") や require(false, "reasonString")をキャッチする +} catch Panic(uint /*errorCode*/) { + // Panicのエラーをキャッチする。例えば、assertが失敗した場合、オーバーフロー、ゼロ除算、配列アクセスの範囲外など +} catch (bytes memory /*lowLevelData*/) { + // もしrevertされて上の二種類の例外にマッチしない場合、この分岐のcatchに入る + // 例えば、revert() require(false) などのカスタムエラーが発生した場合 +} +``` + +## `try-catch`実践 + +### `OnlyEven` + +私たちは外部の`OnlyEven`コントラクトを作成し、`try-catch`を使用して例外を処理します: + +```solidity +contract OnlyEven{ + constructor(uint a){ + require(a != 0, "invalid number"); + assert(a != 1); + } + + function onlyEven(uint256 b) external pure returns(bool success){ + // 奇数の場合はrevertする + require(b % 2 == 0, "Ups! Reverting"); + success = true; + } +} +``` + +`OnlyEven`コントラクトには、コンストラクタと`onlyEven`関数が含まれています。 + +- コンストラクタには引数の`a`があり、`a = 0`の場合は`require`が例外をスローし、`a = 1`の場合は`assert`が例外をスローします。それ以外の場合は正常です。 +- `onlyEven`関数には引数`b`があり、`b`が奇数の場合、`require`が例外をスローします。 + +### 外部関数呼び出し時の例外を処理する + +まず、`TryCatch`コントラクトにいくつかのイベントと状態変数を定義します: + +```solidity + // 成功イベント +event SuccessEvent(); + + // 失敗イベント +event CatchEvent(string message); +event CatchByte(bytes data); + +// OnlyEvenのコントラクト変数を宣言 +OnlyEven even; + +constructor() { + even = new OnlyEven(2); +} +``` + +`SuccessEvent`は呼び出しが成功した場合に発生するイベントで、`CatchEvent`と`CatchByte`は例外が発生した場合に発生するイベントで、それぞれ`require/revert`と`assert`例外に対応しています。`even`は`OnlyEven`コントラクトの状態変数です。 + +それで、`execute`関数で外部関数`onlyEven`の例外を処理するために`try-catch`を使用します: + +```solidity +// external callにてtry-catchを使う +function execute(uint amount) external returns (bool success) { + try even.onlyEven(amount) returns(bool _success){ + // callが成功した場合 + emit SuccessEvent(); + return _success; + } catch Error(string memory reason) { + // callが失敗した場合 + emit CatchEvent(reason); + } +} +``` + +### remix にて検証、外部関数呼び出しの例外を処理 + +`execute(0)`を実行する場合、`0`は偶数であり、`require(b % 2 == 0, "Ups! Reverting");`を満たすため、例外はスローされず、呼び出しは成功し、`SuccessEvent`イベントが発生します。 + +![30-1](./img/30-1.png) + +`execute(1)`を実行する場合、`1`は奇数であり、`require(b % 2 == 0, "Ups! Reverting");`を満たさないため、例外がスローされ、呼び出しは失敗し、`CatchEvent`イベントが発生します。 + +![30-2](./img/30-2.png) + +### コントラクト作成時の例外を処理 + +ここでは、私たちは`try-catch`を使用してコントラクト作成時の例外を処理します。`try`モジュールを`OnlyEven`コントラクトの作成に変更するだけです: + +```solidity +// コントラクト作成時にtry-catchを使用する(コントラクトの作成はexternal callとみなされる) +// executeNew(0)の場合、失敗してイベント`CatchEvent`を放出 +// executeNew(1)の場合、失敗してイベント`CatchEvent`を放出 +// executeNew(2)の場合、成功してイベント`SuccessEvent`を放出 +function executeNew(uint a) external returns (bool success) { + try new OnlyEven(a) returns(OnlyEven _even){ + // callが成功した場合 + emit SuccessEvent(); + success = _even.onlyEven(a); + } catch Error(string memory reason) { + // catchが失敗した場合のrequire()やrevert() + emit CatchEvent(reason); + } catch (bytes memory reason) { + // assert()のエラーをcatchでキャッチする + // assert()の場合、エラーのタイプは`Panic(uint256)`なので、`Error(stiing)`とは異なる。だから、このcatchに入る + emit CatchByte(reason); + } +} +``` + +### remix にて検証、コントラクト作成時の例外を処理 + +`executeNew(0)`を run するとき、`0`は`require(a != 0, "invalid number");`を満たさないため、例外がスローされ、`CatchEvent`イベントが発生します。 + +![30-3](./img/30-3.png) + +`executeNew(1)`を実行する場合、`1`は`assert(a != 1);`を満たさないため、例外がスローされ、`CatchByte`イベントが発生します。 + +![30-4](./img/30-4.png) + +`executeNew(2)`を実行する場合、`2`は`require(a != 0, "invalid number");`と`assert(a != 1);`を満たすため、成功し、`SuccessEvent`イベントが発生します。 + +![30-5](./img/30-5.png) + +## まとめ + +今回は、`Solidity`で`try-catch`を使用してスマートコントラクト内の例外を処理する方法について説明しました: + +- 外部コントラクトの呼び出しやコントラクトの作成にのみ使用可能 +- もし`try`が成功した場合、返り値の変数を宣言しなければならず、返り値の型と一致しなければなりません。