物は試しでやってみた。
最初
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract Loop { mapping (address=>uint) Num; function updateNum() public { Num[msg.sender] = block.timestamp; updateNum(); } }
Error: non-payable method cannot override value
で怒られてしまいました。
payable にする
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract Loop2 { mapping (address=>uint) Num; function updateNum() public payable { Num[msg.sender] = block.timestamp; updateNum(); } }
Error: sender doesn't have enough funds to send tx. The upfront cost is: 123000000000000000000 and the sender's account only has: 100000000000000000000
GAS fee を 0 にする
GASを0にしてみました。 するとスタックオーバーフローが発生しましたね。処理のネスト数が限界を超えたのでしょうか。
Error: VM Exception while processing transaction: stack overflow
インクリメントにしてみる
今度は時刻ではなくインクリメントしていこうと思います。またget関数も追加しました。
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract Loop3 { mapping (address=>uint) Num; function updateNum() public payable { Num[msg.sender] = Num[msg.sender] + 1; updateNum(); } function getNum() public view returns(uint){ return Num[msg.sender]; } }
再び実行しましたが変わらずError: VM Exception while processing transaction: stack overflow
のままでした。(そりゃそうだ)
ここでgetNum
関数を実行してみましたがトランザクションに保存されているデータは0
のままでした。
当たり前ですが、処理に失敗するとトランザクションにデータは書き込まれないようです。(トランザクション自体は生成されていました)
1024で条件付してみる
スタックサイズが1024らしいのでこれで試しました。
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract Loop4 { mapping (address=>uint) Num; function updateNum() public payable { require(Num[msg.sender] < 1023); Num[msg.sender] = Num[msg.sender] + 1; updateNum(); } function getNum() public view returns(uint){ return Num[msg.sender]; } }
だめでした。あとなんか Ganache が落ちたのでこのタイミングでチェーンをリセットしました。
面倒なのでネスト回数を指定できるようにした
reset するための関数を足した。 成功したらデータ消したいので追加。
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; contract Loop7 { mapping (address=>uint) Num; function updateNum(uint _count) public payable { if (Num[msg.sender] < _count) { Num[msg.sender] = Num[msg.sender] + 1; updateNum(_count); } } function resetNum() public { Num[msg.sender] = 0; } function getNum() public view returns(uint){ return Num[msg.sender]; } }
その結果かんたんに試せるようになったのでどのぐらいネストできるのかを色々試してみました。 - 200回->OK - 500回->OK - 800回->NG - 700回->NG - 600回->NG - 550回->NG - 525回->NG - 513回->NG - 506回->OK - 510回->NG - 508回->NG - 507回->OK
結果507回ネストしても大丈夫ということがわかりました。
結論(答え出ていない)
- スタックサイズが1024なのでその制限にひっかかったっぽい
- 507 x 2 = 1014 なので リターンアドレスとローカル変数?かなんかで2個づつスタックがつまれてる
- あと9?10?がなんなのかはよくわからない。
- 通常は gas 代で処理止まるので基本は起きないはず