vDiceでみるブロックチェーン上のスマートコントラクトへのアクセスについて


Ethereumは、ブロックチェーン上のコントラクトを誰もが参照できる点に魅力がある。

ここではvDiceのサービスを例にブロックチェーン上にデプロイされたコントラクトメソッドを呼び出してみる。

このサービス自体はギャンブルなので、利用自体を推奨するわけではないので注意してほしい。

vDice：
https://www.vdice.io/
![0.png](https://qiita-image-store.s3.amazonaws.com/0/142016/0ce27758-3b30-c2af-11c8-9998321080d9.png)

###vDiceの特徴
 -vDiceのコントラクトは0～9999までのランダムな数字を出力する。
 -プレイヤーは出力される0～9999までの値で、コントラクトが出力する値を予測する。
 -たとえば、出力される値が500以下と予測するのであればコントラクトＡに、9000以下と予測するのであればコントラクトＢのアドレスにETHを送付する。
 -予想が的中すればオッズ分のETHが、自身のウォレットに上乗せされて返金される。また、予測される範囲の値は狭ければ狭いほど、的中したときにリターンとして得られる倍率は高くなる。（≦500のとき19.6倍）

#やったこと
 - これまでvDiceのサービスとして実行された結果を確認する。
 - 過去vDiceのコントラクトが出力した値がまんべんなく0～9999の間に納まっているか確認する。
(今回9000以下と予測するコントラクトアドレスを対象に、これまでの勝敗の結果を出力してみる。
0～9999の間で9000以下と予測するため、結果自体も90%以内に収まっているか確認をする。）

　確認対象のコントラクトアドレス
　・0x7DA90089A73edD14c75B0C827cb54f4248D47eCc　（≦9000)

本家Webページを見ると、直近10件の取引履歴しか表示されていないが
ブロックチェーンの特性として、実行されたコントラクト結果は確認できるはず。


###Step1.コントラクトコードを読む
まず、ブロックチェーンに組み込まれているvDiceのコントラクトを見てみよう。

https://etherscan.io/address/0x7DA90089A73edD14c75B0C827cb54f4248D47eCc#code


```java:
   function bet()
        payable
        onlyIfNotStopped {

        uint oraclizeFee = OraclizeI(OAR.getAddress()).getPrice("URL", ORACLIZE_GAS_LIMIT + safeGas);
        if (oraclizeFee >= msg.value) throw;
        uint betValue = msg.value - oraclizeFee;
        if ((((betValue * ((10000 - edge) - pwin)) / pwin ) <= (maxWin * getBankroll()) / 10000) && (betValue >= minBet)) {
            LOG_NewBet(msg.sender, betValue);
            bytes32 myid =
                oraclize_query(
                    "nested",
                    "[URL] ['json(https://api.random.org/json-rpc/1/invoke).result.random.data.0', '\\n{\"jsonrpc\":\"2.0\",\"method\":\"generateSignedIntegers\",\"params\":{\"apiKey\":${[decrypt] BIYkzb1GQzRZFNsTzF7fh+n8VmT8GEyW3mHYlrU8It5O6/bam6/LVVxqkury8YZDJPjm0mWQeqQGebGAVSFrFw16/VHJ65QMFBfIHN2frhav/d10ARqECjoOvse5v4/DIT3LQUHPEx0Z/5UdtqYTQydW/pbC5BM=},\"n\":1,\"min\":1,\"max\":10000${[identity] \"}\"},\"id\":1${[identity] \"}\"}']",
                    ORACLIZE_GAS_LIMIT + safeGas
                );
            bets[myid] = Bet(msg.sender, betValue, 0);
            betsKeys.push(myid);
        }
        else {
            throw;
        }
    }

```
vDiceのコントラクト自体、下記のurlからAPIにより乱数を取得していることがわかる。
https://api.random.org


次にgetBet関数、numBets関数を見てみる。

```:
    function getBet(uint id)
        constant
        returns(address, uint, uint) {

        if (id < betsKeys.length) {
            bytes32 betKey = betsKeys[id];
            return (bets[betKey].playerAddress, bets[betKey].amountBet, bets[betKey].numberRolled);
        }
    }

    function numBets()
        constant
        returns(uint) {

        return betsKeys.length;
    }
```

getBet関数は引数にbetsKeys.length > idとなる値を入力することでプレイヤーのアドレス、Bet額、結果(numRoll)を返す。

下段にあるnumBets関数はbetsKeys.lengthを返すことがわかる


つまり、過去の結果をすべて出力するためには次のことさえできれば良い。

1.numBets関数でbetskeys配列の要素数を取得する。
2.getBet関数をbetskeys配列の要素数分実行する。


###Step2.コントラクト結果を参照してみる
ためしにEtherscan上から、結果を確認する。

getBet関数にid=7600を入力し、Queryボタンを押す。

https://etherscan.io/address/0x7DA90089A73edD14c75B0C827cb54f4248D47eCc#readContract
![9.png](https://qiita-image-store.s3.amazonaws.com/0/142016/1548c1ac-c963-669a-56cc-b9b797960bbf.png)

出力されるのは、上から次のとおり
・プレイヤーアドレス
・ベット額（ETHの単位にするには10^18で割る）
・結果

###Step3.環境の準備
　Ethereumのライブネットと同期した自ノードの環境を準備する。
　http://qiita.com/umidachi/items/44a50ce86e94ff0df8f2

###Step4.コントラクトメソッドをABI形式にする。

次のコントラクトメソッドについて、ABI形式のファンクションパラメータを作る

- numBets関数

```:gethコンソール
> web3.sha3("numBets()").substring(0,10)

結果:
"0xdf06f906"

```
numBets関数は引数がない。そのためファンクションパラメータは16進4バイトデータだけですむ。

- getBets関数

```:gethコンソール
> web3.sha3("getBet(uint256)").substring(0,10)

結果:
"0x061e494f"

```

一方、getBet関数の引数はuint256。
そのため、abiのメソッドパラメータは16進4バイトデータ+
32バイトデータの構成になる。

pythonでabiパラメータを作るとしたら、次の記述だと楽に作れる。


```pytb3:例

b="0x061e494f" #getBet(uint256)
d="{0:064x}".format((bet_id))
getBet_abi=b+d

```


###Step5.pythonで実装する。

サービス当初のベット結果から直近の結果を得るまで、pythonの記載は次のようになる。

```py3:vDice.py
import requests
import json

#Json-rpcでeth_callを呼び出す
def eth_call(url,to,data):
	headers = {'content-type': 'application/json'}
	payload = {
		"jsonrpc": "2.0",
		"method": "eth_call",
		"params":[{"to":to,
				   "data":data},
				   "latest"],
		"id": 0
	}

	response = requests.post(url, data=json.dumps(payload), headers=headers).json()
	#print(response)
	return response['result']

def numBet():
	contract_address={"vdice":"0x7DA90089A73edD14c75B0C827cb54f4248D47eCc"}
	numBets_abi="0xdf06f906"
	result=eth_call(url,contract_address['vdice'],numBets_abi)
	i=int(result,16)
	return i

def getBet(bet_id):
	contract_address={"vdice":"0x7DA90089A73edD14c75B0C827cb54f4248D47eCc"}
	b="0x061e494f" #getBet(uint256)
	d="{0:064x}".format((bet_id))
	getBet_abi=b+d

	result=eth_call(url,contract_address['vdice'],getBet_abi)

	d=result[2:194]	   #先頭0xの表記を省略
	#print(d)
	print("PlayerAddress:",d[24:64])
	print("AmountBet:",int(d[64:128],16)/pow(10,18))
	print("numRoll:",int(d[129:193],16))


if __name__ == "__main__":
	url = "http://localhost:8545"
	betnum=numBet()

	print("---betnum:",betnum)
	for i in range(1,betnum):
		print("---num:",i)
		getBet(i-1)

```

今回、betNumは7642が最大であるため、0～7642回分のgetBet関数の結果を出力する。

```console:出力結果

---betnum: 7642
---num: 1
PlayerAddress: d19e13d40188a0123da1561c8f063ce5cadecd56
AmountBet: 0.22
numRoll: 7821
---num: 2
PlayerAddress: d19e13d40188a0123da1561c8f063ce5cadecd56
AmountBet: 0.2104874128878282
numRoll: 3903
---num: 3
PlayerAddress: 3f69019bc9d6e62432900caa46021e16c1f376a6
AmountBet: 0.2104874128878282
numRoll: 7746
---num: 4
PlayerAddress: 3f69019bc9d6e62432900caa46021e16c1f376a6
AmountBet: 0.2104874128878282
numRoll: 353
---num: 5
PlayerAddress: d19e13d40188a0123da1561c8f063ce5cadecd56
AmountBet: 0.2104874128878282

........
........

```

補足：
getBet関数の生のレスポンスは例として次のようになる

```json:getBetのレスポンス
{'jsonrpc': '2.0', 'id': 0, 'result': '0x0000000000000000000000006bd933140ff6b40
630ef12928979eb22d17ddc7100000000000000000000000000000000000000000000000004174e8
b1d32337e0000000000000000000000000000000000000000000000000000000000000056'}

```

'result'に結果が格納されるが内容は次のとおりとなっている。

・プレイヤーアドレス　[32byte]
・ベット額（ETHの単位にするには10^18を割る）[32byte]
・結果(0～9999)[32byte]

先頭0xを含めると194文字が返却されるため
受け入れ結果をわかりやすくするため次の記載をしている。


```py3:参考
    d=result[2:194]    #先頭0xの表記を省略
    print("PlayerAddress:",d[24:64])
    print("AmountBet:",int(d[64:128],16)/pow(10,18))
    print("numRoll:",int(d[129:193],16))

```



###確認結果

取得結果をCSVに出力するコードに書き換え、下図のとおり散布図にしてみた。

![chart.png](https://qiita-image-store.s3.amazonaws.com/0/142016/b0906d07-2a59-0866-2c42-44b64a09a81d.png)



横軸がベット回数。右に行くほど、直近の結果になる。
縦軸はベット結果(0～9999)を示す。

図を見る限り満遍なくプロットされているように見える。

結果として
 - 総ベット数：7642回
 - 出力結果が9001以上になった回数（つまり予想が外れた回数）:762回

つまり
9000以下が出力される確率：　1 - ( 762/7642 ) =0.9003 ≒ 90 %
　
上記結果から、次のvDiceのコントラクトは統計的に90%相当の確率で的中することがわかる。
　・0x7DA90089A73edD14c75B0C827cb54f4248D47eCc　（≦9000)


ブロックチェーンは同期さえしていれば、不特定多数の人間がコントラクトの結果を見ることができる、興味深い仕組みだ。