#成果物
とりあえず、LightSailで簡易公開。
Metamask入れて、Ropstenネットワークを選択して遊べます!!
メインネットでは遊べないです。
今は閉鎖しちゃいました!
http://13.113.175.211:3000/
#概要
カイジのチンチロを作りたかった・・・
Ethereumを使って。
完成形はユーザ登録とユーザの持ち金(GTIP)の量を管理するにとどまりました。
Rails側で乱数生成、勝敗の決定をして
勝敗に応じてコントラクトの関数を叩いてウォレットに紐づくユーザのGTIPを動かそう!
という取り組みになりました。
#技術レベル
Rails(勉強したて)
Solidity(ゾンビやったくらい)
Html/CSS(ぱんぴぃ)
決して綺麗なコードではないですが、勉強の軌跡として残しておきます!
#Webソース
https://github.com/tokai-son/Gambreum_web
ちょっと整理できていないです。JSファイルに細かく分離したかった…
#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;
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を作成する必要がある」
コードを見るとなーんだ、と思うのですが
ここにたどり着くまでに紆余曲折。
ポイントはhex_dataの部分、Ethereum ABIの形式に合わせる必要があります。
簡単に言うと、
1 関数名(引数の型スペース無、名前は省略)を256でハッシュし
その最初の4Byteだけ使います。
2 引数として渡すデータを16進数で渡す。ただし、32Byteの長さになるように0でパッティング
(3 引数が可変長なら付加情報が必要です。詳しくは、ここを見て!)
最後に、こいつらを全部繋げて先頭に”0x”をつければ完成。
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
以下で、TransactionをAPI経由で送付しています。
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を使う(ゾンビで習ってた…)
3 Metamaskでテストネット以外を選択している場合を検知する
ここは後から気づきました…
クライアント側で「web3.eth.net.getNetworkType」を呼んで判定させれば良さそう!
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使ってみる
・HTTPSでアクセスできるようにする
・本環境をDockerのimageにしてみる
この辺りをやってみたいと思います。