diff --git a/DApps/TestDApp2/client.js b/DApps/TestDApp2/client.js index fa1d0ae..fba9e4b 100644 --- a/DApps/TestDApp2/client.js +++ b/DApps/TestDApp2/client.js @@ -1,58 +1,67 @@ + window.MyDApp2 = (function(){ - return new DCLib.DApp({ - code : 'test_v2', - - logic : function(){ - var balance = 0 - var history = [] + const GameLogic = function(){ + var balance = 0 + var deposit = 0 + var profit = 0 + var history = [] - var setBalance = function(deposit){ - balance = deposit*1 - return balance - } + var setDeposit = function(d){ + deposit = DCLib.Utils.bet4dec(d) + balance = d*1 + return balance + } - var getBalance = function(){ - return balance - } + var getDeposit = function(){ return DCLib.Utils.bet2dec(deposit) } + var getBalance = function(){ return DCLib.Utils.bet2dec(balance) } + var getProfit = function(){ return DCLib.Utils.bet2dec(profit) } - var Roll = function(user_bet, user_num, random_hash){ - let profit = -user_bet - - const random_num = DCLib.numFromHash(random_hash, 0, 65536) - console.log(random_num) - - if (user_num > random_num) { - profit = (user_bet * (65536 - 1310) / user_num) - user_bet - } - if (user_num == random_num) { - profit = user_bet - } - - balance += profit*1 - - const roll_item = { - timestamp : new Date().getTime(), - user_bet : user_bet, - profit : profit, - user_num : user_num, - balance : balance, - random_hash : random_hash, - random_num : random_num, - } - - history.push(roll_item) - - return roll_item + var Roll = function(user_bet, user_num, random_hash){ + let i_profit = -user_bet + + const random_num = DCLib.numFromHash(random_hash, 0, 65536) + console.log(random_num) + + if (user_num > random_num) { + i_profit = (user_bet * (65536 - 1310) / user_num) - user_bet + } + if (user_num == random_num) { + i_profit = user_bet } - return { - setBalance: setBalance, - getBalance: getBalance, - roll: Roll, - history: history, + profit += i_profit*1 + balance = deposit + profit + + const roll_item = { + timestamp : new Date().getTime(), + user_bet : user_bet, + profit : i_profit, + user_num : user_num, + balance : balance, + random_hash : random_hash, + random_num : random_num, } + + history.push(roll_item) + + return roll_item } + + return { + __getProfit : getProfit, + setDeposit : setDeposit, + getDeposit : getDeposit, + getBalance : getBalance, + + roll : Roll, + history : history, + } + } + + return new DCLib.DApp({ + code : 'test_v2', + logic : GameLogic, }) })() diff --git a/src/app.config.js b/src/app.config.js index 7188086..d0db549 100644 --- a/src/app.config.js +++ b/src/app.config.js @@ -79,6 +79,9 @@ module.exports = { api_url: 'https://platform.dao.casino/api/', confirm_timeout: 7000, + gasPrice : 40*1000000000, + gasLimit : 40*100000, + contracts: { erc20 : require('./contracts/contracts/erc20.js'), paychannel : require('./contracts/contracts/paychannel.js'), diff --git a/src/contracts/contracts/paychannel.js b/src/contracts/contracts/paychannel.js index fdac73b..380cc3a 100644 --- a/src/contracts/contracts/paychannel.js +++ b/src/contracts/contracts/paychannel.js @@ -6,8 +6,8 @@ /___/ Dao.Casino PaymentChannleContract - 0xb7ae656f01ddb85c33af4e9f2b45448a61e838e3 - https://ropsten.etherscan.io/address/0xb7ae656f01ddb85c33af4e9f2b45448a61e838e3#code + 0x029c61e3e9958b06bb63cc5c213c47cd114ab971 + https://ropsten.etherscan.io/address/0x029c61e3e9958b06bb63cc5c213c47cd114ab971#code version 1.0 more about payment channels: @@ -17,6 +17,6 @@ module.exports = { - address : '0xb7ae656f01ddb85c33af4e9f2b45448a61e838e3', + address : '0x029c61e3e9958b06bb63cc5c213c47cd114ab971', abi : JSON.parse('[{"constant":false,"inputs":[{"name":"id","type":"bytes32"},{"name":"playerBalance","type":"uint256"},{"name":"bankrollBalance","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"sig","type":"bytes"}],"name":"closeByConsent","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"},{"name":"playerBalance","type":"uint256"},{"name":"bankrollBalance","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"sig","type":"bytes"}],"name":"update","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"channels","outputs":[{"name":"player","type":"address"},{"name":"bankroller","type":"address"},{"name":"playerBalance","type":"uint256"},{"name":"bankrollBalance","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"h","type":"bytes32"},{"name":"signature","type":"bytes"}],"name":"recoverSigner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"},{"name":"player","type":"address"},{"name":"bankrollerAddress","type":"address"},{"name":"playerDeposit","type":"uint256"},{"name":"bankrollDeposit","type":"uint256"},{"name":"nonce","type":"uint256"},{"name":"time","type":"uint256"},{"name":"sig","type":"bytes"}],"name":"open","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"bytes32"}],"name":"closeByTime","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"signature","type":"bytes"}],"name":"signatureSplit","outputs":[{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"v","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"action","type":"string"},{"indexed":false,"name":"id","type":"bytes32"},{"indexed":false,"name":"playerBalance","type":"uint256"},{"indexed":false,"name":"bankrollBalance","type":"uint256"},{"indexed":false,"name":"nonce","type":"uint256"}],"name":"logChannel","type":"event"}]') } diff --git a/src/model/DApps/DApp.js b/src/model/DApps/DApp.js index 17cbb0b..7de3b85 100644 --- a/src/model/DApps/DApp.js +++ b/src/model/DApps/DApp.js @@ -4,13 +4,57 @@ import Rtc from 'rtc' import * as Utils from '../utils' -// @TODO -const default_paymentchannel_contract = { - address : '0x...', - abi : JSON.parse('{}') +const Account = Eth.Wallet +const _openkey = Account.get().openkey +const web3 = Account.web3 + +const ERC20 = new web3.eth.Contract( + _config.contracts.erc20.abi, + _config.contracts.erc20.address +) + +const ERC20approve = async function(spender, amount, callback=false){ + return new Promise(async (resolve, reject) => { + console.log('Check how many tokens user '+_openkey+' is still allowed to withdraw from contract '+spender+' . . . ') + + let allowance = await ERC20.methods.allowance( _openkey, spender).call() + + console.log('💸 allowance:', allowance) + + if (allowance < amount) { + console.log('allowance lower than need deposit') + + console.group('Call .approve on ERC20') + console.log('Allow paychannle to withdraw from your account, multiple times, up to the '+amount+' amount.') + + const receipt = await ERC20.methods.approve( + spender, + amount * 9 + ).send({ + from : _openkey, + gasPrice : _config.gasPrice, + gas : (await ERC20.methods.approve(spender, amount * 9).estimateGas({from : _openkey})), + }).on('error', err=>{ + console.error(err) + reject(false, err) + }) + + console.log('📌 ERC20.approve receipt:', receipt) + + allowance = await ERC20.methods.allowance( _openkey, spender).call() + + console.log('💸💸💸 allowance:', allowance) + + console.groupEnd() + } + + resolve(true, null) + if (callback) callback() + }) } + /* TODO: Bankroller - выпилить eth-ligthwallet - заменить его на web3 @@ -44,7 +88,7 @@ export default class DApp { this.hash = Utils.checksum( params.logic ) this.users = {} - this.sharedRoom = new Rtc( (Eth.Wallet.get().openkey || false) , 'dapp_room_'+this.hash ) + this.sharedRoom = new Rtc( (_openkey || false) , 'dapp_room_'+this.hash ) console.groupCollapsed('DApp %c'+this.code+' %ccreated','color:orange','color:default') console.info(' >>> Unique DApp logic checksum/hash would be used for connect to bankrollers:') @@ -67,7 +111,7 @@ export default class DApp { return } - Eth.getBetsBalance( Eth.Wallet.get().openkey , bets=>{ + Eth.getBetsBalance( _openkey , bets=>{ this.sharedRoom.sendMsg({ action : 'bankroller_active', deposit : bets*100000000, @@ -106,14 +150,9 @@ export default class DApp { id : connection_id, num : Object.keys(this.users).length, logic : new this.logic(), - room : new Rtc( Eth.Wallet.get().openkey, this.hash+'_'+connection_id ) + room : new Rtc( _openkey, this.hash+'_'+connection_id ) } - this.response(params, {id:connection_id}, this.sharedRoom) - - console.log('User '+user_id+' connected to '+this.code) - - const signMsg = async (rawMsg=false)=>{ if (!rawMsg) return '' @@ -121,11 +160,11 @@ export default class DApp { console.log('signMsg', rawMsg) - const sig = Eth.Wallet.lib.signing.concatSig( Eth.Wallet.lib.signing.signMsg( - Eth.Wallet.getKs(), - await Eth.Wallet.getPwDerivedKey(), + const sig = Account.lib.signing.concatSig( Account.lib.signing.signMsg( + Account.getKs(), + await Account.getPwDerivedKey(), rawMsg, - Eth.Wallet.get().openkey + _openkey ) ) console.log('sig:',sig) @@ -162,8 +201,13 @@ export default class DApp { let User = this.users[data.user_id] if (data.action=='open_channel') { + console.log('user room action open channel') this._openChannel(data) } + if (data.action=='close_channel') { + console.log('user room action close channel') + this._closeChannel(data) + } // call user logic function if (data.action=='call') { @@ -190,12 +234,165 @@ export default class DApp { } } this.users[user_id].room.on('all', listen_all) + + + setTimeout(()=>{ + this.response(params, {id:connection_id}, this.sharedRoom) + console.log('User '+user_id+' connected to '+this.code) + }, 999) } - _openChannel(params){ - console.log(params) + PayChannel(){ + if (!this.PayChannelContract) { + this.PayChannelContract = new web3.eth.Contract( pay_contract_abi, pay_contract_address ) + } + return this.PayChannelContract + } + + async _openChannel(params){ + const response_room = this.users[params.user_id].room + + console.log('_openChannel', params) + const pay_contract_abi = _config.contracts.paychannel.abi + const pay_contract_address = _config.contracts.paychannel.address + + const channel_id = params.open_args.channel_id + const player_address = params.user_id + const bankroller_address = _openkey + const player_deposit = params.open_args.player_deposit + const bankroller_deposit = params.open_args.player_deposit*2 + const session = params.open_args.session + const ttl_blocks = params.open_args.ttl_blocks + const signed_args = params.open_args.signed_args + + + const approve = await ERC20approve(pay_contract_address, bankroller_deposit*1000) + + console.log(channel_id, player_address, bankroller_address, player_deposit, bankroller_deposit, session, ttl_blocks) + + const rec_openkey = web3.eth.accounts.recover( Utils.sha3(channel_id, player_address, bankroller_address, player_deposit, bankroller_deposit, session, ttl_blocks), signed_args ) + if (player_address!=rec_openkey) { + console.error('🚫 invalid sig on open channel', rec_openkey) + this.response(params, { error:'Invalid sig' }, response_room) + return + } + // estimateGas - в данном случае работает неккоректно и + // возвращает лимит газа аж на целый блок + // из-за чего транзакцию никто не майнит, т.к. она одна на весь блок + // const gasLimit = await this.PayChannel().methods.open(channel_id,player_address,bankroller_address,player_deposit,bankroller_deposit,session,ttl_blocks, signed_args).estimateGas({from: _openkey}) + + const gasLimit = 900000 + + console.log('Send open channel trancsaction') + console.log('⛽ gasLimit:', gasLimit) + + const receipt = await this.PayChannel().methods + .open( + channel_id , // random bytes32 id + player_address , + bankroller_address , + player_deposit , + bankroller_deposit , + session , // integer num/counter + ttl_blocks , // channel ttl in blocks count + signed_args + ).send({ + gas : gasLimit, + gasPrice : 1.2 * _config.gasPrice, + from : _openkey, + }) + .on('transactionHash', transactionHash=>{ + console.log('# openchannel TX pending', transactionHash) + console.log('https://ropsten.etherscan.io/tx/'+transactionHash) + console.log('⏳ wait receipt...') + }) + .on('error', err=>{ + console.warn('Open channel error', err) + this.response(params, { error:'cant open channel', more:err }, response_room) + }) + + console.log('open channel result', receipt) + + this.users[params.user_id].paychannel = { + channel_id : channel_id , + player_deposit : player_deposit , + bankroller_deposit : bankroller_deposit , + session : session , + } + + this.response(params, { receipt:receipt }, response_room) } + async _closeChannel(params){ + const response_room = this.users[params.user_id].room + console.log('_closeChannel', params) + + const channel_id = params.close_args.channel_id // bytes32 id, + const player_balance = params.close_args.player_balance // uint playerBalance, + const bankroller_balance = params.close_args.bankroller_balance // uint bankrollBalance, + const session = 0 // uint session=0px + const signed_args = params.close_args.signed_args + + // Check Sig + const rec_openkey = web3.eth.accounts.recover( Utils.sha3(channel_id, player_balance, bankroller_balance, session), signed_args ) + if (params.user_id != rec_openkey) { + console.error('🚫 invalid sig on open channel', rec_openkey) + this.response(params, { error:'Invalid sig' }, response_room) + return + } + + // Check user results with out results + const channel = this.users[params.user_id].channel + const user_profit = this.users[params.user_id].logic.__getProfit() + + const l_player_balance = user_profit + channel.player_deposit + const l_bankroller_balance = -user_profit + channel.bankroller_deposit + + if (l_player_balance!=player_balance || l_bankroller_balance!=bankroller_balance) { + console.error('Invalid profit',{ + l_player_balance : l_player_balance, + player_balance : player_balance, + l_bankroller_balance : l_bankroller_balance, + bankroller_balance : bankroller_balance, + }) + this.response(params, { error:'Invalid profit' }, response_room) + return + } + + + const gasLimit = 900000 + console.log('Send close channel trancsaction') + console.log('⛽ gasLimit:', gasLimit) + + const receipt = await this.PayChannel().methods + .close( + channel_id, + player_balance, + bankroller_balance, + session, + signed_args, + ).send({ + gas : gasLimit, + gasPrice : 1.2 * _config.gasPrice, + from : _openkey, + }) + .on('transactionHash', transactionHash=>{ + console.log('# openchannel TX pending', transactionHash) + console.log('https://ropsten.etherscan.io/tx/'+transactionHash) + console.log('⏳ wait receipt...') + }) + .on('error', err=>{ + console.warn('Close channel error', err) + this.response(params, { error:'cant close channel', more:err }, response_room) + }) + + console.log('Close channel receipt', receipt) + if (receipt.transactionHash) { + delete this.users[params.user_id].paychannel + } + + this.response(params, { receipt:receipt }, response_room) + } // Send message and wait response request(params, callback=false, Room=false){ diff --git a/src/model/DApps/DApps.js b/src/model/DApps/DApps.js index bf7f153..4d0bc75 100644 --- a/src/model/DApps/DApps.js +++ b/src/model/DApps/DApps.js @@ -77,12 +77,12 @@ class _DCLib { sigRecover(raw_msg, signed_msg){ raw_msg = Utils.remove0x(raw_msg) - return this.web3.eth.accounts.recover(raw_msg, signed_msg) + return this.web3.eth.accounts.recover(raw_msg, signed_msg).toLowerCase() } checkSig(raw_msg, signed_msg, need_address){ raw_msg = Utils.remove0x(raw_msg) - return ( need_address==this.web3.eth.accounts.recover(raw_msg, signed_msg) ) + return ( need_address.toLowerCase() == this.web3.eth.accounts.recover(raw_msg, signed_msg).toLowerCase() ) } async getBalances(address, callback=false){ diff --git a/src/model/Eth/Wallet.js b/src/model/Eth/Wallet.js index a7205d5..32b37bb 100644 --- a/src/model/Eth/Wallet.js +++ b/src/model/Eth/Wallet.js @@ -4,6 +4,8 @@ import DB from 'DB/DB' import * as Utils from 'utils' +import {sign as signHash} from 'web3/packages/web3-eth-accounts/node_modules/eth-lib/lib/account.js' + const WEB3 = require('web3/packages/web3') const web3 = new WEB3( new WEB3.providers.HttpProvider(_config.rpc_url) ) @@ -237,7 +239,7 @@ export default class Wallet { if (typeof this.signTransaction === 'function') return this.exportPrivateKey( privkey => { _web3acc = this.web3.eth.accounts.privateKeyToAccount( '0x'+privkey ) - this.web3.eth.accounts.wallet.add( privkey ) + this.web3.eth.accounts.wallet.add( '0x'+privkey ) this.signTransaction = _web3acc.signTransaction }) } @@ -250,6 +252,10 @@ export default class Wallet { return _web3acc.sign(raw) } + signHash(hash){ + return signHash(hash, this.exportPrivateKey() ) + } + } diff --git a/src/model/utils.js b/src/model/utils.js index 3d51d7f..a1d78ab 100644 --- a/src/model/utils.js +++ b/src/model/utils.js @@ -1,7 +1,7 @@ export const ABI = require('ethereumjs-abi') export const bigInt = require('big-integer') -const web3_sha3 = require('web3/packages/web3-utils').sha3 +const web3_sha3 = require('web3/packages/web3-utils').soliditySha3 export const sha3 = web3_sha3