Solidity使ってギャンブルゲーム作ってみた

前回のnoteでなんとなく作り始め、色々ボロが出たチンチロゲーム…
遂に完成したので、反省点多めで共有します。

まずは、ソースコード(Web)。
ちょっと整理できていないです。JSファイルに細かく分離したかった…
1ヶ月くらい仕事の合間を縫ってrailsとフロント勉強した割には頑張った()

そして、Solidityのソースコード。カオスです。

pragma solidity ^0.4.24;

import "./GambreumTip.sol";
import "../node_modules/zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
import "../node_modules/zeppelin-solidity/contracts/ownership/Ownable.sol";

contract GambreumPlayer is GambreumTip {

	struct PlayerInfo {
		string username;
		uint winrate;
		bool locked;
	}

	event PlayerCreated(string username);

	mapping (address => PlayerInfo) public addressToPlayerInfo;
	mapping (address => uint) public addressToBalance;

	/*
	constructor() public {
	}
	*/

	function createPlayer(string _name) public {
		emit PlayerCreated(_name);
		require(keccak256(_name) != keccak256("username"));
		string memory player_name = addressToPlayerInfo[msg.sender].username;
		require(bytes(player_name).length == 0);
		addressToPlayerInfo[msg.sender] = PlayerInfo(_name, 0, false);
		balances[msg.sender] += 100; //初期配布分の100GTIP
	}

	function viewPlayerInfo() public view returns (string username, uint winrate, bool locked) {
		PlayerInfo memory player_info = addressToPlayerInfo[msg.sender];
		return (player_info.username, player_info.winrate, player_info.locked);
	}

	function publishTokenToPlayer(uint value, address to) public onlyOwner {
		balances[to] += value;
	}

	function returnToken(uint value, address player_address) public onlyOwner {
		balances[player_address] -= value;
	}
}

最初やろうとしていたことからだいぶ脱線しましたw
あと、後から気づいたんですが「ERC20じゃなくてもよくね?」

が、truffleを用いた一連の開発手順は勉強になりました。
「書く、デバック(今度これ読もうかな)、コンパイル、テスト、デプロイ」

今回、こんな感じで作りました。
クライアント -> Local Rails -> infura -> Contract in Ropsten

一番苦労した事は、トランザクションをサーバ側から叩くことです。
サーバからコントラクトのオーナーアカウントで
onlyOwner属性の関数を叩く必要があります。

クライアントからコントラクト叩くのは
Web3とMetamaskちゃんが頑張ってくれました。
がサーバからトランザクションをAPI経由で叩くには…
APIに投げる「署名つきのRaw Transactionを作成する必要がある」
そして、これ読みました。

コードを見るとなーんだ、と思うのですが
ここにたどり着くまでに紆余曲折。

>こいつの中でトランザクションを作っています。(Ethereum.rb使いました)

  def exeRewardProc(amount, user_wallet, is_earn)
   # Create instanse from my private key whose is Gambrerum Owner.
   key = Eth::Key.new priv: "<秘密鍵 MetamaskからExportした>"

   # Get transaction count
   response = getMyTransactionCount()
   string_response = response.body
   json_response = JSON.parse(string_response)
   my_nonce = json_response["result"]

   # Create hex_data as a payload on this tx.
   if(is_earn == true)
     selector = Digest::SHA3.hexdigest("publishTokenToPlayer(uint256,address)", 256).slice(0..7) # 最初の4Byteを使う
   else
     selector = Digest::SHA3.hexdigest("returnToken(uint256,address)", 256).slice(0..7) # 最初の4Byteを使う
   end
   amount = amount.to_s(16) # 0xへ
   arg1_uint = amount.rjust(64, "0") #FIX: 10進数だから16に直す!
   arg2_address = user_wallet.slice(2..-1)
   arg2_address = arg2_address.rjust(64, "0")
   hex_data = "0x" + selector + arg1_uint + arg2_address

   tx = Eth::Tx.new({
     data: hex_data,
     gas_limit: DEFAULT_GAS_LIMIT,
     gas_price: DEFAULT_GAS_PRICE,
     nonce: my_nonce.hex,
     from: "0x6CaFf8d3958dB8EF53b1Abbe10622c26DBFa4778",
     to: CONTRACT_ADDRESS,
     value: 0
   })

   # Sign this transaction by the key
   tx.sign key

   transaction_response = sendMyRawTransaction(tx.hex)
   transaction_response_string = transaction_response.body
   logger.debug(transaction_response_string)
   transaction_response_json = JSON.parse(transaction_response_string)
   return transaction_response_json["result"]
 end

>そして、API叩く部分。infuraのDoc見ながら作りました。

  def sendMyRawTransaction(signed_pay_load)
   uri = URI.parse(ROPSTEN_URL)
   request = Net::HTTP::Post.new(uri)
   request.content_type = "application/json"
   request.body = JSON.dump({
     "jsonrpc" => "2.0",
     "method" => "eth_sendRawTransaction",
     "params" => [
       signed_pay_load
     ],
     "id" => 1
   })

   req_options = {
     use_ssl: uri.scheme == "https",
   }

   response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
     http.request(request)
   end

   return response
 end

なんだかんだ、サイコロ回して勝ち負け決まって
トランザクションが発行されTIPが動くとやったー!ってなりました。

ただ、まだまだ改善の余地ありです。
1 TIPを動かすトランザクションが承認されるまで待たんといかん
  当たり前ですが、実はもうTIPないのに次の勝負!
  なんてこともできちゃいます・・・
  解決策:トランザクションを発行されたらアカウントをロック
      サーバ側でDBを別途用意し、トランザクションを監視

2 マイナスになるくらい負けると偉いことになる
  TIPの管理を下記のソースないのbalanceという変数で管理
  zeppelin-solidity/contracts/token/ERC20/BasicToken.sol
 
  〜 uint256...これがマイナスに振れた時、世界は反転する… 〜

  解決策:掛け金がマイナスにならない健全なギャンブルをして頂く。 
        and
      SafeMathを使う(ゾンビで習ったはず)

// BasicToken.sol

pragma solidity ^0.4.24;


import "./ERC20Basic.sol";
import "../../math/SafeMath.sol";


/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is ERC20Basic {
 using SafeMath for uint256;

 mapping(address => uint256) internal balances;

~ snip ~

このように、大変色々な意味で勉強になりました。
やはり、1から手を動かして作るのは大変ですけど、楽しいですね。

今後は…
・Rspecを使って本ソースコードにテストフレームワークを適応させてみる
・本リソースを公開サーバに配置してみる
・本環境をDockerのimageにしてみる

この記事が気に入ったらサポートをしてみませんか?