From ed9fc00f486222ccf285de517a2a54f0836cbfdb Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 31 Aug 2018 21:16:34 -0400 Subject: [PATCH 1/9] Asking for available relayers functionality --- test-dapp/app/components/callgasrelayed.js | 35 ++---- test-dapp/app/status-gas-relayer.js | 132 +++++++++++++++++++++ 2 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 test-dapp/app/status-gas-relayer.js diff --git a/test-dapp/app/components/callgasrelayed.js b/test-dapp/app/components/callgasrelayed.js index c443cce..ee246b2 100644 --- a/test-dapp/app/components/callgasrelayed.js +++ b/test-dapp/app/components/callgasrelayed.js @@ -15,6 +15,10 @@ import TextField from '@material-ui/core/TextField'; import config from '../config'; import web3 from 'Embark/web3'; import {withStyles} from '@material-ui/core/styles'; + +import StatusGasRelayer, {Contracts} from '../status-gas-relayer'; + + const styles = theme => ({ root: { width: '100%', @@ -98,7 +102,7 @@ class CallGasRelayed extends Component { } } - obtainRelayers = event => { + obtainRelayers = async event => { event.preventDefault(); const {web3, kid, skid} = this.props; @@ -110,28 +114,15 @@ class CallGasRelayed extends Component { this.props.clearMessages(); try { - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - symKeyID: skid, - payload: web3.utils.toHex({ - 'contract': this.props.identityAddress, - 'address': web3.eth.defaultAccount, - 'action': 'availability', - 'gasToken': this.state.gasToken, - 'gasPrice': this.state.gasPrice - }) - }; + const s = new StatusGasRelayer.AvailableRelayers(Contracts.Identity, this.props.identityAddress, web3.eth.defaultAccount) + .setRelayersSymKeyID(skid) + .setAsymmetricKeyID(kid) + .setGas(this.state.gasToken, this.state.gasPrice); + await s.post(web3); + + console.log("Message sent"); + this.setState({submitting: false}); - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } diff --git a/test-dapp/app/status-gas-relayer.js b/test-dapp/app/status-gas-relayer.js new file mode 100644 index 0000000..1225a2b --- /dev/null +++ b/test-dapp/app/status-gas-relayer.js @@ -0,0 +1,132 @@ +export const Contracts = { + 'Identity': 'IdentityGasRelay', + 'SNT': 'SNTController' +}; + +export const Functions = { + 'Identity': { + 'call': 'callGasRelayed', + 'approveAndCall': 'approveAndCallGasRelayed' + } +} + +export const Actions = { + 'Availability': 'availability', + 'Transaction': 'transaction' +} + +const relayerSymmmetricKeyID = "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b"; + +class StatusGasRelayer { + constructor(build, web3) { + this.web3 = web3; + if (arguments.length === 2 && this.validateBuild(build)) { + Object.defineProperties(this, { + message: { + value: build._getMessage(web3), + writable: false + }, + topic: { + value: web3.utils.toHex(build.contractName).slice(0, 10), + writable: false + }, + kid: { + value: build.kid, + writable: false + }, + skid: { + value: build.skid, + writable: false + } + }); + } + } + + post = async (options) => { + options = options || {}; + + let skid = options.skid || this.skid; + if(!skid){ + skid = await this.web3.shh.addSymKey(relayerSymmmetricKeyID); + } + + let kid = options.kid || this.kid; + if(!kid){ + kid = await this.web3.shh.newKeyPair(); + } + + const sendOptions = { + ttl: options.ttl || 1000, + sig: kid, + powTarget: options.powTarget || 1, + powTime: options.powTime || 20, + topic: this.topic, + symKeyID: skid, + payload: this.web3.utils.toHex(this.message) + }; + + return await web3.shh.post(sendOptions); + } + + validateBuild = (build) => { + return (String(build.constructor) === String(StatusGasRelayer.AvailableRelayers)); + } + + static get AvailableRelayers() { + return AvailableRelayersAction; + } +} + +class Action { + setGas(token, price, limit){ + this.gasToken = token; + this.gasPrice = price; + this.gasLimit = limit; + return this; + } + + setOperation(operation){ + this.operation = operation; + return this; + } +} + +class AvailableRelayersAction extends Action { + constructor(contractName, contractAddress, accountAddress) { + super(); + this.contractName = contractName; + this.contractAddress = contractAddress; + this.accountAddress = accountAddress; + + this.operation = Actions.Availability; + + return this; + } + + setRelayersSymKeyID = (skid) => { + this.skid = skid; + return this; + } + + setAsymmetricKeyID = (kid) => { + this.kid = kid; + return this; + } + + _getMessage = (web3) => { + return { + contract: this.contractAddress, + address: this.accountAddress || web3.eth.defaultAccount, + action: Actions.Availability, + gasToken: this.gasToken, + gasPrice: this.gasPrice + }; + } + + post(web3, options) { + const s = new StatusGasRelayer(this, web3); + return s.post(options); + } +} + +export default StatusGasRelayer; \ No newline at end of file From 7733916b21b0396b17ef0102fa0ead7ad030d64b Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 31 Aug 2018 23:05:48 -0400 Subject: [PATCH 2/9] Fixing estimation errors --- gas-relayer/src/message-processor.js | 5 +++-- gas-relayer/src/strategy/BaseStrategy.js | 5 ++--- gas-relayer/src/strategy/IdentityStrategy.js | 7 +++++-- gas-relayer/src/strategy/SNTStrategy.js | 3 ++- test-dapp/app/components/transfersnt.js | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/gas-relayer/src/message-processor.js b/gas-relayer/src/message-processor.js index 78292ad..4eaeae1 100644 --- a/gas-relayer/src/message-processor.js +++ b/gas-relayer/src/message-processor.js @@ -78,7 +78,6 @@ class MessageProcessor { if(!validationResult.success){ reply(validationResult.message); - return; } return validationResult; @@ -95,6 +94,8 @@ class MessageProcessor { async processTransaction(contract, input, reply){ const validationResult = await this.processStrategy(contract, input, reply); + if(!validationResult.success) return; + let p = { from: this.config.node.blockchain.account, to: input.contract, @@ -108,7 +109,7 @@ class MessageProcessor { } p.gas = parseInt(validationResult.estimatedGas * 1.05, 10); // Tune this - + const nodeBalance = await this.web3.eth.getBalance(this.config.node.blockchain.account); if(nodeBalance < p.gas){ diff --git a/gas-relayer/src/strategy/BaseStrategy.js b/gas-relayer/src/strategy/BaseStrategy.js index 6ab5d26..32259ca 100644 --- a/gas-relayer/src/strategy/BaseStrategy.js +++ b/gas-relayer/src/strategy/BaseStrategy.js @@ -77,16 +77,15 @@ class BaseStrategy { })); let simAccounts = await web3Sim.eth.getAccounts(); - + let simulatedReceipt = await web3Sim.eth.sendTransaction({ from: simAccounts[0], - to: input.address, + to: input.contract, value: 0, data: input.payload, gasLimit: 9500000 // 95% of current chain latest gas block limit }); - return simulatedReceipt; } diff --git a/gas-relayer/src/strategy/IdentityStrategy.js b/gas-relayer/src/strategy/IdentityStrategy.js index a2c86d0..da9a80a 100644 --- a/gas-relayer/src/strategy/IdentityStrategy.js +++ b/gas-relayer/src/strategy/IdentityStrategy.js @@ -65,12 +65,15 @@ class IdentityStrategy extends Strategy { let estimatedGas = 0; try { // Geth tends to fail estimation with proxies, so we simulate it with ganache - estimatedGas = await this._simulateTransaction(input); - if(gasLimit.mul(this.web3.utils.toBN(1.05)).lt(estimatedGas)) { + const simReceipt = await this._simulateTransaction(input); + estimatedGas = this.web3.utils.toBN(simReceipt.gasUsed); + if(gasLimit.lt(estimatedGas)) { return {success: false, message: "Gas limit below estimated gas (" + estimatedGas + ")"}; } } catch(exc){ if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"}; + + console.log(exc); } return { diff --git a/gas-relayer/src/strategy/SNTStrategy.js b/gas-relayer/src/strategy/SNTStrategy.js index 80476d1..85cf449 100644 --- a/gas-relayer/src/strategy/SNTStrategy.js +++ b/gas-relayer/src/strategy/SNTStrategy.js @@ -41,7 +41,8 @@ class SNTStrategy extends Strategy { const latestBlock = await this.web3.eth.getBlock("latest"); let estimatedGas = 0; try { - estimatedGas = await this._estimateGas(input, latestBlock.gasLimit); + const simulatedReceipt = await this._simulateTransaction(input, latestBlock.gasLimit); + estimatedGas = this.web3.utils.toBN(simulatedReceipt.gasUsed); } catch(exc){ if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"}; } diff --git a/test-dapp/app/components/transfersnt.js b/test-dapp/app/components/transfersnt.js index e3d6abc..fd9b36f 100644 --- a/test-dapp/app/components/transfersnt.js +++ b/test-dapp/app/components/transfersnt.js @@ -123,7 +123,7 @@ class TransferSNT extends Component { 'contract': SNTController.options.address, 'address': accounts[2], 'action': 'availability', - 'gasToken': this.state.gasToken, + 'gasToken': STT.options.address, 'gasPrice': this.state.gasPrice }) }; From 8efd444a79e43b2a0731106ac2adc5695b37c1c3 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 31 Aug 2018 23:22:01 -0400 Subject: [PATCH 3/9] Adding operations to gas relayer lib --- test-dapp/app/status-gas-relayer.js | 123 +++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/test-dapp/app/status-gas-relayer.js b/test-dapp/app/status-gas-relayer.js index 1225a2b..ae82941 100644 --- a/test-dapp/app/status-gas-relayer.js +++ b/test-dapp/app/status-gas-relayer.js @@ -89,8 +89,55 @@ class Action { this.operation = operation; return this; } + + setRelayersSymKeyID = (skid) => { + this.skid = skid; + return this; + } + + setAsymmetricKeyID = (kid) => { + this.kid = kid; + return this; + } } +class IdentityGasRelayedAction extends Action { + constructor(contractAddress, accountAddress) { + super(); + this.contractName = Contracts.Identity; + this.contractAddress = contractAddress; + this.accountAddress = accountAddress; + + this.operation = Actions.Transaction; + return this; + } + + setTransaction = (to, value, data) => { + this.to = to; + this.value = value; + this.data = data; + + return this; + } + + getNonce = async (web3) => { + const contract = web3.eth.contract(identityGasRelayABI, this.contractAddress); + const nonce = await contract.methods.nonce().call(); + return nonce; + } + + sign = (web3) => { + + } + + _getMessage = async web3 => { + return { + + }; + } +} + + class AvailableRelayersAction extends Action { constructor(contractName, contractAddress, accountAddress) { super(); @@ -103,17 +150,7 @@ class AvailableRelayersAction extends Action { return this; } - setRelayersSymKeyID = (skid) => { - this.skid = skid; - return this; - } - - setAsymmetricKeyID = (kid) => { - this.kid = kid; - return this; - } - - _getMessage = (web3) => { + _getMessage = web3 => { return { contract: this.contractAddress, address: this.accountAddress || web3.eth.defaultAccount, @@ -129,4 +166,68 @@ class AvailableRelayersAction extends Action { } } + + +const identityGasRelayABI = [ + { + "constant": true, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_dataHash", + "type": "bytes32" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_gasLimit", + "type": "uint256" + }, + { + "name": "_gasToken", + "type": "address" + } + ], + "name": "callGasRelayHash", + "outputs": [ + { + "name": "_callGasRelayHash", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xe27e2e5c" + }, + { + "constant": true, + "inputs": [], + "name": "nonce", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xaffed0e0" + } +]; + export default StatusGasRelayer; \ No newline at end of file From 3f56fd467680d30a0a7fb2c910ecfe063fbc4fd5 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 1 Sep 2018 00:44:14 -0400 Subject: [PATCH 4/9] Changing logic related to estimation --- gas-relayer/src/message-processor.js | 4 ++-- gas-relayer/src/strategy/IdentityStrategy.js | 22 ++++++++++++++----- .../contracts/identity/IdentityGasRelay.sol | 4 ++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/gas-relayer/src/message-processor.js b/gas-relayer/src/message-processor.js index 4eaeae1..054952f 100644 --- a/gas-relayer/src/message-processor.js +++ b/gas-relayer/src/message-processor.js @@ -108,10 +108,10 @@ class MessageProcessor { validationResult.estimatedGas = await this.web3.eth.estimateGas(p); } - p.gas = parseInt(validationResult.estimatedGas * 1.05, 10); // Tune this + p.gas = Math.floor(parseInt(validationResult.estimatedGas, 10)); // Tune this const nodeBalance = await this.web3.eth.getBalance(this.config.node.blockchain.account); - + if(nodeBalance < p.gas){ reply("Relayer unavailable"); console.error("Relayer doesn't have enough gas to process trx: %s, required %s", nodeBalance, p.gas); diff --git a/gas-relayer/src/strategy/IdentityStrategy.js b/gas-relayer/src/strategy/IdentityStrategy.js index da9a80a..dcc7bbc 100644 --- a/gas-relayer/src/strategy/IdentityStrategy.js +++ b/gas-relayer/src/strategy/IdentityStrategy.js @@ -1,6 +1,9 @@ const Strategy = require('./BaseStrategy'); const erc20ABI = require('../../abi/ERC20Token.json'); +const CallGasRelayed = "0xfd0dded5"; +const ApproveAndCallGasRelayed = "0x59f4ac61"; + /** * Class representing a strategy to validate a `transaction` request when the topic is related to Identities. * @extends Strategy @@ -64,16 +67,25 @@ class IdentityStrategy extends Strategy { let estimatedGas = 0; try { - // Geth tends to fail estimation with proxies, so we simulate it with ganache - const simReceipt = await this._simulateTransaction(input); - estimatedGas = this.web3.utils.toBN(simReceipt.gasUsed); + // TODO: Investigate why sometimes geth fails estimations with proxies + if(input.functionName == CallGasRelayed){ + estimatedGas = await this._estimateGas(input); + } else { + const tmp = Math.floor(parseInt((await this._estimateGas(input)).toString(10), 10) * 1.05); + + console.log(tmp); + estimatedGas = this.web3.utils.toBN(tmp); // TODO: tune this + } + if(gasLimit.lt(estimatedGas)) { return {success: false, message: "Gas limit below estimated gas (" + estimatedGas + ")"}; } } catch(exc){ if(exc.message.indexOf("revert") > -1) return {success: false, message: "Transaction will revert"}; - - console.log(exc); + else { + console.error(exc); + return {success: false, message: "Couldn't process transaction"}; + } } return { diff --git a/test-dapp/contracts/identity/IdentityGasRelay.sol b/test-dapp/contracts/identity/IdentityGasRelay.sol index 8e6941f..99a80a6 100644 --- a/test-dapp/contracts/identity/IdentityGasRelay.sol +++ b/test-dapp/contracts/identity/IdentityGasRelay.sol @@ -119,7 +119,7 @@ contract IdentityGasRelay is Identity { uint startGas = gasleft(); //verify transaction parameters - require(startGas >= _gasLimit); + //require(startGas >= _gasLimit); // TODO: Tune this require(_nonce == nonce); //verify if signatures are valid and came from correct actors; @@ -255,7 +255,7 @@ contract IdentityGasRelay is Identity { uint startGas = gasleft(); //verify transaction parameters - require(startGas >= _gasLimit); + // require(startGas >= _gasLimit); // TODO: tune this require(_nonce == nonce); //verify if signatures are valid and came from correct actors; From 69e8167499974ee7a72df9f0c87032bfeec4f016 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 1 Sep 2018 00:45:28 -0400 Subject: [PATCH 5/9] Adding logic to obtain signatures --- test-dapp/app/components/callgasrelayed.js | 28 +++++-------- test-dapp/app/status-gas-relayer.js | 46 +++++++++++++++++----- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/test-dapp/app/components/callgasrelayed.js b/test-dapp/app/components/callgasrelayed.js index ee246b2..f508042 100644 --- a/test-dapp/app/components/callgasrelayed.js +++ b/test-dapp/app/components/callgasrelayed.js @@ -70,7 +70,7 @@ class CallGasRelayed extends Component { }); }; - sign = (event) => { + sign = async (event) => { if(event) event.preventDefault(); this.setState({ @@ -78,25 +78,15 @@ class CallGasRelayed extends Component { transactionError: '' }); - IdentityGasRelay.options.address = this.props.identityAddress; - try { - IdentityGasRelay.methods.callGasRelayHash( - this.state.to, - this.state.value, - web3.utils.soliditySha3({t: 'bytes', v: this.state.data}), - this.props.nonce, - this.state.gasPrice, - this.state.gasLimit, - this.state.gasToken - ) - .call() - .then(message => { - return web3.eth.sign(message, web3.eth.defaultAccount); - }) - .then(signature => { - this.setState({signature}); - }); + + const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) + .setTransaction(this.state.to, this.state.value, this.state.data) + .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit); + + const signature = await s.sign(web3); + + this.setState({signature}); } catch(error){ this.setState({transactionError: error.message}); } diff --git a/test-dapp/app/status-gas-relayer.js b/test-dapp/app/status-gas-relayer.js index ae82941..7dac953 100644 --- a/test-dapp/app/status-gas-relayer.js +++ b/test-dapp/app/status-gas-relayer.js @@ -8,12 +8,12 @@ export const Functions = { 'call': 'callGasRelayed', 'approveAndCall': 'approveAndCallGasRelayed' } -} +}; export const Actions = { 'Availability': 'availability', 'Transaction': 'transaction' -} +}; const relayerSymmmetricKeyID = "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b"; @@ -65,16 +65,23 @@ class StatusGasRelayer { payload: this.web3.utils.toHex(this.message) }; - return await web3.shh.post(sendOptions); + const msgId = await this.web3.shh.post(sendOptions); + return msgId; } validateBuild = (build) => { - return (String(build.constructor) === String(StatusGasRelayer.AvailableRelayers)); + return (String(build.constructor) === String(StatusGasRelayer.AvailableRelayers) || + String(build.constructor) === String(StatusGasRelayer.Identity) + ); } static get AvailableRelayers() { return AvailableRelayersAction; } + + static get Identity() { + return IdentityGasRelayedAction; + } } class Action { @@ -120,14 +127,35 @@ class IdentityGasRelayedAction extends Action { return this; } - getNonce = async (web3) => { - const contract = web3.eth.contract(identityGasRelayABI, this.contractAddress); + _nonce = async(contract) => { const nonce = await contract.methods.nonce().call(); return nonce; } - sign = (web3) => { + getNonce = async (web3) => { + const contract = new web3.eth.Contract(identityGasRelayABI, this.contractAddress); + const nonce = await this._nonce(contract); + return nonce; + } + sign = async (web3) => { + const contract = new web3.eth.Contract(identityGasRelayABI, this.contractAddress); + const nonce = await this._nonce(contract); + + // TODO: this depends of the operation to execute + const hashedMessage = await contract.methods.callGasRelayHash( + this.to, + this.value, + web3.utils.soliditySha3({t: 'bytes', v: this.data}), + nonce, + this.gasPrice, + this.gasLimit, + this.gasToken + ).call(); + + const signature = await web3.eth.sign(hashedMessage, this.accountAddress); + + return signature; } _getMessage = async web3 => { @@ -166,8 +194,6 @@ class AvailableRelayersAction extends Action { } } - - const identityGasRelayABI = [ { "constant": true, @@ -230,4 +256,4 @@ const identityGasRelayABI = [ } ]; -export default StatusGasRelayer; \ No newline at end of file +export default StatusGasRelayer; From 1f14d25decdce1a624ff24491c03109028f46613 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 1 Sep 2018 07:23:24 -0400 Subject: [PATCH 6/9] Sending transaction logic for js lib. --- test-dapp/app/components/callgasrelayed.js | 51 ++------ test-dapp/app/status-gas-relayer.js | 140 +++++++++++++++++---- 2 files changed, 131 insertions(+), 60 deletions(-) diff --git a/test-dapp/app/components/callgasrelayed.js b/test-dapp/app/components/callgasrelayed.js index f508042..fef747e 100644 --- a/test-dapp/app/components/callgasrelayed.js +++ b/test-dapp/app/components/callgasrelayed.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import StatusGasRelayer, {Contracts} from '../status-gas-relayer'; import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -6,7 +7,6 @@ import CardContent from '@material-ui/core/CardContent'; import CardHeader from '@material-ui/core/CardHeader'; import EmbarkJS from 'Embark/EmbarkJS'; import Grid from '@material-ui/core/Grid'; -import IdentityGasRelay from 'Embark/contracts/IdentityGasRelay'; import MySnackbarContentWrapper from './snackbar'; import PropTypes from 'prop-types'; import STT from 'Embark/contracts/STT'; @@ -16,9 +16,6 @@ import config from '../config'; import web3 from 'Embark/web3'; import {withStyles} from '@material-ui/core/styles'; -import StatusGasRelayer, {Contracts} from '../status-gas-relayer'; - - const styles = theme => ({ root: { width: '100%', @@ -83,7 +80,7 @@ class CallGasRelayed extends Component { const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) .setTransaction(this.state.to, this.state.value, this.state.data) .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit); - + const signature = await s.sign(web3); this.setState({signature}); @@ -118,7 +115,7 @@ class CallGasRelayed extends Component { } } - sendTransaction = event => { + sendTransaction = async event => { event.preventDefault(); const {web3, kid} = this.props; @@ -135,38 +132,16 @@ class CallGasRelayed extends Component { this.props.clearMessages(); try { - let jsonAbi = IdentityGasRelay._jsonInterface.filter(x => x.name == "callGasRelayed")[0]; - let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ - this.state.to, - this.state.value, - this.state.data, - this.props.nonce, - this.state.gasPrice, - this.state.gasLimit, - this.state.gasToken, - this.state.signature - ]); - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - pubKey: relayer, - payload: web3.utils.toHex({ - 'contract': this.props.identityAddress, - 'address': web3.eth.defaultAccount, - 'action': 'transaction', - 'encodedFunctionCall': funCall - }) - }; - - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); + const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) + .setTransaction(this.state.to, this.state.value, this.state.data) + .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit) + .setRelayer(relayer) + .setAsymmetricKeyID(kid); + + await s.post(this.state.signature, web3); + + this.setState({submitting: false}); + console.log("Message sent"); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } diff --git a/test-dapp/app/status-gas-relayer.js b/test-dapp/app/status-gas-relayer.js index 7dac953..f1049e1 100644 --- a/test-dapp/app/status-gas-relayer.js +++ b/test-dapp/app/status-gas-relayer.js @@ -19,21 +19,34 @@ const relayerSymmmetricKeyID = "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395 class StatusGasRelayer { constructor(build, web3) { + if (arguments.length !== 2 || !this.validateBuild(build)) throw new Error("Invalid build"); + this.web3 = web3; - if (arguments.length === 2 && this.validateBuild(build)) { + + Object.defineProperties(this, { + message: { + value: build._getMessage(web3), + writable: false + }, + topic: { + value: web3.utils.toHex(build.contractName).slice(0, 10), + writable: false + }, + kid: { + value: build.kid, + writable: false + } + }); + + if(build.operation === Actions.Transaction){ Object.defineProperties(this, { - message: { - value: build._getMessage(web3), + pubKey: { + value: build.relayer, writable: false - }, - topic: { - value: web3.utils.toHex(build.contractName).slice(0, 10), - writable: false - }, - kid: { - value: build.kid, - writable: false - }, + } + }); + } else { + Object.defineProperties(this, { skid: { value: build.skid, writable: false @@ -45,10 +58,8 @@ class StatusGasRelayer { post = async (options) => { options = options || {}; + let pubKey = options.pubKey || this.pubKey; let skid = options.skid || this.skid; - if(!skid){ - skid = await this.web3.shh.addSymKey(relayerSymmmetricKeyID); - } let kid = options.kid || this.kid; if(!kid){ @@ -61,10 +72,18 @@ class StatusGasRelayer { powTarget: options.powTarget || 1, powTime: options.powTime || 20, topic: this.topic, - symKeyID: skid, payload: this.web3.utils.toHex(this.message) }; + if(pubKey){ + sendOptions.pubKey = pubKey; + } else { + if(!skid){ + skid = await this.web3.shh.addSymKey(relayerSymmmetricKeyID); + } + sendOptions.symKeyID = skid; + } + const msgId = await this.web3.shh.post(sendOptions); return msgId; } @@ -97,6 +116,11 @@ class Action { return this; } + setRelayer(relayer){ + this.relayer = relayer; + return this; + } + setRelayersSymKeyID = (skid) => { this.skid = skid; return this; @@ -138,7 +162,7 @@ class IdentityGasRelayedAction extends Action { return nonce; } - sign = async (web3) => { + sign = async web3 => { const contract = new web3.eth.Contract(identityGasRelayABI, this.contractAddress); const nonce = await this._nonce(contract); @@ -153,14 +177,38 @@ class IdentityGasRelayedAction extends Action { this.gasToken ).call(); - const signature = await web3.eth.sign(hashedMessage, this.accountAddress); + const signedMessage = await web3.eth.sign(hashedMessage, this.accountAddress); - return signature; + return signedMessage; } - _getMessage = async web3 => { - return { + post = async (signature, web3, options) => { + this.nonce = await this.getNonce(web3); + this.signature = signature; + + const s = new StatusGasRelayer(this, web3); + return s.post(options); + } + _getMessage = web3 => { + // TODO: this depends on the operation to execute + let jsonAbi = identityGasRelayABI.find(x => x.name == "callGasRelayed"); + let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ + this.to, + this.value, + this.data, + this.nonce, + this.gasPrice, + this.gasLimit, + this.gasToken, + this.signature + ]); + + return { + 'contract': this.contractAddress, + 'address': this.accountAddress, + 'action': Actions.Transaction, + 'encodedFunctionCall': funCall }; } } @@ -253,7 +301,55 @@ const identityGasRelayABI = [ "stateMutability": "view", "type": "function", "signature": "0xaffed0e0" - } + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_gasLimit", + "type": "uint256" + }, + { + "name": "_gasToken", + "type": "address" + }, + { + "name": "_messageSignatures", + "type": "bytes" + } + ], + "name": "callGasRelayed", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xfd0dded5" + } ]; export default StatusGasRelayer; From f3557762cda864168c1a9fec27257d46742c6623 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 1 Sep 2018 07:42:44 -0400 Subject: [PATCH 7/9] Added approveAndCallGasRelayed function --- test-dapp/app/components/callgasrelayed.js | 4 +- test-dapp/app/status-gas-relayer.js | 184 ++++++++++++++++++--- 2 files changed, 164 insertions(+), 24 deletions(-) diff --git a/test-dapp/app/components/callgasrelayed.js b/test-dapp/app/components/callgasrelayed.js index fef747e..70c6475 100644 --- a/test-dapp/app/components/callgasrelayed.js +++ b/test-dapp/app/components/callgasrelayed.js @@ -1,5 +1,5 @@ import React, {Component} from 'react'; -import StatusGasRelayer, {Contracts} from '../status-gas-relayer'; +import StatusGasRelayer, {Contracts, Functions} from '../status-gas-relayer'; import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -78,6 +78,7 @@ class CallGasRelayed extends Component { try { const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) + .setContractFunction(Functions.Identity.call) .setTransaction(this.state.to, this.state.value, this.state.data) .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit); @@ -133,6 +134,7 @@ class CallGasRelayed extends Component { try { const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) + .setContractFunction(Functions.Identity.call) .setTransaction(this.state.to, this.state.value, this.state.data) .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit) .setRelayer(relayer) diff --git a/test-dapp/app/status-gas-relayer.js b/test-dapp/app/status-gas-relayer.js index f1049e1..6fc9578 100644 --- a/test-dapp/app/status-gas-relayer.js +++ b/test-dapp/app/status-gas-relayer.js @@ -143,6 +143,11 @@ class IdentityGasRelayedAction extends Action { return this; } + setContractFunction = contractFunction => { + this.contractFunction = contractFunction; + return this; + } + setTransaction = (to, value, data) => { this.to = to; this.value = value; @@ -151,6 +156,11 @@ class IdentityGasRelayedAction extends Action { return this; } + setBaseToken = baseToken => { + this.baseToken = baseToken; + return this; + } + _nonce = async(contract) => { const nonce = await contract.methods.nonce().call(); return nonce; @@ -165,18 +175,34 @@ class IdentityGasRelayedAction extends Action { sign = async web3 => { const contract = new web3.eth.Contract(identityGasRelayABI, this.contractAddress); const nonce = await this._nonce(contract); + let hashedMessage; - // TODO: this depends of the operation to execute - const hashedMessage = await contract.methods.callGasRelayHash( - this.to, - this.value, - web3.utils.soliditySha3({t: 'bytes', v: this.data}), - nonce, - this.gasPrice, - this.gasLimit, - this.gasToken - ).call(); - + switch(this.contractFunction){ + case Functions.Identity.call: + hashedMessage = await contract.methods.callGasRelayHash( + this.to, + this.value, + web3.utils.soliditySha3({t: 'bytes', v: this.data}), + nonce, + this.gasPrice, + this.gasLimit, + this.gasToken + ).call(); + break; + case Functions.Identity.approveAndCall: + hashedMessage = await contract.methods.deployGasRelayHash( + this.value, + web3.utils.soliditySha3({t: 'bytes', v: this.data}), + nonce, + this.gasPrice, + this.gasLimit, + this.gasToken + ).call(); + break; + default: + throw new Error("Function not allowed"); + } + const signedMessage = await web3.eth.sign(hashedMessage, this.accountAddress); return signedMessage; @@ -191,18 +217,38 @@ class IdentityGasRelayedAction extends Action { } _getMessage = web3 => { - // TODO: this depends on the operation to execute - let jsonAbi = identityGasRelayABI.find(x => x.name == "callGasRelayed"); - let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ - this.to, - this.value, - this.data, - this.nonce, - this.gasPrice, - this.gasLimit, - this.gasToken, - this.signature - ]); + let jsonAbi = identityGasRelayABI.find(x => x.name == this.contractFunction); + let funCall; + + switch(this.contractFunction){ + case Functions.Identity.call: + funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ + this.to, + this.value, + this.data, + this.nonce, + this.gasPrice, + this.gasLimit, + this.gasToken, + this.signature + ]); + break; + case Functions.Identity.approveAndCall: + funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ + this.baseToken, + this.to, + this.value, + this.data, + this.nonce, + this.gasPrice, + this.gasLimit, + this.gasToken, + this.signature + ]); + break; + default: + throw new Error("Function not allowed"); + } return { 'contract': this.contractAddress, @@ -349,6 +395,98 @@ const identityGasRelayABI = [ "stateMutability": "nonpayable", "type": "function", "signature": "0xfd0dded5" + }, + { + "constant": false, + "inputs": [ + { + "name": "_baseToken", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_gasLimit", + "type": "uint256" + }, + { + "name": "_gasToken", + "type": "address" + }, + { + "name": "_messageSignatures", + "type": "bytes" + } + ], + "name": "approveAndCallGasRelayed", + "outputs": [ + { + "name": "success", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x59f4ac61" + }, + { + "constant": true, + "inputs": [ + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_dataHash", + "type": "bytes32" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_gasLimit", + "type": "uint256" + }, + { + "name": "_gasToken", + "type": "address" + } + ], + "name": "deployGasRelayHash", + "outputs": [ + { + "name": "_callGasRelayHash", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x86962d85" } ]; From 4b97ac87047ad2879c0c6db450ad8cd095df4521 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 1 Sep 2018 07:47:43 -0400 Subject: [PATCH 8/9] Updated approveAndCall section --- gas-relayer/src/strategy/IdentityStrategy.js | 2 - .../components/approveandcallgasrelayed.js | 106 ++++++------------ 2 files changed, 33 insertions(+), 75 deletions(-) diff --git a/gas-relayer/src/strategy/IdentityStrategy.js b/gas-relayer/src/strategy/IdentityStrategy.js index dcc7bbc..5871747 100644 --- a/gas-relayer/src/strategy/IdentityStrategy.js +++ b/gas-relayer/src/strategy/IdentityStrategy.js @@ -72,8 +72,6 @@ class IdentityStrategy extends Strategy { estimatedGas = await this._estimateGas(input); } else { const tmp = Math.floor(parseInt((await this._estimateGas(input)).toString(10), 10) * 1.05); - - console.log(tmp); estimatedGas = this.web3.utils.toBN(tmp); // TODO: tune this } diff --git a/test-dapp/app/components/approveandcallgasrelayed.js b/test-dapp/app/components/approveandcallgasrelayed.js index 6fec346..63a0b5c 100644 --- a/test-dapp/app/components/approveandcallgasrelayed.js +++ b/test-dapp/app/components/approveandcallgasrelayed.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import StatusGasRelayer, {Contracts, Functions} from '../status-gas-relayer'; import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -15,7 +16,7 @@ import TextField from '@material-ui/core/TextField'; import config from '../config'; import web3 from 'Embark/web3'; import {withStyles} from '@material-ui/core/styles'; -import { Typography } from '@material-ui/core'; + const styles = theme => ({ root: { width: '100%', @@ -63,7 +64,7 @@ class ApproveAndCallGasRelayed extends Component { }); }; - sign = (event) => { + sign = async (event) => { if(event) event.preventDefault(); this.setState({ @@ -74,27 +75,21 @@ class ApproveAndCallGasRelayed extends Component { IdentityGasRelay.options.address = this.props.identityAddress; try { - IdentityGasRelay.methods.deployGasRelayHash( - this.state.value, - web3.utils.soliditySha3({t: 'bytes', v: this.state.data}), - this.props.nonce, - this.state.gasPrice, - this.state.gasLimit, - this.state.gasToken - ) - .call() - .then(message => { - return web3.eth.sign(message, web3.eth.defaultAccount); - }) - .then(signature => { - this.setState({signature}); - }); + const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) + .setContractFunction(Functions.Identity.approveAndCall) + .setTransaction(this.state.to, this.state.value, this.state.data) + .setBaseToken(this.state.baseToken) + .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit); + + const signature = await s.sign(web3); + + this.setState({signature}); } catch(error){ this.setState({transactionError: error.message}); } } - obtainRelayers = event => { + obtainRelayers = async event => { event.preventDefault(); const {web3, kid, skid} = this.props; @@ -106,34 +101,20 @@ class ApproveAndCallGasRelayed extends Component { this.props.clearMessages(); try { - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - symKeyID: skid, - payload: web3.utils.toHex({ - 'contract': this.props.identityAddress, - 'address': web3.eth.defaultAccount, - 'action': 'availability', - 'gasToken': this.state.gasToken, - 'gasPrice': this.state.gasPrice - }) - }; - - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); + const s = new StatusGasRelayer.AvailableRelayers(Contracts.Identity, this.props.identityAddress, web3.eth.defaultAccount) + .setRelayersSymKeyID(skid) + .setAsymmetricKeyID(kid) + .setGas(this.state.gasToken, this.state.gasPrice); + await s.post(web3); + + console.log("Message sent"); + this.setState({submitting: false}); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } } - sendTransaction = event => { + sendTransaction = async event => { event.preventDefault(); const {web3, kid} = this.props; @@ -151,39 +132,18 @@ class ApproveAndCallGasRelayed extends Component { try { - let jsonAbi = IdentityGasRelay._jsonInterface.filter(x => x.name == "approveAndCallGasRelayed")[0]; - let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ - this.state.baseToken, - this.state.to, - this.state.value, - this.state.data, - this.props.nonce, - this.state.gasPrice, - this.state.gasLimit, - this.state.gasToken, - this.state.signature - ]); - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - pubKey: relayer, - payload: web3.utils.toHex({ - 'contract': this.props.identityAddress, - 'address': web3.eth.defaultAccount, - 'action': 'transaction', - 'encodedFunctionCall': funCall - }) - }; + const s = new StatusGasRelayer.Identity(this.props.identityAddress, web3.eth.defaultAccount) + .setContractFunction(Functions.Identity.approveAndCall) + .setTransaction(this.state.to, this.state.value, this.state.data) + .setBaseToken(this.state.baseToken) + .setGas(this.state.gasToken, this.state.gasPrice, this.state.gasLimit) + .setRelayer(relayer) + .setAsymmetricKeyID(kid); - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); + await s.post(this.state.signature, web3); + + this.setState({submitting: false}); + console.log("Message sent"); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } From 16ed849c84fa0a4e515efe5aa1fdd1523262833a Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 1 Sep 2018 16:35:56 -0400 Subject: [PATCH 9/9] Updating library to include snt controller functions --- test-dapp/app/components/execute.js | 109 +++------ test-dapp/app/components/transfersnt.js | 105 +++------ test-dapp/app/status-gas-relayer.js | 286 +++++++++++++++++++++++- 3 files changed, 350 insertions(+), 150 deletions(-) diff --git a/test-dapp/app/components/execute.js b/test-dapp/app/components/execute.js index cdf5ff5..7e335ed 100644 --- a/test-dapp/app/components/execute.js +++ b/test-dapp/app/components/execute.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import StatusGasRelayer, {Contracts} from '../status-gas-relayer'; import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -9,11 +10,11 @@ import MySnackbarContentWrapper from './snackbar'; import PropTypes from 'prop-types'; import SNTController from 'Embark/contracts/SNTController'; import STT from 'Embark/contracts/STT'; +import TestContract from 'Embark/contracts/TestContract'; import TextField from '@material-ui/core/TextField'; import config from '../config'; import web3 from 'Embark/web3'; import {withStyles} from '@material-ui/core/styles'; -import TestContract from 'Embark/contracts/TestContract'; const styles = theme => ({ root: { @@ -55,7 +56,7 @@ class Execute extends Component { }); }; - sign = (event) => { + sign = async (event) => { if(event) event.preventDefault(); this.setState({ @@ -63,28 +64,16 @@ class Execute extends Component { transactionError: '' }); - SNTController.options.address = this.props.identityAddress; - try { - let message = ""; - SNTController.methods.getExecuteGasRelayedHash( - this.state.allowedContract, - this.state.data, - this.props.nonce, - this.state.gasPrice, - this.state.gasMinimal - ) - .call() - .then(result => { - message = result; - return web3.eth.getAccounts(); - }) - .then(accounts => { - return web3.eth.sign(message, accounts[2]); - }) - .then(signature => { - this.setState({signature}); - }); + const accounts = await web3.eth.getAccounts(); + + const s = new StatusGasRelayer.SNTController(SNTController.options.address, accounts[2]) + .execute(this.state.allowedContract, this.state.data) + .setGas(this.state.gasPrice, this.state.gasMinimal); + + const signature = await s.sign(web3); + + this.setState({signature}); } catch(error){ this.setState({transactionError: error.message}); } @@ -112,32 +101,18 @@ class Execute extends Component { this.props.clearMessages(); - const accounts = await web3.eth.getAccounts(); - - try { - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - symKeyID: skid, - payload: web3.utils.toHex({ - 'contract': SNTController.options.address, - 'address': accounts[2], - 'action': 'availability', - 'gasToken': STT.options.address, - 'gasPrice': this.state.gasPrice - }) - }; + const accounts = await web3.eth.getAccounts(); + + const s = new StatusGasRelayer.AvailableRelayers(Contracts.SNT, SNTController.options.address, accounts[2]) + .setRelayersSymKeyID(skid) + .setAsymmetricKeyID(kid) + .setGas(STT.options.address, this.state.gasPrice); + await s.post(web3); + + console.log("Message sent"); + this.setState({submitting: false}); - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } @@ -161,39 +136,19 @@ class Execute extends Component { this.props.clearMessages(); try { - let jsonAbi = SNTController._jsonInterface.filter(x => x.name == "executeGasRelayed")[0]; - let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ - this.state.allowedContract, - this.state.data, - this.props.nonce, - this.state.gasPrice, - this.state.gasMinimal, - this.state.signature - ]); - const accounts = await web3.eth.getAccounts(); - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - pubKey: relayer, - payload: web3.utils.toHex({ - 'contract': SNTController.options.address, - 'address': accounts[2], - 'action': 'transaction', - 'encodedFunctionCall': funCall - }) - }; + const s = new StatusGasRelayer.SNTController(SNTController.options.address, accounts[2]) + .execute(this.state.allowedContract, this.state.data) + .setGas(this.state.gasPrice, this.state.gasMinimal) + .setRelayer(relayer) + .setAsymmetricKeyID(kid); + + await s.post(this.state.signature, web3); + + this.setState({submitting: false}); + console.log("Message sent"); - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } diff --git a/test-dapp/app/components/transfersnt.js b/test-dapp/app/components/transfersnt.js index fd9b36f..d9129c1 100644 --- a/test-dapp/app/components/transfersnt.js +++ b/test-dapp/app/components/transfersnt.js @@ -1,4 +1,5 @@ import React, {Component} from 'react'; +import StatusGasRelayer, {Contracts} from '../status-gas-relayer'; import Button from '@material-ui/core/Button'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -9,12 +10,12 @@ import MySnackbarContentWrapper from './snackbar'; import PropTypes from 'prop-types'; import SNTController from 'Embark/contracts/SNTController'; import STT from 'Embark/contracts/STT'; - import TestContract from 'Embark/contracts/TestContract'; import TextField from '@material-ui/core/TextField'; import config from '../config'; import web3 from 'Embark/web3'; import {withStyles} from '@material-ui/core/styles'; + const styles = theme => ({ root: { width: '100%', @@ -64,7 +65,7 @@ class TransferSNT extends Component { }); } - sign = (event) => { + sign = async (event) => { if(event) event.preventDefault(); this.setState({ @@ -72,27 +73,18 @@ class TransferSNT extends Component { transactionError: '' }); - SNTController.options.address = this.props.identityAddress; try { - let message = ""; - SNTController.methods.getTransferSNTHash( - this.state.to, - this.state.amount, - this.props.nonce, - this.state.gasPrice - ) - .call() - .then(result => { - message = result; - return web3.eth.getAccounts(); - }) - .then(accounts => { - return web3.eth.sign(message, accounts[2]); - }) - .then(signature => { - this.setState({signature}); - }); + const accounts = await web3.eth.getAccounts(); + + const s = new StatusGasRelayer.SNTController(SNTController.options.address, accounts[2]) + .transferSNT(this.state.to, this.state.amount) + .setGas(this.state.gasPrice); + + const signature = await s.sign(web3); + + this.setState({signature}); + } catch(error){ this.setState({transactionError: error.message}); } @@ -109,31 +101,19 @@ class TransferSNT extends Component { }); this.props.clearMessages(); - const accounts = await web3.eth.getAccounts(); try { - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - symKeyID: skid, - payload: web3.utils.toHex({ - 'contract': SNTController.options.address, - 'address': accounts[2], - 'action': 'availability', - 'gasToken': STT.options.address, - 'gasPrice': this.state.gasPrice - }) - }; + const accounts = await web3.eth.getAccounts(); + + const s = new StatusGasRelayer.AvailableRelayers(Contracts.SNT, SNTController.options.address, accounts[2]) + .setRelayersSymKeyID(skid) + .setAsymmetricKeyID(kid) + .setGas(STT.options.address, this.state.gasPrice); + await s.post(web3); + + console.log("Message sent"); + this.setState({submitting: false}); - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } @@ -156,38 +136,19 @@ class TransferSNT extends Component { this.props.clearMessages(); try { - let jsonAbi = SNTController._jsonInterface.filter(x => x.name == "transferSNT")[0]; - let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ - this.state.to, - this.state.amount, - this.props.nonce, - this.state.gasPrice, - this.state.signature - ]); - const accounts = await web3.eth.getAccounts(); - const sendOptions = { - ttl: 1000, - sig: kid, - powTarget: 1, - powTime: 20, - topic: this.state.topic, - pubKey: relayer, - payload: web3.utils.toHex({ - 'contract': SNTController.options.address, - 'address': accounts[2], - 'action': 'transaction', - 'encodedFunctionCall': funCall - }) - }; + const s = new StatusGasRelayer.SNTController(SNTController.options.address, accounts[2]) + .transferSNT(this.state.to, this.state.amount) + .setGas(this.state.gasPrice) + .setRelayer(relayer) + .setAsymmetricKeyID(kid); + + await s.post(this.state.signature, web3); + + this.setState({submitting: false}); + console.log("Message sent"); - web3.shh.post(sendOptions) - .then(() => { - this.setState({submitting: false}); - console.log("Message sent"); - return true; - }); } catch(error){ this.setState({messagingError: error.message, submitting: false}); } diff --git a/test-dapp/app/status-gas-relayer.js b/test-dapp/app/status-gas-relayer.js index 6fc9578..934ebbd 100644 --- a/test-dapp/app/status-gas-relayer.js +++ b/test-dapp/app/status-gas-relayer.js @@ -7,6 +7,10 @@ export const Functions = { 'Identity': { 'call': 'callGasRelayed', 'approveAndCall': 'approveAndCallGasRelayed' + }, + 'SNT': { + 'transfer': 'transferSNT', + 'execute': 'executeGasRelayed' } }; @@ -90,7 +94,8 @@ class StatusGasRelayer { validateBuild = (build) => { return (String(build.constructor) === String(StatusGasRelayer.AvailableRelayers) || - String(build.constructor) === String(StatusGasRelayer.Identity) + String(build.constructor) === String(StatusGasRelayer.Identity) || + String(build.constructor) === String(StatusGasRelayer.SNTController) ); } @@ -101,6 +106,10 @@ class StatusGasRelayer { static get Identity() { return IdentityGasRelayedAction; } + + static get SNTController() { + return SNTControllerAction; + } } class Action { @@ -259,6 +268,124 @@ class IdentityGasRelayedAction extends Action { } } +class SNTControllerAction extends Action { + constructor(contractAddress, accountAddress) { + super(); + this.contractName = Contracts.SNT; + this.contractAddress = contractAddress; + this.accountAddress = accountAddress; + + this.operation = Actions.Transaction; + return this; + } + + transferSNT = (to, value) => { + this.to = to; + this.value = value; + this.contractFunction = Functions.SNT.transfer; + return this; + } + + execute = (contract, data) => { + this.allowedContract = contract; + this.data = data; + this.contractFunction = Functions.SNT.execute; + return this; + } + + _nonce = async(contract) => { + const nonce = await contract.methods.signNonce(this.accountAddress).call(); + return nonce; + } + + getNonce = async (web3) => { + const contract = new web3.eth.Contract(sntControllerABI, this.contractAddress); + const nonce = await this._nonce(contract); + return nonce; + } + + setGas(price, minimal){ + this.gasPrice = price; + this.gasMinimal = minimal; + return this; + } + + sign = async web3 => { + const contract = new web3.eth.Contract(sntControllerABI, this.contractAddress); + const nonce = await this._nonce(contract); + let hashedMessage; + + switch(this.contractFunction){ + case Functions.SNT.execute: + hashedMessage = await contract.methods.getExecuteGasRelayedHash( + this.allowedContract, + this.data, + nonce, + this.gasPrice, + this.gasMinimal, + ).call(); + break; + case Functions.SNT.transfer: + hashedMessage = await contract.methods.getTransferSNTHash( + this.to, + this.value, + nonce, + this.gasPrice + ).call(); + break; + default: + throw new Error("Function not allowed"); + } + + const signedMessage = await web3.eth.sign(hashedMessage, this.accountAddress); + + return signedMessage; + } + + post = async (signature, web3, options) => { + this.nonce = await this.getNonce(web3); + this.signature = signature; + const s = new StatusGasRelayer(this, web3); + return s.post(options); + } + + _getMessage = web3 => { + let jsonAbi = sntControllerABI.find(x => x.name == this.contractFunction); + let funCall; + + switch(this.contractFunction){ + case Functions.SNT.execute: + funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ + this.allowedContract, + this.data, + this.nonce, + this.gasPrice, + this.gasMinimal, + this.signature + ]); + break; + case Functions.SNT.transfer: + funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ + this.to, + this.value, + this.nonce, + this.gasPrice, + this.signature + ]); + break; + default: + throw new Error("Function not allowed"); + } + + return { + 'contract': this.contractAddress, + 'address': this.accountAddress, + 'action': Actions.Transaction, + 'encodedFunctionCall': funCall + }; + } +} + class AvailableRelayersAction extends Action { constructor(contractName, contractAddress, accountAddress) { @@ -288,6 +415,163 @@ class AvailableRelayersAction extends Action { } } +const sntControllerABI = [ + { + "constant": true, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + } + ], + "name": "getTransferSNTHash", + "outputs": [ + { + "name": "txHash", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x0c1f1f25" + }, + { + "constant": true, + "inputs": [ + { + "name": "_allowedContract", + "type": "address" + }, + { + "name": "_data", + "type": "bytes" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_gasMinimal", + "type": "uint256" + } + ], + "name": "getExecuteGasRelayedHash", + "outputs": [ + { + "name": "execHash", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x31c128b1" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_amount", + "type": "uint256" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_signature", + "type": "bytes" + } + ], + "name": "transferSNT", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x916b6511" + }, + { + "constant": false, + "inputs": [ + { + "name": "_allowedContract", + "type": "address" + }, + { + "name": "_data", + "type": "bytes" + }, + { + "name": "_nonce", + "type": "uint256" + }, + { + "name": "_gasPrice", + "type": "uint256" + }, + { + "name": "_gasMinimal", + "type": "uint256" + }, + { + "name": "_signature", + "type": "bytes" + } + ], + "name": "executeGasRelayed", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x754e6ab0" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "signNonce", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x907920c7" + } +]; + const identityGasRelayABI = [ { "constant": true,