mirror of
https://github.com/status-im/snt-gas-relay.git
synced 2025-02-10 05:24:25 +00:00
Extracting identity strategy
This commit is contained in:
parent
79abda8091
commit
db54978c97
@ -12,7 +12,7 @@
|
|||||||
"port": 8545
|
"port": 8545
|
||||||
},
|
},
|
||||||
"blockchain": {
|
"blockchain": {
|
||||||
"account": "0x1847ab5a71eaa95315c3fc2d3dfb53b7e6e8f313"
|
"account": "0x47B9F15c410E2f254E5e065ca515379b090c63e1"
|
||||||
},
|
},
|
||||||
"whisper": {
|
"whisper": {
|
||||||
"symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
|
"symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"IdentityGasRelay": {
|
"IdentityGasRelay": {
|
||||||
"abiFile": "../abi/IdentityGasRelay.json",
|
"abiFile": "../abi/IdentityGasRelay.json",
|
||||||
"isIdentity": true,
|
"isIdentity": true,
|
||||||
"factoryAddress": "0x6202a2b202Ccf859fd93Ecee33C2D20f20462836",
|
"factoryAddress": "0x9AA17f02d8f6Bc2553d06c60f99Bcc97Cd5E5A89",
|
||||||
"kernelVerification": "isKernel(bytes32)",
|
"kernelVerification": "isKernel(bytes32)",
|
||||||
"allowedFunctions": [
|
"allowedFunctions": [
|
||||||
{
|
{
|
||||||
|
@ -4,6 +4,7 @@ class ContractSettings {
|
|||||||
this.tokens = config.tokens;
|
this.tokens = config.tokens;
|
||||||
this.topics = [];
|
this.topics = [];
|
||||||
this.contracts = config.contracts;
|
this.contracts = config.contracts;
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
this.web3 = web3;
|
this.web3 = web3;
|
||||||
this.events = eventEmitter;
|
this.events = eventEmitter;
|
||||||
@ -51,7 +52,7 @@ class ContractSettings {
|
|||||||
this.pendingToLoad--;
|
this.pendingToLoad--;
|
||||||
if(this.pendingToLoad == 0) this.events.emit("setup:complete", this);
|
if(this.pendingToLoad == 0) this.events.emit("setup:complete", this);
|
||||||
})
|
})
|
||||||
.catch(function(err){
|
.catch((err) => {
|
||||||
console.error("Invalid contract for " + contractName);
|
console.error("Invalid contract for " + contractName);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit();
|
process.exit();
|
||||||
@ -92,7 +93,8 @@ class ContractSettings {
|
|||||||
|
|
||||||
// Obtaining strategy
|
// Obtaining strategy
|
||||||
if(this.contracts[topicName].strategy){
|
if(this.contracts[topicName].strategy){
|
||||||
this.contracts[topicName].strategy = require(this.contracts[topicName].strategy);
|
const strategy = require(this.contracts[topicName].strategy);
|
||||||
|
this.contracts[topicName].strategy = new strategy(this.web3, this.config, this, this.contracts[topicName]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._obtainContractBytecode(topicName);
|
this._obtainContractBytecode(topicName);
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
const md5 = require('md5');
|
|
||||||
const erc20ABI = require('../abi/ERC20Token.json');
|
|
||||||
const ganache = require("ganache-cli");
|
|
||||||
const Web3 = require('web3');
|
|
||||||
|
|
||||||
class MessageProcessor {
|
class MessageProcessor {
|
||||||
|
|
||||||
constructor(config, settings, web3, kId){
|
constructor(config, settings, web3, kId){
|
||||||
@ -26,10 +21,10 @@ class MessageProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _validateInput(message, input){
|
async _validateInput(message){
|
||||||
const contract = this.settings.getContractByTopic(message.topic);
|
const contract = this.settings.getContractByTopic(message.topic);
|
||||||
|
|
||||||
if(!/^0x[0-9a-f]{40}$/i.test(input.address)){
|
if(!/^0x[0-9a-f]{40}$/i.test(message.input.address)){
|
||||||
this._reply('Invalid address', message);
|
this._reply('Invalid address', message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -39,14 +34,14 @@ class MessageProcessor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!contract.functionSignatures.includes(input.functionName)){
|
if(!contract.functionSignatures.includes(message.input.functionName)){
|
||||||
this._reply('Function not allowed', message);
|
this._reply('Function not allowed', message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get code from address and compare it against the contract code
|
// Get code from address and compare it against the contract code
|
||||||
if(!contract.isIdentity){
|
if(!contract.isIdentity){
|
||||||
const code = this.web3.utils.soliditySha3(await this.web3.eth.getCode(input.address));
|
const code = this.web3.utils.soliditySha3(await this.web3.eth.getCode(message.input.address));
|
||||||
if(code != contract.code){
|
if(code != contract.code){
|
||||||
this._reply('Invalid contract code', message);
|
this._reply('Invalid contract code', message);
|
||||||
return false;
|
return false;
|
||||||
@ -55,19 +50,6 @@ class MessageProcessor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _validateInstance(message, input){
|
|
||||||
const contract = this.settings.getContractByTopic(message.topic);
|
|
||||||
const instanceCodeHash = this.web3.utils.soliditySha3(await this.web3.eth.getCode(input.address));
|
|
||||||
const kernelVerifSignature = this.web3.utils.soliditySha3(contract.kernelVerification).slice(0, 10);
|
|
||||||
if(instanceCodeHash == null) return false;
|
|
||||||
|
|
||||||
let verificationResult = await this.web3.eth.call({
|
|
||||||
to: contract.factoryAddress,
|
|
||||||
data: kernelVerifSignature + instanceCodeHash.slice(2)});
|
|
||||||
|
|
||||||
return this.web3.eth.abi.decodeParameter('bool', verificationResult);;
|
|
||||||
}
|
|
||||||
|
|
||||||
_extractInput(message){
|
_extractInput(message){
|
||||||
let obj = {
|
let obj = {
|
||||||
address: null,
|
address: null,
|
||||||
@ -77,7 +59,8 @@ class MessageProcessor {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let parsedObj = JSON.parse(this.web3.utils.toAscii(message));
|
let parsedObj = JSON.parse(this.web3.utils.toAscii(message.payload));
|
||||||
|
|
||||||
obj.address = parsedObj.address;
|
obj.address = parsedObj.address;
|
||||||
obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10);
|
obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10);
|
||||||
obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10);
|
obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10);
|
||||||
@ -86,109 +69,61 @@ class MessageProcessor {
|
|||||||
console.error("Couldn't parse " + message);
|
console.error("Couldn't parse " + message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
message.input = obj;
|
||||||
}
|
|
||||||
|
|
||||||
_obtainParametersFunc(contract, input){
|
|
||||||
const parameterList = this.web3.eth.abi.decodeParameters(contract.allowedFunctions[input.functionName].inputs, input.functionParameters);
|
|
||||||
return function(parameterName){
|
|
||||||
return parameterList[parameterName];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
_getFactor(input, contract, gasToken){
|
_getFactor(input, contract, gasToken){
|
||||||
if(contract.allowedFunctions[input.functionName].isToken){
|
if(contract.allowedFunctions[input.functionName].isToken){
|
||||||
return this.web3.utils.toBN(this.settings.getToken(gasToken).pricePlugin.getFactor());
|
return this.web3.utils.toBN(this.settings.getToken(gasToken).pricePlugin.getFactor());
|
||||||
} else {
|
} else {
|
||||||
return this.web3.utils.toBN(1);
|
return this.web3.utils.toBN(1);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
async getBalance(token, input, gasToken){
|
|
||||||
// Determining balances of token used
|
|
||||||
if(token.symbol == "ETH"){
|
|
||||||
return new this.web3.utils.BN(await this.web3.eth.getBalance(input.address));
|
|
||||||
} else {
|
|
||||||
const Token = new this.web3.eth.Contract(erc20ABI.abi);
|
|
||||||
Token.options.address = gasToken;
|
|
||||||
return new this.web3.utils.BN(await Token.methods.balanceOf(input.address).call());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _estimateGas(input, gasLimit){
|
|
||||||
let web3Sim = new Web3(ganache.provider({
|
|
||||||
fork: `${this.config.node.ganache.protocol}://${this.config.node.ganache.host}:${this.config.node.ganache.port}`,
|
|
||||||
locked: false,
|
|
||||||
gasLimit: 10000000
|
|
||||||
}));
|
|
||||||
|
|
||||||
let simAccounts = await web3Sim.eth.getAccounts();
|
|
||||||
|
|
||||||
let simulatedReceipt = await web3Sim.eth.sendTransaction({
|
|
||||||
from: simAccounts[0],
|
|
||||||
to: input.address,
|
|
||||||
value: 0,
|
|
||||||
data: input.payload,
|
|
||||||
gasLimit: gasLimit * 0.95 // 95% of current chain latest gas block limit
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return web3Sim.utils.toBN(simulatedReceipt.gasUsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
async process(error, message){
|
async process(error, message){
|
||||||
if(error){
|
if(error){
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
|
this._extractInput(message);
|
||||||
let input = this._extractInput(message.payload);
|
|
||||||
|
|
||||||
const contract = this.settings.getContractByTopic(message.topic);
|
const contract = this.settings.getContractByTopic(message.topic);
|
||||||
|
|
||||||
console.info("Processing request to: %s, %s", input.address, input.functionName);
|
console.info("Processing request to: %s, %s", message.input.address, message.input.functionName);
|
||||||
|
|
||||||
if(!await this._validateInput(message, input)) return; // TODO Log
|
if(!await this._validateInput(message)) return; // TODO Log
|
||||||
|
|
||||||
if(contract.strategy){
|
if(contract.strategy){
|
||||||
let validationResult = contract.strategy(message, input)
|
let validationResult = await contract.strategy.execute(message);
|
||||||
if(validationResult.success){
|
if(!validationResult.success){
|
||||||
return this._reply(validationResult.message, message);
|
return this._reply(validationResult.message, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestBlock = await this.web3.eth.getBlock("latest");
|
|
||||||
let estimatedGas = 0;
|
|
||||||
try {
|
|
||||||
estimatedGas = await this._estimateGas(input, latestBlock.gasLimit);
|
|
||||||
if(gasLimit.lt(estimatedGas)) {
|
|
||||||
return this._reply("Gas limit below estimated gas", message);
|
|
||||||
}
|
|
||||||
} catch(exc){
|
|
||||||
if(exc.message.indexOf("revert") > -1)
|
|
||||||
return this._reply("Transaction will revert", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
let p = {
|
let p = {
|
||||||
from: this.config.node.blockchain.account,
|
from: this.config.node.blockchain.account,
|
||||||
to: input.address,
|
to: message.input.address,
|
||||||
value: 0,
|
value: 0,
|
||||||
data: input.payload,
|
data: message.input.payload,
|
||||||
gas: gasLimit.toString(),
|
|
||||||
gasPrice: this.config.gasPrice
|
gasPrice: this.config.gasPrice
|
||||||
};
|
};
|
||||||
|
|
||||||
this.web3.eth.sendTransaction(p)
|
this.web3.eth.estimateGas(p)
|
||||||
.then((receipt) => {
|
.then((estimatedGas) => {
|
||||||
return this._reply("Transaction mined;"
|
p.gas = parseInt(estimatedGas * 1.1)
|
||||||
+ receipt.transactionHash
|
return this.web3.eth.sendTransaction(p);
|
||||||
+ ';'
|
})
|
||||||
+ JSON.stringify(receipt)
|
.then((receipt) => {
|
||||||
, message);
|
return this._reply("Transaction mined;"
|
||||||
}).catch((err) => {
|
+ receipt.transactionHash
|
||||||
this._reply("Couldn't mine transaction: " + err.message, message);
|
+ ';'
|
||||||
// TODO log this?
|
+ JSON.stringify(receipt)
|
||||||
console.error(err);
|
, message);
|
||||||
});
|
}).catch((err) => {
|
||||||
|
this._reply("Couldn't mine transaction: " + err.message, message);
|
||||||
|
// TODO log this?
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,55 +1,122 @@
|
|||||||
const identityStrategy = () => {
|
const erc20ABI = require('../../abi/ERC20Token.json');
|
||||||
|
const ganache = require("ganache-cli");
|
||||||
|
const Web3 = require('web3');
|
||||||
|
|
||||||
|
class IdentityStrategy {
|
||||||
|
|
||||||
|
constructor(web3, config, settings, contract){
|
||||||
/*
|
this.web3 = web3;
|
||||||
// TODO extract this. Determine strategy depending on contract
|
this.settings = settings;
|
||||||
|
this.contract = contract;
|
||||||
if(contract.isIdentity){
|
this.config = config;
|
||||||
let validInstance = await this._validateInstance(message, input);
|
|
||||||
if(!validInstance){
|
|
||||||
return this._reply("Invalid identity instance", message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = this._obtainParametersFunc(contract, input);
|
|
||||||
|
|
||||||
const token = this.settings.getToken(params('_gasToken'));
|
|
||||||
if(token == undefined)
|
|
||||||
return this._reply("Token not allowed", message);
|
|
||||||
|
|
||||||
const gasPrice = this.web3.utils.toBN(params('_gasPrice'));
|
|
||||||
const gasLimit = this.web3.utils.toBN(params('_gasLimit'));
|
|
||||||
|
|
||||||
// Determine if enough balance for baseToken
|
|
||||||
if(contract.allowedFunctions[input.functionName].isToken){
|
|
||||||
const Token = new this.web3.eth.Contract(erc20ABI);
|
|
||||||
Token.options.address = params('_baseToken');
|
|
||||||
const baseToken = new this.web3.utils.BN(await Token.methods.balanceOf(input.address).call());
|
|
||||||
if(balance.lt(this.web3.utils.BN(params('_value')))){
|
|
||||||
this._reply("Identity has not enough balance for specified value", message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const gasToken = params('_gasToken');
|
|
||||||
const balance = await this.getBalance(token, input, gasToken);
|
|
||||||
|
|
||||||
if(balance.lt(this.web3.utils.toBN(gasPrice.mul(gasLimit)))) {
|
|
||||||
this._reply("Identity has not enough tokens for gasPrice*gasLimit", message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
message: "Test"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_obtainParametersFunc(message){
|
||||||
|
const parameterList = this.web3.eth.abi.decodeParameters(this.contract.allowedFunctions[message.input.functionName].inputs, message.input.functionParameters);
|
||||||
|
return function(parameterName){
|
||||||
|
return parameterList[parameterName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _validateInstance(message){
|
||||||
|
const instanceCodeHash = this.web3.utils.soliditySha3(await this.web3.eth.getCode(message.input.address));
|
||||||
|
const kernelVerifSignature = this.web3.utils.soliditySha3(this.contract.kernelVerification).slice(0, 10);
|
||||||
|
if(instanceCodeHash == null) return false;
|
||||||
|
|
||||||
|
let verificationResult = await this.web3.eth.call({
|
||||||
|
to: this.contract.factoryAddress,
|
||||||
|
data: kernelVerifSignature + instanceCodeHash.slice(2)});
|
||||||
|
|
||||||
|
return this.web3.eth.abi.decodeParameter('bool', verificationResult);;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBalance(token, message, gasToken){
|
||||||
|
// Determining balances of token used
|
||||||
|
if(token.symbol == "ETH"){
|
||||||
|
return new this.web3.utils.BN(await this.web3.eth.getBalance(message.input.address));
|
||||||
|
} else {
|
||||||
|
const Token = new this.web3.eth.Contract(erc20ABI.abi);
|
||||||
|
Token.options.address = gasToken;
|
||||||
|
return new this.web3.utils.BN(await Token.methods.balanceOf(message.input.address).call());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _estimateGas(message, gasLimit){
|
||||||
|
let web3Sim = new Web3(ganache.provider({
|
||||||
|
fork: `${this.config.node.ganache.protocol}://${this.config.node.ganache.host}:${this.config.node.ganache.port}`,
|
||||||
|
locked: false,
|
||||||
|
gasLimit: 10000000
|
||||||
|
}));
|
||||||
|
|
||||||
|
let simAccounts = await web3Sim.eth.getAccounts();
|
||||||
|
|
||||||
|
let simulatedReceipt = await web3Sim.eth.sendTransaction({
|
||||||
|
from: simAccounts[0],
|
||||||
|
to: message.input.address,
|
||||||
|
value: 0,
|
||||||
|
data: message.input.payload,
|
||||||
|
gasLimit: gasLimit * 0.95 // 95% of current chain latest gas block limit
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return web3Sim.utils.toBN(simulatedReceipt.gasUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(message){
|
||||||
|
|
||||||
|
if(this.contract.isIdentity){
|
||||||
|
let validInstance = await this._validateInstance(message);
|
||||||
|
if(!validInstance){
|
||||||
|
return { success: false, message: "Invalid identity instance" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = this._obtainParametersFunc(message);
|
||||||
|
|
||||||
|
// Verifying if token is allowed
|
||||||
|
const token = this.settings.getToken(params('_gasToken'));
|
||||||
|
if(token == undefined)
|
||||||
|
return { success: false, message: "Token not allowed" };
|
||||||
|
|
||||||
|
|
||||||
|
// Determine if enough balance for baseToken
|
||||||
|
const gasPrice = this.web3.utils.toBN(params('_gasPrice'));
|
||||||
|
const gasLimit = this.web3.utils.toBN(params('_gasLimit'));
|
||||||
|
if(this.contract.allowedFunctions[message.input.functionName].isToken){
|
||||||
|
const Token = new this.web3.eth.Contract(erc20ABI);
|
||||||
|
Token.options.address = params('_baseToken');
|
||||||
|
const baseToken = new this.web3.utils.BN(await Token.methods.balanceOf(message.input.address).call());
|
||||||
|
if(balance.lt(this.web3.utils.BN(params('_value')))){
|
||||||
|
return { success: false, message: "Identity has not enough balance for specified value" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gasPrice * limit calculation
|
||||||
|
const gasToken = params('_gasToken');
|
||||||
|
const balance = await this.getBalance(token, message, gasToken);
|
||||||
|
if(balance.lt(this.web3.utils.toBN(gasPrice.mul(gasLimit)))) {
|
||||||
|
return { success: false, message: "Identity has not enough tokens for gasPrice*gasLimit"};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const latestBlock = await this.web3.eth.getBlock("latest");
|
||||||
|
let estimatedGas = 0;
|
||||||
|
try {
|
||||||
|
estimatedGas = await this._estimateGas(message, latestBlock.gasLimit);
|
||||||
|
if(gasLimit.lt(estimatedGas)) {
|
||||||
|
return { success: false, message: "Gas limit below estimated gas" };
|
||||||
|
}
|
||||||
|
} catch(exc){
|
||||||
|
if(exc.message.indexOf("revert") > -1)
|
||||||
|
return { success: false, message: "Transaction will revert" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Test"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = identityStrategy;
|
module.exports = IdentityStrategy;
|
Loading…
x
Reference in New Issue
Block a user