[ethereum]solidityのデザインパターンの学習（State Machine Pattern）

# 記事の内容
solidityにもデザインパターンってあるのかな？と思い調べてみるとめちゃくちゃありました。
この記事では「State Machine Pattern」を実際に実装してみて動きを確認してみます。

# 参考
LayerXさんのブログを参考にさせていただきました。
[Smart Contract Design Pattern](https://medium.com/layerx-jp/smart-contract-design-pattern-34a6401fe743)

# State Machine Pattern
## 解決する課題
課題：コントラクトの状態に応じて振る舞いを変更したい

参考の記事だとデポジットの入金→一定期間ロック→デポジットの出金という流れが例として挙げられています。

同じ様な考えだと用途だとライブチケットなどをトークンで発行する場合に使えるかもしれません。

・チケットをトークンで発行
・開演時間までロック
・入場したら使用済みにする

## 実装
テスト用にロックの時間部分だけ修正しましたが、その他は参考にさせていただいたサイトのままです。。

~~~ DepositLock.sol
pragma solidity ^0.5.16;

contract DepositLock {
    
    enum Stages {
        AcceptsingDeposits,
        FreezingDeposits,
        ReleasingDeposits
    }

    Stages public stage = Stages.AcceptsingDeposits;

    uint public creationTime = now;

    mapping(address => uint ) balances;

    modifier atStage(Stages _stage) {
        require(stage == _stage);
        _;
    }

    modifier timedTransactions() {
        if(stage == Stages.AcceptsingDeposits && now >= creationTime + 3 minutes) 
            nextStage();
        if(stage == Stages.FreezingDeposits && now >= creationTime + 5 minutes) 
            nextStage();
        _;
    }

    function nextStage() internal {
        stage = Stages(uint(stage) + 1);
    }

    function deposit() public payable timedTransactions atStage(Stages.AcceptsingDeposits) {
        balances[msg.sender] += msg.value;
    }

    function withDraw() public timedTransactions atStage(Stages.ReleasingDeposits) {
        uint ammount = balances[msg.sender];
        balances[msg.sender] = 0;
        msg.sender.transfer(ammount);
    }
}
~~~

ざっくりコード解説

~~~
    enum Stages {
        AcceptsingDeposits,
        FreezingDeposits,
        ReleasingDeposits
    }
~~~

入金したトークンの状態を定義する

~~~
    Stages public stage = Stages.AcceptsingDeposits;

    uint public creationTime = now;

    mapping(address => uint ) balances;
~~~

ロジックで使用する変数の定義

~~~
    modifier atStage(Stages _stage) {
        require(stage == _stage);
        _;
    }
~~~

今の状態を判定する修飾子

~~~
    modifier timedTransactions() {
        if(stage == Stages.AcceptsingDeposits && now >= creationTime + 3 minutes) 
            nextStage();
        if(stage == Stages.FreezingDeposits && now >= creationTime + 5 minutes) 
            nextStage();
        _;
    }
~~~

今の状態判定＋そのコントラクトが作成されてからどのくらいの時間が経過しているかを判定

自分の理解だと「creationTime」にはコントラクト作成日時が入ってるから、ここでの時間判定はほぼ意味を成さないのでは？？

~~~
    function nextStage() internal {
        stage = Stages(uint(stage) + 1);
    }
~~~
stageを次に進める。
こういう進め方が出来るのは初めて知った。
Goとかでも出来るのかな？

~~~
    function deposit() public payable timedTransactions atStage(Stages.AcceptsingDeposits) {
        balances[msg.sender] += msg.value;
    }
~~~

depositの入金関数
修飾子は「timedTransactions」→「atStage」の順で実行される。
「timedTransactions」を先に指定することで、「atStage」は一定時間経過後、次に進んだステージで判定される

デポジットが入金されてからの一定期間で状態遷移をするのであれば、ここでdepositTimeみたいなのを設定したほうが良いかも

~~~
    function withDraw() public timedTransactions atStage(Stages.ReleasingDeposits) {
        uint ammount = balances[msg.sender];
        balances[msg.sender] = 0;
        msg.sender.transfer(ammount);
    }
~~~
デポジットを引き出す
このロジックだとデポジットを引き出した後のstageがフリーズ状態のままなので、動かしてみると１回こっきりしか使えないようになってます。

msg.sender毎に「stage」を持たせて、timedTransactions でフリーズからアクセプト状態に遷移するようなロジックを組み込むと何度も使えるようになりそうです。


# 感想
デザインパターンを学習することで、スマートコントラクトがどのような場面で学習で活用出来るのか理解を深めることが出来そうです。
Solidityはクセがありますが、仕組みとして複雑なコードを書いたりしないので、シンプルなコードですね。

このコードだとコントラクトにデポジットを入金して取り出すと一回こっきりの使い捨て。
複数回使う場合だとコントラクトを都度作る必要がある。一つのコントラクト内で使いまわせるように実装するのと比べると何が違うのだろう？
このあたりも追加で学習していく必要がありそう。